summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am54
-rw-r--r--src/Makefile.in1345
-rw-r--r--src/app_common.cpp40
-rw-r--r--src/app_common.hpp23
-rw-r--r--src/include/Makefile.am4
-rw-r--r--src/include/Makefile.in491
-rw-r--r--src/include/depth_first_search.hpp226
-rw-r--r--src/include/test_global.hpp43
-rw-r--r--src/ixion_formula_tokenizer.cpp156
-rw-r--r--src/ixion_parser.cpp134
-rw-r--r--src/ixion_sorter.cpp93
-rw-r--r--src/libixion/Makefile.am164
-rw-r--r--src/libixion/Makefile.in1552
-rw-r--r--src/libixion/address.cpp568
-rw-r--r--src/libixion/address_iterator.cpp316
-rw-r--r--src/libixion/calc_status.cpp29
-rw-r--r--src/libixion/calc_status.hpp57
-rw-r--r--src/libixion/cell.cpp578
-rw-r--r--src/libixion/cell_access.cpp176
-rw-r--r--src/libixion/cell_queue_manager.cpp149
-rw-r--r--src/libixion/cell_queue_manager.hpp47
-rw-r--r--src/libixion/column_store_type.hpp73
-rw-r--r--src/libixion/compute_engine.cpp132
-rw-r--r--src/libixion/compute_engine_test.cpp125
-rw-r--r--src/libixion/compute_engine_vulkan.cpp234
-rw-r--r--src/libixion/compute_engine_vulkan.hpp39
-rw-r--r--src/libixion/config.cpp27
-rw-r--r--src/libixion/constants.inl.in7
-rw-r--r--src/libixion/debug.cpp35
-rw-r--r--src/libixion/debug.hpp47
-rw-r--r--src/libixion/dirty_cell_tracker.cpp418
-rw-r--r--src/libixion/dirty_cell_tracker_test.cpp395
-rw-r--r--src/libixion/document.cpp237
-rw-r--r--src/libixion/document_test.cpp190
-rw-r--r--src/libixion/exceptions.cpp117
-rw-r--r--src/libixion/formula.cpp464
-rw-r--r--src/libixion/formula_calc.cpp89
-rw-r--r--src/libixion/formula_function_opcode.cpp26
-rw-r--r--src/libixion/formula_functions.cpp2389
-rw-r--r--src/libixion/formula_functions.hpp129
-rw-r--r--src/libixion/formula_interpreter.cpp1753
-rw-r--r--src/libixion/formula_interpreter.hpp125
-rw-r--r--src/libixion/formula_lexer.cpp345
-rw-r--r--src/libixion/formula_lexer.hpp55
-rw-r--r--src/libixion/formula_name_resolver.cpp2057
-rw-r--r--src/libixion/formula_parser.cpp268
-rw-r--r--src/libixion/formula_parser.hpp80
-rw-r--r--src/libixion/formula_result.cpp421
-rw-r--r--src/libixion/formula_tokens.cpp303
-rw-r--r--src/libixion/formula_value_stack.cpp625
-rw-r--r--src/libixion/formula_value_stack.hpp153
-rw-r--r--src/libixion/general_test.cpp1495
-rw-r--r--src/libixion/global.cpp103
-rw-r--r--src/libixion/impl_types.cpp40
-rw-r--r--src/libixion/impl_types.hpp69
-rw-r--r--src/libixion/info.cpp41
-rw-r--r--src/libixion/interface.cpp19
-rw-r--r--src/libixion/ixion_test_track_deps.cpp155
-rw-r--r--src/libixion/lexer_tokens.cpp156
-rw-r--r--src/libixion/lexer_tokens.hpp75
-rw-r--r--src/libixion/matrix.cpp331
-rw-r--r--src/libixion/model_context.cpp425
-rw-r--r--src/libixion/model_context_impl.cpp1126
-rw-r--r--src/libixion/model_context_impl.hpp206
-rw-r--r--src/libixion/model_iterator.cpp408
-rw-r--r--src/libixion/model_types.cpp16
-rw-r--r--src/libixion/model_types.hpp31
-rw-r--r--src/libixion/module.cpp111
-rw-r--r--src/libixion/name_resolver_test.cpp1131
-rw-r--r--src/libixion/named_expressions_iterator.cpp85
-rw-r--r--src/libixion/queue_entry.cpp17
-rw-r--r--src/libixion/queue_entry.hpp29
-rw-r--r--src/libixion/table.cpp40
-rw-r--r--src/libixion/types.cpp133
-rw-r--r--src/libixion/utf8.cpp72
-rw-r--r--src/libixion/utf8.hpp27
-rw-r--r--src/libixion/utils.cpp70
-rw-r--r--src/libixion/utils.hpp111
-rw-r--r--src/libixion/vulkan_obj.cpp870
-rw-r--r--src/libixion/vulkan_obj.hpp319
-rw-r--r--src/libixion/workbook.cpp55
-rw-r--r--src/libixion/workbook.hpp78
-rw-r--r--src/model_parser.cpp1203
-rw-r--r--src/model_parser.hpp157
-rw-r--r--src/python/Makefile.am35
-rw-r--r--src/python/Makefile.in1145
-rw-r--r--src/python/document.cpp313
-rw-r--r--src/python/document.hpp21
-rw-r--r--src/python/global.cpp39
-rw-r--r--src/python/global.hpp45
-rw-r--r--src/python/python.cpp174
-rw-r--r--src/python/sheet.cpp376
-rw-r--r--src/python/sheet.hpp44
-rw-r--r--src/python/test-env.sh9
-rw-r--r--src/session_handler.cpp169
-rw-r--r--src/session_handler.hpp66
-rw-r--r--src/sort_input_parser.cpp151
-rw-r--r--src/sort_input_parser.hpp54
-rw-r--r--src/table_handler.cpp141
-rw-r--r--src/table_handler.hpp59
-rw-r--r--src/test/Makefile.am9
-rw-r--r--src/test/Makefile.in646
-rw-r--r--src/test/test_global.cpp24
103 files changed, 30327 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..c081dfd
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,54 @@
+SUBDIRS = include test libixion python
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/include \
+ -I$(top_srcdir)/src/include \
+ $(MDDS_CFLAGS) $(BOOST_CPPFLAGS)
+
+bin_PROGRAMS = ixion-parser ixion-sorter ixion-formula-tokenizer
+
+ixion_parser_SOURCES = \
+ ixion_parser.cpp \
+ model_parser.hpp \
+ model_parser.cpp \
+ app_common.hpp \
+ app_common.cpp \
+ session_handler.hpp \
+ session_handler.cpp \
+ table_handler.hpp \
+ table_handler.cpp
+
+ixion_parser_LDADD = libixion/libixion-@IXION_API_VERSION@.la \
+ $(BOOST_PROGRAM_OPTIONS_LIBS)
+
+ixion_sorter_SOURCES = \
+ ixion_sorter.cpp \
+ app_common.cpp \
+ sort_input_parser.hpp \
+ sort_input_parser.cpp
+
+ixion_sorter_LDADD = libixion/libixion-@IXION_API_VERSION@.la \
+ $(BOOST_PROGRAM_OPTIONS_LIBS)
+
+ixion_formula_tokenizer_SOURCES = \
+ ixion_formula_tokenizer.cpp
+
+ixion_formula_tokenizer_LDADD = libixion/libixion-@IXION_API_VERSION@.la \
+ $(BOOST_PROGRAM_OPTIONS_LIBS)
+
+
+AM_TESTS_ENVIRONMENT = PATH=.libs$${PATH:+:$${PATH}}; export PATH; \
+ LD_LIBRARY_PATH=libixion/.libs$${LD_LIBRARY_PATH:+:$${LD_LIBRARY_PATH}}; export LD_LIBRARY_PATH; \
+ DYLD_LIBRARY_PATH=$${LD_LIBRARY_PATH}}; export DYLD_LIBRARY_PATH;
+
+TESTS = \
+ ../test/parser-test-t0.sh \
+ ../test/parser-test-t1.sh \
+ ../test/parser-test-t2.sh \
+ ../test/parser-test-t3.sh \
+ ../test/parser-test-t4.sh \
+ ../test/parser-test-t5.sh \
+ ../test/parser-test-t6.sh \
+ ../test/parser-test-t7.sh \
+ ../test/parser-test-t8.sh
+
diff --git a/src/Makefile.in b/src/Makefile.in
new file mode 100644
index 0000000..b95e641
--- /dev/null
+++ b/src/Makefile.in
@@ -0,0 +1,1345 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+bin_PROGRAMS = ixion-parser$(EXEEXT) ixion-sorter$(EXEEXT) \
+ ixion-formula-tokenizer$(EXEEXT)
+subdir = src
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \
+ $(top_srcdir)/m4/ax_cxx_compile_stdcxx_17.m4 \
+ $(top_srcdir)/m4/boost.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \
+ $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(bindir)"
+PROGRAMS = $(bin_PROGRAMS)
+am_ixion_formula_tokenizer_OBJECTS = \
+ ixion_formula_tokenizer.$(OBJEXT)
+ixion_formula_tokenizer_OBJECTS = \
+ $(am_ixion_formula_tokenizer_OBJECTS)
+am__DEPENDENCIES_1 =
+ixion_formula_tokenizer_DEPENDENCIES = \
+ libixion/libixion-@IXION_API_VERSION@.la $(am__DEPENDENCIES_1)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+am_ixion_parser_OBJECTS = ixion_parser.$(OBJEXT) \
+ model_parser.$(OBJEXT) app_common.$(OBJEXT) \
+ session_handler.$(OBJEXT) table_handler.$(OBJEXT)
+ixion_parser_OBJECTS = $(am_ixion_parser_OBJECTS)
+ixion_parser_DEPENDENCIES = libixion/libixion-@IXION_API_VERSION@.la \
+ $(am__DEPENDENCIES_1)
+am_ixion_sorter_OBJECTS = ixion_sorter.$(OBJEXT) app_common.$(OBJEXT) \
+ sort_input_parser.$(OBJEXT)
+ixion_sorter_OBJECTS = $(am_ixion_sorter_OBJECTS)
+ixion_sorter_DEPENDENCIES = libixion/libixion-@IXION_API_VERSION@.la \
+ $(am__DEPENDENCIES_1)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/app_common.Po \
+ ./$(DEPDIR)/ixion_formula_tokenizer.Po \
+ ./$(DEPDIR)/ixion_parser.Po ./$(DEPDIR)/ixion_sorter.Po \
+ ./$(DEPDIR)/model_parser.Po ./$(DEPDIR)/session_handler.Po \
+ ./$(DEPDIR)/sort_input_parser.Po ./$(DEPDIR)/table_handler.Po
+am__mv = mv -f
+CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS)
+LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CXXFLAGS) $(CXXFLAGS)
+AM_V_CXX = $(am__v_CXX_@AM_V@)
+am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@)
+am__v_CXX_0 = @echo " CXX " $@;
+am__v_CXX_1 =
+CXXLD = $(CXX)
+CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
+ $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CXXLD = $(am__v_CXXLD_@AM_V@)
+am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@)
+am__v_CXXLD_0 = @echo " CXXLD " $@;
+am__v_CXXLD_1 =
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(ixion_formula_tokenizer_SOURCES) $(ixion_parser_SOURCES) \
+ $(ixion_sorter_SOURCES)
+DIST_SOURCES = $(ixion_formula_tokenizer_SOURCES) \
+ $(ixion_parser_SOURCES) $(ixion_sorter_SOURCES)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ check recheck distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__tty_colors_dummy = \
+ mgn= red= grn= lgn= blu= brg= std=; \
+ am__color_tests=no
+am__tty_colors = { \
+ $(am__tty_colors_dummy); \
+ if test "X$(AM_COLOR_TESTS)" = Xno; then \
+ am__color_tests=no; \
+ elif test "X$(AM_COLOR_TESTS)" = Xalways; then \
+ am__color_tests=yes; \
+ elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \
+ am__color_tests=yes; \
+ fi; \
+ if test $$am__color_tests = yes; then \
+ red=''; \
+ grn=''; \
+ lgn=''; \
+ blu=''; \
+ mgn=''; \
+ brg=''; \
+ std=''; \
+ fi; \
+}
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__recheck_rx = ^[ ]*:recheck:[ ]*
+am__global_test_result_rx = ^[ ]*:global-test-result:[ ]*
+am__copy_in_global_log_rx = ^[ ]*:copy-in-global-log:[ ]*
+# A command that, given a newline-separated list of test names on the
+# standard input, print the name of the tests that are to be re-run
+# upon "make recheck".
+am__list_recheck_tests = $(AWK) '{ \
+ recheck = 1; \
+ while ((rc = (getline line < ($$0 ".trs"))) != 0) \
+ { \
+ if (rc < 0) \
+ { \
+ if ((getline line2 < ($$0 ".log")) < 0) \
+ recheck = 0; \
+ break; \
+ } \
+ else if (line ~ /$(am__recheck_rx)[nN][Oo]/) \
+ { \
+ recheck = 0; \
+ break; \
+ } \
+ else if (line ~ /$(am__recheck_rx)[yY][eE][sS]/) \
+ { \
+ break; \
+ } \
+ }; \
+ if (recheck) \
+ print $$0; \
+ close ($$0 ".trs"); \
+ close ($$0 ".log"); \
+}'
+# A command that, given a newline-separated list of test names on the
+# standard input, create the global log from their .trs and .log files.
+am__create_global_log = $(AWK) ' \
+function fatal(msg) \
+{ \
+ print "fatal: making $@: " msg | "cat >&2"; \
+ exit 1; \
+} \
+function rst_section(header) \
+{ \
+ print header; \
+ len = length(header); \
+ for (i = 1; i <= len; i = i + 1) \
+ printf "="; \
+ printf "\n\n"; \
+} \
+{ \
+ copy_in_global_log = 1; \
+ global_test_result = "RUN"; \
+ while ((rc = (getline line < ($$0 ".trs"))) != 0) \
+ { \
+ if (rc < 0) \
+ fatal("failed to read from " $$0 ".trs"); \
+ if (line ~ /$(am__global_test_result_rx)/) \
+ { \
+ sub("$(am__global_test_result_rx)", "", line); \
+ sub("[ ]*$$", "", line); \
+ global_test_result = line; \
+ } \
+ else if (line ~ /$(am__copy_in_global_log_rx)[nN][oO]/) \
+ copy_in_global_log = 0; \
+ }; \
+ if (copy_in_global_log) \
+ { \
+ rst_section(global_test_result ": " $$0); \
+ while ((rc = (getline line < ($$0 ".log"))) != 0) \
+ { \
+ if (rc < 0) \
+ fatal("failed to read from " $$0 ".log"); \
+ print line; \
+ }; \
+ printf "\n"; \
+ }; \
+ close ($$0 ".trs"); \
+ close ($$0 ".log"); \
+}'
+# Restructured Text title.
+am__rst_title = { sed 's/.*/ & /;h;s/./=/g;p;x;s/ *$$//;p;g' && echo; }
+# Solaris 10 'make', and several other traditional 'make' implementations,
+# pass "-e" to $(SHELL), and POSIX 2008 even requires this. Work around it
+# by disabling -e (using the XSI extension "set +e") if it's set.
+am__sh_e_setup = case $$- in *e*) set +e;; esac
+# Default flags passed to test drivers.
+am__common_driver_flags = \
+ --color-tests "$$am__color_tests" \
+ --enable-hard-errors "$$am__enable_hard_errors" \
+ --expect-failure "$$am__expect_failure"
+# To be inserted before the command running the test. Creates the
+# directory for the log if needed. Stores in $dir the directory
+# containing $f, in $tst the test, in $log the log. Executes the
+# developer- defined test setup AM_TESTS_ENVIRONMENT (if any), and
+# passes TESTS_ENVIRONMENT. Set up options for the wrapper that
+# will run the test scripts (or their associated LOG_COMPILER, if
+# thy have one).
+am__check_pre = \
+$(am__sh_e_setup); \
+$(am__vpath_adj_setup) $(am__vpath_adj) \
+$(am__tty_colors); \
+srcdir=$(srcdir); export srcdir; \
+case "$@" in \
+ */*) am__odir=`echo "./$@" | sed 's|/[^/]*$$||'`;; \
+ *) am__odir=.;; \
+esac; \
+test "x$$am__odir" = x"." || test -d "$$am__odir" \
+ || $(MKDIR_P) "$$am__odir" || exit $$?; \
+if test -f "./$$f"; then dir=./; \
+elif test -f "$$f"; then dir=; \
+else dir="$(srcdir)/"; fi; \
+tst=$$dir$$f; log='$@'; \
+if test -n '$(DISABLE_HARD_ERRORS)'; then \
+ am__enable_hard_errors=no; \
+else \
+ am__enable_hard_errors=yes; \
+fi; \
+case " $(XFAIL_TESTS) " in \
+ *[\ \ ]$$f[\ \ ]* | *[\ \ ]$$dir$$f[\ \ ]*) \
+ am__expect_failure=yes;; \
+ *) \
+ am__expect_failure=no;; \
+esac; \
+$(AM_TESTS_ENVIRONMENT) $(TESTS_ENVIRONMENT)
+# A shell command to get the names of the tests scripts with any registered
+# extension removed (i.e., equivalently, the names of the test logs, with
+# the '.log' extension removed). The result is saved in the shell variable
+# '$bases'. This honors runtime overriding of TESTS and TEST_LOGS. Sadly,
+# we cannot use something simpler, involving e.g., "$(TEST_LOGS:.log=)",
+# since that might cause problem with VPATH rewrites for suffix-less tests.
+# See also 'test-harness-vpath-rewrite.sh' and 'test-trs-basic.sh'.
+am__set_TESTS_bases = \
+ bases='$(TEST_LOGS)'; \
+ bases=`for i in $$bases; do echo $$i; done | sed 's/\.log$$//'`; \
+ bases=`echo $$bases`
+AM_TESTSUITE_SUMMARY_HEADER = ' for $(PACKAGE_STRING)'
+RECHECK_LOGS = $(TEST_LOGS)
+TEST_SUITE_LOG = test-suite.log
+TEST_EXTENSIONS = @EXEEXT@ .test
+LOG_DRIVER = $(SHELL) $(top_srcdir)/test-driver
+LOG_COMPILE = $(LOG_COMPILER) $(AM_LOG_FLAGS) $(LOG_FLAGS)
+am__set_b = \
+ case '$@' in \
+ */*) \
+ case '$*' in \
+ */*) b='$*';; \
+ *) b=`echo '$@' | sed 's/\.log$$//'`; \
+ esac;; \
+ *) \
+ b='$*';; \
+ esac
+am__test_logs1 = $(TESTS:=.log)
+am__test_logs2 = $(am__test_logs1:@EXEEXT@.log=.log)
+TEST_LOGS = $(am__test_logs2:.test.log=.log)
+TEST_LOG_DRIVER = $(SHELL) $(top_srcdir)/test-driver
+TEST_LOG_COMPILE = $(TEST_LOG_COMPILER) $(AM_TEST_LOG_FLAGS) \
+ $(TEST_LOG_FLAGS)
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp \
+ $(top_srcdir)/test-driver
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AS = @AS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_CPPFLAGS = @BOOST_CPPFLAGS@
+BOOST_FILESYSTEM_LDFLAGS = @BOOST_FILESYSTEM_LDFLAGS@
+BOOST_FILESYSTEM_LDPATH = @BOOST_FILESYSTEM_LDPATH@
+BOOST_FILESYSTEM_LIBS = @BOOST_FILESYSTEM_LIBS@
+BOOST_LDPATH = @BOOST_LDPATH@
+BOOST_PROGRAM_OPTIONS_LDFLAGS = @BOOST_PROGRAM_OPTIONS_LDFLAGS@
+BOOST_PROGRAM_OPTIONS_LDPATH = @BOOST_PROGRAM_OPTIONS_LDPATH@
+BOOST_PROGRAM_OPTIONS_LIBS = @BOOST_PROGRAM_OPTIONS_LIBS@
+BOOST_ROOT = @BOOST_ROOT@
+BOOST_SYSTEM_LDFLAGS = @BOOST_SYSTEM_LDFLAGS@
+BOOST_SYSTEM_LDPATH = @BOOST_SYSTEM_LDPATH@
+BOOST_SYSTEM_LIBS = @BOOST_SYSTEM_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+HAVE_CXX17 = @HAVE_CXX17@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+IXION_API_VERSION = @IXION_API_VERSION@
+IXION_MAJOR_API_VERSION = @IXION_MAJOR_API_VERSION@
+IXION_MAJOR_VERSION = @IXION_MAJOR_VERSION@
+IXION_MICRO_VERSION = @IXION_MICRO_VERSION@
+IXION_MINOR_API_VERSION = @IXION_MINOR_API_VERSION@
+IXION_MINOR_VERSION = @IXION_MINOR_VERSION@
+IXION_VERSION = @IXION_VERSION@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MDDS_CFLAGS = @MDDS_CFLAGS@
+MDDS_LIBS = @MDDS_LIBS@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POW_LIB = @POW_LIB@
+PYTHON = @PYTHON@
+PYTHON_CFLAGS = @PYTHON_CFLAGS@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_LIBS = @PYTHON_LIBS@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VERSION = @VERSION@
+VULKAN_CFLAGS = @VULKAN_CFLAGS@
+VULKAN_LIBS = @VULKAN_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = include test libixion python
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/include \
+ -I$(top_srcdir)/src/include \
+ $(MDDS_CFLAGS) $(BOOST_CPPFLAGS)
+
+ixion_parser_SOURCES = \
+ ixion_parser.cpp \
+ model_parser.hpp \
+ model_parser.cpp \
+ app_common.hpp \
+ app_common.cpp \
+ session_handler.hpp \
+ session_handler.cpp \
+ table_handler.hpp \
+ table_handler.cpp
+
+ixion_parser_LDADD = libixion/libixion-@IXION_API_VERSION@.la \
+ $(BOOST_PROGRAM_OPTIONS_LIBS)
+
+ixion_sorter_SOURCES = \
+ ixion_sorter.cpp \
+ app_common.cpp \
+ sort_input_parser.hpp \
+ sort_input_parser.cpp
+
+ixion_sorter_LDADD = libixion/libixion-@IXION_API_VERSION@.la \
+ $(BOOST_PROGRAM_OPTIONS_LIBS)
+
+ixion_formula_tokenizer_SOURCES = \
+ ixion_formula_tokenizer.cpp
+
+ixion_formula_tokenizer_LDADD = libixion/libixion-@IXION_API_VERSION@.la \
+ $(BOOST_PROGRAM_OPTIONS_LIBS)
+
+AM_TESTS_ENVIRONMENT = PATH=.libs$${PATH:+:$${PATH}}; export PATH; \
+ LD_LIBRARY_PATH=libixion/.libs$${LD_LIBRARY_PATH:+:$${LD_LIBRARY_PATH}}; export LD_LIBRARY_PATH; \
+ DYLD_LIBRARY_PATH=$${LD_LIBRARY_PATH}}; export DYLD_LIBRARY_PATH;
+
+TESTS = \
+ ../test/parser-test-t0.sh \
+ ../test/parser-test-t1.sh \
+ ../test/parser-test-t2.sh \
+ ../test/parser-test-t3.sh \
+ ../test/parser-test-t4.sh \
+ ../test/parser-test-t5.sh \
+ ../test/parser-test-t6.sh \
+ ../test/parser-test-t7.sh \
+ ../test/parser-test-t8.sh
+
+all: all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .cpp .lo .log .o .obj .test .test$(EXEEXT) .trs
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+install-binPROGRAMS: $(bin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ || test -f $$p1 \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-binPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(bindir)" && rm -f $$files
+
+clean-binPROGRAMS:
+ @list='$(bin_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+ixion-formula-tokenizer$(EXEEXT): $(ixion_formula_tokenizer_OBJECTS) $(ixion_formula_tokenizer_DEPENDENCIES) $(EXTRA_ixion_formula_tokenizer_DEPENDENCIES)
+ @rm -f ixion-formula-tokenizer$(EXEEXT)
+ $(AM_V_CXXLD)$(CXXLINK) $(ixion_formula_tokenizer_OBJECTS) $(ixion_formula_tokenizer_LDADD) $(LIBS)
+
+ixion-parser$(EXEEXT): $(ixion_parser_OBJECTS) $(ixion_parser_DEPENDENCIES) $(EXTRA_ixion_parser_DEPENDENCIES)
+ @rm -f ixion-parser$(EXEEXT)
+ $(AM_V_CXXLD)$(CXXLINK) $(ixion_parser_OBJECTS) $(ixion_parser_LDADD) $(LIBS)
+
+ixion-sorter$(EXEEXT): $(ixion_sorter_OBJECTS) $(ixion_sorter_DEPENDENCIES) $(EXTRA_ixion_sorter_DEPENDENCIES)
+ @rm -f ixion-sorter$(EXEEXT)
+ $(AM_V_CXXLD)$(CXXLINK) $(ixion_sorter_OBJECTS) $(ixion_sorter_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/app_common.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ixion_formula_tokenizer.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ixion_parser.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ixion_sorter.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/model_parser.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/session_handler.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sort_input_parser.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/table_handler.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.cpp.o:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $<
+
+.cpp.obj:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.cpp.lo:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+# Recover from deleted '.trs' file; this should ensure that
+# "rm -f foo.log; make foo.trs" re-run 'foo.test', and re-create
+# both 'foo.log' and 'foo.trs'. Break the recipe in two subshells
+# to avoid problems with "make -n".
+.log.trs:
+ rm -f $< $@
+ $(MAKE) $(AM_MAKEFLAGS) $<
+
+# Leading 'am--fnord' is there to ensure the list of targets does not
+# expand to empty, as could happen e.g. with make check TESTS=''.
+am--fnord $(TEST_LOGS) $(TEST_LOGS:.log=.trs): $(am__force_recheck)
+am--force-recheck:
+ @:
+
+$(TEST_SUITE_LOG): $(TEST_LOGS)
+ @$(am__set_TESTS_bases); \
+ am__f_ok () { test -f "$$1" && test -r "$$1"; }; \
+ redo_bases=`for i in $$bases; do \
+ am__f_ok $$i.trs && am__f_ok $$i.log || echo $$i; \
+ done`; \
+ if test -n "$$redo_bases"; then \
+ redo_logs=`for i in $$redo_bases; do echo $$i.log; done`; \
+ redo_results=`for i in $$redo_bases; do echo $$i.trs; done`; \
+ if $(am__make_dryrun); then :; else \
+ rm -f $$redo_logs && rm -f $$redo_results || exit 1; \
+ fi; \
+ fi; \
+ if test -n "$$am__remaking_logs"; then \
+ echo "fatal: making $(TEST_SUITE_LOG): possible infinite" \
+ "recursion detected" >&2; \
+ elif test -n "$$redo_logs"; then \
+ am__remaking_logs=yes $(MAKE) $(AM_MAKEFLAGS) $$redo_logs; \
+ fi; \
+ if $(am__make_dryrun); then :; else \
+ st=0; \
+ errmsg="fatal: making $(TEST_SUITE_LOG): failed to create"; \
+ for i in $$redo_bases; do \
+ test -f $$i.trs && test -r $$i.trs \
+ || { echo "$$errmsg $$i.trs" >&2; st=1; }; \
+ test -f $$i.log && test -r $$i.log \
+ || { echo "$$errmsg $$i.log" >&2; st=1; }; \
+ done; \
+ test $$st -eq 0 || exit 1; \
+ fi
+ @$(am__sh_e_setup); $(am__tty_colors); $(am__set_TESTS_bases); \
+ ws='[ ]'; \
+ results=`for b in $$bases; do echo $$b.trs; done`; \
+ test -n "$$results" || results=/dev/null; \
+ all=` grep "^$$ws*:test-result:" $$results | wc -l`; \
+ pass=` grep "^$$ws*:test-result:$$ws*PASS" $$results | wc -l`; \
+ fail=` grep "^$$ws*:test-result:$$ws*FAIL" $$results | wc -l`; \
+ skip=` grep "^$$ws*:test-result:$$ws*SKIP" $$results | wc -l`; \
+ xfail=`grep "^$$ws*:test-result:$$ws*XFAIL" $$results | wc -l`; \
+ xpass=`grep "^$$ws*:test-result:$$ws*XPASS" $$results | wc -l`; \
+ error=`grep "^$$ws*:test-result:$$ws*ERROR" $$results | wc -l`; \
+ if test `expr $$fail + $$xpass + $$error` -eq 0; then \
+ success=true; \
+ else \
+ success=false; \
+ fi; \
+ br='==================='; br=$$br$$br$$br$$br; \
+ result_count () \
+ { \
+ if test x"$$1" = x"--maybe-color"; then \
+ maybe_colorize=yes; \
+ elif test x"$$1" = x"--no-color"; then \
+ maybe_colorize=no; \
+ else \
+ echo "$@: invalid 'result_count' usage" >&2; exit 4; \
+ fi; \
+ shift; \
+ desc=$$1 count=$$2; \
+ if test $$maybe_colorize = yes && test $$count -gt 0; then \
+ color_start=$$3 color_end=$$std; \
+ else \
+ color_start= color_end=; \
+ fi; \
+ echo "$${color_start}# $$desc $$count$${color_end}"; \
+ }; \
+ create_testsuite_report () \
+ { \
+ result_count $$1 "TOTAL:" $$all "$$brg"; \
+ result_count $$1 "PASS: " $$pass "$$grn"; \
+ result_count $$1 "SKIP: " $$skip "$$blu"; \
+ result_count $$1 "XFAIL:" $$xfail "$$lgn"; \
+ result_count $$1 "FAIL: " $$fail "$$red"; \
+ result_count $$1 "XPASS:" $$xpass "$$red"; \
+ result_count $$1 "ERROR:" $$error "$$mgn"; \
+ }; \
+ { \
+ echo "$(PACKAGE_STRING): $(subdir)/$(TEST_SUITE_LOG)" | \
+ $(am__rst_title); \
+ create_testsuite_report --no-color; \
+ echo; \
+ echo ".. contents:: :depth: 2"; \
+ echo; \
+ for b in $$bases; do echo $$b; done \
+ | $(am__create_global_log); \
+ } >$(TEST_SUITE_LOG).tmp || exit 1; \
+ mv $(TEST_SUITE_LOG).tmp $(TEST_SUITE_LOG); \
+ if $$success; then \
+ col="$$grn"; \
+ else \
+ col="$$red"; \
+ test x"$$VERBOSE" = x || cat $(TEST_SUITE_LOG); \
+ fi; \
+ echo "$${col}$$br$${std}"; \
+ echo "$${col}Testsuite summary"$(AM_TESTSUITE_SUMMARY_HEADER)"$${std}"; \
+ echo "$${col}$$br$${std}"; \
+ create_testsuite_report --maybe-color; \
+ echo "$$col$$br$$std"; \
+ if $$success; then :; else \
+ echo "$${col}See $(subdir)/$(TEST_SUITE_LOG)$${std}"; \
+ if test -n "$(PACKAGE_BUGREPORT)"; then \
+ echo "$${col}Please report to $(PACKAGE_BUGREPORT)$${std}"; \
+ fi; \
+ echo "$$col$$br$$std"; \
+ fi; \
+ $$success || exit 1
+
+check-TESTS:
+ @list='$(RECHECK_LOGS)'; test -z "$$list" || rm -f $$list
+ @list='$(RECHECK_LOGS:.log=.trs)'; test -z "$$list" || rm -f $$list
+ @test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+ @set +e; $(am__set_TESTS_bases); \
+ log_list=`for i in $$bases; do echo $$i.log; done`; \
+ trs_list=`for i in $$bases; do echo $$i.trs; done`; \
+ log_list=`echo $$log_list`; trs_list=`echo $$trs_list`; \
+ $(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) TEST_LOGS="$$log_list"; \
+ exit $$?;
+recheck: all
+ @test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+ @set +e; $(am__set_TESTS_bases); \
+ bases=`for i in $$bases; do echo $$i; done \
+ | $(am__list_recheck_tests)` || exit 1; \
+ log_list=`for i in $$bases; do echo $$i.log; done`; \
+ log_list=`echo $$log_list`; \
+ $(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) \
+ am__force_recheck=am--force-recheck \
+ TEST_LOGS="$$log_list"; \
+ exit $$?
+../test/parser-test-t0.sh.log: ../test/parser-test-t0.sh
+ @p='../test/parser-test-t0.sh'; \
+ b='../test/parser-test-t0.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+../test/parser-test-t1.sh.log: ../test/parser-test-t1.sh
+ @p='../test/parser-test-t1.sh'; \
+ b='../test/parser-test-t1.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+../test/parser-test-t2.sh.log: ../test/parser-test-t2.sh
+ @p='../test/parser-test-t2.sh'; \
+ b='../test/parser-test-t2.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+../test/parser-test-t3.sh.log: ../test/parser-test-t3.sh
+ @p='../test/parser-test-t3.sh'; \
+ b='../test/parser-test-t3.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+../test/parser-test-t4.sh.log: ../test/parser-test-t4.sh
+ @p='../test/parser-test-t4.sh'; \
+ b='../test/parser-test-t4.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+../test/parser-test-t5.sh.log: ../test/parser-test-t5.sh
+ @p='../test/parser-test-t5.sh'; \
+ b='../test/parser-test-t5.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+../test/parser-test-t6.sh.log: ../test/parser-test-t6.sh
+ @p='../test/parser-test-t6.sh'; \
+ b='../test/parser-test-t6.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+../test/parser-test-t7.sh.log: ../test/parser-test-t7.sh
+ @p='../test/parser-test-t7.sh'; \
+ b='../test/parser-test-t7.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+../test/parser-test-t8.sh.log: ../test/parser-test-t8.sh
+ @p='../test/parser-test-t8.sh'; \
+ b='../test/parser-test-t8.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+.test.log:
+ @p='$<'; \
+ $(am__set_b); \
+ $(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+@am__EXEEXT_TRUE@.test$(EXEEXT).log:
+@am__EXEEXT_TRUE@ @p='$<'; \
+@am__EXEEXT_TRUE@ $(am__set_b); \
+@am__EXEEXT_TRUE@ $(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \
+@am__EXEEXT_TRUE@ --log-file $$b.log --trs-file $$b.trs \
+@am__EXEEXT_TRUE@ $(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \
+@am__EXEEXT_TRUE@ "$$tst" $(AM_TESTS_FD_REDIRECT)
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-TESTS
+check: check-recursive
+all-am: Makefile $(PROGRAMS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(bindir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+ -test -z "$(TEST_LOGS)" || rm -f $(TEST_LOGS)
+ -test -z "$(TEST_LOGS:.log=.trs)" || rm -f $(TEST_LOGS:.log=.trs)
+ -test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-binPROGRAMS clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/app_common.Po
+ -rm -f ./$(DEPDIR)/ixion_formula_tokenizer.Po
+ -rm -f ./$(DEPDIR)/ixion_parser.Po
+ -rm -f ./$(DEPDIR)/ixion_sorter.Po
+ -rm -f ./$(DEPDIR)/model_parser.Po
+ -rm -f ./$(DEPDIR)/session_handler.Po
+ -rm -f ./$(DEPDIR)/sort_input_parser.Po
+ -rm -f ./$(DEPDIR)/table_handler.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am: install-binPROGRAMS
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f ./$(DEPDIR)/app_common.Po
+ -rm -f ./$(DEPDIR)/ixion_formula_tokenizer.Po
+ -rm -f ./$(DEPDIR)/ixion_parser.Po
+ -rm -f ./$(DEPDIR)/ixion_sorter.Po
+ -rm -f ./$(DEPDIR)/model_parser.Po
+ -rm -f ./$(DEPDIR)/session_handler.Po
+ -rm -f ./$(DEPDIR)/sort_input_parser.Po
+ -rm -f ./$(DEPDIR)/table_handler.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am: uninstall-binPROGRAMS
+
+.MAKE: $(am__recursive_targets) check-am install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-TESTS check-am clean \
+ clean-binPROGRAMS clean-generic clean-libtool cscopelist-am \
+ ctags ctags-am distclean distclean-compile distclean-generic \
+ distclean-libtool distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-binPROGRAMS \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs installdirs-am \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
+ pdf pdf-am ps ps-am recheck tags tags-am uninstall \
+ uninstall-am uninstall-binPROGRAMS
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/app_common.cpp b/src/app_common.cpp
new file mode 100644
index 0000000..38e5c7e
--- /dev/null
+++ b/src/app_common.cpp
@@ -0,0 +1,40 @@
+/* -*- 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 "app_common.hpp"
+
+#include <ixion/exceptions.hpp>
+
+#include <fstream>
+#include <sstream>
+
+namespace ixion { namespace detail {
+
+std::string_view get_formula_result_output_separator()
+{
+ static const char* sep =
+ "---------------------------------------------------------";
+ return sep;
+}
+
+std::string load_file_content(const std::string& filepath)
+{
+ std::ifstream file(filepath.c_str());
+ if (!file)
+ // failed to open the specified file.
+ throw file_not_found(filepath);
+
+ std::ostringstream os;
+ os << file.rdbuf();
+ file.close();
+
+ return os.str();
+}
+
+}} // namespace ixion::detail
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/app_common.hpp b/src/app_common.hpp
new file mode 100644
index 0000000..d467e46
--- /dev/null
+++ b/src/app_common.hpp
@@ -0,0 +1,23 @@
+/* -*- 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/.
+ */
+
+#ifndef INCLUDED_IXION_SRC_APP_COMMON_HPP
+#define INCLUDED_IXION_SRC_APP_COMMON_HPP
+
+#include <string>
+
+namespace ixion { namespace detail {
+
+std::string_view get_formula_result_output_separator();
+
+std::string load_file_content(const std::string& filepath);
+
+}} // namespace ixion::detail
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/include/Makefile.am b/src/include/Makefile.am
new file mode 100644
index 0000000..ca4a861
--- /dev/null
+++ b/src/include/Makefile.am
@@ -0,0 +1,4 @@
+
+EXTRA_DIST = \
+ depth_first_search.hpp \
+ test_global.hpp
diff --git a/src/include/Makefile.in b/src/include/Makefile.in
new file mode 100644
index 0000000..377ac52
--- /dev/null
+++ b/src/include/Makefile.in
@@ -0,0 +1,491 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/include
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \
+ $(top_srcdir)/m4/ax_cxx_compile_stdcxx_17.m4 \
+ $(top_srcdir)/m4/boost.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \
+ $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+SOURCES =
+DIST_SOURCES =
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+am__DIST_COMMON = $(srcdir)/Makefile.in
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AS = @AS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_CPPFLAGS = @BOOST_CPPFLAGS@
+BOOST_FILESYSTEM_LDFLAGS = @BOOST_FILESYSTEM_LDFLAGS@
+BOOST_FILESYSTEM_LDPATH = @BOOST_FILESYSTEM_LDPATH@
+BOOST_FILESYSTEM_LIBS = @BOOST_FILESYSTEM_LIBS@
+BOOST_LDPATH = @BOOST_LDPATH@
+BOOST_PROGRAM_OPTIONS_LDFLAGS = @BOOST_PROGRAM_OPTIONS_LDFLAGS@
+BOOST_PROGRAM_OPTIONS_LDPATH = @BOOST_PROGRAM_OPTIONS_LDPATH@
+BOOST_PROGRAM_OPTIONS_LIBS = @BOOST_PROGRAM_OPTIONS_LIBS@
+BOOST_ROOT = @BOOST_ROOT@
+BOOST_SYSTEM_LDFLAGS = @BOOST_SYSTEM_LDFLAGS@
+BOOST_SYSTEM_LDPATH = @BOOST_SYSTEM_LDPATH@
+BOOST_SYSTEM_LIBS = @BOOST_SYSTEM_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+HAVE_CXX17 = @HAVE_CXX17@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+IXION_API_VERSION = @IXION_API_VERSION@
+IXION_MAJOR_API_VERSION = @IXION_MAJOR_API_VERSION@
+IXION_MAJOR_VERSION = @IXION_MAJOR_VERSION@
+IXION_MICRO_VERSION = @IXION_MICRO_VERSION@
+IXION_MINOR_API_VERSION = @IXION_MINOR_API_VERSION@
+IXION_MINOR_VERSION = @IXION_MINOR_VERSION@
+IXION_VERSION = @IXION_VERSION@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MDDS_CFLAGS = @MDDS_CFLAGS@
+MDDS_LIBS = @MDDS_LIBS@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POW_LIB = @POW_LIB@
+PYTHON = @PYTHON@
+PYTHON_CFLAGS = @PYTHON_CFLAGS@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_LIBS = @PYTHON_LIBS@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VERSION = @VERSION@
+VULKAN_CFLAGS = @VULKAN_CFLAGS@
+VULKAN_LIBS = @VULKAN_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+EXTRA_DIST = \
+ depth_first_search.hpp \
+ test_global.hpp
+
+all: all-am
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/include/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/include/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+tags TAGS:
+
+ctags CTAGS:
+
+cscope cscopelist:
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-am
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: all all-am check check-am clean clean-generic clean-libtool \
+ cscopelist-am ctags-am distclean distclean-generic \
+ distclean-libtool distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/include/depth_first_search.hpp b/src/include/depth_first_search.hpp
new file mode 100644
index 0000000..f68b1bd
--- /dev/null
+++ b/src/include/depth_first_search.hpp
@@ -0,0 +1,226 @@
+/* -*- 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/.
+ */
+
+#ifndef INCLUDED_IXION_DEPTH_FIRST_SEARCH_HPP
+#define INCLUDED_IXION_DEPTH_FIRST_SEARCH_HPP
+
+#include "ixion/exceptions.hpp"
+
+#include <vector>
+#include <set>
+#include <map>
+#include <exception>
+#include <iostream>
+#include <unordered_map>
+
+namespace ixion {
+
+template<typename _ValueType, typename _ValueHashType>
+class depth_first_search
+{
+public:
+ typedef _ValueType value_type;
+ typedef _ValueHashType value_hash_type;
+
+private:
+ typedef std::unordered_map<value_type, size_t, value_hash_type> value_index_map_type;
+
+ enum cell_color_type { white, gray, black };
+
+ class dfs_error : public general_error
+ {
+ public:
+ dfs_error(const std::string& msg) : general_error(msg) {}
+ };
+
+ struct node_data
+ {
+ cell_color_type color;
+ value_type node;
+ size_t time_visited;
+ size_t time_finished;
+
+ node_data() : color(white), time_visited(0), time_finished(0) {}
+ };
+
+public:
+ typedef std::set<value_type> precedent_cells_type;
+ typedef std::map<value_type, precedent_cells_type> precedent_map_type;
+
+ class back_inserter
+ {
+ std::vector<value_type>& m_sorted;
+ public:
+ back_inserter(std::vector<value_type>& sorted) : m_sorted(sorted) {}
+
+ void operator()(const value_type& v)
+ {
+ m_sorted.push_back(v);
+ }
+ };
+
+ /**
+ * Stores all precedent-dependent relations which are to be used to
+ * perform topological sort.
+ */
+ class relations
+ {
+ friend class depth_first_search;
+
+ public:
+ void insert(value_type pre, value_type dep)
+ {
+ typename precedent_map_type::iterator itr = m_map.find(pre);
+ if (itr == m_map.end())
+ {
+ // First dependent for this precedent.
+ std::pair<typename precedent_map_type::iterator, bool> r =
+ m_map.insert(
+ typename precedent_map_type::value_type(pre, precedent_cells_type()));
+
+ if (!r.second)
+ throw dfs_error("failed to insert a new set instance");
+
+ itr = r.first;
+ }
+
+ itr->second.insert(dep);
+ }
+
+ private:
+ const precedent_map_type& get() const { return m_map; }
+
+ precedent_map_type m_map;
+ };
+
+ template<typename _Iter>
+ depth_first_search(
+ const _Iter& begin, const _Iter& end,
+ const relations& rels, back_inserter handler);
+
+ void run();
+
+private:
+ void init();
+
+ void visit(size_t cell_index);
+ size_t get_cell_index(const value_type& p) const;
+ const precedent_cells_type* get_precedent_cells(value_type cell);
+
+private:
+ const precedent_map_type& m_precedent_map;
+ back_inserter m_handler;
+ size_t m_value_count;
+ value_index_map_type m_value_indices;
+
+ size_t m_time_stamp;
+ std::vector<node_data> m_values;
+};
+
+template<typename _ValueType, typename _ValueHashType>
+template<typename _Iter>
+depth_first_search<_ValueType,_ValueHashType>::depth_first_search(
+ const _Iter& begin, const _Iter& end,
+ const relations& rels, back_inserter handler) :
+ m_precedent_map(rels.get()),
+ m_handler(std::move(handler)),
+ m_value_count(std::distance(begin, end)),
+ m_time_stamp(0),
+ m_values(m_value_count)
+{
+ // Construct value node to index mapping.
+ size_t i = 0;
+ for (_Iter it = begin; it != end; ++it, ++i)
+ m_value_indices.insert(
+ typename value_index_map_type::value_type(*it, i));
+}
+
+template<typename _ValueType, typename _ValueHashType>
+void depth_first_search<_ValueType,_ValueHashType>::init()
+{
+ std::vector<node_data> values(m_value_count);
+
+ // Now, construct index to cell node mapping.
+ for (const auto& vi : m_value_indices)
+ values[vi.second].node = vi.first;
+
+ m_values.swap(values);
+ m_time_stamp = 0;
+}
+
+template<typename _ValueType, typename _ValueHashType>
+void depth_first_search<_ValueType,_ValueHashType>::run()
+{
+ init();
+
+ try
+ {
+ for (size_t i = 0; i < m_value_count; ++i)
+ if (m_values[i].color == white)
+ visit(i);
+ }
+ catch(const dfs_error& e)
+ {
+ std::cout << "dfs error: " << e.what() << std::endl;
+ }
+}
+
+template<typename _ValueType, typename _ValueHashType>
+void depth_first_search<_ValueType,_ValueHashType>::visit(size_t cell_index)
+{
+ value_type p = m_values[cell_index].node;
+ m_values[cell_index].color = gray;
+ m_values[cell_index].time_visited = ++m_time_stamp;
+
+ do
+ {
+ const precedent_cells_type* depends = get_precedent_cells(p);
+ if (!depends)
+ // No dependent cells.
+ break;
+
+ for (const value_type& dcell : *depends)
+ {
+ size_t dcell_id = get_cell_index(dcell);
+ if (m_values[dcell_id].color == white)
+ {
+ visit(dcell_id);
+ }
+ }
+ }
+ while (false);
+
+ m_values[cell_index].color = black;
+ m_values[cell_index].time_finished = ++m_time_stamp;
+ m_handler(m_values[cell_index].node);
+}
+
+template<typename _ValueType, typename _ValueHashType>
+size_t depth_first_search<_ValueType,_ValueHashType>::get_cell_index(const value_type& p) const
+{
+ typename value_index_map_type::const_iterator itr = m_value_indices.find(p);
+ if (itr == m_value_indices.end())
+ throw dfs_error("cell ptr to index mapping failed.");
+ return itr->second;
+}
+
+template<typename _ValueType, typename _ValueHashType>
+const typename depth_first_search<_ValueType,_ValueHashType>::precedent_cells_type*
+depth_first_search<_ValueType,_ValueHashType>::get_precedent_cells(value_type cell)
+{
+ typename precedent_map_type::const_iterator itr = m_precedent_map.find(cell);
+ if (itr == m_precedent_map.end())
+ // This cell has no dependent cells.
+ return nullptr;
+
+ return &itr->second;
+}
+
+}
+
+#endif
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/include/test_global.hpp b/src/include/test_global.hpp
new file mode 100644
index 0000000..1832f50
--- /dev/null
+++ b/src/include/test_global.hpp
@@ -0,0 +1,43 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#ifdef NDEBUG
+// release build
+#undef NDEBUG
+#include <cassert>
+#define NDEBUG
+#else
+// debug build
+#include <cassert>
+#endif
+
+#include <iostream>
+#include <chrono>
+#include <string>
+
+namespace ixion { namespace test {
+
+class stack_printer
+{
+public:
+ explicit stack_printer(std::string msg);
+ ~stack_printer();
+
+private:
+ double get_time() const;
+
+ std::string m_msg;
+ double m_start_time;
+};
+
+}}
+
+#define IXION_TEST_FUNC_SCOPE ixion::test::stack_printer __sp__(__func__)
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/ixion_formula_tokenizer.cpp b/src/ixion_formula_tokenizer.cpp
new file mode 100644
index 0000000..2fd429d
--- /dev/null
+++ b/src/ixion_formula_tokenizer.cpp
@@ -0,0 +1,156 @@
+/* -*- 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/.
+ */
+
+#ifndef IXION_FORMULA_PARSER_CPP
+#define IXION_FORMULA_PARSER_CPP
+
+#include <string>
+#include <vector>
+#include <iostream>
+
+#include <boost/program_options.hpp>
+
+#include "ixion/formula.hpp"
+#include "ixion/model_context.hpp"
+#include "ixion/formula_name_resolver.hpp"
+#include "ixion/config.hpp"
+
+using std::cout;
+using std::endl;
+
+std::vector<std::string> parse_sheet_names(const std::string& s)
+{
+ std::vector<std::string> names;
+
+ const char* p = s.data();
+ const char* p_end = p + s.size();
+ const char* p0 = nullptr;
+
+ for (; p != p_end; ++p)
+ {
+ if (!p0)
+ p0 = p;
+
+ if (*p == ',')
+ {
+ size_t n = std::distance(p0, p);
+ names.emplace_back(p0, n);
+ p0 = nullptr;
+ }
+ }
+
+ if (p0)
+ {
+ size_t n = std::distance(p0, p);
+ names.emplace_back(p0, n);
+ }
+
+ return names;
+}
+
+void tokenize_formula(const std::string& formula, const std::string& sheets)
+{
+ using namespace ixion;
+
+ model_context cxt;
+
+ for (const std::string& name : parse_sheet_names(sheets))
+ cxt.append_sheet(name);
+
+ std::unique_ptr<formula_name_resolver> resolver =
+ formula_name_resolver::get(formula_name_resolver_t::excel_a1, &cxt);
+
+ config cfg = cxt.get_config();
+ cfg.sep_function_arg = ',';
+ cxt.set_config(cfg);
+
+ abs_address_t pos;
+
+ formula_tokens_t tokens = parse_formula_string(cxt, pos, *resolver, formula);
+
+ cout << "* original formula string: " << formula << endl;
+
+ std::string normalized = print_formula_tokens(cxt, pos, *resolver, tokens);
+ cout << "* normalized formula string: " << normalized << endl;
+
+ cout << "* individual tokens:" << endl;
+ for (const auto& t : tokens)
+ cout << " * " << t << endl;
+}
+
+int main (int argc, char** argv)
+{
+ namespace po = ::boost::program_options;
+
+ po::options_description desc("Allowed options");
+ desc.add_options()
+ ("help,h", "print this help.")
+ ("sheets", po::value<std::string>(), "Sheet names.");
+
+ po::options_description hidden("Hidden options");
+ hidden.add_options()
+ ("formula-expression", po::value<std::string>(), "formula expression");
+
+ po::options_description cmd_opt;
+ cmd_opt.add(desc).add(hidden);
+
+ po::positional_options_description po_desc;
+ po_desc.add("formula-expression", -1);
+
+ po::variables_map vm;
+ try
+ {
+ po::store(
+ po::command_line_parser(argc, argv).options(cmd_opt).positional(po_desc).run(), vm);
+ po::notify(vm);
+ }
+ catch (const std::exception& e)
+ {
+ // Unknown options.
+ cout << e.what() << endl;
+ cout << desc;
+ return EXIT_FAILURE;
+ }
+
+ auto print_help = [desc]()
+ {
+ cout << "Usage: ixion-formula-tokenizer [options] FORMULA_EXPRESSION" << endl
+ << endl
+ << desc;
+ };
+
+ if (vm.count("help"))
+ {
+ print_help();
+ return EXIT_SUCCESS;
+ }
+
+ if (!vm.count("formula-expression"))
+ {
+ cout << "formula expression is not given." << endl;
+ print_help();
+ return EXIT_FAILURE;
+ }
+
+ try
+ {
+ std::string formula = vm["formula-expression"].as<std::string>();
+ std::string sheets = vm.count("sheets") ? vm["sheets"].as<std::string>() : std::string();
+ tokenize_formula(formula, sheets);
+ }
+ catch (const std::exception& e)
+ {
+ cout << "Failed to parse formula expression: " << e.what() << endl;
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/ixion_parser.cpp b/src/ixion_parser.cpp
new file mode 100644
index 0000000..d6ace96
--- /dev/null
+++ b/src/ixion_parser.cpp
@@ -0,0 +1,134 @@
+/* -*- 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 <string>
+#include <vector>
+#include <iostream>
+#include <thread>
+
+#include <boost/program_options.hpp>
+
+using namespace std;
+using namespace ixion;
+
+namespace {
+
+class parse_file
+{
+ const size_t m_thread_count;
+public:
+ parse_file(size_t thread_count) : m_thread_count(thread_count) {}
+
+ void operator() (const string& fpath) const
+ {
+ double start_time = get_current_time();
+ cout << detail::get_formula_result_output_separator() << endl;
+ cout << "parsing " << fpath << endl;
+
+ try
+ {
+ model_parser parser(fpath, m_thread_count);
+ parser.parse();
+ }
+ catch (const exception& e)
+ {
+ cerr << e.what() << endl;
+ cerr << "failed to parse " << fpath << endl;
+ throw;
+ }
+
+ cout << detail::get_formula_result_output_separator() << endl;
+ cout << "(duration: " << get_current_time() - start_time << " sec)" << endl;
+ cout << detail::get_formula_result_output_separator() << endl;
+ }
+};
+
+const char* help_thread =
+"Specify the number of threads to use for calculation. Note that the number "
+"specified by this option corresponds with the number of calculation threads "
+"i.e. those child threads that perform cell interpretations. The main thread "
+"does not perform any calculations; instead, it creates a new child thread to "
+"manage the calculation threads, the number of which is specified by the arg. "
+"Therefore, the total number of threads used by this program will be arg + 1."
+;
+
+}
+
+int main (int argc, char** argv)
+{
+ namespace po = ::boost::program_options;
+
+ size_t thread_count = 0;
+ po::options_description desc("Allowed options");
+ desc.add_options()
+ ("help,h", "Print this help.")
+ ("thread,t", po::value<size_t>(), help_thread);
+
+ po::options_description hidden("Hidden options");
+ hidden.add_options()
+ ("input-file", po::value<std::vector<std::string>>(), "input file");
+
+ po::options_description cmd_opt;
+ cmd_opt.add(desc).add(hidden);
+
+ po::positional_options_description po_desc;
+ po_desc.add("input-file", -1);
+
+ po::variables_map vm;
+ try
+ {
+ po::store(
+ po::command_line_parser(argc, argv).options(cmd_opt).positional(po_desc).run(), vm);
+ po::notify(vm);
+ }
+ catch (const exception& e)
+ {
+ // Unknown options.
+ cout << e.what() << endl;
+ cout << desc;
+ return EXIT_FAILURE;
+ }
+
+ if (vm.count("help"))
+ {
+ cout << "Usage: ixion-parser [options] FILE1 FILE2 ..." << endl
+ << endl
+ << "The FILE must contain the definitions of cells according to the cell definition rule." << endl << endl
+ << desc;
+ return EXIT_SUCCESS;
+ }
+
+ if (vm.count("thread"))
+ thread_count = vm["thread"].as<size_t>();
+
+ vector<string> files;
+ if (vm.count("input-file"))
+ files = vm["input-file"].as< vector<string> >();
+
+ if (thread_count > 0)
+ {
+ cout << "Using " << thread_count << " threads" << endl;
+ cout << "Number of CPUS: " << std::thread::hardware_concurrency() << endl;
+ }
+
+ try
+ {
+ // Parse all files one at a time.
+ for_each(files.begin(), files.end(), parse_file(thread_count));
+ }
+ catch (const exception&)
+ {
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/ixion_sorter.cpp b/src/ixion_sorter.cpp
new file mode 100644
index 0000000..619cf9e
--- /dev/null
+++ b/src/ixion_sorter.cpp
@@ -0,0 +1,93 @@
+/* -*- 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 "sort_input_parser.hpp"
+
+#include <cstdlib>
+#include <string>
+#include <vector>
+#include <iostream>
+
+#include <boost/program_options.hpp>
+
+using namespace std;
+
+namespace po = ::boost::program_options;
+
+void print_help(const po::options_description& desc)
+{
+ cout << "Usage: ixion-parser [options] FILE" << endl
+ << endl
+ << "FILE must contain a list of dependencies." << endl << endl
+ << desc;
+}
+
+int main (int argc, char** argv)
+{
+ po::options_description desc("Allowed options");
+ desc.add_options()
+ ("help,h", "print this help.");
+
+ po::options_description hidden("Hidden options");
+ hidden.add_options()
+ ("input-file", po::value<vector<string>>(), "input file");
+
+ po::options_description cmd_opt;
+ cmd_opt.add(desc).add(hidden);
+
+ po::positional_options_description po_desc;
+ po_desc.add("input-file", -1);
+
+ po::variables_map vm;
+ try
+ {
+ po::store(
+ po::command_line_parser(argc, argv).options(cmd_opt).positional(po_desc).run(), vm);
+ po::notify(vm);
+ }
+ catch (const exception& e)
+ {
+ // Unknown options.
+ cerr << e.what() << endl;
+ print_help(desc);
+ return EXIT_FAILURE;
+ }
+
+ if (vm.count("help"))
+ {
+ print_help(desc);
+ return EXIT_SUCCESS;
+ }
+
+ vector<string> files;
+ if (vm.count("input-file"))
+ files = vm["input-file"].as<vector<string>>();
+
+ if (files.size() != 1)
+ {
+ cerr << "Takes exactly one input file." << endl;
+ print_help(desc);
+ return EXIT_FAILURE;
+ }
+
+ const string& filepath = files[0];
+ try
+ {
+ ::ixion::sort_input_parser parser(filepath);
+ parser.parse();
+ parser.print();
+ }
+ catch (const exception& e)
+ {
+ // Unknown options.
+ cerr << e.what() << endl;
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/Makefile.am b/src/libixion/Makefile.am
new file mode 100644
index 0000000..9bdce5f
--- /dev/null
+++ b/src/libixion/Makefile.am
@@ -0,0 +1,164 @@
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/include \
+ -I$(top_srcdir)/src/include \
+ -DIXION_BUILD \
+ $(MDDS_CFLAGS) \
+ $(BOOST_CPPFLAGS)
+
+if BUILD_VULKAN
+
+AM_CPPFLAGS += -DBUILD_VULKAN
+
+endif # BUILD_VULKAN
+
+check_PROGRAMS = \
+ document-test \
+ general-test \
+ ixion-test-track-deps \
+ compute-engine-test \
+ dirty-cell-tracker-test \
+ name-resolver-test
+
+lib_LTLIBRARIES = libixion-@IXION_API_VERSION@.la
+libixion_@IXION_API_VERSION@_la_SOURCES = \
+ address.cpp \
+ address_iterator.cpp \
+ calc_status.hpp \
+ calc_status.cpp \
+ cell.cpp \
+ cell_access.cpp \
+ column_store_type.hpp \
+ compute_engine.cpp \
+ config.cpp \
+ debug.hpp \
+ debug.cpp \
+ dirty_cell_tracker.cpp \
+ document.cpp \
+ exceptions.cpp \
+ formula.cpp \
+ formula_calc.cpp \
+ formula_function_opcode.cpp \
+ formula_functions.hpp \
+ formula_functions.cpp \
+ formula_interpreter.hpp \
+ formula_interpreter.cpp \
+ formula_lexer.hpp \
+ formula_lexer.cpp \
+ formula_name_resolver.cpp \
+ formula_parser.hpp \
+ formula_parser.cpp \
+ formula_result.cpp \
+ formula_tokens.cpp \
+ formula_value_stack.hpp \
+ formula_value_stack.cpp \
+ global.cpp \
+ impl_types.hpp \
+ impl_types.cpp \
+ info.cpp \
+ lexer_tokens.hpp \
+ lexer_tokens.cpp \
+ matrix.cpp \
+ model_context.cpp \
+ model_context_impl.hpp \
+ model_context_impl.cpp \
+ model_iterator.cpp \
+ model_types.hpp \
+ model_types.cpp \
+ module.cpp \
+ named_expressions_iterator.cpp \
+ queue_entry.hpp \
+ queue_entry.cpp \
+ table.cpp \
+ types.cpp \
+ utf8.hpp \
+ utf8.cpp \
+ utils.hpp \
+ utils.cpp \
+ workbook.hpp \
+ workbook.cpp \
+ interface.cpp
+
+if BUILD_VULKAN
+
+# VULKAN module
+
+pkglib_LTLIBRARIES = ixion-@IXION_API_VERSION@-vulkan.la
+ixion_@IXION_API_VERSION@_vulkan_la_SOURCES = \
+ compute_engine_vulkan.hpp \
+ compute_engine_vulkan.cpp \
+ vulkan_obj.hpp \
+ vulkan_obj.cpp
+
+ixion_@IXION_API_VERSION@_vulkan_la_LDFLAGS = \
+ -module -avoid-version -export-symbols-regex \
+ register_module
+
+ixion_@IXION_API_VERSION@_vulkan_la_LIBADD = \
+ libixion-@IXION_API_VERSION@.la \
+ $(VULKAN_LIBS)
+
+ixion_@IXION_API_VERSION@_vulkan_la_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ $(VULKAN_CPPFLAGS)
+
+endif # BUILD_VULKAN
+
+if IXION_THREADS
+
+libixion_@IXION_API_VERSION@_la_SOURCES += \
+ cell_queue_manager.hpp \
+ cell_queue_manager.cpp
+
+endif
+
+libixion_@IXION_API_VERSION@_la_LDFLAGS = \
+ -no-undefined \
+ -Wl,-rpath,'$$ORIGIN' -Wl,-rpath,'$$ORIGIN/$(PACKAGE)' \
+ $(BOOST_FILESYSTEM_LDFLAGS)
+
+libixion_@IXION_API_VERSION@_la_LIBADD = \
+ $(BOOST_FILESYSTEM_LIBS)
+
+document_test_SOURCES = document_test.cpp
+document_test_LDADD = \
+ libixion-@IXION_API_VERSION@.la \
+ ../test/libixion-test.a
+
+general_test_SOURCES = general_test.cpp
+general_test_LDADD = \
+ libixion-@IXION_API_VERSION@.la \
+ ../test/libixion-test.a \
+ $(BOOST_PROGRAM_OPTIONS_LIBS)
+
+name_resolver_test_SOURCES = name_resolver_test.cpp
+name_resolver_test_LDADD = \
+ libixion-@IXION_API_VERSION@.la \
+ ../test/libixion-test.a \
+ $(BOOST_PROGRAM_OPTIONS_LIBS)
+
+ixion_test_track_deps_SOURCES = ixion_test_track_deps.cpp
+ixion_test_track_deps_LDADD = \
+ libixion-@IXION_API_VERSION@.la \
+ ../test/libixion-test.a \
+ $(BOOST_PROGRAM_OPTIONS_LIBS)
+
+compute_engine_test_SOURCES = compute_engine_test.cpp
+compute_engine_test_LDADD = \
+ ../test/libixion-test.a \
+ libixion-@IXION_API_VERSION@.la
+
+dirty_cell_tracker_test_SOURCES = dirty_cell_tracker_test.cpp
+dirty_cell_tracker_test_LDADD = \
+ ../test/libixion-test.a \
+ libixion-@IXION_API_VERSION@.la
+
+AM_TESTS_ENVIRONMENT =
+
+TESTS = \
+ document-test \
+ general-test \
+ ixion-test-track-deps \
+ compute-engine-test \
+ dirty-cell-tracker-test \
+ name-resolver-test
diff --git a/src/libixion/Makefile.in b/src/libixion/Makefile.in
new file mode 100644
index 0000000..baf3327
--- /dev/null
+++ b/src/libixion/Makefile.in
@@ -0,0 +1,1552 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+@BUILD_VULKAN_TRUE@am__append_1 = -DBUILD_VULKAN
+check_PROGRAMS = document-test$(EXEEXT) general-test$(EXEEXT) \
+ ixion-test-track-deps$(EXEEXT) compute-engine-test$(EXEEXT) \
+ dirty-cell-tracker-test$(EXEEXT) name-resolver-test$(EXEEXT)
+@IXION_THREADS_TRUE@am__append_2 = \
+@IXION_THREADS_TRUE@ cell_queue_manager.hpp \
+@IXION_THREADS_TRUE@ cell_queue_manager.cpp
+
+TESTS = document-test$(EXEEXT) general-test$(EXEEXT) \
+ ixion-test-track-deps$(EXEEXT) compute-engine-test$(EXEEXT) \
+ dirty-cell-tracker-test$(EXEEXT) name-resolver-test$(EXEEXT)
+subdir = src/libixion
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \
+ $(top_srcdir)/m4/ax_cxx_compile_stdcxx_17.m4 \
+ $(top_srcdir)/m4/boost.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \
+ $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES = constants.inl
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(libdir)" "$(DESTDIR)$(pkglibdir)"
+LTLIBRARIES = $(lib_LTLIBRARIES) $(pkglib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+@BUILD_VULKAN_TRUE@ixion_@IXION_API_VERSION@_vulkan_la_DEPENDENCIES = \
+@BUILD_VULKAN_TRUE@ libixion-@IXION_API_VERSION@.la \
+@BUILD_VULKAN_TRUE@ $(am__DEPENDENCIES_1)
+am__ixion_@IXION_API_VERSION@_vulkan_la_SOURCES_DIST = \
+ compute_engine_vulkan.hpp compute_engine_vulkan.cpp \
+ vulkan_obj.hpp vulkan_obj.cpp
+@BUILD_VULKAN_TRUE@am_ixion_@IXION_API_VERSION@_vulkan_la_OBJECTS = ixion_@IXION_API_VERSION@_vulkan_la-compute_engine_vulkan.lo \
+@BUILD_VULKAN_TRUE@ ixion_@IXION_API_VERSION@_vulkan_la-vulkan_obj.lo
+ixion_@IXION_API_VERSION@_vulkan_la_OBJECTS = \
+ $(am_ixion_@IXION_API_VERSION@_vulkan_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+ixion_@IXION_API_VERSION@_vulkan_la_LINK = $(LIBTOOL) $(AM_V_lt) \
+ --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \
+ $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) \
+ $(ixion_@IXION_API_VERSION@_vulkan_la_LDFLAGS) $(LDFLAGS) -o \
+ $@
+@BUILD_VULKAN_TRUE@am_ixion_@IXION_API_VERSION@_vulkan_la_rpath = \
+@BUILD_VULKAN_TRUE@ -rpath $(pkglibdir)
+libixion_@IXION_API_VERSION@_la_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am__libixion_@IXION_API_VERSION@_la_SOURCES_DIST = address.cpp \
+ address_iterator.cpp calc_status.hpp calc_status.cpp cell.cpp \
+ cell_access.cpp column_store_type.hpp compute_engine.cpp \
+ config.cpp debug.hpp debug.cpp dirty_cell_tracker.cpp \
+ document.cpp exceptions.cpp formula.cpp formula_calc.cpp \
+ formula_function_opcode.cpp formula_functions.hpp \
+ formula_functions.cpp formula_interpreter.hpp \
+ formula_interpreter.cpp formula_lexer.hpp formula_lexer.cpp \
+ formula_name_resolver.cpp formula_parser.hpp \
+ formula_parser.cpp formula_result.cpp formula_tokens.cpp \
+ formula_value_stack.hpp formula_value_stack.cpp global.cpp \
+ impl_types.hpp impl_types.cpp info.cpp lexer_tokens.hpp \
+ lexer_tokens.cpp matrix.cpp model_context.cpp \
+ model_context_impl.hpp model_context_impl.cpp \
+ model_iterator.cpp model_types.hpp model_types.cpp module.cpp \
+ named_expressions_iterator.cpp queue_entry.hpp queue_entry.cpp \
+ table.cpp types.cpp utf8.hpp utf8.cpp utils.hpp utils.cpp \
+ workbook.hpp workbook.cpp interface.cpp cell_queue_manager.hpp \
+ cell_queue_manager.cpp
+@IXION_THREADS_TRUE@am__objects_1 = cell_queue_manager.lo
+am_libixion_@IXION_API_VERSION@_la_OBJECTS = address.lo \
+ address_iterator.lo calc_status.lo cell.lo cell_access.lo \
+ compute_engine.lo config.lo debug.lo dirty_cell_tracker.lo \
+ document.lo exceptions.lo formula.lo formula_calc.lo \
+ formula_function_opcode.lo formula_functions.lo \
+ formula_interpreter.lo formula_lexer.lo \
+ formula_name_resolver.lo formula_parser.lo formula_result.lo \
+ formula_tokens.lo formula_value_stack.lo global.lo \
+ impl_types.lo info.lo lexer_tokens.lo matrix.lo \
+ model_context.lo model_context_impl.lo model_iterator.lo \
+ model_types.lo module.lo named_expressions_iterator.lo \
+ queue_entry.lo table.lo types.lo utf8.lo utils.lo workbook.lo \
+ interface.lo $(am__objects_1)
+libixion_@IXION_API_VERSION@_la_OBJECTS = \
+ $(am_libixion_@IXION_API_VERSION@_la_OBJECTS)
+libixion_@IXION_API_VERSION@_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) \
+ $(libixion_@IXION_API_VERSION@_la_LDFLAGS) $(LDFLAGS) -o $@
+am_compute_engine_test_OBJECTS = compute_engine_test.$(OBJEXT)
+compute_engine_test_OBJECTS = $(am_compute_engine_test_OBJECTS)
+compute_engine_test_DEPENDENCIES = ../test/libixion-test.a \
+ libixion-@IXION_API_VERSION@.la
+am_dirty_cell_tracker_test_OBJECTS = \
+ dirty_cell_tracker_test.$(OBJEXT)
+dirty_cell_tracker_test_OBJECTS = \
+ $(am_dirty_cell_tracker_test_OBJECTS)
+dirty_cell_tracker_test_DEPENDENCIES = ../test/libixion-test.a \
+ libixion-@IXION_API_VERSION@.la
+am_document_test_OBJECTS = document_test.$(OBJEXT)
+document_test_OBJECTS = $(am_document_test_OBJECTS)
+document_test_DEPENDENCIES = libixion-@IXION_API_VERSION@.la \
+ ../test/libixion-test.a
+am_general_test_OBJECTS = general_test.$(OBJEXT)
+general_test_OBJECTS = $(am_general_test_OBJECTS)
+general_test_DEPENDENCIES = libixion-@IXION_API_VERSION@.la \
+ ../test/libixion-test.a $(am__DEPENDENCIES_1)
+am_ixion_test_track_deps_OBJECTS = ixion_test_track_deps.$(OBJEXT)
+ixion_test_track_deps_OBJECTS = $(am_ixion_test_track_deps_OBJECTS)
+ixion_test_track_deps_DEPENDENCIES = libixion-@IXION_API_VERSION@.la \
+ ../test/libixion-test.a $(am__DEPENDENCIES_1)
+am_name_resolver_test_OBJECTS = name_resolver_test.$(OBJEXT)
+name_resolver_test_OBJECTS = $(am_name_resolver_test_OBJECTS)
+name_resolver_test_DEPENDENCIES = libixion-@IXION_API_VERSION@.la \
+ ../test/libixion-test.a $(am__DEPENDENCIES_1)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/address.Plo \
+ ./$(DEPDIR)/address_iterator.Plo ./$(DEPDIR)/calc_status.Plo \
+ ./$(DEPDIR)/cell.Plo ./$(DEPDIR)/cell_access.Plo \
+ ./$(DEPDIR)/cell_queue_manager.Plo \
+ ./$(DEPDIR)/compute_engine.Plo \
+ ./$(DEPDIR)/compute_engine_test.Po ./$(DEPDIR)/config.Plo \
+ ./$(DEPDIR)/debug.Plo ./$(DEPDIR)/dirty_cell_tracker.Plo \
+ ./$(DEPDIR)/dirty_cell_tracker_test.Po \
+ ./$(DEPDIR)/document.Plo ./$(DEPDIR)/document_test.Po \
+ ./$(DEPDIR)/exceptions.Plo ./$(DEPDIR)/formula.Plo \
+ ./$(DEPDIR)/formula_calc.Plo \
+ ./$(DEPDIR)/formula_function_opcode.Plo \
+ ./$(DEPDIR)/formula_functions.Plo \
+ ./$(DEPDIR)/formula_interpreter.Plo \
+ ./$(DEPDIR)/formula_lexer.Plo \
+ ./$(DEPDIR)/formula_name_resolver.Plo \
+ ./$(DEPDIR)/formula_parser.Plo ./$(DEPDIR)/formula_result.Plo \
+ ./$(DEPDIR)/formula_tokens.Plo \
+ ./$(DEPDIR)/formula_value_stack.Plo \
+ ./$(DEPDIR)/general_test.Po ./$(DEPDIR)/global.Plo \
+ ./$(DEPDIR)/impl_types.Plo ./$(DEPDIR)/info.Plo \
+ ./$(DEPDIR)/interface.Plo \
+ ./$(DEPDIR)/ixion_@IXION_API_VERSION@_vulkan_la-compute_engine_vulkan.Plo \
+ ./$(DEPDIR)/ixion_@IXION_API_VERSION@_vulkan_la-vulkan_obj.Plo \
+ ./$(DEPDIR)/ixion_test_track_deps.Po \
+ ./$(DEPDIR)/lexer_tokens.Plo ./$(DEPDIR)/matrix.Plo \
+ ./$(DEPDIR)/model_context.Plo \
+ ./$(DEPDIR)/model_context_impl.Plo \
+ ./$(DEPDIR)/model_iterator.Plo ./$(DEPDIR)/model_types.Plo \
+ ./$(DEPDIR)/module.Plo ./$(DEPDIR)/name_resolver_test.Po \
+ ./$(DEPDIR)/named_expressions_iterator.Plo \
+ ./$(DEPDIR)/queue_entry.Plo ./$(DEPDIR)/table.Plo \
+ ./$(DEPDIR)/types.Plo ./$(DEPDIR)/utf8.Plo \
+ ./$(DEPDIR)/utils.Plo ./$(DEPDIR)/workbook.Plo
+am__mv = mv -f
+CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS)
+LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CXXFLAGS) $(CXXFLAGS)
+AM_V_CXX = $(am__v_CXX_@AM_V@)
+am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@)
+am__v_CXX_0 = @echo " CXX " $@;
+am__v_CXX_1 =
+CXXLD = $(CXX)
+CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
+ $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CXXLD = $(am__v_CXXLD_@AM_V@)
+am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@)
+am__v_CXXLD_0 = @echo " CXXLD " $@;
+am__v_CXXLD_1 =
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(ixion_@IXION_API_VERSION@_vulkan_la_SOURCES) \
+ $(libixion_@IXION_API_VERSION@_la_SOURCES) \
+ $(compute_engine_test_SOURCES) \
+ $(dirty_cell_tracker_test_SOURCES) $(document_test_SOURCES) \
+ $(general_test_SOURCES) $(ixion_test_track_deps_SOURCES) \
+ $(name_resolver_test_SOURCES)
+DIST_SOURCES = \
+ $(am__ixion_@IXION_API_VERSION@_vulkan_la_SOURCES_DIST) \
+ $(am__libixion_@IXION_API_VERSION@_la_SOURCES_DIST) \
+ $(compute_engine_test_SOURCES) \
+ $(dirty_cell_tracker_test_SOURCES) $(document_test_SOURCES) \
+ $(general_test_SOURCES) $(ixion_test_track_deps_SOURCES) \
+ $(name_resolver_test_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__tty_colors_dummy = \
+ mgn= red= grn= lgn= blu= brg= std=; \
+ am__color_tests=no
+am__tty_colors = { \
+ $(am__tty_colors_dummy); \
+ if test "X$(AM_COLOR_TESTS)" = Xno; then \
+ am__color_tests=no; \
+ elif test "X$(AM_COLOR_TESTS)" = Xalways; then \
+ am__color_tests=yes; \
+ elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \
+ am__color_tests=yes; \
+ fi; \
+ if test $$am__color_tests = yes; then \
+ red=''; \
+ grn=''; \
+ lgn=''; \
+ blu=''; \
+ mgn=''; \
+ brg=''; \
+ std=''; \
+ fi; \
+}
+am__recheck_rx = ^[ ]*:recheck:[ ]*
+am__global_test_result_rx = ^[ ]*:global-test-result:[ ]*
+am__copy_in_global_log_rx = ^[ ]*:copy-in-global-log:[ ]*
+# A command that, given a newline-separated list of test names on the
+# standard input, print the name of the tests that are to be re-run
+# upon "make recheck".
+am__list_recheck_tests = $(AWK) '{ \
+ recheck = 1; \
+ while ((rc = (getline line < ($$0 ".trs"))) != 0) \
+ { \
+ if (rc < 0) \
+ { \
+ if ((getline line2 < ($$0 ".log")) < 0) \
+ recheck = 0; \
+ break; \
+ } \
+ else if (line ~ /$(am__recheck_rx)[nN][Oo]/) \
+ { \
+ recheck = 0; \
+ break; \
+ } \
+ else if (line ~ /$(am__recheck_rx)[yY][eE][sS]/) \
+ { \
+ break; \
+ } \
+ }; \
+ if (recheck) \
+ print $$0; \
+ close ($$0 ".trs"); \
+ close ($$0 ".log"); \
+}'
+# A command that, given a newline-separated list of test names on the
+# standard input, create the global log from their .trs and .log files.
+am__create_global_log = $(AWK) ' \
+function fatal(msg) \
+{ \
+ print "fatal: making $@: " msg | "cat >&2"; \
+ exit 1; \
+} \
+function rst_section(header) \
+{ \
+ print header; \
+ len = length(header); \
+ for (i = 1; i <= len; i = i + 1) \
+ printf "="; \
+ printf "\n\n"; \
+} \
+{ \
+ copy_in_global_log = 1; \
+ global_test_result = "RUN"; \
+ while ((rc = (getline line < ($$0 ".trs"))) != 0) \
+ { \
+ if (rc < 0) \
+ fatal("failed to read from " $$0 ".trs"); \
+ if (line ~ /$(am__global_test_result_rx)/) \
+ { \
+ sub("$(am__global_test_result_rx)", "", line); \
+ sub("[ ]*$$", "", line); \
+ global_test_result = line; \
+ } \
+ else if (line ~ /$(am__copy_in_global_log_rx)[nN][oO]/) \
+ copy_in_global_log = 0; \
+ }; \
+ if (copy_in_global_log) \
+ { \
+ rst_section(global_test_result ": " $$0); \
+ while ((rc = (getline line < ($$0 ".log"))) != 0) \
+ { \
+ if (rc < 0) \
+ fatal("failed to read from " $$0 ".log"); \
+ print line; \
+ }; \
+ printf "\n"; \
+ }; \
+ close ($$0 ".trs"); \
+ close ($$0 ".log"); \
+}'
+# Restructured Text title.
+am__rst_title = { sed 's/.*/ & /;h;s/./=/g;p;x;s/ *$$//;p;g' && echo; }
+# Solaris 10 'make', and several other traditional 'make' implementations,
+# pass "-e" to $(SHELL), and POSIX 2008 even requires this. Work around it
+# by disabling -e (using the XSI extension "set +e") if it's set.
+am__sh_e_setup = case $$- in *e*) set +e;; esac
+# Default flags passed to test drivers.
+am__common_driver_flags = \
+ --color-tests "$$am__color_tests" \
+ --enable-hard-errors "$$am__enable_hard_errors" \
+ --expect-failure "$$am__expect_failure"
+# To be inserted before the command running the test. Creates the
+# directory for the log if needed. Stores in $dir the directory
+# containing $f, in $tst the test, in $log the log. Executes the
+# developer- defined test setup AM_TESTS_ENVIRONMENT (if any), and
+# passes TESTS_ENVIRONMENT. Set up options for the wrapper that
+# will run the test scripts (or their associated LOG_COMPILER, if
+# thy have one).
+am__check_pre = \
+$(am__sh_e_setup); \
+$(am__vpath_adj_setup) $(am__vpath_adj) \
+$(am__tty_colors); \
+srcdir=$(srcdir); export srcdir; \
+case "$@" in \
+ */*) am__odir=`echo "./$@" | sed 's|/[^/]*$$||'`;; \
+ *) am__odir=.;; \
+esac; \
+test "x$$am__odir" = x"." || test -d "$$am__odir" \
+ || $(MKDIR_P) "$$am__odir" || exit $$?; \
+if test -f "./$$f"; then dir=./; \
+elif test -f "$$f"; then dir=; \
+else dir="$(srcdir)/"; fi; \
+tst=$$dir$$f; log='$@'; \
+if test -n '$(DISABLE_HARD_ERRORS)'; then \
+ am__enable_hard_errors=no; \
+else \
+ am__enable_hard_errors=yes; \
+fi; \
+case " $(XFAIL_TESTS) " in \
+ *[\ \ ]$$f[\ \ ]* | *[\ \ ]$$dir$$f[\ \ ]*) \
+ am__expect_failure=yes;; \
+ *) \
+ am__expect_failure=no;; \
+esac; \
+$(AM_TESTS_ENVIRONMENT) $(TESTS_ENVIRONMENT)
+# A shell command to get the names of the tests scripts with any registered
+# extension removed (i.e., equivalently, the names of the test logs, with
+# the '.log' extension removed). The result is saved in the shell variable
+# '$bases'. This honors runtime overriding of TESTS and TEST_LOGS. Sadly,
+# we cannot use something simpler, involving e.g., "$(TEST_LOGS:.log=)",
+# since that might cause problem with VPATH rewrites for suffix-less tests.
+# See also 'test-harness-vpath-rewrite.sh' and 'test-trs-basic.sh'.
+am__set_TESTS_bases = \
+ bases='$(TEST_LOGS)'; \
+ bases=`for i in $$bases; do echo $$i; done | sed 's/\.log$$//'`; \
+ bases=`echo $$bases`
+AM_TESTSUITE_SUMMARY_HEADER = ' for $(PACKAGE_STRING)'
+RECHECK_LOGS = $(TEST_LOGS)
+AM_RECURSIVE_TARGETS = check recheck
+TEST_SUITE_LOG = test-suite.log
+TEST_EXTENSIONS = @EXEEXT@ .test
+LOG_DRIVER = $(SHELL) $(top_srcdir)/test-driver
+LOG_COMPILE = $(LOG_COMPILER) $(AM_LOG_FLAGS) $(LOG_FLAGS)
+am__set_b = \
+ case '$@' in \
+ */*) \
+ case '$*' in \
+ */*) b='$*';; \
+ *) b=`echo '$@' | sed 's/\.log$$//'`; \
+ esac;; \
+ *) \
+ b='$*';; \
+ esac
+am__test_logs1 = $(TESTS:=.log)
+am__test_logs2 = $(am__test_logs1:@EXEEXT@.log=.log)
+TEST_LOGS = $(am__test_logs2:.test.log=.log)
+TEST_LOG_DRIVER = $(SHELL) $(top_srcdir)/test-driver
+TEST_LOG_COMPILE = $(TEST_LOG_COMPILER) $(AM_TEST_LOG_FLAGS) \
+ $(TEST_LOG_FLAGS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/constants.inl.in \
+ $(top_srcdir)/depcomp $(top_srcdir)/test-driver
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AS = @AS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_CPPFLAGS = @BOOST_CPPFLAGS@
+BOOST_FILESYSTEM_LDFLAGS = @BOOST_FILESYSTEM_LDFLAGS@
+BOOST_FILESYSTEM_LDPATH = @BOOST_FILESYSTEM_LDPATH@
+BOOST_FILESYSTEM_LIBS = @BOOST_FILESYSTEM_LIBS@
+BOOST_LDPATH = @BOOST_LDPATH@
+BOOST_PROGRAM_OPTIONS_LDFLAGS = @BOOST_PROGRAM_OPTIONS_LDFLAGS@
+BOOST_PROGRAM_OPTIONS_LDPATH = @BOOST_PROGRAM_OPTIONS_LDPATH@
+BOOST_PROGRAM_OPTIONS_LIBS = @BOOST_PROGRAM_OPTIONS_LIBS@
+BOOST_ROOT = @BOOST_ROOT@
+BOOST_SYSTEM_LDFLAGS = @BOOST_SYSTEM_LDFLAGS@
+BOOST_SYSTEM_LDPATH = @BOOST_SYSTEM_LDPATH@
+BOOST_SYSTEM_LIBS = @BOOST_SYSTEM_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+HAVE_CXX17 = @HAVE_CXX17@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+IXION_API_VERSION = @IXION_API_VERSION@
+IXION_MAJOR_API_VERSION = @IXION_MAJOR_API_VERSION@
+IXION_MAJOR_VERSION = @IXION_MAJOR_VERSION@
+IXION_MICRO_VERSION = @IXION_MICRO_VERSION@
+IXION_MINOR_API_VERSION = @IXION_MINOR_API_VERSION@
+IXION_MINOR_VERSION = @IXION_MINOR_VERSION@
+IXION_VERSION = @IXION_VERSION@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MDDS_CFLAGS = @MDDS_CFLAGS@
+MDDS_LIBS = @MDDS_LIBS@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POW_LIB = @POW_LIB@
+PYTHON = @PYTHON@
+PYTHON_CFLAGS = @PYTHON_CFLAGS@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_LIBS = @PYTHON_LIBS@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VERSION = @VERSION@
+VULKAN_CFLAGS = @VULKAN_CFLAGS@
+VULKAN_LIBS = @VULKAN_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_srcdir)/src/include \
+ -DIXION_BUILD $(MDDS_CFLAGS) $(BOOST_CPPFLAGS) $(am__append_1)
+lib_LTLIBRARIES = libixion-@IXION_API_VERSION@.la
+libixion_@IXION_API_VERSION@_la_SOURCES = address.cpp \
+ address_iterator.cpp calc_status.hpp calc_status.cpp cell.cpp \
+ cell_access.cpp column_store_type.hpp compute_engine.cpp \
+ config.cpp debug.hpp debug.cpp dirty_cell_tracker.cpp \
+ document.cpp exceptions.cpp formula.cpp formula_calc.cpp \
+ formula_function_opcode.cpp formula_functions.hpp \
+ formula_functions.cpp formula_interpreter.hpp \
+ formula_interpreter.cpp formula_lexer.hpp formula_lexer.cpp \
+ formula_name_resolver.cpp formula_parser.hpp \
+ formula_parser.cpp formula_result.cpp formula_tokens.cpp \
+ formula_value_stack.hpp formula_value_stack.cpp global.cpp \
+ impl_types.hpp impl_types.cpp info.cpp lexer_tokens.hpp \
+ lexer_tokens.cpp matrix.cpp model_context.cpp \
+ model_context_impl.hpp model_context_impl.cpp \
+ model_iterator.cpp model_types.hpp model_types.cpp module.cpp \
+ named_expressions_iterator.cpp queue_entry.hpp queue_entry.cpp \
+ table.cpp types.cpp utf8.hpp utf8.cpp utils.hpp utils.cpp \
+ workbook.hpp workbook.cpp interface.cpp $(am__append_2)
+
+# VULKAN module
+@BUILD_VULKAN_TRUE@pkglib_LTLIBRARIES = ixion-@IXION_API_VERSION@-vulkan.la
+@BUILD_VULKAN_TRUE@ixion_@IXION_API_VERSION@_vulkan_la_SOURCES = \
+@BUILD_VULKAN_TRUE@ compute_engine_vulkan.hpp \
+@BUILD_VULKAN_TRUE@ compute_engine_vulkan.cpp \
+@BUILD_VULKAN_TRUE@ vulkan_obj.hpp \
+@BUILD_VULKAN_TRUE@ vulkan_obj.cpp
+
+@BUILD_VULKAN_TRUE@ixion_@IXION_API_VERSION@_vulkan_la_LDFLAGS = \
+@BUILD_VULKAN_TRUE@ -module -avoid-version -export-symbols-regex \
+@BUILD_VULKAN_TRUE@ register_module
+
+@BUILD_VULKAN_TRUE@ixion_@IXION_API_VERSION@_vulkan_la_LIBADD = \
+@BUILD_VULKAN_TRUE@ libixion-@IXION_API_VERSION@.la \
+@BUILD_VULKAN_TRUE@ $(VULKAN_LIBS)
+
+@BUILD_VULKAN_TRUE@ixion_@IXION_API_VERSION@_vulkan_la_CPPFLAGS = \
+@BUILD_VULKAN_TRUE@ $(AM_CPPFLAGS) \
+@BUILD_VULKAN_TRUE@ $(VULKAN_CPPFLAGS)
+
+libixion_@IXION_API_VERSION@_la_LDFLAGS = \
+ -no-undefined \
+ -Wl,-rpath,'$$ORIGIN' -Wl,-rpath,'$$ORIGIN/$(PACKAGE)' \
+ $(BOOST_FILESYSTEM_LDFLAGS)
+
+libixion_@IXION_API_VERSION@_la_LIBADD = \
+ $(BOOST_FILESYSTEM_LIBS)
+
+document_test_SOURCES = document_test.cpp
+document_test_LDADD = \
+ libixion-@IXION_API_VERSION@.la \
+ ../test/libixion-test.a
+
+general_test_SOURCES = general_test.cpp
+general_test_LDADD = \
+ libixion-@IXION_API_VERSION@.la \
+ ../test/libixion-test.a \
+ $(BOOST_PROGRAM_OPTIONS_LIBS)
+
+name_resolver_test_SOURCES = name_resolver_test.cpp
+name_resolver_test_LDADD = \
+ libixion-@IXION_API_VERSION@.la \
+ ../test/libixion-test.a \
+ $(BOOST_PROGRAM_OPTIONS_LIBS)
+
+ixion_test_track_deps_SOURCES = ixion_test_track_deps.cpp
+ixion_test_track_deps_LDADD = \
+ libixion-@IXION_API_VERSION@.la \
+ ../test/libixion-test.a \
+ $(BOOST_PROGRAM_OPTIONS_LIBS)
+
+compute_engine_test_SOURCES = compute_engine_test.cpp
+compute_engine_test_LDADD = \
+ ../test/libixion-test.a \
+ libixion-@IXION_API_VERSION@.la
+
+dirty_cell_tracker_test_SOURCES = dirty_cell_tracker_test.cpp
+dirty_cell_tracker_test_LDADD = \
+ ../test/libixion-test.a \
+ libixion-@IXION_API_VERSION@.la
+
+AM_TESTS_ENVIRONMENT =
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .cpp .lo .log .o .obj .test .test$(EXEEXT) .trs
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/libixion/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/libixion/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+constants.inl: $(top_builddir)/config.status $(srcdir)/constants.inl.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+
+clean-checkPROGRAMS:
+ @list='$(check_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+install-libLTLIBRARIES: $(lib_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libdir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \
+ }
+
+uninstall-libLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \
+ done
+
+clean-libLTLIBRARIES:
+ -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES)
+ @list='$(lib_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+install-pkglibLTLIBRARIES: $(pkglib_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(pkglib_LTLIBRARIES)'; test -n "$(pkglibdir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkglibdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkglibdir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(pkglibdir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(pkglibdir)"; \
+ }
+
+uninstall-pkglibLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkglib_LTLIBRARIES)'; test -n "$(pkglibdir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(pkglibdir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(pkglibdir)/$$f"; \
+ done
+
+clean-pkglibLTLIBRARIES:
+ -test -z "$(pkglib_LTLIBRARIES)" || rm -f $(pkglib_LTLIBRARIES)
+ @list='$(pkglib_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+ixion-@IXION_API_VERSION@-vulkan.la: $(ixion_@IXION_API_VERSION@_vulkan_la_OBJECTS) $(ixion_@IXION_API_VERSION@_vulkan_la_DEPENDENCIES) $(EXTRA_ixion_@IXION_API_VERSION@_vulkan_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(ixion_@IXION_API_VERSION@_vulkan_la_LINK) $(am_ixion_@IXION_API_VERSION@_vulkan_la_rpath) $(ixion_@IXION_API_VERSION@_vulkan_la_OBJECTS) $(ixion_@IXION_API_VERSION@_vulkan_la_LIBADD) $(LIBS)
+
+libixion-@IXION_API_VERSION@.la: $(libixion_@IXION_API_VERSION@_la_OBJECTS) $(libixion_@IXION_API_VERSION@_la_DEPENDENCIES) $(EXTRA_libixion_@IXION_API_VERSION@_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libixion_@IXION_API_VERSION@_la_LINK) -rpath $(libdir) $(libixion_@IXION_API_VERSION@_la_OBJECTS) $(libixion_@IXION_API_VERSION@_la_LIBADD) $(LIBS)
+
+compute-engine-test$(EXEEXT): $(compute_engine_test_OBJECTS) $(compute_engine_test_DEPENDENCIES) $(EXTRA_compute_engine_test_DEPENDENCIES)
+ @rm -f compute-engine-test$(EXEEXT)
+ $(AM_V_CXXLD)$(CXXLINK) $(compute_engine_test_OBJECTS) $(compute_engine_test_LDADD) $(LIBS)
+
+dirty-cell-tracker-test$(EXEEXT): $(dirty_cell_tracker_test_OBJECTS) $(dirty_cell_tracker_test_DEPENDENCIES) $(EXTRA_dirty_cell_tracker_test_DEPENDENCIES)
+ @rm -f dirty-cell-tracker-test$(EXEEXT)
+ $(AM_V_CXXLD)$(CXXLINK) $(dirty_cell_tracker_test_OBJECTS) $(dirty_cell_tracker_test_LDADD) $(LIBS)
+
+document-test$(EXEEXT): $(document_test_OBJECTS) $(document_test_DEPENDENCIES) $(EXTRA_document_test_DEPENDENCIES)
+ @rm -f document-test$(EXEEXT)
+ $(AM_V_CXXLD)$(CXXLINK) $(document_test_OBJECTS) $(document_test_LDADD) $(LIBS)
+
+general-test$(EXEEXT): $(general_test_OBJECTS) $(general_test_DEPENDENCIES) $(EXTRA_general_test_DEPENDENCIES)
+ @rm -f general-test$(EXEEXT)
+ $(AM_V_CXXLD)$(CXXLINK) $(general_test_OBJECTS) $(general_test_LDADD) $(LIBS)
+
+ixion-test-track-deps$(EXEEXT): $(ixion_test_track_deps_OBJECTS) $(ixion_test_track_deps_DEPENDENCIES) $(EXTRA_ixion_test_track_deps_DEPENDENCIES)
+ @rm -f ixion-test-track-deps$(EXEEXT)
+ $(AM_V_CXXLD)$(CXXLINK) $(ixion_test_track_deps_OBJECTS) $(ixion_test_track_deps_LDADD) $(LIBS)
+
+name-resolver-test$(EXEEXT): $(name_resolver_test_OBJECTS) $(name_resolver_test_DEPENDENCIES) $(EXTRA_name_resolver_test_DEPENDENCIES)
+ @rm -f name-resolver-test$(EXEEXT)
+ $(AM_V_CXXLD)$(CXXLINK) $(name_resolver_test_OBJECTS) $(name_resolver_test_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/address.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/address_iterator.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/calc_status.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cell.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cell_access.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cell_queue_manager.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/compute_engine.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/compute_engine_test.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/config.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/debug.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dirty_cell_tracker.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dirty_cell_tracker_test.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/document.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/document_test.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/exceptions.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/formula.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/formula_calc.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/formula_function_opcode.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/formula_functions.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/formula_interpreter.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/formula_lexer.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/formula_name_resolver.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/formula_parser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/formula_result.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/formula_tokens.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/formula_value_stack.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/general_test.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/global.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/impl_types.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/info.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/interface.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ixion_@IXION_API_VERSION@_vulkan_la-compute_engine_vulkan.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ixion_@IXION_API_VERSION@_vulkan_la-vulkan_obj.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ixion_test_track_deps.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lexer_tokens.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/matrix.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/model_context.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/model_context_impl.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/model_iterator.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/model_types.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/module.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/name_resolver_test.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/named_expressions_iterator.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/queue_entry.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/table.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/types.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/utf8.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/utils.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/workbook.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.cpp.o:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $<
+
+.cpp.obj:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.cpp.lo:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $<
+
+ixion_@IXION_API_VERSION@_vulkan_la-compute_engine_vulkan.lo: compute_engine_vulkan.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ixion_@IXION_API_VERSION@_vulkan_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ixion_@IXION_API_VERSION@_vulkan_la-compute_engine_vulkan.lo -MD -MP -MF $(DEPDIR)/ixion_@IXION_API_VERSION@_vulkan_la-compute_engine_vulkan.Tpo -c -o ixion_@IXION_API_VERSION@_vulkan_la-compute_engine_vulkan.lo `test -f 'compute_engine_vulkan.cpp' || echo '$(srcdir)/'`compute_engine_vulkan.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/ixion_@IXION_API_VERSION@_vulkan_la-compute_engine_vulkan.Tpo $(DEPDIR)/ixion_@IXION_API_VERSION@_vulkan_la-compute_engine_vulkan.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='compute_engine_vulkan.cpp' object='ixion_@IXION_API_VERSION@_vulkan_la-compute_engine_vulkan.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ixion_@IXION_API_VERSION@_vulkan_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ixion_@IXION_API_VERSION@_vulkan_la-compute_engine_vulkan.lo `test -f 'compute_engine_vulkan.cpp' || echo '$(srcdir)/'`compute_engine_vulkan.cpp
+
+ixion_@IXION_API_VERSION@_vulkan_la-vulkan_obj.lo: vulkan_obj.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ixion_@IXION_API_VERSION@_vulkan_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ixion_@IXION_API_VERSION@_vulkan_la-vulkan_obj.lo -MD -MP -MF $(DEPDIR)/ixion_@IXION_API_VERSION@_vulkan_la-vulkan_obj.Tpo -c -o ixion_@IXION_API_VERSION@_vulkan_la-vulkan_obj.lo `test -f 'vulkan_obj.cpp' || echo '$(srcdir)/'`vulkan_obj.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/ixion_@IXION_API_VERSION@_vulkan_la-vulkan_obj.Tpo $(DEPDIR)/ixion_@IXION_API_VERSION@_vulkan_la-vulkan_obj.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='vulkan_obj.cpp' object='ixion_@IXION_API_VERSION@_vulkan_la-vulkan_obj.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ixion_@IXION_API_VERSION@_vulkan_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ixion_@IXION_API_VERSION@_vulkan_la-vulkan_obj.lo `test -f 'vulkan_obj.cpp' || echo '$(srcdir)/'`vulkan_obj.cpp
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+# Recover from deleted '.trs' file; this should ensure that
+# "rm -f foo.log; make foo.trs" re-run 'foo.test', and re-create
+# both 'foo.log' and 'foo.trs'. Break the recipe in two subshells
+# to avoid problems with "make -n".
+.log.trs:
+ rm -f $< $@
+ $(MAKE) $(AM_MAKEFLAGS) $<
+
+# Leading 'am--fnord' is there to ensure the list of targets does not
+# expand to empty, as could happen e.g. with make check TESTS=''.
+am--fnord $(TEST_LOGS) $(TEST_LOGS:.log=.trs): $(am__force_recheck)
+am--force-recheck:
+ @:
+
+$(TEST_SUITE_LOG): $(TEST_LOGS)
+ @$(am__set_TESTS_bases); \
+ am__f_ok () { test -f "$$1" && test -r "$$1"; }; \
+ redo_bases=`for i in $$bases; do \
+ am__f_ok $$i.trs && am__f_ok $$i.log || echo $$i; \
+ done`; \
+ if test -n "$$redo_bases"; then \
+ redo_logs=`for i in $$redo_bases; do echo $$i.log; done`; \
+ redo_results=`for i in $$redo_bases; do echo $$i.trs; done`; \
+ if $(am__make_dryrun); then :; else \
+ rm -f $$redo_logs && rm -f $$redo_results || exit 1; \
+ fi; \
+ fi; \
+ if test -n "$$am__remaking_logs"; then \
+ echo "fatal: making $(TEST_SUITE_LOG): possible infinite" \
+ "recursion detected" >&2; \
+ elif test -n "$$redo_logs"; then \
+ am__remaking_logs=yes $(MAKE) $(AM_MAKEFLAGS) $$redo_logs; \
+ fi; \
+ if $(am__make_dryrun); then :; else \
+ st=0; \
+ errmsg="fatal: making $(TEST_SUITE_LOG): failed to create"; \
+ for i in $$redo_bases; do \
+ test -f $$i.trs && test -r $$i.trs \
+ || { echo "$$errmsg $$i.trs" >&2; st=1; }; \
+ test -f $$i.log && test -r $$i.log \
+ || { echo "$$errmsg $$i.log" >&2; st=1; }; \
+ done; \
+ test $$st -eq 0 || exit 1; \
+ fi
+ @$(am__sh_e_setup); $(am__tty_colors); $(am__set_TESTS_bases); \
+ ws='[ ]'; \
+ results=`for b in $$bases; do echo $$b.trs; done`; \
+ test -n "$$results" || results=/dev/null; \
+ all=` grep "^$$ws*:test-result:" $$results | wc -l`; \
+ pass=` grep "^$$ws*:test-result:$$ws*PASS" $$results | wc -l`; \
+ fail=` grep "^$$ws*:test-result:$$ws*FAIL" $$results | wc -l`; \
+ skip=` grep "^$$ws*:test-result:$$ws*SKIP" $$results | wc -l`; \
+ xfail=`grep "^$$ws*:test-result:$$ws*XFAIL" $$results | wc -l`; \
+ xpass=`grep "^$$ws*:test-result:$$ws*XPASS" $$results | wc -l`; \
+ error=`grep "^$$ws*:test-result:$$ws*ERROR" $$results | wc -l`; \
+ if test `expr $$fail + $$xpass + $$error` -eq 0; then \
+ success=true; \
+ else \
+ success=false; \
+ fi; \
+ br='==================='; br=$$br$$br$$br$$br; \
+ result_count () \
+ { \
+ if test x"$$1" = x"--maybe-color"; then \
+ maybe_colorize=yes; \
+ elif test x"$$1" = x"--no-color"; then \
+ maybe_colorize=no; \
+ else \
+ echo "$@: invalid 'result_count' usage" >&2; exit 4; \
+ fi; \
+ shift; \
+ desc=$$1 count=$$2; \
+ if test $$maybe_colorize = yes && test $$count -gt 0; then \
+ color_start=$$3 color_end=$$std; \
+ else \
+ color_start= color_end=; \
+ fi; \
+ echo "$${color_start}# $$desc $$count$${color_end}"; \
+ }; \
+ create_testsuite_report () \
+ { \
+ result_count $$1 "TOTAL:" $$all "$$brg"; \
+ result_count $$1 "PASS: " $$pass "$$grn"; \
+ result_count $$1 "SKIP: " $$skip "$$blu"; \
+ result_count $$1 "XFAIL:" $$xfail "$$lgn"; \
+ result_count $$1 "FAIL: " $$fail "$$red"; \
+ result_count $$1 "XPASS:" $$xpass "$$red"; \
+ result_count $$1 "ERROR:" $$error "$$mgn"; \
+ }; \
+ { \
+ echo "$(PACKAGE_STRING): $(subdir)/$(TEST_SUITE_LOG)" | \
+ $(am__rst_title); \
+ create_testsuite_report --no-color; \
+ echo; \
+ echo ".. contents:: :depth: 2"; \
+ echo; \
+ for b in $$bases; do echo $$b; done \
+ | $(am__create_global_log); \
+ } >$(TEST_SUITE_LOG).tmp || exit 1; \
+ mv $(TEST_SUITE_LOG).tmp $(TEST_SUITE_LOG); \
+ if $$success; then \
+ col="$$grn"; \
+ else \
+ col="$$red"; \
+ test x"$$VERBOSE" = x || cat $(TEST_SUITE_LOG); \
+ fi; \
+ echo "$${col}$$br$${std}"; \
+ echo "$${col}Testsuite summary"$(AM_TESTSUITE_SUMMARY_HEADER)"$${std}"; \
+ echo "$${col}$$br$${std}"; \
+ create_testsuite_report --maybe-color; \
+ echo "$$col$$br$$std"; \
+ if $$success; then :; else \
+ echo "$${col}See $(subdir)/$(TEST_SUITE_LOG)$${std}"; \
+ if test -n "$(PACKAGE_BUGREPORT)"; then \
+ echo "$${col}Please report to $(PACKAGE_BUGREPORT)$${std}"; \
+ fi; \
+ echo "$$col$$br$$std"; \
+ fi; \
+ $$success || exit 1
+
+check-TESTS: $(check_PROGRAMS)
+ @list='$(RECHECK_LOGS)'; test -z "$$list" || rm -f $$list
+ @list='$(RECHECK_LOGS:.log=.trs)'; test -z "$$list" || rm -f $$list
+ @test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+ @set +e; $(am__set_TESTS_bases); \
+ log_list=`for i in $$bases; do echo $$i.log; done`; \
+ trs_list=`for i in $$bases; do echo $$i.trs; done`; \
+ log_list=`echo $$log_list`; trs_list=`echo $$trs_list`; \
+ $(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) TEST_LOGS="$$log_list"; \
+ exit $$?;
+recheck: all $(check_PROGRAMS)
+ @test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+ @set +e; $(am__set_TESTS_bases); \
+ bases=`for i in $$bases; do echo $$i; done \
+ | $(am__list_recheck_tests)` || exit 1; \
+ log_list=`for i in $$bases; do echo $$i.log; done`; \
+ log_list=`echo $$log_list`; \
+ $(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) \
+ am__force_recheck=am--force-recheck \
+ TEST_LOGS="$$log_list"; \
+ exit $$?
+document-test.log: document-test$(EXEEXT)
+ @p='document-test$(EXEEXT)'; \
+ b='document-test'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+general-test.log: general-test$(EXEEXT)
+ @p='general-test$(EXEEXT)'; \
+ b='general-test'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+ixion-test-track-deps.log: ixion-test-track-deps$(EXEEXT)
+ @p='ixion-test-track-deps$(EXEEXT)'; \
+ b='ixion-test-track-deps'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+compute-engine-test.log: compute-engine-test$(EXEEXT)
+ @p='compute-engine-test$(EXEEXT)'; \
+ b='compute-engine-test'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+dirty-cell-tracker-test.log: dirty-cell-tracker-test$(EXEEXT)
+ @p='dirty-cell-tracker-test$(EXEEXT)'; \
+ b='dirty-cell-tracker-test'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+name-resolver-test.log: name-resolver-test$(EXEEXT)
+ @p='name-resolver-test$(EXEEXT)'; \
+ b='name-resolver-test'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+.test.log:
+ @p='$<'; \
+ $(am__set_b); \
+ $(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+@am__EXEEXT_TRUE@.test$(EXEEXT).log:
+@am__EXEEXT_TRUE@ @p='$<'; \
+@am__EXEEXT_TRUE@ $(am__set_b); \
+@am__EXEEXT_TRUE@ $(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \
+@am__EXEEXT_TRUE@ --log-file $$b.log --trs-file $$b.trs \
+@am__EXEEXT_TRUE@ $(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \
+@am__EXEEXT_TRUE@ "$$tst" $(AM_TESTS_FD_REDIRECT)
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) $(check_PROGRAMS)
+ $(MAKE) $(AM_MAKEFLAGS) check-TESTS
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+install-checkPROGRAMS: install-libLTLIBRARIES
+
+install-pkglibLTLIBRARIES: install-libLTLIBRARIES
+
+installdirs:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(pkglibdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+ -test -z "$(TEST_LOGS)" || rm -f $(TEST_LOGS)
+ -test -z "$(TEST_LOGS:.log=.trs)" || rm -f $(TEST_LOGS:.log=.trs)
+ -test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-checkPROGRAMS clean-generic clean-libLTLIBRARIES \
+ clean-libtool clean-pkglibLTLIBRARIES mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/address.Plo
+ -rm -f ./$(DEPDIR)/address_iterator.Plo
+ -rm -f ./$(DEPDIR)/calc_status.Plo
+ -rm -f ./$(DEPDIR)/cell.Plo
+ -rm -f ./$(DEPDIR)/cell_access.Plo
+ -rm -f ./$(DEPDIR)/cell_queue_manager.Plo
+ -rm -f ./$(DEPDIR)/compute_engine.Plo
+ -rm -f ./$(DEPDIR)/compute_engine_test.Po
+ -rm -f ./$(DEPDIR)/config.Plo
+ -rm -f ./$(DEPDIR)/debug.Plo
+ -rm -f ./$(DEPDIR)/dirty_cell_tracker.Plo
+ -rm -f ./$(DEPDIR)/dirty_cell_tracker_test.Po
+ -rm -f ./$(DEPDIR)/document.Plo
+ -rm -f ./$(DEPDIR)/document_test.Po
+ -rm -f ./$(DEPDIR)/exceptions.Plo
+ -rm -f ./$(DEPDIR)/formula.Plo
+ -rm -f ./$(DEPDIR)/formula_calc.Plo
+ -rm -f ./$(DEPDIR)/formula_function_opcode.Plo
+ -rm -f ./$(DEPDIR)/formula_functions.Plo
+ -rm -f ./$(DEPDIR)/formula_interpreter.Plo
+ -rm -f ./$(DEPDIR)/formula_lexer.Plo
+ -rm -f ./$(DEPDIR)/formula_name_resolver.Plo
+ -rm -f ./$(DEPDIR)/formula_parser.Plo
+ -rm -f ./$(DEPDIR)/formula_result.Plo
+ -rm -f ./$(DEPDIR)/formula_tokens.Plo
+ -rm -f ./$(DEPDIR)/formula_value_stack.Plo
+ -rm -f ./$(DEPDIR)/general_test.Po
+ -rm -f ./$(DEPDIR)/global.Plo
+ -rm -f ./$(DEPDIR)/impl_types.Plo
+ -rm -f ./$(DEPDIR)/info.Plo
+ -rm -f ./$(DEPDIR)/interface.Plo
+ -rm -f ./$(DEPDIR)/ixion_@IXION_API_VERSION@_vulkan_la-compute_engine_vulkan.Plo
+ -rm -f ./$(DEPDIR)/ixion_@IXION_API_VERSION@_vulkan_la-vulkan_obj.Plo
+ -rm -f ./$(DEPDIR)/ixion_test_track_deps.Po
+ -rm -f ./$(DEPDIR)/lexer_tokens.Plo
+ -rm -f ./$(DEPDIR)/matrix.Plo
+ -rm -f ./$(DEPDIR)/model_context.Plo
+ -rm -f ./$(DEPDIR)/model_context_impl.Plo
+ -rm -f ./$(DEPDIR)/model_iterator.Plo
+ -rm -f ./$(DEPDIR)/model_types.Plo
+ -rm -f ./$(DEPDIR)/module.Plo
+ -rm -f ./$(DEPDIR)/name_resolver_test.Po
+ -rm -f ./$(DEPDIR)/named_expressions_iterator.Plo
+ -rm -f ./$(DEPDIR)/queue_entry.Plo
+ -rm -f ./$(DEPDIR)/table.Plo
+ -rm -f ./$(DEPDIR)/types.Plo
+ -rm -f ./$(DEPDIR)/utf8.Plo
+ -rm -f ./$(DEPDIR)/utils.Plo
+ -rm -f ./$(DEPDIR)/workbook.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-libLTLIBRARIES install-pkglibLTLIBRARIES
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/address.Plo
+ -rm -f ./$(DEPDIR)/address_iterator.Plo
+ -rm -f ./$(DEPDIR)/calc_status.Plo
+ -rm -f ./$(DEPDIR)/cell.Plo
+ -rm -f ./$(DEPDIR)/cell_access.Plo
+ -rm -f ./$(DEPDIR)/cell_queue_manager.Plo
+ -rm -f ./$(DEPDIR)/compute_engine.Plo
+ -rm -f ./$(DEPDIR)/compute_engine_test.Po
+ -rm -f ./$(DEPDIR)/config.Plo
+ -rm -f ./$(DEPDIR)/debug.Plo
+ -rm -f ./$(DEPDIR)/dirty_cell_tracker.Plo
+ -rm -f ./$(DEPDIR)/dirty_cell_tracker_test.Po
+ -rm -f ./$(DEPDIR)/document.Plo
+ -rm -f ./$(DEPDIR)/document_test.Po
+ -rm -f ./$(DEPDIR)/exceptions.Plo
+ -rm -f ./$(DEPDIR)/formula.Plo
+ -rm -f ./$(DEPDIR)/formula_calc.Plo
+ -rm -f ./$(DEPDIR)/formula_function_opcode.Plo
+ -rm -f ./$(DEPDIR)/formula_functions.Plo
+ -rm -f ./$(DEPDIR)/formula_interpreter.Plo
+ -rm -f ./$(DEPDIR)/formula_lexer.Plo
+ -rm -f ./$(DEPDIR)/formula_name_resolver.Plo
+ -rm -f ./$(DEPDIR)/formula_parser.Plo
+ -rm -f ./$(DEPDIR)/formula_result.Plo
+ -rm -f ./$(DEPDIR)/formula_tokens.Plo
+ -rm -f ./$(DEPDIR)/formula_value_stack.Plo
+ -rm -f ./$(DEPDIR)/general_test.Po
+ -rm -f ./$(DEPDIR)/global.Plo
+ -rm -f ./$(DEPDIR)/impl_types.Plo
+ -rm -f ./$(DEPDIR)/info.Plo
+ -rm -f ./$(DEPDIR)/interface.Plo
+ -rm -f ./$(DEPDIR)/ixion_@IXION_API_VERSION@_vulkan_la-compute_engine_vulkan.Plo
+ -rm -f ./$(DEPDIR)/ixion_@IXION_API_VERSION@_vulkan_la-vulkan_obj.Plo
+ -rm -f ./$(DEPDIR)/ixion_test_track_deps.Po
+ -rm -f ./$(DEPDIR)/lexer_tokens.Plo
+ -rm -f ./$(DEPDIR)/matrix.Plo
+ -rm -f ./$(DEPDIR)/model_context.Plo
+ -rm -f ./$(DEPDIR)/model_context_impl.Plo
+ -rm -f ./$(DEPDIR)/model_iterator.Plo
+ -rm -f ./$(DEPDIR)/model_types.Plo
+ -rm -f ./$(DEPDIR)/module.Plo
+ -rm -f ./$(DEPDIR)/name_resolver_test.Po
+ -rm -f ./$(DEPDIR)/named_expressions_iterator.Plo
+ -rm -f ./$(DEPDIR)/queue_entry.Plo
+ -rm -f ./$(DEPDIR)/table.Plo
+ -rm -f ./$(DEPDIR)/types.Plo
+ -rm -f ./$(DEPDIR)/utf8.Plo
+ -rm -f ./$(DEPDIR)/utils.Plo
+ -rm -f ./$(DEPDIR)/workbook.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-libLTLIBRARIES uninstall-pkglibLTLIBRARIES
+
+.MAKE: check-am install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-TESTS \
+ check-am clean clean-checkPROGRAMS clean-generic \
+ clean-libLTLIBRARIES clean-libtool clean-pkglibLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-libLTLIBRARIES \
+ install-man install-pdf install-pdf-am \
+ install-pkglibLTLIBRARIES install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
+ pdf pdf-am ps ps-am recheck tags tags-am uninstall \
+ uninstall-am uninstall-libLTLIBRARIES \
+ uninstall-pkglibLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/libixion/address.cpp b/src/libixion/address.cpp
new file mode 100644
index 0000000..2b9b82c
--- /dev/null
+++ b/src/libixion/address.cpp
@@ -0,0 +1,568 @@
+/* -*- 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 "ixion/address.hpp"
+
+#include <sstream>
+#include <limits>
+
+namespace ixion {
+
+static const row_t row_max = std::numeric_limits<row_t>::max();
+static const col_t column_max = std::numeric_limits<col_t>::max();
+
+const row_t row_unset = row_max - 9;
+const row_t row_upper_bound = row_max - 10;
+
+const col_t column_unset = column_max - 9;
+const col_t column_upper_bound = column_max / 26 - 26;
+
+abs_address_t::abs_address_t() : sheet(0), row(0), column(0) {}
+abs_address_t::abs_address_t(init_invalid) : sheet(-1), row(-1), column(-1) {}
+
+abs_address_t::abs_address_t(sheet_t _sheet, row_t _row, col_t _column) :
+ sheet(_sheet), row(_row), column(_column) {}
+
+abs_address_t::abs_address_t(const abs_address_t& r) :
+ sheet(r.sheet), row(r.row), column(r.column) {}
+
+bool abs_address_t::valid() const
+{
+ return sheet >= 0 && row >= 0 && column >= 0 && row <= row_unset && column <= column_unset;
+}
+
+std::string abs_address_t::get_name() const
+{
+ std::ostringstream os;
+ os << "(sheet=" << sheet << "; row=" << row << "; column=" << column << ")";
+ return os.str();
+}
+
+std::size_t abs_address_t::hash::operator()(const abs_address_t& addr) const
+{
+ return addr.sheet + addr.row + addr.column;
+}
+
+bool operator== (const abs_address_t& left, const abs_address_t& right)
+{
+ return left.sheet == right.sheet &&
+ left.row == right.row &&
+ left.column == right.column;
+}
+
+bool operator!= (const abs_address_t& left, const abs_address_t& right)
+{
+ return !operator==(left, right);
+}
+
+bool operator< (const abs_address_t& left, const abs_address_t& right)
+{
+ if (left.sheet != right.sheet)
+ return left.sheet < right.sheet;
+
+ if (left.row != right.row)
+ return left.row < right.row;
+
+ return left.column < right.column;
+}
+
+address_t::address_t() :
+ sheet(0), row(0), column(0), abs_sheet(true), abs_row(true), abs_column(true) {}
+
+address_t::address_t(sheet_t _sheet, row_t _row, col_t _column, bool _abs_sheet, bool _abs_row, bool _abs_column) :
+ sheet(_sheet), row(_row), column(_column),
+ abs_sheet(_abs_sheet), abs_row(_abs_row), abs_column(_abs_column) {}
+
+address_t::address_t(const address_t& r) :
+ sheet(r.sheet), row(r.row), column(r.column),
+ abs_sheet(r.abs_sheet), abs_row(r.abs_row), abs_column(r.abs_column) {}
+
+address_t::address_t(const abs_address_t& r) :
+ sheet(r.sheet), row(r.row), column(r.column),
+ abs_sheet(true), abs_row(true), abs_column(true) {}
+
+bool address_t::valid() const
+{
+ if (abs_sheet)
+ {
+ if (sheet < 0)
+ return false;
+ }
+
+ if (row > row_unset)
+ return false;
+
+ if (abs_row)
+ {
+ if (row < 0)
+ return false;
+ }
+ else
+ {
+ if (row < -row_upper_bound)
+ return false;
+ }
+
+ if (column > column_unset)
+ return false;
+
+ if (abs_column)
+ {
+ if (column < 0)
+ return false;
+ }
+ else
+ {
+ if (column < -column_upper_bound)
+ return false;
+ }
+
+ return true;
+}
+
+abs_address_t address_t::to_abs(const abs_address_t& origin) const
+{
+ abs_address_t abs_addr;
+ abs_addr.sheet = sheet;
+ abs_addr.row = row;
+ abs_addr.column = column;
+
+ if (!is_valid_sheet(origin.sheet))
+ // If the origin sheet is invalid, then any sheet position relative to that should be invalid.
+ abs_addr.sheet = origin.sheet;
+ else if (!abs_sheet)
+ abs_addr.sheet += origin.sheet;
+
+ if (!abs_row && row <= row_upper_bound)
+ abs_addr.row += origin.row;
+
+ if (!abs_column && column <= column_upper_bound)
+ abs_addr.column += origin.column;
+
+ return abs_addr;
+}
+
+std::string address_t::get_name() const
+{
+ std::ostringstream os;
+ os << "(row=" << row << " [";
+ if (abs_row)
+ os << "abs";
+ else
+ os << "rel";
+ os << "]; column=" << column << " [";
+ if (abs_column)
+ os << "abs";
+ else
+ os << "rel";
+ os << "])";
+ return os.str();
+}
+
+void address_t::set_absolute(bool abs)
+{
+ abs_sheet = abs;
+ abs_row = abs;
+ abs_column = abs;
+}
+
+std::size_t address_t::hash::operator()(const address_t& addr) const
+{
+ return 0;
+}
+
+bool operator== (const address_t& left, const address_t& right)
+{
+ return left.sheet == right.sheet &&
+ left.row == right.row &&
+ left.column == right.column &&
+ left.abs_sheet == right.abs_sheet &&
+ left.abs_row == right.abs_row &&
+ left.abs_column == right.abs_column;
+}
+
+bool operator!=(const address_t& left, const address_t& right)
+{
+ return !operator==(left, right);
+}
+
+bool operator< (const address_t& left, const address_t& right)
+{
+ // Not sure how to compare absolute and relative addresses, but let's make
+ // absolute address always greater than relative one until we find a
+ // better way.
+
+ if (left.abs_sheet != right.abs_sheet)
+ return left.abs_sheet < right.abs_sheet;
+
+ if (left.abs_row != right.abs_row)
+ return left.abs_row < right.abs_row;
+
+ if (left.abs_column != right.abs_column)
+ return left.abs_column < right.abs_column;
+
+ if (left.sheet != right.sheet)
+ return left.sheet < right.sheet;
+
+ if (left.row != right.row)
+ return left.row < right.row;
+
+ return left.column < right.column;
+}
+
+abs_rc_address_t::abs_rc_address_t() : row(0), column(0)
+{
+}
+
+abs_rc_address_t::abs_rc_address_t(init_invalid) :
+ row(-1), column(-1) {}
+
+abs_rc_address_t::abs_rc_address_t(row_t _row, col_t _column) :
+ row(_row), column(_column) {}
+
+abs_rc_address_t::abs_rc_address_t(const abs_rc_address_t& r) :
+ row(r.row), column(r.column) {}
+
+abs_rc_address_t::abs_rc_address_t(const abs_address_t& r) :
+ row(r.row), column(r.column) {}
+
+bool abs_rc_address_t::valid() const
+{
+ return row >= 0 && column >= 0 && row <= row_unset && column <= column_unset;
+}
+
+std::size_t abs_rc_address_t::hash::operator() (const abs_rc_address_t& addr) const
+{
+ size_t hv = addr.column;
+ hv <<= 16;
+ hv += addr.row;
+ return hv;
+}
+
+bool operator== (const abs_rc_address_t& left, const abs_rc_address_t& right)
+{
+ return left.row == right.row && left.column == right.column;
+}
+
+bool operator!= (const abs_rc_address_t& left, const abs_rc_address_t& right)
+{
+ return !operator==(left, right);
+}
+
+bool operator< (const abs_rc_address_t& left, const abs_rc_address_t& right)
+{
+ if (left.row != right.row)
+ return left.row < right.row;
+
+ return left.column < right.column;
+}
+
+rc_address_t::rc_address_t() :
+ row(0), column(0), abs_row(true), abs_column(true) {}
+
+rc_address_t::rc_address_t(row_t _row, col_t _column, bool _abs_row, bool _abs_column) :
+ row(_row), column(_column), abs_row(_abs_row), abs_column(_abs_column) {}
+
+rc_address_t::rc_address_t(const rc_address_t& r) :
+ row(r.row), column(r.column), abs_row(r.abs_row), abs_column(r.abs_column) {}
+
+std::size_t rc_address_t::hash::operator()(const rc_address_t& addr) const
+{
+ std::size_t hv = addr.column;
+ hv <<= 16;
+ hv += addr.row;
+ return hv;
+}
+
+abs_range_t::abs_range_t() {}
+abs_range_t::abs_range_t(init_invalid) :
+ first(abs_address_t::invalid), last(abs_address_t::invalid) {}
+
+abs_range_t::abs_range_t(sheet_t _sheet, row_t _row, col_t _col) :
+ first(_sheet, _row, _col), last(_sheet, _row, _col) {}
+
+abs_range_t::abs_range_t(sheet_t _sheet, row_t _row, col_t _col, row_t _row_span, col_t _col_span) :
+ first(_sheet, _row, _col), last(_sheet, _row + _row_span - 1, _col + _col_span - 1)
+{
+ if (_row_span < 1 || _col_span < 1)
+ {
+ std::ostringstream os;
+ os << "abs_range_t: invalid span (row=" << _row_span << "; col=" << _col_span << ")";
+ throw std::range_error(os.str());
+ }
+}
+
+abs_range_t::abs_range_t(const abs_address_t& addr, row_t row_span, col_t col_span) :
+ first(addr), last(addr)
+{
+ if (row_span > 0)
+ last.row += row_span - 1;
+ if (col_span > 0)
+ last.column += col_span - 1;
+}
+
+abs_range_t::abs_range_t(const abs_address_t& addr) :
+ first(addr), last(addr) {}
+
+std::size_t abs_range_t::hash::operator() (const abs_range_t& range) const
+{
+ abs_address_t::hash adr_hash;
+ return adr_hash(range.first) + 65536*adr_hash(range.last);
+}
+
+bool abs_range_t::valid() const
+{
+ return first.valid() && last.valid() &&
+ first.sheet <= last.sheet &&
+ first.column <= last.column &&
+ first.row <= last.row;
+}
+
+void abs_range_t::set_all_columns()
+{
+ first.column = column_unset;
+ last.column = column_unset;
+}
+
+void abs_range_t::set_all_rows()
+{
+ first.row = row_unset;
+ last.row = row_unset;
+}
+
+bool abs_range_t::all_columns() const
+{
+ return first.column == column_unset && last.column == column_unset;
+}
+
+bool abs_range_t::all_rows() const
+{
+ return first.row == row_unset && last.row == row_unset;
+}
+
+bool abs_range_t::contains(const abs_address_t& addr) const
+{
+ return first.sheet <= addr.sheet && addr.sheet <= last.sheet &&
+ first.row <= addr.row && addr.row <= last.row &&
+ first.column <= addr.column && addr.column <= last.column;
+}
+
+void abs_range_t::reorder()
+{
+ if (first.sheet > last.sheet)
+ std::swap(first.sheet, last.sheet);
+
+ if (first.row > last.row)
+ std::swap(first.row, last.row);
+
+ if (first.column > last.column)
+ std::swap(first.column, last.column);
+}
+
+bool operator==(const abs_range_t& left, const abs_range_t& right)
+{
+ return left.first == right.first && left.last == right.last;
+}
+
+bool operator!=(const abs_range_t& left, const abs_range_t& right)
+{
+ return !operator==(left, right);
+}
+
+bool operator<(const abs_range_t& left, const abs_range_t& right)
+{
+ if (left.first != right.first)
+ return left.first < right.first;
+ return left.last < right.last;
+}
+
+abs_rc_range_t::abs_rc_range_t() {}
+abs_rc_range_t::abs_rc_range_t(init_invalid) :
+ first(abs_rc_address_t::invalid), last(abs_rc_address_t::invalid) {}
+
+abs_rc_range_t::abs_rc_range_t(const abs_rc_range_t& other) :
+ first(other.first), last(other.last) {}
+
+abs_rc_range_t::abs_rc_range_t(const abs_range_t& other) :
+ first(other.first), last(other.last) {}
+
+size_t abs_rc_range_t::hash::operator() (const abs_rc_range_t& range) const
+{
+ abs_rc_address_t::hash adr_hash;
+ return adr_hash(range.first) + 65536*adr_hash(range.last);
+}
+
+bool abs_rc_range_t::valid() const
+{
+ if (!first.valid() || !last.valid())
+ return false;
+
+ if (first.row != row_unset && last.row != row_unset)
+ {
+ if (first.row > last.row)
+ return false;
+ }
+
+ if (first.column != column_unset && last.column != column_unset)
+ {
+ if (first.column > last.column)
+ return false;
+ }
+
+ return true;
+}
+
+void abs_rc_range_t::set_all_columns()
+{
+ first.column = column_unset;
+ last.column = column_unset;
+}
+
+void abs_rc_range_t::set_all_rows()
+{
+ first.row = row_unset;
+ last.row = row_unset;
+}
+
+bool abs_rc_range_t::all_columns() const
+{
+ return first.column == column_unset && last.column == column_unset;
+}
+
+bool abs_rc_range_t::all_rows() const
+{
+ return first.row == row_unset && last.row == row_unset;
+}
+
+bool abs_rc_range_t::contains(const abs_rc_address_t& addr) const
+{
+ return first.row <= addr.row && addr.row <= last.row &&
+ first.column <= addr.column && addr.column <= last.column;
+}
+
+bool operator==(const abs_rc_range_t& left, const abs_rc_range_t& right)
+{
+ return left.first == right.first && left.last == right.last;
+}
+
+bool operator!=(const abs_rc_range_t& left, const abs_rc_range_t& right)
+{
+ return !operator==(left, right);
+}
+
+bool operator<(const abs_rc_range_t& left, const abs_rc_range_t& right)
+{
+ if (left.first != right.first)
+ return left.first < right.first;
+ return left.last < right.last;
+}
+
+range_t::range_t() {}
+range_t::range_t(const address_t& _first, const address_t& _last) :
+ first(_first), last(_last) {}
+
+range_t::range_t(const range_t& r) : first(r.first), last(r.last) {}
+range_t::range_t(const abs_range_t& r) : first(r.first), last(r.last) {}
+
+bool range_t::valid() const
+{
+ return first.valid() && last.valid();
+}
+
+void range_t::set_all_columns()
+{
+ first.column = column_unset;
+ last.column = column_unset;
+}
+
+void range_t::set_all_rows()
+{
+ first.row = row_unset;
+ last.row = row_unset;
+}
+
+bool range_t::all_columns() const
+{
+ return first.column == column_unset && last.column == column_unset;
+}
+
+bool range_t::all_rows() const
+{
+ return first.row == row_unset && last.row == row_unset;
+}
+
+abs_range_t range_t::to_abs(const abs_address_t& origin) const
+{
+ abs_range_t ret;
+ ret.first = first.to_abs(origin);
+ ret.last = last.to_abs(origin);
+ return ret;
+}
+
+void range_t::set_absolute(bool abs)
+{
+ first.set_absolute(abs);
+ last.set_absolute(abs);
+}
+
+std::size_t range_t::hash::operator() (const range_t& range) const
+{
+ address_t::hash adr_hash;
+ return adr_hash(range.first) + 65536*adr_hash(range.last);
+}
+
+bool operator==(const range_t& left, const range_t& right)
+{
+ return left.first == right.first && left.last == right.last;
+}
+
+bool operator!=(const range_t& left, const range_t& right)
+{
+ return !operator==(left, right);
+}
+
+std::ostream& operator<<(std::ostream& os, const abs_address_t& addr)
+{
+ os << "(sheet:" << addr.sheet << "; row:" << addr.row << "; column:" << addr.column << ")";
+ return os;
+}
+
+std::ostream& operator<<(std::ostream& os, const abs_rc_address_t& addr)
+{
+ os << "(row:" << addr.row << "; column:" << addr.column << ")";
+ return os;
+}
+
+std::ostream& operator<<(std::ostream& os, const address_t& addr)
+{
+ os << "(sheet:" << addr.sheet << " " << (addr.abs_sheet?"abs":"rel")
+ << "; row:" << addr.row << " " << (addr.abs_row?"abs":"rel")
+ <<"; column:" << addr.column << " " << (addr.abs_column?"abs":"rel") << ")";
+ return os;
+}
+
+std::ostream& operator<<(std::ostream& os, const abs_range_t& range)
+{
+ os << range.first << "-" << range.last;
+ return os;
+}
+
+std::ostream& operator<<(std::ostream& os, const abs_rc_range_t& range)
+{
+ os << range.first << "-" << range.last;
+ return os;
+}
+
+std::ostream& operator<<(std::ostream& os, const range_t& range)
+{
+ os << range.first << "-" << range.last;
+ return os;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/address_iterator.cpp b/src/libixion/address_iterator.cpp
new file mode 100644
index 0000000..0911d41
--- /dev/null
+++ b/src/libixion/address_iterator.cpp
@@ -0,0 +1,316 @@
+/* -*- 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 "ixion/address_iterator.hpp"
+#include "ixion/address.hpp"
+#include "ixion/global.hpp"
+
+#include <cassert>
+#include <functional>
+
+namespace ixion {
+
+namespace {
+
+bool inc_sheet(const abs_range_t& range, abs_address_t& pos)
+{
+ if (pos.sheet < range.last.sheet)
+ {
+ ++pos.sheet;
+ pos.row = range.first.row;
+ pos.column = range.first.column;
+ return true;
+ }
+
+ return false;
+}
+
+bool dec_sheet(const abs_range_t& range, abs_address_t& pos)
+{
+ if (range.first.sheet < pos.sheet)
+ {
+ --pos.sheet;
+ pos.row = range.last.row;
+ pos.column = range.last.column;
+ return true;
+ }
+
+ return false;
+}
+
+void inc_vertical(const abs_range_t& range, abs_address_t& pos, bool& end_pos)
+{
+ if (end_pos)
+ throw std::out_of_range("attempting to increment past the end position.");
+
+ if (pos.row < range.last.row)
+ {
+ ++pos.row;
+ return;
+ }
+
+ if (pos.column < range.last.column)
+ {
+ ++pos.column;
+ pos.row = range.first.row;
+ return;
+ }
+
+ if (inc_sheet(range, pos))
+ return;
+
+ assert(pos == range.last);
+ end_pos = true;
+}
+
+void dec_vertical(const abs_range_t& range, abs_address_t& pos, bool& end_pos)
+{
+ if (end_pos)
+ {
+ end_pos = false;
+ assert(pos == range.last);
+ return;
+ }
+
+ if (range.first.row < pos.row)
+ {
+ --pos.row;
+ return;
+ }
+
+ assert(pos.row == range.first.row);
+
+ if (range.first.column < pos.column)
+ {
+ --pos.column;
+ pos.row = range.last.row;
+ return;
+ }
+
+ assert(pos.column == range.first.column);
+
+ if (dec_sheet(range, pos))
+ return;
+
+ assert(pos == range.first);
+ throw std::out_of_range("Attempting to decrement beyond the first position.");
+}
+
+void inc_horizontal(const abs_range_t& range, abs_address_t& pos, bool& end_pos)
+{
+ if (end_pos)
+ throw std::out_of_range("attempting to increment past the end position.");
+
+ if (pos.column < range.last.column)
+ {
+ ++pos.column;
+ return;
+ }
+
+ if (pos.row < range.last.row)
+ {
+ ++pos.row;
+ pos.column = range.first.column;
+ return;
+ }
+
+ if (inc_sheet(range, pos))
+ return;
+
+ assert(pos == range.last);
+ end_pos = true;
+}
+
+void dec_horizontal(const abs_range_t& range, abs_address_t& pos, bool& end_pos)
+{
+ if (end_pos)
+ {
+ end_pos = false;
+ assert(pos == range.last);
+ return;
+ }
+
+ if (range.first.column < pos.column)
+ {
+ --pos.column;
+ return;
+ }
+
+ assert(pos.column == range.first.column);
+
+ if (range.first.row < pos.row)
+ {
+ --pos.row;
+ pos.column = range.last.column;
+ return;
+ }
+
+ assert(pos.row == range.first.row);
+
+ if (dec_sheet(range, pos))
+ return;
+
+ assert(pos == range.first);
+ throw std::out_of_range("Attempting to decrement beyond the first position.");
+}
+
+using update_func_type = std::function<void(const abs_range_t&,abs_address_t&,bool&)>;
+
+} // anonymous namespace
+
+struct abs_address_iterator::impl
+{
+ const abs_range_t m_range;
+ rc_direction_t m_dir;
+
+ impl(const abs_range_t& range, rc_direction_t dir) :
+ m_range(range), m_dir(dir) {}
+};
+
+struct abs_address_iterator::const_iterator::impl_node
+{
+ const abs_range_t* mp_range;
+ abs_address_t m_pos;
+ bool m_end_pos; //< flag that indicates whether the node is at the position past the last valid address.
+
+ update_func_type m_func_inc;
+ update_func_type m_func_dec;
+
+ impl_node() : mp_range(nullptr), m_pos(abs_address_t::invalid), m_end_pos(false) {}
+
+ impl_node(const abs_range_t& range, rc_direction_t dir, bool end) :
+ mp_range(&range),
+ m_pos(end ? range.last : range.first),
+ m_end_pos(end)
+ {
+ switch (dir)
+ {
+ case rc_direction_t::horizontal:
+ m_func_inc = inc_horizontal;
+ m_func_dec = dec_horizontal;
+ break;
+ case rc_direction_t::vertical:
+ m_func_inc = inc_vertical;
+ m_func_dec = dec_vertical;
+ break;
+ default:
+ throw std::logic_error("unhandled direction value.");
+ }
+ }
+
+ impl_node(const impl_node& r) :
+ mp_range(r.mp_range),
+ m_pos(r.m_pos),
+ m_end_pos(r.m_end_pos),
+ m_func_inc(r.m_func_inc),
+ m_func_dec(r.m_func_dec) {}
+
+ bool equals(const impl_node& r) const
+ {
+ return mp_range == r.mp_range && m_pos == r.m_pos && m_end_pos == r.m_end_pos;
+ }
+
+ void inc()
+ {
+ m_func_inc(*mp_range, m_pos, m_end_pos);
+ }
+
+ void dec()
+ {
+ m_func_dec(*mp_range, m_pos, m_end_pos);
+ }
+};
+
+abs_address_iterator::const_iterator::const_iterator() :
+ mp_impl(std::make_unique<impl_node>()) {}
+
+abs_address_iterator::const_iterator::const_iterator(
+ const abs_range_t& range, rc_direction_t dir, bool end) :
+ mp_impl(std::make_unique<impl_node>(range, dir, end)) {}
+
+abs_address_iterator::const_iterator::const_iterator(const const_iterator& r) :
+ mp_impl(std::make_unique<impl_node>(*r.mp_impl)) {}
+
+abs_address_iterator::const_iterator::const_iterator(const_iterator&& r) :
+ mp_impl(std::move(r.mp_impl)) {}
+
+abs_address_iterator::const_iterator::~const_iterator() {}
+
+abs_address_iterator::const_iterator& abs_address_iterator::const_iterator::operator++()
+{
+ mp_impl->inc();
+ return *this;
+}
+
+abs_address_iterator::const_iterator abs_address_iterator::const_iterator::operator++(int)
+{
+ auto saved = *this;
+ mp_impl->inc();
+ return saved;
+}
+
+abs_address_iterator::const_iterator& abs_address_iterator::const_iterator::operator--()
+{
+ mp_impl->dec();
+ return *this;
+}
+
+abs_address_iterator::const_iterator abs_address_iterator::const_iterator::operator--(int)
+{
+ auto saved = *this;
+ mp_impl->dec();
+ return saved;
+}
+
+const abs_address_iterator::const_iterator::value_type& abs_address_iterator::const_iterator::operator*() const
+{
+ return mp_impl->m_pos;
+}
+
+const abs_address_iterator::const_iterator::value_type* abs_address_iterator::const_iterator::operator->() const
+{
+ return &mp_impl->m_pos;
+}
+
+bool abs_address_iterator::const_iterator::operator== (const const_iterator& r) const
+{
+ return mp_impl->equals(*r.mp_impl);
+}
+
+bool abs_address_iterator::const_iterator::operator!= (const const_iterator& r) const
+{
+ return !operator==(r);
+}
+
+abs_address_iterator::abs_address_iterator(const abs_range_t& range, rc_direction_t dir) :
+ mp_impl(std::make_unique<impl>(range, dir)) {}
+
+abs_address_iterator::~abs_address_iterator() {}
+
+abs_address_iterator::const_iterator abs_address_iterator::begin() const
+{
+ return cbegin();
+}
+
+abs_address_iterator::const_iterator abs_address_iterator::end() const
+{
+ return cend();
+}
+
+abs_address_iterator::const_iterator abs_address_iterator::cbegin() const
+{
+ return const_iterator(mp_impl->m_range, mp_impl->m_dir, false);
+}
+
+abs_address_iterator::const_iterator abs_address_iterator::cend() const
+{
+ return const_iterator(mp_impl->m_range, mp_impl->m_dir, true);
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/calc_status.cpp b/src/libixion/calc_status.cpp
new file mode 100644
index 0000000..300b6e1
--- /dev/null
+++ b/src/libixion/calc_status.cpp
@@ -0,0 +1,29 @@
+/* -*- 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 "calc_status.hpp"
+
+namespace ixion {
+
+calc_status::calc_status() : result(nullptr), circular_safe(false), refcount(0) {}
+calc_status::calc_status(const rc_size_t& _group_size) :
+ result(nullptr), group_size(_group_size), circular_safe(false), refcount(0) {}
+
+void calc_status::add_ref()
+{
+ ++refcount;
+}
+
+void calc_status::release_ref()
+{
+ if (--refcount == 0)
+ delete this;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/calc_status.hpp b/src/libixion/calc_status.hpp
new file mode 100644
index 0000000..fc25121
--- /dev/null
+++ b/src/libixion/calc_status.hpp
@@ -0,0 +1,57 @@
+/* -*- 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/.
+ */
+
+#ifndef INCLUDED_IXION_CALC_STATUS_HPP
+#define INCLUDED_IXION_CALC_STATUS_HPP
+
+#include "ixion/formula_result.hpp"
+
+#include <mutex>
+#include <condition_variable>
+
+#include <boost/intrusive_ptr.hpp>
+
+namespace ixion {
+
+struct calc_status
+{
+ calc_status(const calc_status&) = delete;
+ calc_status& operator=(const calc_status&) = delete;
+
+ std::mutex mtx;
+ std::condition_variable cond;
+ std::unique_ptr<formula_result> result;
+
+ const rc_size_t group_size;
+ bool circular_safe;
+
+ size_t refcount;
+
+ calc_status();
+ calc_status(const rc_size_t& _group_size);
+
+ void add_ref();
+ void release_ref();
+};
+
+inline void intrusive_ptr_add_ref(calc_status* p)
+{
+ p->add_ref();
+}
+
+inline void intrusive_ptr_release(calc_status* p)
+{
+ p->release_ref();
+}
+
+using calc_status_ptr_t = boost::intrusive_ptr<calc_status>;
+
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/cell.cpp b/src/libixion/cell.cpp
new file mode 100644
index 0000000..22e8dc3
--- /dev/null
+++ b/src/libixion/cell.cpp
@@ -0,0 +1,578 @@
+/* -*- 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 <ixion/cell.hpp>
+#include <ixion/address.hpp>
+#include <ixion/exceptions.hpp>
+#include <ixion/formula_result.hpp>
+#include <ixion/formula_tokens.hpp>
+#include <ixion/interface/session_handler.hpp>
+#include <ixion/global.hpp>
+#include <ixion/matrix.hpp>
+#include <ixion/formula_name_resolver.hpp>
+#include <ixion/formula.hpp>
+#include <ixion/model_context.hpp>
+
+#include "formula_interpreter.hpp"
+#include "debug.hpp"
+
+#include <cassert>
+#include <string>
+#include <sstream>
+#include <iostream>
+#include <algorithm>
+#include <functional>
+
+#include "calc_status.hpp"
+
+#define FORMULA_CIRCULAR_SAFE 0x01
+#define FORMULA_SHARED_TOKENS 0x02
+
+using namespace std;
+
+namespace ixion {
+
+namespace {
+
+#if IXION_LOGGING
+
+[[maybe_unused]] std::string gen_trace_output(const formula_cell& fc, const model_context& cxt, const abs_address_t& pos)
+{
+ auto resolver = formula_name_resolver::get(formula_name_resolver_t::excel_a1, &cxt);
+ std::ostringstream os;
+ os << "pos=" << pos.get_name() << "; formula='" << print_formula_tokens(cxt, pos, *resolver, fc.get_tokens()->get()) << "'";
+ return os.str();
+}
+
+#endif
+
+}
+
+struct formula_cell::impl
+{
+ mutable calc_status_ptr_t m_calc_status;
+ formula_tokens_store_ptr_t m_tokens;
+ rc_address_t m_group_pos;
+
+ impl() : impl(-1, -1, new calc_status, formula_tokens_store_ptr_t()) {}
+
+ impl(const formula_tokens_store_ptr_t& tokens) : impl(-1, -1, new calc_status, tokens) {}
+
+ impl(row_t row, col_t col, const calc_status_ptr_t& cs,
+ const formula_tokens_store_ptr_t& tokens) :
+ m_calc_status(cs),
+ m_tokens(tokens),
+ m_group_pos(row, col, false, false) {}
+
+ /**
+ * Block until the result becomes available.
+ *
+ * @param lock mutex lock associated with the result cache data.
+ */
+ void wait_for_interpreted_result(std::unique_lock<std::mutex>& lock) const
+ {
+ IXION_TRACE("Wait for the interpreted result");
+
+ while (!m_calc_status->result)
+ {
+ IXION_TRACE("Waiting...");
+ m_calc_status->cond.wait(lock);
+ }
+ }
+
+ void reset_flag()
+ {
+ m_calc_status->circular_safe = false;
+ }
+
+ /**
+ * Check if this cell contains a circular reference.
+ *
+ * @return true if this cell contains no circular reference, hence
+ * considered "safe", false otherwise.
+ */
+ bool is_circular_safe() const
+ {
+ return m_calc_status->circular_safe;
+ }
+
+ bool check_ref_for_circular_safety(const formula_cell& ref, const abs_address_t& pos)
+ {
+ if (!ref.mp_impl->is_circular_safe())
+ {
+ // Circular dependency detected !!
+ IXION_DEBUG("Circular dependency detected !!");
+ assert(!m_calc_status->result);
+ m_calc_status->result =
+ std::make_unique<formula_result>(formula_error_t::ref_result_not_available);
+
+ return false;
+ }
+ return true;
+ }
+
+ void check_calc_status_or_throw() const
+ {
+ if (!m_calc_status->result)
+ {
+ // Result not cached yet. Reference error.
+ IXION_DEBUG("Result not cached yet. This is a reference error.");
+ throw formula_error(formula_error_t::ref_result_not_available);
+ }
+
+ if (m_calc_status->result->get_type() == formula_result::result_type::error)
+ {
+ // Error condition.
+ IXION_DEBUG("Error in result.");
+ throw formula_error(m_calc_status->result->get_error());
+ }
+ }
+
+ double fetch_value_from_result() const
+ {
+ check_calc_status_or_throw();
+
+ switch (m_calc_status->result->get_type())
+ {
+ case formula_result::result_type::boolean:
+ return m_calc_status->result->get_boolean() ? 1.0 : 0.0;
+ case formula_result::result_type::value:
+ return m_calc_status->result->get_value();
+ case formula_result::result_type::matrix:
+ {
+ IXION_TRACE("Fetching a matrix result.");
+ const matrix& m = m_calc_status->result->get_matrix();
+ row_t row_size = m.row_size();
+ col_t col_size = m.col_size();
+
+ if (m_group_pos.row >= row_size || m_group_pos.column >= col_size)
+ throw formula_error(formula_error_t::invalid_value_type);
+
+ matrix::element elem = m.get(m_group_pos.row, m_group_pos.column);
+
+ switch (elem.type)
+ {
+ case matrix::element_type::numeric:
+ return std::get<double>(elem.value);
+ case matrix::element_type::empty:
+ return 0.0;
+ case matrix::element_type::boolean:
+ return std::get<bool>(elem.value) ? 1.0 : 0.0;
+ case matrix::element_type::string:
+ case matrix::element_type::error:
+ default:
+ throw formula_error(formula_error_t::invalid_value_type);
+ }
+ }
+ default:
+ {
+ std::ostringstream os;
+ os << "numeric result was requested, but the actual result is of "
+ << m_calc_status->result->get_type() << " type.";
+ throw formula_error(
+ formula_error_t::invalid_value_type,
+ os.str()
+ );
+ }
+ }
+ }
+
+ std::string_view fetch_string_from_result() const
+ {
+ check_calc_status_or_throw();
+
+ switch (m_calc_status->result->get_type())
+ {
+ case formula_result::result_type::string:
+ return m_calc_status->result->get_string();
+ case formula_result::result_type::matrix:
+ {
+ const matrix& m = m_calc_status->result->get_matrix();
+ row_t row_size = m.row_size();
+ col_t col_size = m.col_size();
+
+ if (m_group_pos.row >= row_size || m_group_pos.column >= col_size)
+ throw formula_error(formula_error_t::invalid_value_type);
+
+ matrix::element elem = m.get(m_group_pos.row, m_group_pos.column);
+
+ switch (elem.type)
+ {
+ case matrix::element_type::string:
+ return std::get<std::string_view>(elem.value);
+ case matrix::element_type::numeric:
+ case matrix::element_type::empty:
+ case matrix::element_type::boolean:
+ case matrix::element_type::error:
+ default:
+ throw formula_error(formula_error_t::invalid_value_type);
+ }
+ }
+ default:
+ {
+ std::ostringstream os;
+ os << "string result was requested, but the actual result is of "
+ << m_calc_status->result->get_type() << " type.";
+ throw formula_error(
+ formula_error_t::invalid_value_type,
+ os.str()
+ );
+ }
+ }
+
+ return std::string_view{};
+ }
+
+ bool is_grouped() const
+ {
+ return m_group_pos.column >= 0 && m_group_pos.row >= 0;
+ }
+
+ bool is_group_parent() const
+ {
+ return m_group_pos.column == 0 && m_group_pos.row == 0;
+ }
+
+ bool calc_allowed() const
+ {
+ if (!is_grouped())
+ return true;
+
+ return is_group_parent();
+ }
+
+ formula_result get_single_formula_result(const formula_result& src) const
+ {
+ if (!is_grouped())
+ return src; // returns a copy.
+
+ if (src.get_type() != formula_result::result_type::matrix)
+ // A grouped cell should have a matrix result whose size equals the
+ // size of the group. But in case of anything else, just return the
+ // stored value.
+ return src;
+
+ const matrix& m = src.get_matrix();
+ row_t row_size = m.row_size();
+ col_t col_size = m.col_size();
+
+ if (m_group_pos.row >= row_size || m_group_pos.column >= col_size)
+ return formula_result(formula_error_t::invalid_value_type);
+
+ matrix::element elem = m.get(m_group_pos.row, m_group_pos.column);
+
+ switch (elem.type)
+ {
+ case matrix::element_type::numeric:
+ return formula_result(std::get<double>(elem.value));
+ case matrix::element_type::string:
+ {
+ std::string s{std::get<std::string_view>(elem.value)};
+ return formula_result(std::move(s));
+ }
+ case matrix::element_type::error:
+ return formula_result(std::get<formula_error_t>(elem.value));
+ case matrix::element_type::empty:
+ return formula_result();
+ case matrix::element_type::boolean:
+ return formula_result(std::get<bool>(elem.value) ? 1.0 : 0.0);
+ default:
+ throw std::logic_error("unhandled element type of a matrix result value.");
+ }
+ }
+
+ void set_single_formula_result(formula_result result)
+ {
+ if (is_grouped())
+ {
+ std::unique_lock<std::mutex> lock(m_calc_status->mtx);
+
+ if (!m_calc_status->result)
+ {
+ m_calc_status->result =
+ std::make_unique<formula_result>(
+ matrix(m_calc_status->group_size.row, m_calc_status->group_size.column));
+ }
+
+ matrix& m = m_calc_status->result->get_matrix();
+ assert(m_group_pos.row < row_t(m.row_size()));
+ assert(m_group_pos.column < col_t(m.col_size()));
+
+ switch (result.get_type())
+ {
+ case formula_result::result_type::boolean:
+ m.set(m_group_pos.row, m_group_pos.column, result.get_boolean());
+ break;
+ case formula_result::result_type::value:
+ m.set(m_group_pos.row, m_group_pos.column, result.get_value());
+ break;
+ case formula_result::result_type::string:
+ m.set(m_group_pos.row, m_group_pos.column, result.get_string());
+ break;
+ case formula_result::result_type::error:
+ m.set(m_group_pos.row, m_group_pos.column, result.get_error());
+ break;
+ case formula_result::result_type::matrix:
+ throw std::logic_error("setting a cached result of matrix value directly is not yet supported.");
+ }
+
+ return;
+ }
+
+ std::unique_lock<std::mutex> lock(m_calc_status->mtx);
+ m_calc_status->result = std::make_unique<formula_result>(std::move(result));
+ }
+};
+
+formula_cell::formula_cell() : mp_impl(std::make_unique<impl>()) {}
+
+formula_cell::formula_cell(const formula_tokens_store_ptr_t& tokens) :
+ mp_impl(std::make_unique<impl>(tokens)) {}
+
+formula_cell::formula_cell(
+ row_t group_row, col_t group_col,
+ const calc_status_ptr_t& cs,
+ const formula_tokens_store_ptr_t& tokens) :
+ mp_impl(std::make_unique<impl>(group_row, group_col, cs, tokens)) {}
+
+formula_cell::~formula_cell()
+{
+}
+
+const formula_tokens_store_ptr_t& formula_cell::get_tokens() const
+{
+ return mp_impl->m_tokens;
+}
+
+void formula_cell::set_tokens(const formula_tokens_store_ptr_t& tokens)
+{
+ mp_impl->m_tokens = tokens;
+}
+
+double formula_cell::get_value(formula_result_wait_policy_t policy) const
+{
+ std::unique_lock<std::mutex> lock(mp_impl->m_calc_status->mtx);
+ if (policy == formula_result_wait_policy_t::block_until_done)
+ mp_impl->wait_for_interpreted_result(lock);
+ return mp_impl->fetch_value_from_result();
+}
+
+std::string_view formula_cell::get_string(formula_result_wait_policy_t policy) const
+{
+ std::unique_lock<std::mutex> lock(mp_impl->m_calc_status->mtx);
+ if (policy == formula_result_wait_policy_t::block_until_done)
+ mp_impl->wait_for_interpreted_result(lock);
+ return mp_impl->fetch_string_from_result();
+}
+
+void formula_cell::interpret(model_context& context, const abs_address_t& pos)
+{
+ IXION_TRACE(gen_trace_output(*this, context, pos));
+
+ if (!mp_impl->calc_allowed())
+ throw std::logic_error("Calculation on this formula cell is not allowed.");
+
+ calc_status& status = *mp_impl->m_calc_status;
+
+ {
+ std::lock_guard<std::mutex> lock(status.mtx);
+
+ if (mp_impl->m_calc_status->result)
+ {
+ // When the result is already cached before the cell is interpreted,
+ // it can mean the cell has circular dependency.
+ if (status.result->get_type() == formula_result::result_type::error)
+ {
+ auto handler = context.create_session_handler();
+ if (handler)
+ {
+ handler->begin_cell_interpret(pos);
+ std::string_view msg = get_formula_error_name(status.result->get_error());
+ handler->set_formula_error(msg);
+ handler->end_cell_interpret();
+ }
+ }
+ return;
+ }
+
+ formula_interpreter fin(this, context);
+ fin.set_origin(pos);
+ status.result = std::make_unique<formula_result>();
+ if (fin.interpret())
+ {
+ // Successful interpretation.
+ *status.result = fin.transfer_result();
+ }
+ else
+ {
+ // Interpretation ended with an error condition.
+ status.result->set_error(fin.get_error());
+ }
+ }
+
+ status.cond.notify_all();
+}
+
+void formula_cell::check_circular(const model_context& cxt, const abs_address_t& pos)
+{
+ // TODO: Check to make sure this is being run on the main thread only.
+ for (const formula_token& t : mp_impl->m_tokens->get())
+ {
+ switch (t.opcode)
+ {
+ case fop_single_ref:
+ {
+ abs_address_t addr = std::get<address_t>(t.value).to_abs(pos);
+ const formula_cell* ref = cxt.get_formula_cell(addr);
+
+ if (!ref)
+ continue;
+
+ if (!mp_impl->check_ref_for_circular_safety(*ref, addr))
+ return;
+
+ break;
+ }
+ case fop_range_ref:
+ {
+ abs_range_t range = std::get<range_t>(t.value).to_abs(pos);
+ for (sheet_t sheet = range.first.sheet; sheet <= range.last.sheet; ++sheet)
+ {
+ rc_size_t sheet_size = cxt.get_sheet_size();
+ col_t col_first = range.first.column, col_last = range.last.column;
+ if (range.all_columns())
+ {
+ col_first = 0;
+ col_last = sheet_size.column - 1;
+ }
+
+ for (col_t col = col_first; col <= col_last; ++col)
+ {
+ row_t row_first = range.first.row, row_last = range.last.row;
+ if (range.all_rows())
+ {
+ assert(row_last == row_unset);
+ row_first = 0;
+ row_last = sheet_size.row - 1;
+ }
+
+ for (row_t row = row_first; row <= row_last; ++row)
+ {
+ abs_address_t addr(sheet, row, col);
+ if (cxt.get_celltype(addr) != celltype_t::formula)
+ continue;
+
+ if (!mp_impl->check_ref_for_circular_safety(*cxt.get_formula_cell(addr), addr))
+ return;
+ }
+ }
+ }
+
+ break;
+ }
+ default:
+ ;
+ }
+
+ }
+
+ // No circular dependencies. Good.
+ mp_impl->m_calc_status->circular_safe = true;
+}
+
+void formula_cell::reset()
+{
+ std::lock_guard<std::mutex> lock(mp_impl->m_calc_status->mtx);
+ mp_impl->m_calc_status->result.reset();
+ mp_impl->reset_flag();
+}
+
+std::vector<const formula_token*> formula_cell::get_ref_tokens(
+ const model_context& cxt, const abs_address_t& pos) const
+{
+ std::vector<const formula_token*> ret;
+
+ std::function<void(const formula_tokens_t::value_type&)> get_refs = [&](const formula_tokens_t::value_type& t)
+ {
+ switch (t.opcode)
+ {
+ case fop_single_ref:
+ case fop_range_ref:
+ ret.push_back(&t);
+ break;
+ case fop_named_expression:
+ {
+ const named_expression_t* named_exp =
+ cxt.get_named_expression(pos.sheet, std::get<std::string>(t.value));
+
+ if (!named_exp)
+ // silently ignore non-existing names.
+ break;
+
+ // recursive call.
+ std::for_each(named_exp->tokens.begin(), named_exp->tokens.end(), get_refs);
+ break;
+ }
+ default:
+ ; // ignore the rest.
+ }
+ };
+
+ const formula_tokens_t& this_tokens = mp_impl->m_tokens->get();
+
+ std::for_each(this_tokens.begin(), this_tokens.end(), get_refs);
+
+ return ret;
+}
+
+const formula_result& formula_cell::get_raw_result_cache(formula_result_wait_policy_t policy) const
+{
+ std::unique_lock<std::mutex> lock(mp_impl->m_calc_status->mtx);
+
+ if (policy == formula_result_wait_policy_t::block_until_done)
+ mp_impl->wait_for_interpreted_result(lock);
+
+ if (!mp_impl->m_calc_status->result)
+ {
+ IXION_DEBUG("Result not yet available.");
+ throw formula_error(formula_error_t::ref_result_not_available);
+ }
+
+ return *mp_impl->m_calc_status->result;
+}
+
+formula_result formula_cell::get_result_cache(formula_result_wait_policy_t policy) const
+{
+ const formula_result& src = get_raw_result_cache(policy);
+ return mp_impl->get_single_formula_result(src);
+}
+
+void formula_cell::set_result_cache(formula_result result)
+{
+ mp_impl->set_single_formula_result(result);
+}
+
+formula_group_t formula_cell::get_group_properties() const
+{
+ uintptr_t identity = reinterpret_cast<uintptr_t>(mp_impl->m_calc_status.get());
+ return formula_group_t(mp_impl->m_calc_status->group_size, identity, mp_impl->is_grouped());
+}
+
+abs_address_t formula_cell::get_parent_position(const abs_address_t& pos) const
+{
+ if (!mp_impl->is_grouped())
+ return pos;
+
+ abs_address_t parent_pos = pos;
+ parent_pos.column -= mp_impl->m_group_pos.column;
+ parent_pos.row -= mp_impl->m_group_pos.row;
+ return parent_pos;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/cell_access.cpp b/src/libixion/cell_access.cpp
new file mode 100644
index 0000000..f3c038e
--- /dev/null
+++ b/src/libixion/cell_access.cpp
@@ -0,0 +1,176 @@
+/* -*- 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 <ixion/cell_access.hpp>
+#include <ixion/global.hpp>
+#include <ixion/model_context.hpp>
+#include <ixion/formula_result.hpp>
+#include <ixion/exceptions.hpp>
+
+#include "model_context_impl.hpp"
+#include "workbook.hpp"
+#include "utils.hpp"
+#include "model_types.hpp"
+
+namespace ixion {
+
+struct cell_access::impl
+{
+ const model_context& cxt;
+ column_store_t::const_position_type pos;
+ impl(const model_context& _cxt) : cxt(_cxt) {}
+};
+
+cell_access::cell_access(const model_context& cxt, const abs_address_t& addr) :
+ mp_impl(std::make_unique<impl>(cxt))
+{
+ mp_impl->pos = cxt.mp_impl->get_cell_position(addr);
+}
+
+cell_access::cell_access(cell_access&& other) :
+ mp_impl(std::move(other.mp_impl))
+{
+ other.mp_impl = std::make_unique<impl>(mp_impl->cxt);
+}
+
+cell_access& cell_access::operator= (cell_access&& other)
+{
+ mp_impl = std::move(other.mp_impl);
+ other.mp_impl = std::make_unique<impl>(mp_impl->cxt);
+ return *this;
+}
+
+cell_access::~cell_access() {}
+
+celltype_t cell_access::get_type() const
+{
+ return detail::to_celltype(mp_impl->pos.first->type);
+}
+
+cell_value_t cell_access::get_value_type() const
+{
+ return detail::to_cell_value_type(
+ mp_impl->pos, mp_impl->cxt.get_formula_result_wait_policy());
+}
+
+const formula_cell* cell_access::get_formula_cell() const
+{
+ if (mp_impl->pos.first->type != element_type_formula)
+ return nullptr;
+
+ return formula_element_block::at(*mp_impl->pos.first->data, mp_impl->pos.second);
+}
+
+formula_result cell_access::get_formula_result() const
+{
+ const formula_cell* fc = get_formula_cell();
+ if (!fc)
+ throw general_error("cell is not a formula cell.");
+
+ return fc->get_result_cache(mp_impl->cxt.get_formula_result_wait_policy());
+}
+
+double cell_access::get_numeric_value() const
+{
+ switch (mp_impl->pos.first->type)
+ {
+ case element_type_numeric:
+ return numeric_element_block::at(*mp_impl->pos.first->data, mp_impl->pos.second);
+ case element_type_boolean:
+ {
+ auto it = boolean_element_block::cbegin(*mp_impl->pos.first->data);
+ std::advance(it, mp_impl->pos.second);
+ return *it ? 1.0 : 0.0;
+ }
+ case element_type_formula:
+ {
+ const formula_cell* p = formula_element_block::at(*mp_impl->pos.first->data, mp_impl->pos.second);
+ return p->get_value(mp_impl->cxt.get_formula_result_wait_policy());
+ }
+ default:
+ ;
+ }
+ return 0.0;
+}
+
+bool cell_access::get_boolean_value() const
+{
+ switch (mp_impl->pos.first->type)
+ {
+ case element_type_numeric:
+ return numeric_element_block::at(*mp_impl->pos.first->data, mp_impl->pos.second) != 0.0 ? true : false;
+ case element_type_boolean:
+ {
+ auto it = boolean_element_block::cbegin(*mp_impl->pos.first->data);
+ std::advance(it, mp_impl->pos.second);
+ return *it;
+ }
+ case element_type_formula:
+ {
+ const formula_cell* p = formula_element_block::at(*mp_impl->pos.first->data, mp_impl->pos.second);
+ return p->get_value(mp_impl->cxt.get_formula_result_wait_policy()) == 0.0 ? false : true;
+ }
+ default:
+ ;
+ }
+ return false;
+}
+
+std::string_view cell_access::get_string_value() const
+{
+ switch (mp_impl->pos.first->type)
+ {
+ case element_type_string:
+ {
+ string_id_t sid = string_element_block::at(*mp_impl->pos.first->data, mp_impl->pos.second);
+ const std::string* p = mp_impl->cxt.get_string(sid);
+ return p ? *p : std::string_view{};
+ }
+ case element_type_formula:
+ {
+ const formula_cell* p = formula_element_block::at(*mp_impl->pos.first->data, mp_impl->pos.second);
+ return p->get_string(mp_impl->cxt.get_formula_result_wait_policy());
+ }
+ case element_type_empty:
+ return detail::empty_string;
+ default:
+ ;
+ }
+
+ return std::string_view{};
+}
+
+string_id_t cell_access::get_string_identifier() const
+{
+ switch (mp_impl->pos.first->type)
+ {
+ case element_type_string:
+ return string_element_block::at(*mp_impl->pos.first->data, mp_impl->pos.second);
+ default:
+ ;
+ }
+ return empty_string_id;
+}
+
+formula_error_t cell_access::get_error_value() const
+{
+ if (mp_impl->pos.first->type != element_type_formula)
+ // this is not a formula cell.
+ return formula_error_t::no_error;
+
+ const formula_cell* fc = formula_element_block::at(*mp_impl->pos.first->data, mp_impl->pos.second);
+ formula_result res = fc->get_result_cache(mp_impl->cxt.get_formula_result_wait_policy());
+ if (res.get_type() != formula_result::result_type::error)
+ // this formula cell doesn't have an error result.
+ return formula_error_t::no_error;
+
+ return res.get_error();
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/cell_queue_manager.cpp b/src/libixion/cell_queue_manager.cpp
new file mode 100644
index 0000000..729c630
--- /dev/null
+++ b/src/libixion/cell_queue_manager.cpp
@@ -0,0 +1,149 @@
+/* -*- 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 "cell_queue_manager.hpp"
+#include "queue_entry.hpp"
+#include <ixion/cell.hpp>
+#include <ixion/model_context.hpp>
+
+#include <cassert>
+#include <queue>
+#include <future>
+#include <algorithm>
+
+#if !IXION_THREADS
+#error "This file is not to be compiled when the threads are disabled."
+#endif
+
+namespace ixion {
+
+namespace {
+
+class scoped_guard
+{
+ std::thread m_thread;
+public:
+ scoped_guard(std::thread thread) : m_thread(std::move(thread)) {}
+ scoped_guard(scoped_guard&& other) : m_thread(std::move(other.m_thread)) {}
+
+ scoped_guard(const scoped_guard&) = delete;
+ scoped_guard& operator= (const scoped_guard&) = delete;
+
+ ~scoped_guard()
+ {
+ m_thread.join();
+ }
+};
+
+class interpreter_queue
+{
+ using future_type = std::future<void>;
+
+ model_context& m_context;
+
+ std::queue<future_type> m_futures;
+ std::mutex m_mtx;
+ std::condition_variable m_cond;
+
+ size_t m_max_queue;
+
+ void interpret(formula_cell* p, const abs_address_t& pos)
+ {
+ p->interpret(m_context, pos);
+ }
+
+public:
+ interpreter_queue(model_context& cxt, size_t max_queue) :
+ m_context(cxt), m_max_queue(max_queue) {}
+
+ /**
+ * Push one formula cell to the interpreter queue for future
+ * intepretation.
+ *
+ * @param p pointer to formula cell instance.
+ * @param pos position of the formual cell.
+ */
+ void push(formula_cell* p, const abs_address_t& pos)
+ {
+ std::unique_lock<std::mutex> lock(m_mtx);
+
+ while (m_futures.size() >= m_max_queue)
+ m_cond.wait(lock);
+
+ future_type f = std::async(
+ std::launch::async, &interpreter_queue::interpret, this, p, pos);
+ m_futures.push(std::move(f));
+ lock.unlock();
+
+ m_cond.notify_one();
+ }
+
+ /**
+ * Wait for one formula cell to finish its interpretation.
+ */
+ void wait_one()
+ {
+ std::unique_lock<std::mutex> lock(m_mtx);
+
+ while (m_futures.empty())
+ m_cond.wait(lock);
+
+ future_type ret = std::move(m_futures.front());
+ m_futures.pop();
+ lock.unlock();
+
+ ret.get(); // This may throw if an exception was thrown on the thread.
+
+ m_cond.notify_one();
+ }
+};
+
+}
+
+struct formula_cell_queue::impl
+{
+ model_context& m_context;
+ std::vector<queue_entry> m_cells;
+ size_t m_thread_count;
+
+ impl(model_context& cxt, std::vector<queue_entry>&& cells, size_t thread_count) :
+ m_context(cxt),
+ m_cells(cells),
+ m_thread_count(thread_count) {}
+
+ void thread_launch(interpreter_queue* queue)
+ {
+ for (queue_entry& e : m_cells)
+ queue->push(e.p, e.pos);
+ }
+
+ void run()
+ {
+ interpreter_queue queue(m_context, m_thread_count);
+
+ std::thread t(&formula_cell_queue::impl::thread_launch, this, &queue);
+ scoped_guard guard(std::move(t));
+
+ for (size_t i = 0, n = m_cells.size(); i < n; ++i)
+ queue.wait_one();
+ }
+};
+
+formula_cell_queue::formula_cell_queue(
+ model_context& cxt, std::vector<queue_entry>&& cells, size_t thread_count) :
+ mp_impl(std::make_unique<impl>(cxt, std::move(cells), thread_count)) {}
+
+formula_cell_queue::~formula_cell_queue() {}
+
+void formula_cell_queue::run()
+{
+ mp_impl->run();
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/cell_queue_manager.hpp b/src/libixion/cell_queue_manager.hpp
new file mode 100644
index 0000000..b458fa1
--- /dev/null
+++ b/src/libixion/cell_queue_manager.hpp
@@ -0,0 +1,47 @@
+/* -*- 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/.
+ */
+
+#ifndef INCLUDED_IXION_CELL_QUEUE_MANAGER_HPP
+#define INCLUDED_IXION_CELL_QUEUE_MANAGER_HPP
+
+#include <ixion/global.hpp>
+
+#include <memory>
+#include <vector>
+
+namespace ixion {
+
+class formula_cell;
+class model_context;
+struct queue_entry;
+
+/**
+ * Class that manages multi-threaded calculation of formula cells.
+ */
+class formula_cell_queue
+{
+ struct impl;
+ std::unique_ptr<impl> mp_impl;
+
+public:
+ formula_cell_queue() = delete;
+
+ formula_cell_queue(
+ model_context& cxt,
+ std::vector<queue_entry>&& cells,
+ size_t thread_count);
+
+ ~formula_cell_queue();
+
+ void run();
+};
+
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/column_store_type.hpp b/src/libixion/column_store_type.hpp
new file mode 100644
index 0000000..76110c1
--- /dev/null
+++ b/src/libixion/column_store_type.hpp
@@ -0,0 +1,73 @@
+/* -*- 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/.
+ */
+
+#ifndef IXION_COLUMN_STORE_TYPE_HPP
+#define IXION_COLUMN_STORE_TYPE_HPP
+
+#include "ixion/types.hpp"
+#include "ixion/cell.hpp"
+
+#include <mdds/multi_type_vector/types.hpp>
+#include <mdds/multi_type_vector/macro.hpp>
+#include <mdds/multi_type_vector/block_funcs.hpp>
+#include <mdds/multi_type_vector.hpp>
+#include <mdds/multi_type_matrix.hpp>
+
+#include <deque>
+
+namespace ixion {
+
+// Element types
+
+constexpr mdds::mtv::element_t element_type_empty = mdds::mtv::element_type_empty;
+constexpr mdds::mtv::element_t element_type_boolean = mdds::mtv::element_type_boolean;
+constexpr mdds::mtv::element_t element_type_numeric = mdds::mtv::element_type_double;
+constexpr mdds::mtv::element_t element_type_string = mdds::mtv::element_type_uint32;
+constexpr mdds::mtv::element_t element_type_formula = mdds::mtv::element_type_user_start;
+
+// Element block types
+
+using boolean_element_block = mdds::mtv::boolean_element_block;
+using numeric_element_block = mdds::mtv::double_element_block;
+using string_element_block = mdds::mtv::uint32_element_block;
+
+using formula_element_block =
+ mdds::mtv::noncopyable_managed_element_block<element_type_formula, ixion::formula_cell>;
+
+MDDS_MTV_DEFINE_ELEMENT_CALLBACKS_PTR(formula_cell, element_type_formula, nullptr, formula_element_block)
+
+struct column_store_traits : mdds::mtv::default_traits
+{
+ using block_funcs = mdds::mtv::element_block_funcs<
+ boolean_element_block,
+ numeric_element_block,
+ string_element_block,
+ formula_element_block>;
+};
+
+/** Type that represents a whole column. */
+using column_store_t = mdds::multi_type_vector<column_store_traits>;
+
+/** Type that represents a collection of columns. */
+using column_stores_t = std::deque<column_store_t>;
+
+/**
+ * The integer element blocks are used to store string ID's. The actual
+ * string element blocks are not used in the matrix store in ixion.
+ */
+struct matrix_store_traits
+{
+ typedef mdds::mtv::int64_element_block integer_element_block;
+ typedef mdds::mtv::string_element_block string_element_block;
+};
+
+using matrix_store_t = mdds::multi_type_matrix<matrix_store_traits>;
+
+}
+
+#endif
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/compute_engine.cpp b/src/libixion/compute_engine.cpp
new file mode 100644
index 0000000..c6ac077
--- /dev/null
+++ b/src/libixion/compute_engine.cpp
@@ -0,0 +1,132 @@
+/* -*- 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 "ixion/compute_engine.hpp"
+#include "ixion/global.hpp"
+
+#include <iostream>
+#include <unordered_map>
+
+namespace ixion { namespace draft {
+
+namespace {
+
+struct class_factory
+{
+ void* handler;
+ create_compute_engine_t create;
+ destroy_compute_engine_t destroy;
+};
+
+class class_factory_store
+{
+ using store_type = std::unordered_map<std::string, class_factory>;
+ store_type m_store;
+
+public:
+
+ const class_factory* get(std::string_view name) const
+ {
+ auto it = m_store.find(std::string(name));
+ if (it == m_store.end())
+ return nullptr;
+
+ return &it->second;
+ }
+
+ void insert(void* hdl, std::string_view name, create_compute_engine_t func_create, destroy_compute_engine_t func_destroy)
+ {
+ class_factory cf;
+ cf.handler = hdl;
+ cf.create = func_create;
+ cf.destroy = func_destroy;
+ m_store.emplace(name, cf);
+ }
+
+ ~class_factory_store()
+ {
+ for (auto& kv : m_store)
+ {
+ class_factory& cf = kv.second;
+ unload_module(cf.handler);
+ }
+ }
+};
+
+class_factory_store store;
+
+}
+
+struct compute_engine::impl
+{
+ impl() {}
+};
+
+std::shared_ptr<compute_engine> compute_engine::create(std::string_view name)
+{
+ if (name.empty())
+ // Name is not specified. Use the default engine.
+ return std::make_shared<compute_engine>();
+
+ const class_factory* cf = store.get(name);
+ if (!cf)
+ // No class factory for this name. Fall back to default.
+ return std::make_shared<compute_engine>();
+
+ return std::shared_ptr<compute_engine>(cf->create(), cf->destroy);
+}
+
+void compute_engine::add_class(
+ void* hdl, std::string_view name, create_compute_engine_t func_create, destroy_compute_engine_t func_destroy)
+{
+ store.insert(hdl, name, func_create, func_destroy);
+}
+
+compute_engine::compute_engine() :
+ mp_impl(std::make_unique<impl>())
+{
+}
+
+compute_engine::~compute_engine()
+{
+}
+
+std::string_view compute_engine::get_name() const
+{
+ return "default";
+}
+
+void compute_engine::compute_fibonacci(array& io)
+{
+ if (io.type != array_type::uint32)
+ return;
+
+ auto fibonacci = [](uint32_t n) -> uint32_t
+ {
+ if (n <= 1)
+ return n;
+
+ uint32_t curr = 1;
+ uint32_t prev = 1;
+
+ for (uint32_t i = 2; i < n; ++i)
+ {
+ uint32_t temp = curr;
+ curr += prev;
+ prev = temp;
+ }
+
+ return curr;
+ };
+
+ for (uint32_t i = 0; i < io.size; ++i)
+ io.uint32[i] = fibonacci(io.uint32[i]);
+}
+
+}}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/compute_engine_test.cpp b/src/libixion/compute_engine_test.cpp
new file mode 100644
index 0000000..58a45a3
--- /dev/null
+++ b/src/libixion/compute_engine_test.cpp
@@ -0,0 +1,125 @@
+/* -*- 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 "test_global.hpp" // This must be the first header to be included.
+
+#include <ixion/compute_engine.hpp>
+#include <ixion/module.hpp>
+#include <algorithm>
+#include <chrono>
+#include <iostream>
+#include <iterator>
+#include <sstream>
+#include <string>
+#include <vector>
+#include <functional>
+
+using std::cout;
+using std::endl;
+
+namespace {
+
+using test_func_t = std::function<void()>;
+
+std::vector<std::pair<std::string, test_func_t>> tests;
+
+#define REGISTER_TEST(x) tests.emplace_back(#x, x);
+
+template<typename T>
+void print_values(std::string_view msg, const T& values)
+{
+ cout << msg << ": ";
+
+ if (values.size() <= 20)
+ std::copy(values.begin(), values.end(), std::ostream_iterator<typename T::value_type>(cout, " "));
+ else
+ {
+ // Print only the first 15 and last 5 values.
+ auto it = values.begin();
+ auto it_end = it + 15;
+
+ std::copy(it, it_end, std::ostream_iterator<typename T::value_type>(cout, " "));
+
+ cout << "... ";
+
+ it_end = values.end();
+ it = it_end - 5;
+
+ std::copy(it, it_end, std::ostream_iterator<typename T::value_type>(cout, " "));
+ }
+
+ cout << endl;
+}
+
+void print_summary(const std::shared_ptr<ixion::draft::compute_engine>& engine)
+{
+ cout << "--" << endl;
+ cout << "name: " << engine->get_name() << endl;
+
+ std::vector<uint32_t> values(16384u);
+
+ uint32_t n = 0;
+ std::generate(values.begin(), values.end(), [&n] { return n++; });
+
+ ixion::draft::array io{};
+ io.uint32 = values.data();
+ io.size = values.size();
+ io.type = ixion::draft::array_type::uint32;
+
+ print_values("fibonacci input", values);
+ {
+ std::ostringstream os;
+ os << "fibonacci (n=" << values.size() << ")";
+ ixion::test::stack_printer __stack_printer__(os.str());
+ engine->compute_fibonacci(io);
+ }
+ print_values("fibonacci output", values);
+}
+
+}
+
+void test_create_default()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ std::shared_ptr<ixion::draft::compute_engine> p = ixion::draft::compute_engine::create();
+ assert(p);
+ assert(p->get_name() == "default");
+ print_summary(p);
+}
+
+void test_create_vulkan()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ std::shared_ptr<ixion::draft::compute_engine> p = ixion::draft::compute_engine::create("vulkan");
+ assert(p);
+ assert(p->get_name() == "vulkan");
+ print_summary(p);
+}
+
+int main()
+{
+ ixion::draft::init_modules();
+
+ REGISTER_TEST(test_create_default);
+#ifdef BUILD_VULKAN
+ REGISTER_TEST(test_create_vulkan);
+#endif
+
+ for (auto test : tests)
+ {
+ cout << "--------------------------" << endl;
+ cout << " " << test.first << endl;
+ cout << "--------------------------" << endl;
+ test.second();
+ }
+
+ return EXIT_SUCCESS;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/compute_engine_vulkan.cpp b/src/libixion/compute_engine_vulkan.cpp
new file mode 100644
index 0000000..25fad82
--- /dev/null
+++ b/src/libixion/compute_engine_vulkan.cpp
@@ -0,0 +1,234 @@
+/* -*- 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 "compute_engine_vulkan.hpp"
+#include "ixion/module.hpp"
+#include "ixion/env.hpp"
+#include "ixion/exceptions.hpp"
+
+#include "debug.hpp"
+
+#include <iostream>
+#include <vector>
+#include <cstring>
+
+namespace ixion { namespace draft {
+
+namespace {
+
+std::size_t get_byte_size(const array& io)
+{
+ switch (io.type)
+ {
+ case array_type::uint32:
+ return sizeof(uint32_t) * io.size;
+ case array_type::float32:
+ return sizeof(float) * io.size;
+ case array_type::float64:
+ return sizeof(double) * io.size;
+ case array_type::unknown:
+ IXION_DEBUG("array type is unknown.");
+ }
+
+ return 0;
+}
+
+}
+
+/**
+ * This function first writes the source data array to host buffer, then
+ * creates a device local buffer, and create and execute a command to copy
+ * the data in the host buffer to the device local buffer.
+ */
+void compute_engine_vulkan::copy_to_device_local_buffer(
+ array& io, vk_buffer& host_buffer, vk_buffer& device_buffer)
+{
+ vk_command_buffer cmd_copy = m_cmd_pool.create_command_buffer();
+ cmd_copy.begin();
+ cmd_copy.copy_buffer(host_buffer, device_buffer, sizeof(uint32_t)*io.size);
+ cmd_copy.end();
+
+ vk_fence fence(m_device, 0);
+ vk_queue q = m_device.get_queue();
+ q.submit(cmd_copy, fence);
+ fence.wait();
+}
+
+compute_engine_vulkan::compute_engine_vulkan() :
+ compute_engine(),
+ m_instance(),
+ m_device(m_instance),
+ m_cmd_pool(m_device)
+{
+}
+
+compute_engine_vulkan::~compute_engine_vulkan()
+{
+}
+
+std::string_view compute_engine_vulkan::get_name() const
+{
+ return "vulkan";
+}
+
+void compute_engine_vulkan::compute_fibonacci(array& io)
+{
+ runtime_context cxt;
+ cxt.input_buffer_size = io.size;
+
+ std::size_t data_byte_size = get_byte_size(io);
+
+ vk_buffer host_buffer(
+ m_device, data_byte_size,
+ VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
+
+ host_buffer.write_to_memory(io.data, data_byte_size);
+
+ vk_buffer device_buffer(
+ m_device, data_byte_size,
+ VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+
+ copy_to_device_local_buffer(io, host_buffer, device_buffer);
+
+ // Create a descriptor pool, by specifying the number of descriptors for
+ // each type that can be allocated in a single set, as well as the max
+ // number of sets. Here, we are specifying that we will only allocate one
+ // set, and each set will contain only one storage buffer.
+ vk_descriptor_pool desc_pool(m_device, 1u,
+ {
+ { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1u },
+ }
+ );
+
+ // Create a descriptor set layout. This specifies what descriptor type is
+ // bound to what binding location and how many units (in case the
+ // descriptor is an array), and which stages can access it. Here, we are
+ // binding one storage buffer to the binding location of 0, for the
+ // compute stage.
+ vk_descriptor_set_layout ds_layout(m_device,
+ {
+ // binding id, descriptor type, descriptor count, stage flags, sampler (optional)
+ { 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1u, VK_SHADER_STAGE_COMPUTE_BIT, nullptr },
+ }
+ );
+
+ // Create a pipeline layout. A pipeline layout consists of one or more
+ // descriptor set layouts as well as one or more push constants. It
+ // describes the entire resources the pipeline will have access to. Here
+ // we only have one descriptor set layout and no push constants.
+ vk_pipeline_layout pl_layout(m_device, ds_layout);
+
+ // Allocate a descriptor set from the descriptor set layout. This will
+ // get bound to command buffer later as part of the recorded commands.
+ vk_descriptor_set desc_set = desc_pool.allocate(ds_layout);
+
+ // Update the descriptor set with the content of the device-local buffer.
+ // You always have to create a descriptor set first, then update it
+ // afterward. Here, we are binding the actual device buffer instance to
+ // the binding location of 0, just like we specified above.
+ desc_set.update(m_device, 0u, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, device_buffer);
+
+ // Create pipeline cache. You need to create this so that you can
+ // optinally save the cached pipeline state to disk for re-use, which we
+ // don't do here.
+ vk_pipeline_cache pl_cache(m_device);
+
+ // Load shader module for fibonnaci.
+ vk_shader_module fibonnaci_module(m_device, vk_shader_module::module_type::fibonacci);
+
+ // Create a compute pipeline.
+ vk_pipeline pipeline(cxt, m_device, pl_layout, pl_cache, fibonnaci_module);
+
+ // allocate command buffer.
+ vk_command_buffer cmd = m_cmd_pool.create_command_buffer();
+
+ // Record command buffer.
+ cmd.begin();
+
+ // Ensure that the write to the device buffer gets finished before the
+ // compute shader stage.
+ cmd.buffer_memory_barrier(
+ device_buffer,
+ VK_ACCESS_HOST_WRITE_BIT,
+ VK_ACCESS_SHADER_READ_BIT,
+ VK_PIPELINE_STAGE_HOST_BIT,
+ VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT
+ );
+
+ // Bind the pipeline and descriptor set to the compute pipeline.
+ cmd.bind_pipeline(pipeline, VK_PIPELINE_BIND_POINT_COMPUTE);
+ cmd.bind_descriptor_set(VK_PIPELINE_BIND_POINT_COMPUTE, pl_layout, desc_set);
+
+ // Trigger compute work for data size of (n, 1, 1).
+ cmd.dispatch(cxt.input_buffer_size, 1, 1);
+
+ // Ensure that the compute stages finishes before buffer gets copied.
+ cmd.buffer_memory_barrier(
+ device_buffer,
+ VK_ACCESS_SHADER_WRITE_BIT,
+ VK_ACCESS_TRANSFER_READ_BIT,
+ VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+ VK_PIPELINE_STAGE_TRANSFER_BIT
+ );
+
+ // Copy the data from the device local buffer to the host buffer.
+ cmd.copy_buffer(device_buffer, host_buffer, data_byte_size);
+
+ // Ensure that the buffer copying gets done before data is read on the
+ // host cpu side.
+ cmd.buffer_memory_barrier(
+ host_buffer,
+ VK_ACCESS_TRANSFER_WRITE_BIT,
+ VK_ACCESS_HOST_READ_BIT,
+ VK_PIPELINE_STAGE_TRANSFER_BIT,
+ VK_PIPELINE_STAGE_HOST_BIT
+ );
+
+ cmd.end();
+
+ // Submit the command and wait.
+ vk_fence fence(m_device, VK_FENCE_CREATE_SIGNALED_BIT);
+ fence.reset();
+
+ vk_queue q = m_device.get_queue();
+ q.submit(cmd, fence, VK_PIPELINE_STAGE_TRANSFER_BIT);
+ fence.wait();
+
+ // Read the values from the host memory.
+ host_buffer.read_from_memory(io.data, data_byte_size);
+}
+
+compute_engine* create()
+{
+ return new compute_engine_vulkan();
+}
+
+void destroy(const compute_engine* p)
+{
+ delete static_cast<const compute_engine_vulkan*>(p);
+}
+
+}}
+
+extern "C" {
+
+IXION_MOD_EXPORT ixion::draft::module_def* register_module()
+{
+ static ixion::draft::module_def md =
+ {
+ ixion::draft::create,
+ ixion::draft::destroy
+ };
+
+ return &md;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/compute_engine_vulkan.hpp b/src/libixion/compute_engine_vulkan.hpp
new file mode 100644
index 0000000..7d6f581
--- /dev/null
+++ b/src/libixion/compute_engine_vulkan.hpp
@@ -0,0 +1,39 @@
+/* -*- 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/.
+ */
+
+#ifndef INCLUDED_IXION_COMPUTE_ENGINE_VULKAN_HPP
+#define INCLUDED_IXION_COMPUTE_ENGINE_VULKAN_HPP
+
+#include "ixion/compute_engine.hpp"
+
+#include "vulkan_obj.hpp"
+
+namespace ixion { namespace draft {
+
+class compute_engine_vulkan final : public compute_engine
+{
+ vk_instance m_instance;
+ vk_device m_device;
+ vk_command_pool m_cmd_pool;
+
+ void copy_to_device_local_buffer(
+ array& io, vk_buffer& host_buffer, vk_buffer& device_buffer);
+
+public:
+ compute_engine_vulkan();
+ virtual ~compute_engine_vulkan() override;
+
+ virtual std::string_view get_name() const override;
+
+ virtual void compute_fibonacci(array& io) override;
+};
+
+}}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/config.cpp b/src/libixion/config.cpp
new file mode 100644
index 0000000..e4a39e7
--- /dev/null
+++ b/src/libixion/config.cpp
@@ -0,0 +1,27 @@
+/* -*- 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 "ixion/config.hpp"
+
+namespace ixion {
+
+config::config() :
+ sep_function_arg(','),
+ sep_matrix_column(','),
+ sep_matrix_row(';'),
+ output_precision(-1)
+{}
+
+config::config(const config& r) :
+ sep_function_arg(r.sep_function_arg),
+ sep_matrix_column(r.sep_matrix_column),
+ sep_matrix_row(r.sep_matrix_row),
+ output_precision(r.output_precision) {}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/constants.inl.in b/src/libixion/constants.inl.in
new file mode 100644
index 0000000..90fca83
--- /dev/null
+++ b/src/libixion/constants.inl.in
@@ -0,0 +1,7 @@
+
+#define IXION_MAJOR_VERSION @IXION_MAJOR_VERSION@
+#define IXION_MINOR_VERSION @IXION_MINOR_VERSION@
+#define IXION_MICRO_VERSION @IXION_MICRO_VERSION@
+#define IXION_MAJOR_API_VERSION @IXION_MAJOR_API_VERSION@
+#define IXION_MINOR_API_VERSION @IXION_MINOR_API_VERSION@
+
diff --git a/src/libixion/debug.cpp b/src/libixion/debug.cpp
new file mode 100644
index 0000000..f790ada
--- /dev/null
+++ b/src/libixion/debug.cpp
@@ -0,0 +1,35 @@
+/* -*- 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 "debug.hpp"
+#include "ixion/formula_name_resolver.hpp"
+#include "ixion/formula_tokens.hpp"
+#include "ixion/cell.hpp"
+#include "ixion/formula.hpp"
+
+#include <sstream>
+
+namespace ixion { namespace detail {
+
+std::string print_formula_expression(const model_context& cxt, const abs_address_t& pos, const formula_cell& cell)
+{
+ auto resolver = formula_name_resolver::get(formula_name_resolver_t::excel_a1, &cxt);
+ assert(resolver);
+ const formula_tokens_t& tokens = cell.get_tokens()->get();
+ return print_formula_tokens(cxt, pos, *resolver, tokens);
+}
+
+std::string print_formula_token_repr(const formula_token& t)
+{
+ std::ostringstream os;
+ os << t;
+ return os.str();
+}
+
+}}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/debug.hpp b/src/libixion/debug.hpp
new file mode 100644
index 0000000..32f5c6e
--- /dev/null
+++ b/src/libixion/debug.hpp
@@ -0,0 +1,47 @@
+/* -*- 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/.
+ */
+
+#ifndef INCLUDED_IXION_DEBUG_HPP
+#define INCLUDED_IXION_DEBUG_HPP
+
+#include <string>
+
+#if defined(IXION_DEBUG_ON) || defined(IXION_TRACE_ON)
+#include <iostream>
+#endif
+
+#ifdef IXION_DEBUG_ON
+#define IXION_DEBUG(stream) \
+ do { std::cerr << "[ixion]:[DEBUG]:" << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << ": " << stream << std::endl; } while (false)
+#else
+#define IXION_DEBUG(...)
+#endif
+
+#ifdef IXION_TRACE_ON
+#define IXION_TRACE(stream) \
+ do { std::cerr << "[ixion]:[TRACE]:" << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << ": " << stream << std::endl; } while (false)
+#else
+#define IXION_TRACE(...)
+#endif
+
+namespace ixion {
+
+class formula_cell;
+struct formula_token;
+struct abs_address_t;
+class model_context;
+
+namespace detail {
+
+std::string print_formula_expression(const model_context& cxt, const abs_address_t& pos, const formula_cell& cell);
+std::string print_formula_token_repr(const formula_token& t);
+
+}}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/dirty_cell_tracker.cpp b/src/libixion/dirty_cell_tracker.cpp
new file mode 100644
index 0000000..e5311bc
--- /dev/null
+++ b/src/libixion/dirty_cell_tracker.cpp
@@ -0,0 +1,418 @@
+/* -*- 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 "ixion/dirty_cell_tracker.hpp"
+#include "ixion/global.hpp"
+#include "ixion/formula_name_resolver.hpp"
+
+#include "depth_first_search.hpp"
+#include "debug.hpp"
+
+#include <mdds/rtree.hpp>
+#include <deque>
+#include <limits>
+
+namespace ixion {
+
+namespace {
+
+using rtree_type = mdds::rtree<rc_t, abs_range_set_t>;
+using rtree_array_type = std::deque<rtree_type>;
+
+} // anonymous namespace
+
+struct dirty_cell_tracker::impl
+{
+ rtree_array_type m_grids;
+ abs_range_set_t m_volatile_cells;
+
+ mutable std::unique_ptr<formula_name_resolver> m_resolver;
+
+ impl() {}
+
+ rtree_type& fetch_grid_or_resize(size_t n)
+ {
+ if (m_grids.size() <= n)
+ m_grids.resize(n+1);
+
+ return m_grids[n];
+ }
+
+ const rtree_type* fetch_grid(size_t n) const
+ {
+ return (n < m_grids.size()) ? &m_grids[n] : nullptr;
+ }
+
+ rtree_type* fetch_grid(size_t n)
+ {
+ return (n < m_grids.size()) ? &m_grids[n] : nullptr;
+ }
+
+ /**
+ * Given a modified cell range, return all ranges that are directly
+ * affected by it.
+ *
+ * @param range modified cell range.
+ *
+ * @return collection of ranges that are directly affected by the modified
+ * cell range.
+ */
+ abs_range_set_t get_affected_cell_ranges(const abs_range_t& range) const
+ {
+ const rtree_type* grid = fetch_grid(range.first.sheet);
+ if (!grid)
+ return abs_range_set_t();
+
+ rtree_type::const_search_results res = grid->search(
+ {{range.first.row, range.first.column}, {range.last.row, range.last.column}},
+ rtree_type::search_type::overlap);
+
+ abs_range_set_t ranges;
+
+ for (const abs_range_set_t& range_set : res)
+ ranges.insert(range_set.begin(), range_set.end());
+
+ return ranges;
+ }
+
+ std::string print(const abs_range_t& range) const
+ {
+ if (!m_resolver)
+ m_resolver = formula_name_resolver::get(formula_name_resolver_t::excel_a1, nullptr);
+
+ abs_address_t origin(0, 0, 0);
+ range_t rrange = range;
+ rrange.set_absolute(false);
+
+ std::ostringstream os;
+ os << "Sheet" << (rrange.first.sheet+1) << '!';
+
+ if (rrange.first == rrange.last)
+ os << m_resolver->get_name(rrange.first, origin, false);
+ else
+ os << m_resolver->get_name(rrange, origin, false);
+
+ return os.str();
+ }
+};
+
+dirty_cell_tracker::dirty_cell_tracker() : mp_impl(std::make_unique<impl>()) {}
+dirty_cell_tracker::~dirty_cell_tracker() {}
+
+void dirty_cell_tracker::add(const abs_range_t& src, const abs_range_t& dest)
+{
+ if (!src.valid() || src.first.sheet != src.last.sheet)
+ {
+ // source range must be on one sheet.
+ std::ostringstream os;
+ os << "dirty_cell_tracker::add: invalid source range: src=" << src;
+ throw std::invalid_argument(os.str());
+ }
+
+ if (!dest.valid())
+ {
+ std::ostringstream os;
+ os << "dirty_cell_tracker::add: invalid destination range: src=" << src << "; dest=" << dest;
+ throw std::invalid_argument(os.str());
+ }
+
+ if (dest.all_columns() || dest.all_rows())
+ {
+ std::ostringstream os;
+ os << "dirty_cell_tracker::add: unset column or row range is not allowed " << dest;
+ throw std::invalid_argument(os.str());
+ }
+
+ for (sheet_t sheet = dest.first.sheet; sheet <= dest.last.sheet; ++sheet)
+ {
+ rtree_type& tree = mp_impl->fetch_grid_or_resize(sheet);
+
+ rtree_type::extent_type search_box(
+ {{dest.first.row, dest.first.column}, {dest.last.row, dest.last.column}});
+
+ rtree_type::search_results res = tree.search(search_box, rtree_type::search_type::match);
+
+ if (res.begin() == res.end())
+ {
+ // No listener for this destination range. Insert a new one.
+ abs_range_set_t listener;
+ listener.emplace(src);
+ tree.insert(search_box, std::move(listener));
+ }
+ else
+ {
+ // A listener already exists for this destination cell.
+ abs_range_set_t& listener = *res.begin();
+ listener.emplace(src);
+ }
+ }
+}
+
+void dirty_cell_tracker::remove(const abs_range_t& src, const abs_range_t& dest)
+{
+ if (!src.valid() || src.first.sheet != src.last.sheet)
+ {
+ // source range must be on one sheet.
+ std::ostringstream os;
+ os << "dirty_cell_tracker::add: invalid source range: src=" << src;
+ throw std::invalid_argument(os.str());
+ }
+
+ if (!dest.valid())
+ {
+ std::ostringstream os;
+ os << "dirty_cell_tracker::remove: invalid destination range: src=" << src << "; dest=" << dest;
+ throw std::invalid_argument(os.str());
+ }
+
+ if (dest.all_columns() || dest.all_rows())
+ {
+ std::ostringstream os;
+ os << "dirty_cell_tracker::remove: unset column or row range is not allowed " << dest;
+ throw std::invalid_argument(os.str());
+ }
+
+ for (sheet_t sheet = dest.first.sheet; sheet <= dest.last.sheet; ++sheet)
+ {
+ rtree_type* tree = mp_impl->fetch_grid(sheet);
+ if (!tree)
+ {
+ IXION_DEBUG("Nothing is tracked on sheet " << sheet << ".");
+ continue;
+ }
+
+ rtree_type::extent_type search_box(
+ {{dest.first.row, dest.first.column}, {dest.last.row, dest.last.column}});
+
+ rtree_type::search_results res = tree->search(search_box, rtree_type::search_type::match);
+
+ if (res.begin() == res.end())
+ {
+ // No listener for this destination cell. Nothing to remove.
+ IXION_DEBUG(dest << " is not being tracked by anybody on sheet " << sheet << ".");
+ continue;
+ }
+
+ rtree_type::iterator it_listener = res.begin();
+ abs_range_set_t& listener = *it_listener;
+ size_t n_removed = listener.erase(src);
+
+ if (!n_removed)
+ {
+ IXION_DEBUG(src << " was not tracking " << dest << " on sheet " << sheet << ".");
+ }
+
+ if (listener.empty())
+ // Remove this from the R-tree.
+ tree->erase(it_listener);
+ }
+}
+
+void dirty_cell_tracker::add_volatile(const abs_range_t& pos)
+{
+ mp_impl->m_volatile_cells.insert(pos);
+}
+
+void dirty_cell_tracker::remove_volatile(const abs_range_t& pos)
+{
+ mp_impl->m_volatile_cells.erase(pos);
+}
+
+abs_range_set_t dirty_cell_tracker::query_dirty_cells(const abs_range_t& modified_cell) const
+{
+ abs_range_set_t mod_cells;
+ mod_cells.insert(modified_cell);
+ return query_dirty_cells(mod_cells);
+}
+
+abs_range_set_t dirty_cell_tracker::query_dirty_cells(const abs_range_set_t& modified_cells) const
+{
+ abs_range_set_t dirty_formula_cells;
+
+ // Volatile cells are in theory always formula cells and therefore always
+ // should be included.
+ dirty_formula_cells.insert(
+ mp_impl->m_volatile_cells.begin(), mp_impl->m_volatile_cells.end());
+
+ abs_range_set_t cur_modified_cells = modified_cells;
+ for (const abs_range_t& r : mp_impl->m_volatile_cells)
+ cur_modified_cells.insert(r);
+
+ while (!cur_modified_cells.empty())
+ {
+ abs_range_set_t next_modified_cells;
+
+ for (const abs_range_t& mc : cur_modified_cells)
+ {
+ abs_range_set_t affected_ranges = mp_impl->get_affected_cell_ranges(mc);
+ for (const abs_range_t& r : affected_ranges)
+ {
+ auto res = dirty_formula_cells.insert(r);
+ if (res.second)
+ // This affected range has not yet been visited. Put it
+ // in the chain for the next round of checks.
+ next_modified_cells.insert(r);
+ }
+ }
+
+ cur_modified_cells.swap(next_modified_cells);
+ }
+
+ return dirty_formula_cells;
+}
+
+std::vector<abs_range_t> dirty_cell_tracker::query_and_sort_dirty_cells(const abs_range_t& modified_cell) const
+{
+ abs_range_set_t mod_cells;
+ mod_cells.insert(modified_cell);
+ return query_and_sort_dirty_cells(mod_cells);
+}
+
+std::vector<abs_range_t> dirty_cell_tracker::query_and_sort_dirty_cells(
+ const abs_range_set_t& modified_cells, const abs_range_set_t* dirty_formula_cells) const
+{
+ abs_range_set_t cur_modified_cells = modified_cells;
+
+ abs_range_set_t final_dirty_formula_cells;
+
+ // Get the initial set of formula cells affected by the modified cells.
+ // Note that these modified cells are not dirty formula cells.
+ if (!cur_modified_cells.empty())
+ {
+ abs_range_set_t next_modified_cells;
+ for (const abs_range_t& mc : cur_modified_cells)
+ {
+ for (const abs_range_t& r : mp_impl->get_affected_cell_ranges(mc))
+ {
+ auto res = final_dirty_formula_cells.insert(r);
+ if (res.second)
+ // This affected range has not yet been visited. Put it
+ // in the chain for the next round of checks.
+ next_modified_cells.insert(r);
+ }
+ }
+
+ cur_modified_cells.swap(next_modified_cells);
+ }
+
+ // Because the modified cells in the subsequent rounds are all dirty
+ // formula cells, we need to track precedent-dependent relationships for
+ // later sorting.
+
+ cur_modified_cells.insert(mp_impl->m_volatile_cells.begin(), mp_impl->m_volatile_cells.end());
+
+ if (dirty_formula_cells)
+ cur_modified_cells.insert(dirty_formula_cells->begin(), dirty_formula_cells->end());
+
+ using dfs_type = depth_first_search<abs_range_t, abs_range_t::hash>;
+ dfs_type::relations rels;
+
+ while (!cur_modified_cells.empty())
+ {
+ abs_range_set_t next_modified_cells;
+ for (const abs_range_t& mc : cur_modified_cells)
+ {
+ for (const abs_range_t& r : mp_impl->get_affected_cell_ranges(mc))
+ {
+ // Record each precedent-dependent relationship (r =
+ // precedent; mc = dependent).
+ rels.insert(r, mc);
+
+ auto res = final_dirty_formula_cells.insert(r);
+ if (res.second)
+ // This affected range has not yet been visited. Put it
+ // in the chain for the next round of checks.
+ next_modified_cells.insert(r);
+ }
+ }
+
+ cur_modified_cells.swap(next_modified_cells);
+ }
+
+ // Volatile cells are always formula cells and therefore always should be
+ // included.
+ final_dirty_formula_cells.insert(
+ mp_impl->m_volatile_cells.begin(), mp_impl->m_volatile_cells.end());
+
+ if (dirty_formula_cells)
+ {
+ final_dirty_formula_cells.insert(
+ dirty_formula_cells->begin(), dirty_formula_cells->end());
+ }
+
+ // Perform topological sort on the dirty formula cell ranges.
+ std::vector<abs_range_t> retval;
+ dfs_type sorter(final_dirty_formula_cells.begin(), final_dirty_formula_cells.end(), rels, dfs_type::back_inserter(retval));
+ sorter.run();
+
+ return retval;
+}
+
+std::string dirty_cell_tracker::to_string() const
+{
+ auto resolver = formula_name_resolver::get(formula_name_resolver_t::excel_a1, nullptr);
+ const abs_address_t origin(0, 0, 0);
+
+ rc_t max_val = std::numeric_limits<rc_t>::max();
+ std::vector<std::string> lines;
+
+ for (rc_t i = 0, n = mp_impl->m_grids.size(); i < n; ++i)
+ {
+ const rtree_type& grid = mp_impl->m_grids[i];
+ rtree_type::const_search_results res =
+ grid.search({{0, 0}, {max_val, max_val}}, rtree_type::search_type::overlap);
+
+ for (auto it = res.cbegin(); it != res.cend(); ++it)
+ {
+ const rtree_type::extent_type& ext = it.extent();
+ const abs_range_set_t& srcs = *it;
+
+ range_t dest(
+ address_t(i, ext.start.d[0], ext.start.d[1]),
+ address_t(i, ext.end.d[0], ext.end.d[1]));
+
+ dest.set_absolute(false);
+
+ std::string dest_name = ext.is_point() ?
+ resolver->get_name(dest.first, origin, false) :
+ resolver->get_name(dest, origin, false);
+
+ for (const abs_range_t& src : srcs)
+ {
+ std::ostringstream os;
+ os << mp_impl->print(src);
+ os << " -> Sheet" << (dest.first.sheet+1) << '!' << dest_name;
+ lines.push_back(os.str());
+ }
+ }
+ }
+
+ if (lines.empty())
+ return std::string();
+
+ std::ostringstream os;
+ auto it = lines.begin();
+ os << *it;
+ for (++it; it != lines.end(); ++it)
+ os << std::endl << *it;
+ return os.str();
+}
+
+bool dirty_cell_tracker::empty() const
+{
+ for (const rtree_type& grid : mp_impl->m_grids)
+ {
+ if (!grid.empty())
+ return false;
+ }
+
+ return true;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/dirty_cell_tracker_test.cpp b/src/libixion/dirty_cell_tracker_test.cpp
new file mode 100644
index 0000000..3a035fc
--- /dev/null
+++ b/src/libixion/dirty_cell_tracker_test.cpp
@@ -0,0 +1,395 @@
+/* -*- 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 "test_global.hpp" // This must be the first header to be included.
+#include <ixion/dirty_cell_tracker.hpp>
+#include <cassert>
+#include <iostream>
+#include <unordered_map>
+
+using namespace ixion;
+using namespace std;
+
+using ranks_type = std::unordered_map<abs_range_t, size_t, abs_range_t::hash>;
+
+ranks_type create_ranks(const std::vector<abs_range_t>& sorted)
+{
+ ranks_type ranks;
+ size_t rank = 0;
+ for (const abs_range_t& r : sorted)
+ ranks.insert({r, rank++});
+
+ return ranks;
+}
+
+void test_empty_query()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ dirty_cell_tracker tracker;
+
+ // Empty query.
+ abs_range_set_t mod_cells;
+ abs_range_set_t res = tracker.query_dirty_cells(mod_cells);
+ assert(res.empty());
+
+ // A "modified" cell is outside existing sheet range. Make sure we don't
+ // crash here.
+ mod_cells.emplace(1, 1, 1);
+ res = tracker.query_dirty_cells(mod_cells);
+ assert(res.empty());
+
+ auto sorted = tracker.query_and_sort_dirty_cells(mod_cells);
+ assert(sorted.empty());
+
+ // Dirty cells are like newly entered formula cells that need to be
+ // calculated regardless.
+ abs_range_set_t dirty_cells;
+ dirty_cells.emplace(0, 0, 0); // A1
+ dirty_cells.emplace(0, 1, 0); // A2
+ dirty_cells.emplace(0, 0, 1); // B1
+ dirty_cells.emplace(0, 1, 1); // B2
+ sorted = tracker.query_and_sort_dirty_cells(mod_cells, &dirty_cells);
+ assert(sorted.size() == 4);
+}
+
+void test_cell_to_cell()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ dirty_cell_tracker tracker;
+
+ // A2 to listen to A1.
+ abs_address_t A1(0, 0, 0);
+ abs_address_t A2(0, 1, 0);
+ tracker.add(A2, A1);
+
+ // A1 is modified. A2 should be updated.
+ abs_range_set_t mod_cells;
+ mod_cells.insert(A1);
+ abs_range_set_t res = tracker.query_dirty_cells(mod_cells);
+ assert(res.size() == 1);
+ abs_range_t cell = *res.cbegin();
+ assert(cell.first == A2);
+
+ // A3 to listen to A2.
+ abs_address_t A3(0, 2, 0);
+ tracker.add(A3, A2);
+
+ // A1 is modified. Both A2 and A3 should be updated.
+ res = tracker.query_dirty_cells(mod_cells);
+ assert(res.size() == 2);
+
+ assert(res.count(A2) > 0);
+ assert(res.count(A3) > 0);
+
+ // A2 should be ranked lower than A3.
+ auto sorted = tracker.query_and_sort_dirty_cells(mod_cells);
+ auto ranks = create_ranks(sorted);
+ assert(ranks[A2] < ranks[A3]);
+
+ // A2 no longer tracks A1, and because of this, a modification of A1
+ // should not trigger any updates.
+ tracker.remove(A2, A1);
+ res = tracker.query_dirty_cells(mod_cells);
+ assert(res.empty());
+
+ // Add A2->A1 once again, to re-establish the previous chain.
+ tracker.add(A2, A1);
+ res = tracker.query_dirty_cells(mod_cells);
+ assert(res.size() == 2);
+
+ assert(res.count(A2) > 0);
+ assert(res.count(A3) > 0);
+}
+
+void test_cell_to_range()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ dirty_cell_tracker tracker;
+
+ // B2 listens to C1:D4.
+ abs_address_t B2(0, 1, 1);
+ abs_range_t C1_D4(0, 0, 2, 4, 2);
+ tracker.add(B2, C1_D4);
+
+ // D3 gets modified. B2 should be updated.
+ abs_range_set_t mod_cells;
+ mod_cells.emplace(0, 2, 3);
+ abs_range_set_t res = tracker.query_dirty_cells(mod_cells);
+ assert(res.size() == 1);
+ assert(res.count(B2) > 0);
+
+ // E10 listens to A1:B4 which includes B2.
+ abs_address_t E10(0, 9, 4);
+ abs_range_t A1_B4(0, 0, 0, 4, 2);
+ tracker.add(E10, A1_B4);
+
+ // D3 gets modified again. This time both B2 and E10 need updating.
+ res = tracker.query_dirty_cells(mod_cells);
+ assert(res.size() == 2);
+ assert(res.count(B2) > 0);
+ assert(res.count(E10) > 0);
+
+ // B2 no longer listens to C1:D4.
+ tracker.remove(B2, C1_D4);
+
+ // D3 gets modified again, but no cells need updating.
+ res = tracker.query_dirty_cells(mod_cells);
+ assert(res.empty());
+
+ // B3 gets modified. E10 should be updated.
+ mod_cells.clear();
+ abs_address_t B3(0, 2, 1);
+ mod_cells.insert(B3);
+ res = tracker.query_dirty_cells(mod_cells);
+ assert(res.size() == 1);
+ assert(res.count(E10) > 0);
+}
+
+void test_volatile_cells()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ dirty_cell_tracker tracker;
+
+ // We use sheet 2 in this test.
+ abs_address_t A1(1, 0, 0);
+ abs_address_t B2(1, 1, 1);
+ abs_address_t C3(1, 2, 2);
+
+ tracker.add_volatile(A1);
+
+ // No cells have been modified.
+ abs_range_set_t mod_cells;
+ abs_range_set_t res = tracker.query_dirty_cells(mod_cells);
+
+ assert(res.size() == 1);
+ assert(res.count(A1) > 0);
+
+ tracker.add_volatile(B2);
+ res = tracker.query_dirty_cells(mod_cells);
+ assert(res.size() == 2);
+ assert(res.count(A1) > 0);
+ assert(res.count(B2) > 0);
+
+ tracker.add_volatile(C3);
+ res = tracker.query_dirty_cells(mod_cells);
+ assert(res.size() == 3);
+ assert(res.count(A1) > 0);
+ assert(res.count(B2) > 0);
+ assert(res.count(C3) > 0);
+
+ // Start removing the volatile cells one by one.
+ tracker.remove_volatile(B2);
+ res = tracker.query_dirty_cells(mod_cells);
+ assert(res.size() == 2);
+ assert(res.count(A1) > 0);
+ assert(res.count(C3) > 0);
+
+ tracker.remove_volatile(C3);
+ res = tracker.query_dirty_cells(mod_cells);
+ assert(res.size() == 1);
+ assert(res.count(A1) > 0);
+
+ tracker.remove_volatile(A1);
+ res = tracker.query_dirty_cells(mod_cells);
+ assert(res.empty());
+}
+
+void test_volatile_cells_2()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ dirty_cell_tracker tracker;
+
+ abs_address_t A1(1, 0, 0);
+ abs_address_t B2(1, 1, 1);
+ abs_address_t C3(1, 2, 2);
+
+ // B2 tracks A1 and C3 tracks B2.
+ tracker.add(B2, A1);
+ tracker.add(C3, B2);
+
+ // No cells have been modified.
+ abs_range_set_t mod_cells;
+ abs_range_set_t res = tracker.query_dirty_cells(mod_cells);
+ assert(res.empty());
+
+ // Set A1 as volatile cell. All of A2, B2 and C3 should be dirty.
+ tracker.add_volatile(A1);
+ res = tracker.query_dirty_cells(mod_cells);
+ assert(res.size() == 3);
+
+ auto sorted = tracker.query_and_sort_dirty_cells(mod_cells);
+ assert(sorted.size() == 3);
+ auto ranks = create_ranks(sorted);
+ assert(ranks[A1] < ranks[B2]);
+ assert(ranks[B2] < ranks[C3]);
+
+ // Remove A1 as volatile cell. Now no cells should be dirty.
+ tracker.remove_volatile(A1);
+ sorted = tracker.query_and_sort_dirty_cells(mod_cells);
+ assert(sorted.empty());
+
+ // Now, declare A1 as a dirty cell.
+ abs_range_set_t dirty_cells;
+ dirty_cells.insert(A1);
+ sorted = tracker.query_and_sort_dirty_cells(mod_cells, &dirty_cells);
+ assert(sorted.size() == 3);
+}
+
+void test_multi_sheets()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ dirty_cell_tracker tracker;
+
+ // B2 on sheet 2 tracks A10 on sheet 1.
+ abs_address_t s2_B2(1, 1, 1);
+ abs_address_t s1_A10(0, 9, 0);
+ tracker.add(s2_B2, s1_A10);
+
+ // A10 on sheet 1 gets modified.
+ abs_range_set_t res = tracker.query_dirty_cells(s1_A10);
+ assert(res.size() == 1);
+ assert(res.count(s2_B2) > 0);
+
+ // A10 on sheet 2 gets modified. This should trigger no updates.
+ abs_address_t s2_A10(1, 9, 0);
+ res = tracker.query_dirty_cells(s2_A10);
+ assert(res.empty());
+}
+
+void test_recursive_tracking()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ dirty_cell_tracker tracker;
+
+ abs_address_t A1(0, 0, 0), B1(0, 0, 1);
+
+ // A1 and B1 track each other.
+ tracker.add(A1, B1);
+ tracker.add(B1, A1);
+
+ abs_range_set_t res = tracker.query_dirty_cells(A1);
+ assert(res.size() == 2);
+ assert(res.count(A1) > 0);
+ assert(res.count(B1) > 0);
+}
+
+void test_listen_to_cell_in_range()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ dirty_cell_tracker tracker;
+
+ abs_address_t A2(0, 1, 0), E7(0, 6, 4), G11(0, 10, 6);
+ abs_range_t C5_E7(0, 4, 2, 3, 3);
+ abs_range_t A1_A3(0, 0, 0, 3, 1);
+ abs_range_t C1_E1(0, 0, 2, 1, 3);
+ abs_range_t G5_H7(0, 4, 6, 3, 2);
+ abs_range_t E7_G9(0, 6, 4, 3, 3);
+
+ tracker.add(G11, E7);
+ tracker.add(C5_E7, A1_A3);
+ tracker.add(C5_E7, C1_E1);
+
+ cout << "--" << endl;
+ cout << tracker.to_string() << endl;
+
+ abs_range_set_t res = tracker.query_dirty_cells(A2);
+ assert(res.size() == 2);
+ assert(res.count(C5_E7) > 0);
+ assert(res.count(G11) > 0);
+
+ tracker.add(G5_H7, A1_A3);
+ tracker.remove(G11, E7);
+ tracker.add(G11, E7_G9);
+
+ cout << "--" << endl;
+ cout << tracker.to_string() << endl;
+
+ res = tracker.query_dirty_cells(A2);
+ assert(res.size() == 3);
+ assert(res.count(C5_E7) > 0);
+ assert(res.count(G5_H7) > 0);
+ assert(res.count(G11) > 0);
+
+ // Test topological sort results, and make sure they are ranked correctly.
+ std::vector<abs_range_t> sorted = tracker.query_and_sort_dirty_cells(A2);
+
+ auto ranks = create_ranks(sorted);
+ assert(ranks[C5_E7] < ranks[G11]);
+ assert(ranks[G5_H7] < ranks[G11]);
+}
+
+void test_listen_to_3d_range()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ dirty_cell_tracker tracker;
+
+ abs_address_t E7(0, 6, 4);
+ abs_range_t C1_E5_sheets_0_2(0, 0, 2, 5, 3);
+ C1_E5_sheets_0_2.last.sheet += 2;
+ tracker.add(E7, C1_E5_sheets_0_2);
+
+ cout << "--" << endl;
+ cout << tracker.to_string() << endl;
+
+ abs_address_t D3(0, 2, 3);
+ for (sheet_t s = 0; s <= 4; ++s)
+ {
+ D3.sheet = s;
+ auto cells = tracker.query_dirty_cells(D3);
+ if (s <= 2)
+ {
+ assert(cells.size() == 1);
+ assert(E7 == *cells.begin());
+ }
+ else
+ assert(cells.empty());
+ }
+
+ for (sheet_t s = 0; s <= 4; ++s)
+ {
+ abs_range_t E5_F6(0, 4, 4, 2, 2);
+ E5_F6.first.sheet = E5_F6.last.sheet = s;
+ auto cells = tracker.query_dirty_cells(E5_F6);
+ if (s <= 2)
+ {
+ assert(cells.size() == 1);
+ assert(E7 == *cells.begin());
+ }
+ else
+ assert(cells.empty());
+ }
+
+ // Remove the multi-sheet listener, and make sure the tracker is empty.
+ tracker.remove(E7, C1_E5_sheets_0_2);
+ assert(tracker.empty());
+}
+
+int main()
+{
+ test_empty_query();
+ test_cell_to_cell();
+ test_cell_to_range();
+ test_volatile_cells();
+ test_volatile_cells_2();
+ test_multi_sheets();
+ test_recursive_tracking();
+ test_listen_to_cell_in_range();
+ test_listen_to_3d_range();
+
+ return EXIT_SUCCESS;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/document.cpp b/src/libixion/document.cpp
new file mode 100644
index 0000000..3c9854f
--- /dev/null
+++ b/src/libixion/document.cpp
@@ -0,0 +1,237 @@
+/* -*- 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 "ixion/document.hpp"
+#include "ixion/global.hpp"
+#include "ixion/model_context.hpp"
+#include "ixion/formula_name_resolver.hpp"
+#include "ixion/formula.hpp"
+#include "ixion/cell_access.hpp"
+
+#include <cstring>
+#include <sstream>
+
+namespace ixion {
+
+namespace {
+
+abs_address_t to_address(
+ const model_context& cxt, const formula_name_resolver& resolver, const document::cell_pos& pos)
+{
+ switch (pos.type)
+ {
+ case document::cell_pos::cp_type::string:
+ {
+ std::string_view s = std::get<std::string_view>(pos.value);
+ formula_name_t name = resolver.resolve(s, abs_address_t());
+ if (name.type != formula_name_t::cell_reference)
+ {
+ std::ostringstream os;
+ os << "invalid cell address: " << s;
+ throw std::invalid_argument(os.str());
+ }
+
+ return std::get<address_t>(name.value).to_abs(abs_address_t());
+ }
+ case document::cell_pos::cp_type::address:
+ {
+ return std::get<abs_address_t>(pos.value);
+ }
+ }
+
+ throw std::logic_error("unrecognized cell position type.");
+}
+
+} // anonymous namespace
+
+document::cell_pos::cell_pos(const char* p) :
+ type(cp_type::string),
+ value(p)
+{
+}
+
+document::cell_pos::cell_pos(const char* p, size_t n) :
+ type(cp_type::string),
+ value(std::string_view(p, n))
+{
+}
+
+document::cell_pos::cell_pos(const std::string& s) :
+ type(cp_type::string),
+ value(s)
+{
+}
+
+document::cell_pos::cell_pos(const abs_address_t& addr) :
+ type(cp_type::address),
+ value(addr)
+{
+}
+
+struct document::impl
+{
+ model_context cxt;
+ std::unique_ptr<formula_name_resolver> resolver;
+
+ abs_range_set_t modified_cells;
+ abs_range_set_t modified_formula_cells;
+
+ impl() :
+ cxt(),
+ resolver(formula_name_resolver::get(formula_name_resolver_t::excel_a1, &cxt))
+ {}
+
+ impl(formula_name_resolver_t cell_address_type) :
+ cxt(),
+ resolver(formula_name_resolver::get(cell_address_type, &cxt))
+ {}
+
+ void append_sheet(std::string name)
+ {
+ cxt.append_sheet(std::move(name));
+ }
+
+ void set_sheet_name(sheet_t sheet, std::string name)
+ {
+ cxt.set_sheet_name(sheet, std::move(name));
+ }
+
+ cell_access get_cell_access(cell_pos pos) const
+ {
+ abs_address_t addr = to_address(cxt, *resolver, pos);
+ return cxt.get_cell_access(addr);
+ }
+
+ void set_numeric_cell(cell_pos pos, double val)
+ {
+ abs_address_t addr = to_address(cxt, *resolver, pos);
+ unregister_formula_cell(cxt, addr);
+ cxt.set_numeric_cell(addr, val);
+ modified_cells.insert(addr);
+ }
+
+ void set_string_cell(cell_pos pos, std::string_view s)
+ {
+ abs_address_t addr = to_address(cxt, *resolver, pos);
+ unregister_formula_cell(cxt, addr);
+ cxt.set_string_cell(addr, s);
+ modified_cells.insert(addr);
+ }
+
+ void set_boolean_cell(cell_pos pos, bool val)
+ {
+ abs_address_t addr = to_address(cxt, *resolver, pos);
+ unregister_formula_cell(cxt, addr);
+ cxt.set_boolean_cell(addr, val);
+ modified_cells.insert(addr);
+ }
+
+ void empty_cell(cell_pos pos)
+ {
+ abs_address_t addr = to_address(cxt, *resolver, pos);
+ unregister_formula_cell(cxt, addr);
+ cxt.empty_cell(addr);
+ modified_cells.insert(addr);
+ }
+
+ double get_numeric_value(cell_pos pos) const
+ {
+ abs_address_t addr = to_address(cxt, *resolver, pos);
+ return cxt.get_numeric_value(addr);
+ }
+
+ std::string_view get_string_value(cell_pos pos) const
+ {
+ abs_address_t addr = to_address(cxt, *resolver, pos);
+ return cxt.get_string_value(addr);
+ }
+
+ void set_formula_cell(cell_pos pos, std::string_view formula)
+ {
+ abs_address_t addr = to_address(cxt, *resolver, pos);
+ unregister_formula_cell(cxt, addr);
+ auto tokens = parse_formula_string(cxt, addr, *resolver, formula);
+ formula_cell* fc = cxt.set_formula_cell(addr, std::move(tokens));
+ register_formula_cell(cxt, addr, fc);
+ modified_formula_cells.insert(addr);
+ }
+
+ void calculate(size_t thread_count)
+ {
+ auto sorted_cells = query_and_sort_dirty_cells(cxt, modified_cells, &modified_formula_cells);
+ calculate_sorted_cells(cxt, sorted_cells, thread_count);
+ modified_cells.clear();
+ modified_formula_cells.clear();
+ }
+};
+
+document::document() :
+ mp_impl(std::make_unique<impl>()) {}
+
+document::document(formula_name_resolver_t cell_address_type) :
+ mp_impl(std::make_unique<impl>(cell_address_type)) {}
+
+document::~document() {}
+
+void document::append_sheet(std::string name)
+{
+ mp_impl->append_sheet(std::move(name));
+}
+
+void document::set_sheet_name(sheet_t sheet, std::string name)
+{
+ mp_impl->set_sheet_name(sheet, std::move(name));
+}
+
+cell_access document::get_cell_access(cell_pos pos) const
+{
+ return mp_impl->get_cell_access(pos);
+}
+
+void document::set_numeric_cell(cell_pos pos, double val)
+{
+ mp_impl->set_numeric_cell(pos, val);
+}
+
+void document::set_string_cell(cell_pos pos, std::string_view s)
+{
+ mp_impl->set_string_cell(pos, s);
+}
+
+void document::set_boolean_cell(cell_pos pos, bool val)
+{
+ mp_impl->set_boolean_cell(pos, val);
+}
+
+void document::empty_cell(cell_pos pos)
+{
+ mp_impl->empty_cell(pos);
+}
+
+double document::get_numeric_value(cell_pos pos) const
+{
+ return mp_impl->get_numeric_value(pos);
+}
+
+std::string_view document::get_string_value(cell_pos pos) const
+{
+ return mp_impl->get_string_value(pos);
+}
+
+void document::set_formula_cell(cell_pos pos, std::string_view formula)
+{
+ mp_impl->set_formula_cell(pos, formula);
+}
+
+void document::calculate(size_t thread_count)
+{
+ mp_impl->calculate(thread_count);
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/document_test.cpp b/src/libixion/document_test.cpp
new file mode 100644
index 0000000..fb14264
--- /dev/null
+++ b/src/libixion/document_test.cpp
@@ -0,0 +1,190 @@
+/* -*- 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 "test_global.hpp" // This must be the first header to be included.
+
+#include <ixion/document.hpp>
+#include <ixion/address.hpp>
+#include <ixion/macros.hpp>
+#include <ixion/cell_access.hpp>
+
+#include <iostream>
+#include <cassert>
+#include <sstream>
+
+using namespace std;
+using namespace ixion;
+
+namespace {
+
+bool equal(double v1, double v2)
+{
+ // To work around the floating point rounding errors, convert the values to
+ // strings then compare as string numbers.
+ std::ostringstream os1, os2;
+ os1 << v1;
+ os2 << v2;
+ return os1.str() == os2.str();
+}
+
+}
+
+void test_basic_calc()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ abs_address_t A1(0, 0, 0);
+ abs_address_t A2(0, 1, 0);
+ abs_address_t A3(0, 2, 0);
+
+ document doc;
+ doc.append_sheet("test");
+ doc.set_numeric_cell("A1", 1.1);
+ doc.set_numeric_cell(A2, 1.2);
+ doc.set_numeric_cell({IXION_ASCII("A3")}, 1.3);
+
+ assert(doc.get_numeric_value(A1) == 1.1);
+ assert(doc.get_numeric_value("A2") == 1.2);
+ assert(doc.get_numeric_value(A3) == 1.3);
+
+ doc.set_formula_cell("B4", "SUM(A1:A3)");
+ doc.calculate(0);
+
+ double v = doc.get_numeric_value("B4");
+ assert(equal(v, 3.6));
+
+ doc.empty_cell(A2);
+ doc.calculate(0);
+ v = doc.get_numeric_value("B4");
+ assert(equal(v, 2.4));
+}
+
+void test_string_io()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ document doc;
+ doc.append_sheet("test");
+
+ std::string B3("B3"), C4("C4"), D5("D5"), D5_value("Cell D5 value");
+ doc.set_string_cell(B3, "Cell B3");
+ doc.set_string_cell(C4, "Cell C4");
+ doc.set_string_cell(D5, D5_value);
+
+ std::string_view s = doc.get_string_value(B3);
+ assert(s == "Cell B3");
+
+ s = doc.get_string_value(C4);
+ assert(s == "Cell C4");
+
+ s = doc.get_string_value(D5);
+ assert(s == D5_value);
+
+ doc.set_formula_cell("A10", "CONCATENATE(B3, \" and \", C4)");
+ doc.calculate(0);
+
+ s = doc.get_string_value("A10");
+ assert(s == "Cell B3 and Cell C4");
+
+ doc.empty_cell(C4);
+ doc.calculate(0);
+
+ s = doc.get_string_value("A10");
+ assert(s == "Cell B3 and ");
+}
+
+void test_boolean_io()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ document doc;
+ doc.append_sheet("test1");
+ doc.append_sheet("test2");
+
+ doc.set_boolean_cell("test2!B2", true);
+ doc.set_boolean_cell("test2!C3", false);
+
+ doc.set_formula_cell("test1!A1", "SUM(test2!A1:D4)");
+ doc.calculate(0);
+ double v = doc.get_numeric_value("test1!A1");
+ assert(v == 1.0);
+
+ // Trigger recalculation.
+ doc.set_boolean_cell("test2!C4", true);
+ doc.calculate(0);
+ v = doc.get_numeric_value("test1!A1");
+ assert(v == 2.0);
+
+ cell_access ca = doc.get_cell_access("test2!B2");
+ assert(ca.get_type() == celltype_t::boolean);
+ assert(ca.get_value_type() == cell_value_t::boolean);
+
+ ca = doc.get_cell_access("test2!C3");
+ assert(ca.get_type() == celltype_t::boolean);
+ assert(ca.get_value_type() == cell_value_t::boolean);
+}
+
+void test_custom_cell_address_syntax()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ document doc(formula_name_resolver_t::excel_r1c1);
+ doc.append_sheet("MySheet");
+
+ doc.set_numeric_cell("R3C3", 345.0);
+ abs_address_t R3C3(0, 2, 2);
+ double v = doc.get_numeric_value(R3C3);
+ assert(v == 345.0);
+}
+
+void test_rename_sheets()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ document doc(formula_name_resolver_t::excel_r1c1);
+ doc.append_sheet("Sheet1");
+ doc.append_sheet("Sheet2");
+ doc.append_sheet("Sheet3");
+ doc.set_numeric_cell("Sheet3!R3C3", 456.0);
+
+ double v = doc.get_numeric_value("Sheet3!R3C3");
+ assert(v == 456.0);
+
+ doc.set_sheet_name(0, "S1");
+ doc.set_sheet_name(1, "S2");
+ doc.set_sheet_name(2, "S3");
+
+ v = doc.get_numeric_value("S3!R3C3");
+ assert(v == 456.0);
+
+ doc.set_numeric_cell("S2!R4C2", 789.0);
+ v = doc.get_numeric_value("S2!R4C2");
+ assert(v == 789.0);
+
+ try
+ {
+ doc.get_numeric_value("Sheet3!R3C3");
+ assert(!"Exception should've been thrown.");
+ }
+ catch (const std::invalid_argument&)
+ {
+ // correct exception
+ }
+}
+
+int main()
+{
+ test_basic_calc();
+ test_string_io();
+ test_boolean_io();
+ test_custom_cell_address_syntax();
+ test_rename_sheets();
+
+ return EXIT_SUCCESS;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/exceptions.cpp b/src/libixion/exceptions.cpp
new file mode 100644
index 0000000..b87f834
--- /dev/null
+++ b/src/libixion/exceptions.cpp
@@ -0,0 +1,117 @@
+/* -*- 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 "ixion/exceptions.hpp"
+
+#include <sstream>
+
+namespace ixion {
+
+general_error::general_error() : m_msg() {}
+
+general_error::general_error(const std::string& msg) :
+ m_msg(msg) {}
+
+general_error::~general_error() {}
+
+const char* general_error::what() const noexcept
+{
+ return m_msg.c_str();
+}
+
+void general_error::set_message(const std::string& msg)
+{
+ m_msg = msg;
+}
+
+struct formula_error::impl
+{
+ formula_error_t error;
+ std::string msg;
+ std::string buffer;
+
+ impl(formula_error_t _error) :
+ error(_error) {}
+
+ impl(formula_error_t _error, std::string _msg) :
+ error(_error), msg(std::move(_msg)) {}
+};
+
+formula_error::formula_error(formula_error_t fe) :
+ mp_impl(std::make_unique<impl>(fe)) {}
+
+formula_error::formula_error(formula_error_t fe, std::string msg) :
+ mp_impl(std::make_unique<impl>(fe, std::move(msg))) {}
+
+formula_error::formula_error(formula_error&& other) :
+ mp_impl(std::move(other.mp_impl))
+{
+ other.mp_impl = std::make_unique<impl>(formula_error_t::no_error);
+}
+
+formula_error::~formula_error()
+{
+}
+
+const char* formula_error::what() const noexcept
+{
+ std::string_view error_name = get_formula_error_name(mp_impl->error);
+ if (mp_impl->msg.empty())
+ return error_name.data();
+
+ std::ostringstream os;
+ os << mp_impl->msg << " (type: " << error_name << ")";
+ mp_impl->buffer = os.str();
+ return mp_impl->buffer.data();
+}
+
+formula_error_t formula_error::get_error() const
+{
+ return mp_impl->error;
+}
+
+formula_registration_error::formula_registration_error(const std::string& msg)
+{
+ std::ostringstream os;
+ os << "formula_registration_error: " << msg;
+ set_message(os.str());
+}
+
+formula_registration_error::~formula_registration_error() {}
+
+file_not_found::file_not_found(const std::string& fpath) :
+ general_error(fpath)
+{
+ std::ostringstream os;
+ os << "specified file not found: " << fpath;
+ set_message(os.str());
+}
+
+file_not_found::~file_not_found() {}
+
+model_context_error::model_context_error(const std::string& msg, error_type type) :
+ general_error(msg), m_type(type) {}
+
+model_context_error::~model_context_error() {}
+
+model_context_error::error_type model_context_error::get_error_type() const
+{
+ return m_type;
+}
+
+not_implemented_error::not_implemented_error(const std::string& msg)
+{
+ std::ostringstream os;
+ os << "not_implemented_error: " << msg;
+ set_message(os.str());
+}
+
+not_implemented_error::~not_implemented_error() {}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/formula.cpp b/src/libixion/formula.cpp
new file mode 100644
index 0000000..21e990a
--- /dev/null
+++ b/src/libixion/formula.cpp
@@ -0,0 +1,464 @@
+/* -*- 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 <ixion/formula.hpp>
+#include <ixion/formula_name_resolver.hpp>
+#include <ixion/formula_function_opcode.hpp>
+#include <ixion/cell.hpp>
+#include <ixion/dirty_cell_tracker.hpp>
+#include <ixion/types.hpp>
+#include <ixion/model_context.hpp>
+
+#include "formula_lexer.hpp"
+#include "formula_parser.hpp"
+#include "formula_functions.hpp"
+#include "debug.hpp"
+
+#include <sstream>
+#include <algorithm>
+
+namespace ixion {
+
+namespace {
+
+#if IXION_LOGGING
+
+[[maybe_unused]] std::string debug_print_formula_tokens(const formula_tokens_t& tokens)
+{
+ std::ostringstream os;
+
+ for (const formula_token& t : tokens)
+ {
+ os << std::endl << " * " << t;
+ }
+
+ return os.str();
+}
+
+#endif
+
+void print_token(
+ const print_config& config, const model_context& cxt, const abs_address_t& pos,
+ const formula_name_resolver& resolver, const formula_token& token, std::ostream& os)
+{
+ auto sheet_to_print = [&config, &pos](const address_t& ref_addr) -> bool
+ {
+ switch (config.display_sheet)
+ {
+ case display_sheet_t::always:
+ return true;
+ case display_sheet_t::never:
+ return false;
+ case display_sheet_t::only_if_different:
+ return ref_addr.to_abs(pos).sheet != pos.sheet;
+ case display_sheet_t::unspecified:
+ break;
+ }
+ return false;
+ };
+
+ switch (token.opcode)
+ {
+ case fop_array_open:
+ os << '{';
+ break;
+ case fop_array_close:
+ os << '}';
+ break;
+ case fop_close:
+ os << ')';
+ break;
+ case fop_divide:
+ os << '/';
+ break;
+ case fop_minus:
+ os << '-';
+ break;
+ case fop_multiply:
+ os << '*';
+ break;
+ case fop_exponent:
+ os << '^';
+ break;
+ case fop_concat:
+ os << '&';
+ break;
+ case fop_open:
+ os << '(';
+ break;
+ case fop_plus:
+ os << '+';
+ break;
+ case fop_value:
+ os << std::get<double>(token.value);
+ break;
+ case fop_sep:
+ os << cxt.get_config().sep_function_arg;
+ break;
+ case fop_array_row_sep:
+ os << cxt.get_config().sep_matrix_row;
+ break;
+ case fop_function:
+ {
+ auto fop = std::get<formula_function_t>(token.value);
+ os << formula_functions::get_function_name(fop);
+ break;
+ }
+ case fop_single_ref:
+ {
+ const address_t& addr = std::get<address_t>(token.value);
+ bool sheet_name = sheet_to_print(addr);
+ os << resolver.get_name(addr, pos, sheet_name);
+ break;
+ }
+ case fop_range_ref:
+ {
+ const range_t& range = std::get<range_t>(token.value);
+ bool sheet_name = sheet_to_print(range.first);
+ os << resolver.get_name(range, pos, sheet_name);
+ break;
+ }
+ case fop_table_ref:
+ {
+ const table_t& tbl = std::get<table_t>(token.value);
+ os << resolver.get_name(tbl);
+ break;
+ }
+ case fop_string:
+ {
+ auto sid = std::get<string_id_t>(token.value);
+ const std::string* p = cxt.get_string(sid);
+ if (p)
+ os << "\"" << *p << "\"";
+ else
+ IXION_DEBUG("failed to get a string value for the identifier value of " << sid);
+
+ break;
+ }
+ case fop_equal:
+ os << "=";
+ break;
+ case fop_not_equal:
+ os << "<>";
+ break;
+ case fop_less:
+ os << "<";
+ break;
+ case fop_greater:
+ os << ">";
+ break;
+ case fop_less_equal:
+ os << "<=";
+ break;
+ case fop_greater_equal:
+ os << ">=";
+ break;
+ case fop_named_expression:
+ os << std::get<std::string>(token.value);
+ break;
+ case fop_unknown:
+ default:
+ {
+ std::ostringstream repr;
+ repr << token;
+ IXION_DEBUG(
+ "token not printed (repr='" << repr.str()
+ << "'; name='" << get_opcode_name(token.opcode)
+ << "'; opcode='" << get_formula_opcode_string(token.opcode)
+ << "')");
+ }
+ }
+}
+
+}
+
+formula_tokens_t parse_formula_string(
+ model_context& cxt, const abs_address_t& pos,
+ const formula_name_resolver& resolver, std::string_view formula)
+{
+ IXION_TRACE("pos=" << pos.get_name() << "; formula='" << formula << "'");
+ lexer_tokens_t lxr_tokens;
+ formula_lexer lexer(cxt.get_config(), formula.data(), formula.size());
+ lexer.tokenize();
+ lexer.swap_tokens(lxr_tokens);
+
+ IXION_TRACE("lexer tokens: " << print_tokens(lxr_tokens, true));
+
+ formula_tokens_t tokens;
+ formula_parser parser(lxr_tokens, cxt, resolver);
+ parser.set_origin(pos);
+ parser.parse();
+ parser.get_tokens().swap(tokens);
+
+ IXION_TRACE("formula tokens (string): " << print_formula_tokens(cxt, pos, resolver, tokens));
+ IXION_TRACE("formula tokens (individual): " << debug_print_formula_tokens(tokens));
+
+ return tokens;
+}
+
+formula_tokens_t create_formula_error_tokens(
+ model_context& cxt, std::string_view src_formula,
+ std::string_view error)
+{
+ formula_tokens_t tokens;
+ tokens.emplace_back(fop_error);
+ tokens.back().value = 2u;
+
+ string_id_t sid_src_formula = cxt.add_string(src_formula);
+ tokens.emplace_back(sid_src_formula);
+
+ string_id_t sid_error = cxt.add_string(error);
+ tokens.emplace_back(sid_error);
+
+ return tokens;
+}
+
+std::string print_formula_tokens(
+ const model_context& cxt, const abs_address_t& pos,
+ const formula_name_resolver& resolver, const formula_tokens_t& tokens)
+{
+ print_config config;
+ config.display_sheet = display_sheet_t::only_if_different;
+ return print_formula_tokens(config, cxt, pos, resolver, tokens);
+}
+
+std::string print_formula_tokens(
+ const print_config& config, const model_context& cxt, const abs_address_t& pos,
+ const formula_name_resolver& resolver, const formula_tokens_t& tokens)
+{
+ std::ostringstream os;
+
+ if (!tokens.empty() && tokens[0].opcode == fop_error)
+ // Let's not print anything on error tokens.
+ return std::string();
+
+ for (const formula_token& token : tokens)
+ print_token(config, cxt, pos, resolver, token, os);
+
+ return os.str();
+}
+
+std::string print_formula_token(
+ const model_context& cxt, const abs_address_t& pos,
+ const formula_name_resolver& resolver, const formula_token& token)
+{
+ print_config config;
+ config.display_sheet = display_sheet_t::only_if_different;
+ return print_formula_token(config, cxt, pos, resolver, token);
+}
+
+std::string print_formula_token(
+ const print_config& config, const model_context& cxt, const abs_address_t& pos,
+ const formula_name_resolver& resolver, const formula_token& token)
+{
+ std::ostringstream os;
+ print_token(config, cxt, pos, resolver, token, os);
+ return os.str();
+}
+
+namespace {
+
+bool is_volatile(formula_function_t func)
+{
+ switch (func)
+ {
+ case formula_function_t::func_now:
+ return true;
+ default:
+ ;
+ }
+ return false;
+}
+
+bool has_volatile(const formula_tokens_t& tokens)
+{
+ for (const auto& t : tokens)
+ {
+ if (t.opcode != fop_function)
+ continue;
+
+ auto func = std::get<formula_function_t>(t.value);
+ if (is_volatile(func))
+ return true;
+ }
+ return false;
+}
+
+void check_sheet_or_throw(const char* func_name, sheet_t sheet, const model_context& cxt, const abs_address_t& pos, const formula_cell& cell)
+{
+ if (is_valid_sheet(sheet))
+ return;
+
+ IXION_DEBUG("invalid range reference: func=" << func_name
+ << "; pos=" << pos.get_name()
+ << "; formula='" << detail::print_formula_expression(cxt, pos, cell)
+ << "'");
+
+ std::ostringstream os;
+ os << func_name << ": invalid sheet index in " << pos.get_name()
+ << ": formula='" << detail::print_formula_expression(cxt, pos, cell) << "'";
+ throw ixion::formula_registration_error(os.str());
+}
+
+}
+
+void register_formula_cell(
+ model_context& cxt, const abs_address_t& pos, const formula_cell* cell)
+{
+#ifdef IXION_DEBUG_UTILS
+ if (cell)
+ {
+ const formula_cell* check = cxt.get_formula_cell(pos);
+ if (cell != check)
+ {
+ throw std::runtime_error(
+ "The cell instance passed to this call does not match the cell instance found at the specified position.");
+ }
+ }
+#endif
+
+ if (!cell)
+ {
+ cell = cxt.get_formula_cell(pos);
+ if (!cell)
+ // Not a formula cell. Bail out.
+ return;
+ }
+
+ formula_group_t fg_props = cell->get_group_properties();
+ dirty_cell_tracker& tracker = cxt.get_cell_tracker();
+
+ abs_range_t src_pos = pos;
+ if (fg_props.grouped)
+ {
+ // Expand the source range for grouped formula cells.
+ src_pos.last.column += fg_props.size.column - 1;
+ src_pos.last.row += fg_props.size.row - 1;
+ }
+
+ IXION_TRACE("pos=" << pos.get_name()
+ << "; formula='" << detail::print_formula_expression(cxt, pos, *cell)
+ << "'");
+
+ std::vector<const formula_token*> ref_tokens = cell->get_ref_tokens(cxt, pos);
+
+ for (const formula_token* p : ref_tokens)
+ {
+ IXION_TRACE("ref token: " << detail::print_formula_token_repr(*p));
+
+ switch (p->opcode)
+ {
+ case fop_single_ref:
+ {
+ abs_address_t addr = std::get<address_t>(p->value).to_abs(pos);
+ check_sheet_or_throw("register_formula_cell", addr.sheet, cxt, pos, *cell);
+ tracker.add(src_pos, addr);
+ break;
+ }
+ case fop_range_ref:
+ {
+ abs_range_t range = std::get<range_t>(p->value).to_abs(pos);
+ check_sheet_or_throw("register_formula_cell", range.first.sheet, cxt, pos, *cell);
+ rc_size_t sheet_size = cxt.get_sheet_size();
+ if (range.all_columns())
+ {
+ range.first.column = 0;
+ range.last.column = sheet_size.column - 1;
+ }
+ if (range.all_rows())
+ {
+ range.first.row = 0;
+ range.last.row = sheet_size.row - 1;
+ }
+ range.reorder();
+ tracker.add(src_pos, range);
+ break;
+ }
+ default:
+ ; // ignore the rest.
+ }
+ }
+
+ // Check if the cell is volatile.
+ const formula_tokens_store_ptr_t& ts = cell->get_tokens();
+ if (ts && has_volatile(ts->get()))
+ tracker.add_volatile(pos);
+}
+
+void unregister_formula_cell(model_context& cxt, const abs_address_t& pos)
+{
+ // When there is a formula cell at this position, unregister it from
+ // the dependency tree.
+ formula_cell* fcell = cxt.get_formula_cell(pos);
+ if (!fcell)
+ // Not a formula cell. Bail out.
+ return;
+
+ dirty_cell_tracker& tracker = cxt.get_cell_tracker();
+ tracker.remove_volatile(pos);
+
+ // Go through all its existing references, and remove
+ // itself as their listener. This step is important
+ // especially during partial re-calculation.
+ std::vector<const formula_token*> ref_tokens = fcell->get_ref_tokens(cxt, pos);
+
+ for (const formula_token* p : ref_tokens)
+ {
+
+ switch (p->opcode)
+ {
+ case fop_single_ref:
+ {
+ abs_address_t addr = std::get<address_t>(p->value).to_abs(pos);
+ check_sheet_or_throw("unregister_formula_cell", addr.sheet, cxt, pos, *fcell);
+ tracker.remove(pos, addr);
+ break;
+ }
+ case fop_range_ref:
+ {
+ abs_range_t range = std::get<range_t>(p->value).to_abs(pos);
+ check_sheet_or_throw("unregister_formula_cell", range.first.sheet, cxt, pos, *fcell);
+ tracker.remove(pos, range);
+ break;
+ }
+ default:
+ ; // ignore the rest.
+ }
+ }
+}
+
+abs_address_set_t query_dirty_cells(model_context& cxt, const abs_address_set_t& modified_cells)
+{
+ abs_range_set_t modified_ranges;
+ for (const abs_address_t& mc : modified_cells)
+ modified_ranges.insert(mc);
+
+ const dirty_cell_tracker& tracker = cxt.get_cell_tracker();
+ abs_range_set_t dirty_ranges = tracker.query_dirty_cells(modified_ranges);
+
+ // Convert a set of ranges to a set of addresses.
+ abs_address_set_t dirty_cells;
+ std::for_each(dirty_ranges.begin(), dirty_ranges.end(),
+ [&dirty_cells](const abs_range_t& r)
+ {
+ dirty_cells.insert(r.first);
+ }
+ );
+ return dirty_cells;
+}
+
+std::vector<abs_range_t> query_and_sort_dirty_cells(
+ model_context& cxt, const abs_range_set_t& modified_cells,
+ const abs_range_set_t* dirty_formula_cells)
+{
+ const dirty_cell_tracker& tracker = cxt.get_cell_tracker();
+ return tracker.query_and_sort_dirty_cells(modified_cells, dirty_formula_cells);
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/formula_calc.cpp b/src/libixion/formula_calc.cpp
new file mode 100644
index 0000000..0e16382
--- /dev/null
+++ b/src/libixion/formula_calc.cpp
@@ -0,0 +1,89 @@
+/* -*- 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 <ixion/formula.hpp>
+#include <ixion/address.hpp>
+#include <ixion/cell.hpp>
+#include <ixion/formula_name_resolver.hpp>
+#include <ixion/model_context.hpp>
+
+#include "queue_entry.hpp"
+#include "debug.hpp"
+
+#if IXION_THREADS
+#include "cell_queue_manager.hpp"
+#endif
+
+#include <algorithm>
+
+namespace ixion {
+
+namespace {
+
+class calc_scope
+{
+ model_context& m_cxt;
+public:
+ calc_scope(model_context& cxt) : m_cxt(cxt)
+ {
+ m_cxt.notify(formula_event_t::calculation_begins);
+ }
+
+ ~calc_scope()
+ {
+ m_cxt.notify(formula_event_t::calculation_ends);
+ }
+};
+
+}
+
+void calculate_sorted_cells(
+ model_context& cxt, const std::vector<abs_range_t>& formula_cells, size_t thread_count)
+{
+#if IXION_THREADS == 0
+ thread_count = 0; // threads are disabled thus not to be used.
+#endif
+
+ calc_scope cs(cxt);
+
+ std::vector<queue_entry> entries;
+ entries.reserve(formula_cells.size());
+
+ for (const abs_range_t& r : formula_cells)
+ entries.emplace_back(cxt.get_formula_cell(r.first), r.first);
+
+ // Reset cell status.
+ for (queue_entry& e : entries)
+ {
+ e.p->reset();
+ IXION_TRACE("pos=" << e.pos.get_name() << " formula=" << detail::print_formula_expression(cxt, e.pos, *e.p));
+ }
+
+ // First, detect circular dependencies and mark those circular
+ // dependent cells with appropriate error flags.
+ for (queue_entry& e : entries)
+ e.p->check_circular(cxt, e.pos);
+
+ if (!thread_count)
+ {
+ // Interpret cells using just a single thread.
+ for (queue_entry& e : entries)
+ e.p->interpret(cxt, e.pos);
+
+ return;
+ }
+
+#if IXION_THREADS
+ // Interpret cells in topological order using threads.
+ formula_cell_queue queue(cxt, std::move(entries), thread_count);
+ queue.run();
+#endif
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/formula_function_opcode.cpp b/src/libixion/formula_function_opcode.cpp
new file mode 100644
index 0000000..bf7db5a
--- /dev/null
+++ b/src/libixion/formula_function_opcode.cpp
@@ -0,0 +1,26 @@
+/* -*- 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 "ixion/formula_function_opcode.hpp"
+
+#include "formula_functions.hpp"
+
+namespace ixion {
+
+std::string_view get_formula_function_name(formula_function_t func)
+{
+ return formula_functions::get_function_name(func);
+}
+
+formula_function_t get_formula_function_opcode(std::string_view s)
+{
+ return formula_functions::get_function_opcode(s);
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/formula_functions.cpp b/src/libixion/formula_functions.cpp
new file mode 100644
index 0000000..c3e10a6
--- /dev/null
+++ b/src/libixion/formula_functions.cpp
@@ -0,0 +1,2389 @@
+/* -*- 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_functions.hpp"
+#include "debug.hpp"
+#include "column_store_type.hpp" // internal mdds::multi_type_vector
+#include "utils.hpp"
+#include "utf8.hpp"
+
+#include <ixion/formula_tokens.hpp>
+#include <ixion/formula_result.hpp>
+#include <ixion/matrix.hpp>
+#include <ixion/macros.hpp>
+#include <ixion/model_iterator.hpp>
+#include <ixion/cell_access.hpp>
+
+#ifdef max
+#undef max
+#endif
+#ifdef min
+#undef min
+#endif
+
+#include <cassert>
+#include <iostream>
+#include <sstream>
+#include <thread>
+#include <chrono>
+#include <cmath>
+#include <optional>
+#include <iterator>
+
+#include <mdds/sorted_string_map.hpp>
+
+namespace ixion {
+
+namespace {
+
+namespace builtin_funcs {
+
+typedef mdds::sorted_string_map<formula_function_t> map_type;
+
+// Keys must be sorted.
+constexpr map_type::entry entries[] =
+{
+ { IXION_ASCII("ABS"), formula_function_t::func_abs },
+ { IXION_ASCII("ACOS"), formula_function_t::func_acos },
+ { IXION_ASCII("ACOSH"), formula_function_t::func_acosh },
+ { IXION_ASCII("ACOT"), formula_function_t::func_acot },
+ { IXION_ASCII("ACOTH"), formula_function_t::func_acoth },
+ { IXION_ASCII("ADDRESS"), formula_function_t::func_address },
+ { IXION_ASCII("AGGREGATE"), formula_function_t::func_aggregate },
+ { IXION_ASCII("AND"), formula_function_t::func_and },
+ { IXION_ASCII("ARABIC"), formula_function_t::func_arabic },
+ { IXION_ASCII("AREAS"), formula_function_t::func_areas },
+ { IXION_ASCII("ASC"), formula_function_t::func_asc },
+ { IXION_ASCII("ASIN"), formula_function_t::func_asin },
+ { IXION_ASCII("ASINH"), formula_function_t::func_asinh },
+ { IXION_ASCII("ATAN"), formula_function_t::func_atan },
+ { IXION_ASCII("ATAN2"), formula_function_t::func_atan2 },
+ { IXION_ASCII("ATANH"), formula_function_t::func_atanh },
+ { IXION_ASCII("AVEDEV"), formula_function_t::func_avedev },
+ { IXION_ASCII("AVERAGE"), formula_function_t::func_average },
+ { IXION_ASCII("AVERAGEA"), formula_function_t::func_averagea },
+ { IXION_ASCII("AVERAGEIF"), formula_function_t::func_averageif },
+ { IXION_ASCII("AVERAGEIFS"), formula_function_t::func_averageifs },
+ { IXION_ASCII("B"), formula_function_t::func_b },
+ { IXION_ASCII("BAHTTEXT"), formula_function_t::func_bahttext },
+ { IXION_ASCII("BASE"), formula_function_t::func_base },
+ { IXION_ASCII("BETADIST"), formula_function_t::func_betadist },
+ { IXION_ASCII("BETAINV"), formula_function_t::func_betainv },
+ { IXION_ASCII("BINOMDIST"), formula_function_t::func_binomdist },
+ { IXION_ASCII("BITAND"), formula_function_t::func_bitand },
+ { IXION_ASCII("BITLSHIFT"), formula_function_t::func_bitlshift },
+ { IXION_ASCII("BITOR"), formula_function_t::func_bitor },
+ { IXION_ASCII("BITRSHIFT"), formula_function_t::func_bitrshift },
+ { IXION_ASCII("BITXOR"), formula_function_t::func_bitxor },
+ { IXION_ASCII("CEILING"), formula_function_t::func_ceiling },
+ { IXION_ASCII("CELL"), formula_function_t::func_cell },
+ { IXION_ASCII("CHAR"), formula_function_t::func_char },
+ { IXION_ASCII("CHIDIST"), formula_function_t::func_chidist },
+ { IXION_ASCII("CHIINV"), formula_function_t::func_chiinv },
+ { IXION_ASCII("CHISQDIST"), formula_function_t::func_chisqdist },
+ { IXION_ASCII("CHISQINV"), formula_function_t::func_chisqinv },
+ { IXION_ASCII("CHITEST"), formula_function_t::func_chitest },
+ { IXION_ASCII("CHOOSE"), formula_function_t::func_choose },
+ { IXION_ASCII("CLEAN"), formula_function_t::func_clean },
+ { IXION_ASCII("CODE"), formula_function_t::func_code },
+ { IXION_ASCII("COLOR"), formula_function_t::func_color },
+ { IXION_ASCII("COLUMN"), formula_function_t::func_column },
+ { IXION_ASCII("COLUMNS"), formula_function_t::func_columns },
+ { IXION_ASCII("COMBIN"), formula_function_t::func_combin },
+ { IXION_ASCII("COMBINA"), formula_function_t::func_combina },
+ { IXION_ASCII("CONCAT"), formula_function_t::func_concat },
+ { IXION_ASCII("CONCATENATE"), formula_function_t::func_concatenate },
+ { IXION_ASCII("CONFIDENCE"), formula_function_t::func_confidence },
+ { IXION_ASCII("CORREL"), formula_function_t::func_correl },
+ { IXION_ASCII("COS"), formula_function_t::func_cos },
+ { IXION_ASCII("COSH"), formula_function_t::func_cosh },
+ { IXION_ASCII("COT"), formula_function_t::func_cot },
+ { IXION_ASCII("COTH"), formula_function_t::func_coth },
+ { IXION_ASCII("COUNT"), formula_function_t::func_count },
+ { IXION_ASCII("COUNTA"), formula_function_t::func_counta },
+ { IXION_ASCII("COUNTBLANK"), formula_function_t::func_countblank },
+ { IXION_ASCII("COUNTIF"), formula_function_t::func_countif },
+ { IXION_ASCII("COUNTIFS"), formula_function_t::func_countifs },
+ { IXION_ASCII("COVAR"), formula_function_t::func_covar },
+ { IXION_ASCII("CRITBINOM"), formula_function_t::func_critbinom },
+ { IXION_ASCII("CSC"), formula_function_t::func_csc },
+ { IXION_ASCII("CSCH"), formula_function_t::func_csch },
+ { IXION_ASCII("CUMIPMT"), formula_function_t::func_cumipmt },
+ { IXION_ASCII("CUMPRINC"), formula_function_t::func_cumprinc },
+ { IXION_ASCII("CURRENT"), formula_function_t::func_current },
+ { IXION_ASCII("DATE"), formula_function_t::func_date },
+ { IXION_ASCII("DATEDIF"), formula_function_t::func_datedif },
+ { IXION_ASCII("DATEVALUE"), formula_function_t::func_datevalue },
+ { IXION_ASCII("DAVERAGE"), formula_function_t::func_daverage },
+ { IXION_ASCII("DAY"), formula_function_t::func_day },
+ { IXION_ASCII("DAYS"), formula_function_t::func_days },
+ { IXION_ASCII("DAYS360"), formula_function_t::func_days360 },
+ { IXION_ASCII("DB"), formula_function_t::func_db },
+ { IXION_ASCII("DCOUNT"), formula_function_t::func_dcount },
+ { IXION_ASCII("DCOUNTA"), formula_function_t::func_dcounta },
+ { IXION_ASCII("DDB"), formula_function_t::func_ddb },
+ { IXION_ASCII("DDE"), formula_function_t::func_dde },
+ { IXION_ASCII("DECIMAL"), formula_function_t::func_decimal },
+ { IXION_ASCII("DEGREES"), formula_function_t::func_degrees },
+ { IXION_ASCII("DEVSQ"), formula_function_t::func_devsq },
+ { IXION_ASCII("DGET"), formula_function_t::func_dget },
+ { IXION_ASCII("DMAX"), formula_function_t::func_dmax },
+ { IXION_ASCII("DMIN"), formula_function_t::func_dmin },
+ { IXION_ASCII("DOLLAR"), formula_function_t::func_dollar },
+ { IXION_ASCII("DPRODUCT"), formula_function_t::func_dproduct },
+ { IXION_ASCII("DSTDEV"), formula_function_t::func_dstdev },
+ { IXION_ASCII("DSTDEVP"), formula_function_t::func_dstdevp },
+ { IXION_ASCII("DSUM"), formula_function_t::func_dsum },
+ { IXION_ASCII("DVAR"), formula_function_t::func_dvar },
+ { IXION_ASCII("DVARP"), formula_function_t::func_dvarp },
+ { IXION_ASCII("EASTERSUNDAY"), formula_function_t::func_eastersunday },
+ { IXION_ASCII("EFFECT"), formula_function_t::func_effect },
+ { IXION_ASCII("ENCODEURL"), formula_function_t::func_encodeurl },
+ { IXION_ASCII("ERRORTYPE"), formula_function_t::func_errortype },
+ { IXION_ASCII("EUROCONVERT"), formula_function_t::func_euroconvert },
+ { IXION_ASCII("EVEN"), formula_function_t::func_even },
+ { IXION_ASCII("EXACT"), formula_function_t::func_exact },
+ { IXION_ASCII("EXP"), formula_function_t::func_exp },
+ { IXION_ASCII("EXPONDIST"), formula_function_t::func_expondist },
+ { IXION_ASCII("FACT"), formula_function_t::func_fact },
+ { IXION_ASCII("FALSE"), formula_function_t::func_false },
+ { IXION_ASCII("FDIST"), formula_function_t::func_fdist },
+ { IXION_ASCII("FILTERXML"), formula_function_t::func_filterxml },
+ { IXION_ASCII("FIND"), formula_function_t::func_find },
+ { IXION_ASCII("FINDB"), formula_function_t::func_findb },
+ { IXION_ASCII("FINV"), formula_function_t::func_finv },
+ { IXION_ASCII("FISHER"), formula_function_t::func_fisher },
+ { IXION_ASCII("FISHERINV"), formula_function_t::func_fisherinv },
+ { IXION_ASCII("FIXED"), formula_function_t::func_fixed },
+ { IXION_ASCII("FLOOR"), formula_function_t::func_floor },
+ { IXION_ASCII("FORECAST"), formula_function_t::func_forecast },
+ { IXION_ASCII("FORMULA"), formula_function_t::func_formula },
+ { IXION_ASCII("FOURIER"), formula_function_t::func_fourier },
+ { IXION_ASCII("FREQUENCY"), formula_function_t::func_frequency },
+ { IXION_ASCII("FTEST"), formula_function_t::func_ftest },
+ { IXION_ASCII("FV"), formula_function_t::func_fv },
+ { IXION_ASCII("GAMMA"), formula_function_t::func_gamma },
+ { IXION_ASCII("GAMMADIST"), formula_function_t::func_gammadist },
+ { IXION_ASCII("GAMMAINV"), formula_function_t::func_gammainv },
+ { IXION_ASCII("GAMMALN"), formula_function_t::func_gammaln },
+ { IXION_ASCII("GAUSS"), formula_function_t::func_gauss },
+ { IXION_ASCII("GCD"), formula_function_t::func_gcd },
+ { IXION_ASCII("GEOMEAN"), formula_function_t::func_geomean },
+ { IXION_ASCII("GETPIVOTDATA"), formula_function_t::func_getpivotdata },
+ { IXION_ASCII("GOALSEEK"), formula_function_t::func_goalseek },
+ { IXION_ASCII("GROWTH"), formula_function_t::func_growth },
+ { IXION_ASCII("HARMEAN"), formula_function_t::func_harmean },
+ { IXION_ASCII("HLOOKUP"), formula_function_t::func_hlookup },
+ { IXION_ASCII("HOUR"), formula_function_t::func_hour },
+ { IXION_ASCII("HYPERLINK"), formula_function_t::func_hyperlink },
+ { IXION_ASCII("HYPGEOMDIST"), formula_function_t::func_hypgeomdist },
+ { IXION_ASCII("IF"), formula_function_t::func_if },
+ { IXION_ASCII("IFERROR"), formula_function_t::func_iferror },
+ { IXION_ASCII("IFNA"), formula_function_t::func_ifna },
+ { IXION_ASCII("IFS"), formula_function_t::func_ifs },
+ { IXION_ASCII("INDEX"), formula_function_t::func_index },
+ { IXION_ASCII("INDIRECT"), formula_function_t::func_indirect },
+ { IXION_ASCII("INFO"), formula_function_t::func_info },
+ { IXION_ASCII("INT"), formula_function_t::func_int },
+ { IXION_ASCII("INTERCEPT"), formula_function_t::func_intercept },
+ { IXION_ASCII("IPMT"), formula_function_t::func_ipmt },
+ { IXION_ASCII("IRR"), formula_function_t::func_irr },
+ { IXION_ASCII("ISBLANK"), formula_function_t::func_isblank },
+ { IXION_ASCII("ISERR"), formula_function_t::func_iserr },
+ { IXION_ASCII("ISERROR"), formula_function_t::func_iserror },
+ { IXION_ASCII("ISEVEN"), formula_function_t::func_iseven },
+ { IXION_ASCII("ISFORMULA"), formula_function_t::func_isformula },
+ { IXION_ASCII("ISLOGICAL"), formula_function_t::func_islogical },
+ { IXION_ASCII("ISNA"), formula_function_t::func_isna },
+ { IXION_ASCII("ISNONTEXT"), formula_function_t::func_isnontext },
+ { IXION_ASCII("ISNUMBER"), formula_function_t::func_isnumber },
+ { IXION_ASCII("ISODD"), formula_function_t::func_isodd },
+ { IXION_ASCII("ISOWEEKNUM"), formula_function_t::func_isoweeknum },
+ { IXION_ASCII("ISPMT"), formula_function_t::func_ispmt },
+ { IXION_ASCII("ISREF"), formula_function_t::func_isref },
+ { IXION_ASCII("ISTEXT"), formula_function_t::func_istext },
+ { IXION_ASCII("JIS"), formula_function_t::func_jis },
+ { IXION_ASCII("KURT"), formula_function_t::func_kurt },
+ { IXION_ASCII("LARGE"), formula_function_t::func_large },
+ { IXION_ASCII("LCM"), formula_function_t::func_lcm },
+ { IXION_ASCII("LEFT"), formula_function_t::func_left },
+ { IXION_ASCII("LEFTB"), formula_function_t::func_leftb },
+ { IXION_ASCII("LEN"), formula_function_t::func_len },
+ { IXION_ASCII("LENB"), formula_function_t::func_lenb },
+ { IXION_ASCII("LINEST"), formula_function_t::func_linest },
+ { IXION_ASCII("LN"), formula_function_t::func_ln },
+ { IXION_ASCII("LOG"), formula_function_t::func_log },
+ { IXION_ASCII("LOG10"), formula_function_t::func_log10 },
+ { IXION_ASCII("LOGEST"), formula_function_t::func_logest },
+ { IXION_ASCII("LOGINV"), formula_function_t::func_loginv },
+ { IXION_ASCII("LOGNORMDIST"), formula_function_t::func_lognormdist },
+ { IXION_ASCII("LOOKUP"), formula_function_t::func_lookup },
+ { IXION_ASCII("LOWER"), formula_function_t::func_lower },
+ { IXION_ASCII("MATCH"), formula_function_t::func_match },
+ { IXION_ASCII("MAX"), formula_function_t::func_max },
+ { IXION_ASCII("MAXA"), formula_function_t::func_maxa },
+ { IXION_ASCII("MAXIFS"), formula_function_t::func_maxifs },
+ { IXION_ASCII("MDETERM"), formula_function_t::func_mdeterm },
+ { IXION_ASCII("MEDIAN"), formula_function_t::func_median },
+ { IXION_ASCII("MID"), formula_function_t::func_mid },
+ { IXION_ASCII("MIDB"), formula_function_t::func_midb },
+ { IXION_ASCII("MIN"), formula_function_t::func_min },
+ { IXION_ASCII("MINA"), formula_function_t::func_mina },
+ { IXION_ASCII("MINIFS"), formula_function_t::func_minifs },
+ { IXION_ASCII("MINUTE"), formula_function_t::func_minute },
+ { IXION_ASCII("MINVERSE"), formula_function_t::func_minverse },
+ { IXION_ASCII("MIRR"), formula_function_t::func_mirr },
+ { IXION_ASCII("MMULT"), formula_function_t::func_mmult },
+ { IXION_ASCII("MOD"), formula_function_t::func_mod },
+ { IXION_ASCII("MODE"), formula_function_t::func_mode },
+ { IXION_ASCII("MONTH"), formula_function_t::func_month },
+ { IXION_ASCII("MULTIRANGE"), formula_function_t::func_multirange },
+ { IXION_ASCII("MUNIT"), formula_function_t::func_munit },
+ { IXION_ASCII("MVALUE"), formula_function_t::func_mvalue },
+ { IXION_ASCII("N"), formula_function_t::func_n },
+ { IXION_ASCII("NA"), formula_function_t::func_na },
+ { IXION_ASCII("NEG"), formula_function_t::func_neg },
+ { IXION_ASCII("NEGBINOMDIST"), formula_function_t::func_negbinomdist },
+ { IXION_ASCII("NETWORKDAYS"), formula_function_t::func_networkdays },
+ { IXION_ASCII("NOMINAL"), formula_function_t::func_nominal },
+ { IXION_ASCII("NORMDIST"), formula_function_t::func_normdist },
+ { IXION_ASCII("NORMINV"), formula_function_t::func_norminv },
+ { IXION_ASCII("NORMSDIST"), formula_function_t::func_normsdist },
+ { IXION_ASCII("NORMSINV"), formula_function_t::func_normsinv },
+ { IXION_ASCII("NOT"), formula_function_t::func_not },
+ { IXION_ASCII("NOW"), formula_function_t::func_now },
+ { IXION_ASCII("NPER"), formula_function_t::func_nper },
+ { IXION_ASCII("NPV"), formula_function_t::func_npv },
+ { IXION_ASCII("NUMBERVALUE"), formula_function_t::func_numbervalue },
+ { IXION_ASCII("ODD"), formula_function_t::func_odd },
+ { IXION_ASCII("OFFSET"), formula_function_t::func_offset },
+ { IXION_ASCII("OR"), formula_function_t::func_or },
+ { IXION_ASCII("PDURATION"), formula_function_t::func_pduration },
+ { IXION_ASCII("PEARSON"), formula_function_t::func_pearson },
+ { IXION_ASCII("PERCENTILE"), formula_function_t::func_percentile },
+ { IXION_ASCII("PERCENTRANK"), formula_function_t::func_percentrank },
+ { IXION_ASCII("PERMUT"), formula_function_t::func_permut },
+ { IXION_ASCII("PERMUTATIONA"), formula_function_t::func_permutationa },
+ { IXION_ASCII("PHI"), formula_function_t::func_phi },
+ { IXION_ASCII("PI"), formula_function_t::func_pi },
+ { IXION_ASCII("PMT"), formula_function_t::func_pmt },
+ { IXION_ASCII("POISSON"), formula_function_t::func_poisson },
+ { IXION_ASCII("POWER"), formula_function_t::func_power },
+ { IXION_ASCII("PPMT"), formula_function_t::func_ppmt },
+ { IXION_ASCII("PROB"), formula_function_t::func_prob },
+ { IXION_ASCII("PRODUCT"), formula_function_t::func_product },
+ { IXION_ASCII("PROPER"), formula_function_t::func_proper },
+ { IXION_ASCII("PV"), formula_function_t::func_pv },
+ { IXION_ASCII("QUARTILE"), formula_function_t::func_quartile },
+ { IXION_ASCII("RADIANS"), formula_function_t::func_radians },
+ { IXION_ASCII("RAND"), formula_function_t::func_rand },
+ { IXION_ASCII("RANK"), formula_function_t::func_rank },
+ { IXION_ASCII("RATE"), formula_function_t::func_rate },
+ { IXION_ASCII("RAWSUBTRACT"), formula_function_t::func_rawsubtract },
+ { IXION_ASCII("REGEX"), formula_function_t::func_regex },
+ { IXION_ASCII("REPLACE"), formula_function_t::func_replace },
+ { IXION_ASCII("REPLACEB"), formula_function_t::func_replaceb },
+ { IXION_ASCII("REPT"), formula_function_t::func_rept },
+ { IXION_ASCII("RIGHT"), formula_function_t::func_right },
+ { IXION_ASCII("RIGHTB"), formula_function_t::func_rightb },
+ { IXION_ASCII("ROMAN"), formula_function_t::func_roman },
+ { IXION_ASCII("ROUND"), formula_function_t::func_round },
+ { IXION_ASCII("ROUNDDOWN"), formula_function_t::func_rounddown },
+ { IXION_ASCII("ROUNDSIG"), formula_function_t::func_roundsig },
+ { IXION_ASCII("ROUNDUP"), formula_function_t::func_roundup },
+ { IXION_ASCII("ROW"), formula_function_t::func_row },
+ { IXION_ASCII("ROWS"), formula_function_t::func_rows },
+ { IXION_ASCII("RRI"), formula_function_t::func_rri },
+ { IXION_ASCII("RSQ"), formula_function_t::func_rsq },
+ { IXION_ASCII("SEARCH"), formula_function_t::func_search },
+ { IXION_ASCII("SEARCHB"), formula_function_t::func_searchb },
+ { IXION_ASCII("SEC"), formula_function_t::func_sec },
+ { IXION_ASCII("SECH"), formula_function_t::func_sech },
+ { IXION_ASCII("SECOND"), formula_function_t::func_second },
+ { IXION_ASCII("SHEET"), formula_function_t::func_sheet },
+ { IXION_ASCII("SHEETS"), formula_function_t::func_sheets },
+ { IXION_ASCII("SIGN"), formula_function_t::func_sign },
+ { IXION_ASCII("SIN"), formula_function_t::func_sin },
+ { IXION_ASCII("SINH"), formula_function_t::func_sinh },
+ { IXION_ASCII("SKEW"), formula_function_t::func_skew },
+ { IXION_ASCII("SKEWP"), formula_function_t::func_skewp },
+ { IXION_ASCII("SLN"), formula_function_t::func_sln },
+ { IXION_ASCII("SLOPE"), formula_function_t::func_slope },
+ { IXION_ASCII("SMALL"), formula_function_t::func_small },
+ { IXION_ASCII("SQRT"), formula_function_t::func_sqrt },
+ { IXION_ASCII("STANDARDIZE"), formula_function_t::func_standardize },
+ { IXION_ASCII("STDEV"), formula_function_t::func_stdev },
+ { IXION_ASCII("STDEVA"), formula_function_t::func_stdeva },
+ { IXION_ASCII("STDEVP"), formula_function_t::func_stdevp },
+ { IXION_ASCII("STDEVPA"), formula_function_t::func_stdevpa },
+ { IXION_ASCII("STEYX"), formula_function_t::func_steyx },
+ { IXION_ASCII("STYLE"), formula_function_t::func_style },
+ { IXION_ASCII("SUBSTITUTE"), formula_function_t::func_substitute },
+ { IXION_ASCII("SUBTOTAL"), formula_function_t::func_subtotal },
+ { IXION_ASCII("SUM"), formula_function_t::func_sum },
+ { IXION_ASCII("SUMIF"), formula_function_t::func_sumif },
+ { IXION_ASCII("SUMIFS"), formula_function_t::func_sumifs },
+ { IXION_ASCII("SUMPRODUCT"), formula_function_t::func_sumproduct },
+ { IXION_ASCII("SUMSQ"), formula_function_t::func_sumsq },
+ { IXION_ASCII("SUMX2MY2"), formula_function_t::func_sumx2my2 },
+ { IXION_ASCII("SUMX2PY2"), formula_function_t::func_sumx2py2 },
+ { IXION_ASCII("SUMXMY2"), formula_function_t::func_sumxmy2 },
+ { IXION_ASCII("SWITCH"), formula_function_t::func_switch },
+ { IXION_ASCII("SYD"), formula_function_t::func_syd },
+ { IXION_ASCII("T"), formula_function_t::func_t },
+ { IXION_ASCII("TAN"), formula_function_t::func_tan },
+ { IXION_ASCII("TANH"), formula_function_t::func_tanh },
+ { IXION_ASCII("TDIST"), formula_function_t::func_tdist },
+ { IXION_ASCII("TEXT"), formula_function_t::func_text },
+ { IXION_ASCII("TEXTJOIN"), formula_function_t::func_textjoin },
+ { IXION_ASCII("TIME"), formula_function_t::func_time },
+ { IXION_ASCII("TIMEVALUE"), formula_function_t::func_timevalue },
+ { IXION_ASCII("TINV"), formula_function_t::func_tinv },
+ { IXION_ASCII("TODAY"), formula_function_t::func_today },
+ { IXION_ASCII("TRANSPOSE"), formula_function_t::func_transpose },
+ { IXION_ASCII("TREND"), formula_function_t::func_trend },
+ { IXION_ASCII("TRIM"), formula_function_t::func_trim },
+ { IXION_ASCII("TRIMMEAN"), formula_function_t::func_trimmean },
+ { IXION_ASCII("TRUE"), formula_function_t::func_true },
+ { IXION_ASCII("TRUNC"), formula_function_t::func_trunc },
+ { IXION_ASCII("TTEST"), formula_function_t::func_ttest },
+ { IXION_ASCII("TYPE"), formula_function_t::func_type },
+ { IXION_ASCII("UNICHAR"), formula_function_t::func_unichar },
+ { IXION_ASCII("UNICODE"), formula_function_t::func_unicode },
+ { IXION_ASCII("UPPER"), formula_function_t::func_upper },
+ { IXION_ASCII("VALUE"), formula_function_t::func_value },
+ { IXION_ASCII("VAR"), formula_function_t::func_var },
+ { IXION_ASCII("VARA"), formula_function_t::func_vara },
+ { IXION_ASCII("VARP"), formula_function_t::func_varp },
+ { IXION_ASCII("VARPA"), formula_function_t::func_varpa },
+ { IXION_ASCII("VDB"), formula_function_t::func_vdb },
+ { IXION_ASCII("VLOOKUP"), formula_function_t::func_vlookup },
+ { IXION_ASCII("WAIT"), formula_function_t::func_wait },
+ { IXION_ASCII("WEBSERVICE"), formula_function_t::func_webservice },
+ { IXION_ASCII("WEEKDAY"), formula_function_t::func_weekday },
+ { IXION_ASCII("WEEKNUM"), formula_function_t::func_weeknum },
+ { IXION_ASCII("WEIBULL"), formula_function_t::func_weibull },
+ { IXION_ASCII("XOR"), formula_function_t::func_xor },
+ { IXION_ASCII("YEAR"), formula_function_t::func_year },
+ { IXION_ASCII("ZTEST"), formula_function_t::func_ztest },
+};
+
+const map_type& get()
+{
+ static map_type mt(entries, std::size(entries), formula_function_t::func_unknown);
+ return mt;
+}
+
+} // builtin_funcs namespace
+
+constexpr std::string_view unknown_func_name = "unknown";
+
+/**
+ * Traverse all elements of a passed matrix to sum up their values.
+ */
+double sum_matrix_elements(const matrix& mx)
+{
+ double sum = 0.0;
+ size_t rows = mx.row_size();
+ size_t cols = mx.col_size();
+ for (size_t row = 0; row < rows; ++row)
+ for (size_t col = 0; col < cols; ++col)
+ sum += mx.get_numeric(row, col);
+
+ return sum;
+}
+
+numeric_matrix multiply_matrices(const matrix& left, const matrix& right)
+{
+ // The column size of the left matrix must equal the row size of the right
+ // matrix.
+
+ size_t n = left.col_size();
+
+ if (n != right.row_size())
+ throw formula_error(formula_error_t::invalid_expression);
+
+ numeric_matrix left_nm = left.as_numeric();
+ numeric_matrix right_nm = right.as_numeric();
+
+ numeric_matrix output(left_nm.row_size(), right_nm.col_size());
+
+ for (size_t row = 0; row < output.row_size(); ++row)
+ {
+ for (size_t col = 0; col < output.col_size(); ++col)
+ {
+ double v = 0.0;
+ for (size_t i = 0; i < n; ++i)
+ v += left_nm(row, i) * right_nm(i, col);
+
+ output(row, col) = v;
+ }
+ }
+
+ return output;
+}
+
+bool pop_and_check_for_odd_value(formula_value_stack& args)
+{
+ double v = args.pop_value();
+ intmax_t iv = std::trunc(v);
+ iv = std::abs(iv);
+ bool odd = iv & 0x01;
+ return odd;
+}
+
+/**
+ * Pop a single-value argument from the stack and interpret it as a boolean
+ * value if it's either boolean or numeric type, or ignore if it's a string or
+ * error type. The behavior is undefined if called for non-single-value
+ * argument type.
+ */
+std::optional<bool> pop_one_value_as_boolean(const model_context& cxt, formula_value_stack& args)
+{
+ std::optional<bool> ret;
+
+ switch (args.get_type())
+ {
+ case stack_value_t::single_ref:
+ {
+ auto addr = args.pop_single_ref();
+ cell_access ca = cxt.get_cell_access(addr);
+
+ switch (ca.get_value_type())
+ {
+ case cell_value_t::boolean:
+ case cell_value_t::numeric:
+ ret = ca.get_boolean_value();
+ break;
+ default:;
+ }
+
+ break;
+ }
+ case stack_value_t::value:
+ case stack_value_t::boolean:
+ ret = args.pop_boolean();
+ break;
+ case stack_value_t::string:
+ case stack_value_t::error:
+ // ignore string type
+ args.pop_back();
+ break;
+ case stack_value_t::range_ref:
+ case stack_value_t::matrix:
+ // should not be called for non-single value.
+ throw formula_error(formula_error_t::general_error);
+ }
+
+ return ret;
+}
+
+/**
+ * Pop a value from the stack, and insert one or more numeric values to the
+ * specified sequence container.
+ */
+template<typename ContT>
+void append_values_from_stack(
+ const model_context& cxt, formula_value_stack& args, std::back_insert_iterator<ContT> insert_it)
+{
+ static_assert(
+ std::is_floating_point_v<typename ContT::value_type>,
+ "this function only supports a container of floating point values.");
+
+ switch (args.get_type())
+ {
+ case stack_value_t::boolean:
+ case stack_value_t::value:
+ insert_it = args.pop_value();
+ break;
+ case stack_value_t::single_ref:
+ {
+ abs_address_t addr = args.pop_single_ref();
+ auto ca = cxt.get_cell_access(addr);
+ switch (ca.get_value_type())
+ {
+ case cell_value_t::boolean:
+ insert_it = ca.get_boolean_value() ? 1.0 : 0.0;
+ break;
+ case cell_value_t::numeric:
+ insert_it = ca.get_numeric_value();
+ break;
+ default:;
+ }
+ break;
+ }
+ case stack_value_t::range_ref:
+ {
+ const formula_result_wait_policy_t wait_policy = cxt.get_formula_result_wait_policy();
+ abs_range_t range = args.pop_range_ref();
+
+ column_block_callback_t cb = [&insert_it, wait_policy](
+ col_t col, row_t row1, row_t row2, const column_block_shape_t& node)
+ {
+ assert(row1 <= row2);
+ row_t length = row2 - row1 + 1;
+
+ switch (node.type)
+ {
+ case column_block_t::boolean:
+ {
+ auto blk_range = detail::make_element_range<column_block_t::boolean>{}(node, length);
+ auto func = [](bool b) { return b ? 1.0 : 0.0; };
+ std::transform(blk_range.begin(), blk_range.end(), insert_it, func);
+ break;
+ }
+ case column_block_t::numeric:
+ {
+ auto blk_range = detail::make_element_range<column_block_t::numeric>{}(node, length);
+ std::copy(blk_range.begin(), blk_range.end(), insert_it);
+ break;
+ }
+ case column_block_t::formula:
+ {
+ auto blk_range = detail::make_element_range<column_block_t::formula>{}(node, length);
+
+ for (const formula_cell* fc : blk_range)
+ {
+ formula_result res = fc->get_result_cache(wait_policy);
+ switch (res.get_type())
+ {
+ case formula_result::result_type::boolean:
+ insert_it = res.get_boolean() ? 1.0 : 0.0;
+ break;
+ case formula_result::result_type::value:
+ insert_it = res.get_value();
+ break;
+ default:;
+ }
+ }
+
+ break;
+ }
+ default:;
+ }
+ return true;
+ };
+
+ for (sheet_t sheet = range.first.sheet; sheet <= range.last.sheet; ++sheet)
+ cxt.walk(sheet, range, cb);
+
+ break;
+ }
+ default:
+ args.pop_back();
+ }
+}
+
+} // anonymous namespace
+
+// ============================================================================
+
+formula_functions::invalid_arg::invalid_arg(const std::string& msg) :
+ general_error(msg) {}
+
+formula_function_t formula_functions::get_function_opcode(const formula_token& token)
+{
+ assert(token.opcode == fop_function);
+ return std::get<formula_function_t>(token.value);
+}
+
+formula_function_t formula_functions::get_function_opcode(std::string_view s)
+{
+ std::string upper;
+
+ for (char c : s)
+ {
+ if (c > 'Z')
+ {
+ // Convert to upper case.
+ c -= 'a' - 'A';
+ }
+
+ upper.push_back(c);
+ }
+
+ return builtin_funcs::get().find(upper.data(), upper.size());
+}
+
+std::string_view formula_functions::get_function_name(formula_function_t oc)
+{
+ for (const builtin_funcs::map_type::entry& e : builtin_funcs::entries)
+ {
+ if (e.value == oc)
+ return e.key;
+ }
+ return unknown_func_name;
+}
+
+formula_functions::formula_functions(model_context& cxt, const abs_address_t& pos) :
+ m_context(cxt), m_pos(pos)
+{
+}
+
+formula_functions::~formula_functions()
+{
+}
+
+void formula_functions::interpret(formula_function_t oc, formula_value_stack& args)
+{
+ try
+ {
+ switch (oc)
+ {
+ case formula_function_t::func_abs:
+ fnc_abs(args);
+ break;
+ case formula_function_t::func_and:
+ fnc_and(args);
+ break;
+ case formula_function_t::func_average:
+ fnc_average(args);
+ break;
+ case formula_function_t::func_column:
+ fnc_column(args);
+ break;
+ case formula_function_t::func_columns:
+ fnc_columns(args);
+ break;
+ case formula_function_t::func_concatenate:
+ fnc_concatenate(args);
+ break;
+ case formula_function_t::func_count:
+ fnc_count(args);
+ break;
+ case formula_function_t::func_counta:
+ fnc_counta(args);
+ break;
+ case formula_function_t::func_countblank:
+ fnc_countblank(args);
+ break;
+ case formula_function_t::func_exact:
+ fnc_exact(args);
+ break;
+ case formula_function_t::func_false:
+ fnc_false(args);
+ break;
+ case formula_function_t::func_find:
+ fnc_find(args);
+ break;
+ case formula_function_t::func_if:
+ fnc_if(args);
+ break;
+ case formula_function_t::func_isblank:
+ fnc_isblank(args);
+ break;
+ case formula_function_t::func_iserror:
+ fnc_iserror(args);
+ break;
+ case formula_function_t::func_iseven:
+ fnc_iseven(args);
+ break;
+ case formula_function_t::func_isformula:
+ fnc_isformula(args);
+ break;
+ case formula_function_t::func_islogical:
+ fnc_islogical(args);
+ break;
+ case formula_function_t::func_isna:
+ fnc_isna(args);
+ break;
+ case formula_function_t::func_isnontext:
+ fnc_isnontext(args);
+ break;
+ case formula_function_t::func_isnumber:
+ fnc_isnumber(args);
+ break;
+ case formula_function_t::func_isodd:
+ fnc_isodd(args);
+ break;
+ case formula_function_t::func_isref:
+ fnc_isref(args);
+ break;
+ case formula_function_t::func_istext:
+ fnc_istext(args);
+ break;
+ case formula_function_t::func_int:
+ fnc_int(args);
+ break;
+ case formula_function_t::func_left:
+ fnc_left(args);
+ break;
+ case formula_function_t::func_len:
+ fnc_len(args);
+ break;
+ case formula_function_t::func_max:
+ fnc_max(args);
+ break;
+ case formula_function_t::func_median:
+ fnc_median(args);
+ break;
+ case formula_function_t::func_mid:
+ fnc_mid(args);
+ break;
+ case formula_function_t::func_min:
+ fnc_min(args);
+ break;
+ case formula_function_t::func_mmult:
+ fnc_mmult(args);
+ break;
+ case formula_function_t::func_mode:
+ fnc_mode(args);
+ break;
+ case formula_function_t::func_n:
+ fnc_n(args);
+ break;
+ case formula_function_t::func_na:
+ fnc_na(args);
+ break;
+ case formula_function_t::func_not:
+ fnc_not(args);
+ break;
+ case formula_function_t::func_now:
+ fnc_now(args);
+ break;
+ case formula_function_t::func_or:
+ fnc_or(args);
+ break;
+ case formula_function_t::func_pi:
+ fnc_pi(args);
+ break;
+ case formula_function_t::func_replace:
+ fnc_replace(args);
+ break;
+ case formula_function_t::func_rept:
+ fnc_rept(args);
+ break;
+ case formula_function_t::func_right:
+ fnc_right(args);
+ break;
+ case formula_function_t::func_row:
+ fnc_row(args);
+ break;
+ case formula_function_t::func_rows:
+ fnc_rows(args);
+ break;
+ case formula_function_t::func_sheet:
+ fnc_sheet(args);
+ break;
+ case formula_function_t::func_sheets:
+ fnc_sheets(args);
+ break;
+ case formula_function_t::func_substitute:
+ fnc_substitute(args);
+ break;
+ case formula_function_t::func_subtotal:
+ fnc_subtotal(args);
+ break;
+ case formula_function_t::func_sum:
+ fnc_sum(args);
+ break;
+ case formula_function_t::func_t:
+ fnc_t(args);
+ break;
+ case formula_function_t::func_textjoin:
+ fnc_textjoin(args);
+ break;
+ case formula_function_t::func_trim:
+ fnc_trim(args);
+ break;
+ case formula_function_t::func_true:
+ fnc_true(args);
+ break;
+ case formula_function_t::func_type:
+ fnc_type(args);
+ break;
+ case formula_function_t::func_wait:
+ fnc_wait(args);
+ break;
+ case formula_function_t::func_unknown:
+ default:
+ {
+ std::ostringstream os;
+ os << "formula function not implemented yet (name="
+ << get_formula_function_name(oc)
+ << ")";
+ throw not_implemented_error(os.str());
+ }
+ }
+ }
+ catch (const formula_error& e)
+ {
+ using t = std::underlying_type<formula_error_t>::type;
+ formula_error_t err = e.get_error();
+ if (static_cast<t>(err) >= 200u)
+ // re-throw if it's an internal error.
+ throw;
+
+ args.clear();
+ args.push_error(err);
+ }
+}
+
+void formula_functions::fnc_max(formula_value_stack& args) const
+{
+ if (args.empty())
+ throw formula_functions::invalid_arg("MAX requires one or more arguments.");
+
+ double ret = args.pop_value();
+ while (!args.empty())
+ {
+ double v = args.pop_value();
+ if (v > ret)
+ ret = v;
+ }
+ args.push_value(ret);
+}
+
+void formula_functions::fnc_median(formula_value_stack& args) const
+{
+ if (args.empty())
+ throw formula_functions::invalid_arg("MEDIAN requires one or more arguments.");
+
+ std::vector<double> seq;
+
+ while (!args.empty())
+ append_values_from_stack(m_context, args, std::back_inserter(seq));
+
+ std::size_t mid_pos = seq.size() / 2;
+
+ if (seq.size() & 0x01)
+ {
+ // odd number of values
+ auto it_mid = seq.begin() + mid_pos;
+ std::nth_element(seq.begin(), it_mid, seq.end());
+ args.push_value(seq[mid_pos]);
+ }
+ else
+ {
+ // even number of values. Take the average of the two mid values.
+ std::sort(seq.begin(), seq.end());
+ double v = seq[mid_pos - 1] + seq[mid_pos];
+ args.push_value(v / 2.0);
+ }
+}
+
+void formula_functions::fnc_min(formula_value_stack& args) const
+{
+ if (args.empty())
+ throw formula_functions::invalid_arg("MIN requires one or more arguments.");
+
+ double ret = args.pop_value();
+ while (!args.empty())
+ {
+ double v = args.pop_value();
+ if (v < ret)
+ ret = v;
+ }
+ args.push_value(ret);
+}
+
+void formula_functions::fnc_mode(formula_value_stack& args) const
+{
+ if (args.empty())
+ throw formula_functions::invalid_arg("MODE requires one or more arguments.");
+
+ std::vector<double> seq;
+
+ while (!args.empty())
+ append_values_from_stack(m_context, args, std::back_inserter(seq));
+
+ if (seq.empty())
+ {
+ args.push_error(formula_error_t::no_value_available);
+ return;
+ }
+
+ std::sort(seq.begin(), seq.end());
+
+ // keep counting the number of adjacent equal values in the sorted sequence.
+
+ using value_count_type = std::tuple<double, std::size_t>;
+ std::vector<value_count_type> value_counts;
+
+ for (auto it = seq.begin(); it != seq.end(); )
+ {
+ double cur_v = *it;
+ auto it_tail = std::find_if(it, seq.end(), [cur_v](double v) { return cur_v < v; });
+ std::size_t len = std::distance(it, it_tail);
+ value_counts.emplace_back(cur_v, len);
+ it = it_tail;
+ }
+
+ assert(!value_counts.empty());
+
+ // Sort the results by the frequency in descending order first, then the
+ // value in ascending order.
+ auto func_comp = [](value_count_type lhs, value_count_type rhs)
+ {
+ if (std::get<1>(lhs) > std::get<1>(rhs))
+ return true;
+
+ return std::get<0>(lhs) < std::get<0>(rhs);
+ };
+
+ std::sort(value_counts.begin(), value_counts.end(), func_comp);
+ auto [top_value, top_count] = value_counts[0];
+
+ if (top_count == 1)
+ {
+ args.push_error(formula_error_t::no_value_available);
+ return;
+ }
+
+ args.push_value(top_value);
+}
+
+void formula_functions::fnc_sum(formula_value_stack& args) const
+{
+ IXION_TRACE("function: sum");
+
+ if (args.empty())
+ throw formula_functions::invalid_arg("SUM requires one or more arguments.");
+
+ double ret = 0;
+ while (!args.empty())
+ {
+ switch (args.get_type())
+ {
+ case stack_value_t::range_ref:
+ ret += sum_matrix_elements(args.pop_range_value());
+ break;
+ case stack_value_t::single_ref:
+ case stack_value_t::string:
+ case stack_value_t::value:
+ default:
+ ret += args.pop_value();
+ }
+ }
+
+ args.push_value(ret);
+
+ IXION_TRACE("function: sum end (result=" << ret << ")");
+}
+
+void formula_functions::fnc_count(formula_value_stack& args) const
+{
+ double ret = 0;
+
+ while (!args.empty())
+ {
+ switch (args.get_type())
+ {
+ case stack_value_t::value:
+ args.pop_back();
+ ++ret;
+ break;
+ case stack_value_t::range_ref:
+ {
+ abs_range_t range = args.pop_range_ref();
+ ret += m_context.count_range(range, value_numeric | value_boolean);
+ break;
+ }
+ case stack_value_t::single_ref:
+ {
+ abs_address_t pos = args.pop_single_ref();
+ abs_range_t range;
+ range.first = range.last = pos;
+ ret += m_context.count_range(range, value_numeric | value_boolean);
+ break;
+ }
+ default:
+ args.pop_back();
+ }
+ }
+
+ args.push_value(ret);
+}
+
+void formula_functions::fnc_counta(formula_value_stack& args) const
+{
+ double ret = 0;
+
+ while (!args.empty())
+ {
+ switch (args.get_type())
+ {
+ case stack_value_t::string:
+ case stack_value_t::value:
+ args.pop_back();
+ ++ret;
+ break;
+ case stack_value_t::range_ref:
+ {
+ abs_range_t range = args.pop_range_ref();
+ ret += m_context.count_range(range, value_numeric | value_boolean | value_string);
+ break;
+ }
+ case stack_value_t::single_ref:
+ {
+ abs_address_t pos = args.pop_single_ref();
+ abs_range_t range;
+ range.first = range.last = pos;
+ ret += m_context.count_range(range, value_numeric | value_boolean | value_string);
+ break;
+ }
+ default:
+ args.pop_back();
+ }
+
+ }
+
+ args.push_value(ret);
+}
+
+void formula_functions::fnc_countblank(formula_value_stack& args) const
+{
+ if (args.size() != 1)
+ throw formula_functions::invalid_arg("COUNTBLANK requires exactly 1 argument.");
+
+ switch (args.get_type())
+ {
+ case stack_value_t::single_ref:
+ case stack_value_t::range_ref:
+ {
+ abs_range_t range = args.pop_range_ref();
+ double ret = m_context.count_range(range, value_empty);
+ args.push_value(ret);
+ break;
+ }
+ default:
+ throw formula_functions::invalid_arg("COUNTBLANK only takes a reference argument.");
+ }
+}
+
+void formula_functions::fnc_abs(formula_value_stack& args) const
+{
+ if (args.size() != 1)
+ throw formula_functions::invalid_arg("ABS requires exactly 1 argument.");
+
+ double v = args.pop_value();
+ args.push_value(std::abs(v));
+}
+
+void formula_functions::fnc_average(formula_value_stack& args) const
+{
+ if (args.empty())
+ throw formula_functions::invalid_arg("AVERAGE requires one or more arguments.");
+
+ double ret = 0;
+ double count = 0.0;
+ while (!args.empty())
+ {
+ switch (args.get_type())
+ {
+ case stack_value_t::range_ref:
+ {
+ matrix mx = args.pop_range_value();
+ size_t rows = mx.row_size();
+ size_t cols = mx.col_size();
+
+ for (size_t r = 0; r < rows; ++r)
+ {
+ for (size_t c = 0; c < cols; ++c)
+ {
+ if (!mx.is_numeric(r, c))
+ continue;
+
+ ret += mx.get_numeric(r, c);
+ ++count;
+ }
+ }
+ break;
+ }
+ case stack_value_t::single_ref:
+ case stack_value_t::string:
+ case stack_value_t::value:
+ default:
+ ret += args.pop_value();
+ ++count;
+ }
+ }
+
+ args.push_value(ret/count);
+}
+
+void formula_functions::fnc_mmult(formula_value_stack& args) const
+{
+ matrix mx[2];
+ matrix* mxp = mx;
+ const matrix* mxp_end = mxp + 2;
+
+ bool is_arg_invalid = false;
+
+ // NB : the stack is LIFO i.e. the first matrix is the right matrix and
+ // the second one is the left one.
+
+ while (!args.empty())
+ {
+ if (mxp == mxp_end)
+ {
+ is_arg_invalid = true;
+ break;
+ }
+
+ auto m = args.maybe_pop_matrix();
+ if (!m)
+ {
+ is_arg_invalid = true;
+ break;
+ }
+
+ mxp->swap(*m);
+ ++mxp;
+ }
+
+ if (mxp != mxp_end)
+ is_arg_invalid = true;
+
+ if (is_arg_invalid)
+ throw formula_functions::invalid_arg("MMULT requires exactly two ranges.");
+
+ mx[0].swap(mx[1]); // Make it so that 0 -> left and 1 -> right.
+
+ if (!mx[0].is_numeric() || !mx[1].is_numeric())
+ throw formula_functions::invalid_arg(
+ "MMULT requires two numeric ranges. At least one range is not numeric.");
+
+ numeric_matrix ans = multiply_matrices(mx[0], mx[1]);
+
+ args.push_matrix(ans);
+}
+
+void formula_functions::fnc_pi(formula_value_stack& args) const
+{
+ if (!args.empty())
+ throw formula_functions::invalid_arg("PI takes no arguments.");
+
+ args.push_value(M_PI);
+}
+
+void formula_functions::fnc_int(formula_value_stack& args) const
+{
+ if (args.size() != 1)
+ throw formula_functions::invalid_arg("INT requires exactly 1 argument.");
+
+ double v = args.pop_value();
+ args.push_value(std::floor(v));
+}
+
+void formula_functions::fnc_and(formula_value_stack& args) const
+{
+ const formula_result_wait_policy_t wait_policy = m_context.get_formula_result_wait_policy();
+ bool final_result = true;
+
+ while (!args.empty() && final_result)
+ {
+ switch (args.get_type())
+ {
+ case stack_value_t::single_ref:
+ case stack_value_t::value:
+ case stack_value_t::string:
+ {
+ std::optional<bool> v = pop_one_value_as_boolean(m_context, args);
+ if (v)
+ final_result = *v;
+ break;
+ }
+ case stack_value_t::range_ref:
+ {
+ auto range = args.pop_range_ref();
+ sheet_t sheet = range.first.sheet;
+ abs_rc_range_t rc_range = range;
+
+ column_block_callback_t cb = [&final_result, wait_policy](
+ col_t col, row_t row1, row_t row2, const column_block_shape_t& node)
+ {
+ assert(row1 <= row2);
+ row_t length = row2 - row1 + 1;
+
+ switch (node.type)
+ {
+ case column_block_t::empty:
+ case column_block_t::string:
+ case column_block_t::unknown:
+ // non-numeric blocks get skipped.
+ break;
+ case column_block_t::boolean:
+ {
+ auto blk_range = detail::make_element_range<column_block_t::boolean>{}(node, length);
+ bool res = std::all_of(blk_range.begin(), blk_range.end(), [](bool v) { return v; });
+ final_result = res;
+ break;
+ }
+ case column_block_t::numeric:
+ {
+ auto blk_range = detail::make_element_range<column_block_t::numeric>{}(node, length);
+ bool res = std::all_of(blk_range.begin(), blk_range.end(), [](double v) { return v != 0.0; });
+ final_result = res;
+ break;
+ }
+ case column_block_t::formula:
+ {
+ auto blk_range = detail::make_element_range<column_block_t::formula>{}(node, length);
+
+ for (const formula_cell* fc : blk_range)
+ {
+ formula_result res = fc->get_result_cache(wait_policy);
+ switch (res.get_type())
+ {
+ case formula_result::result_type::boolean:
+ final_result = res.get_boolean();
+ break;
+ case formula_result::result_type::value:
+ final_result = res.get_value() != 0.0;
+ break;
+ default:;
+ }
+ }
+
+ break;
+ }
+ }
+
+ return final_result; // returning false will end the walk.
+ };
+
+ m_context.walk(sheet, rc_range, cb);
+ break;
+ }
+ default:
+ throw formula_error(formula_error_t::general_error);
+ }
+ }
+
+ args.clear();
+ args.push_boolean(final_result);
+}
+
+void formula_functions::fnc_or(formula_value_stack& args) const
+{
+ const formula_result_wait_policy_t wait_policy = m_context.get_formula_result_wait_policy();
+ bool final_result = false;
+
+ while (!args.empty())
+ {
+ bool this_result = false;
+
+ switch (args.get_type())
+ {
+ case stack_value_t::single_ref:
+ case stack_value_t::value:
+ case stack_value_t::string:
+ {
+ std::optional<bool> v = pop_one_value_as_boolean(m_context, args);
+ if (v)
+ this_result = *v;
+ break;
+ }
+ case stack_value_t::range_ref:
+ {
+ auto range = args.pop_range_ref();
+ sheet_t sheet = range.first.sheet;
+ abs_rc_range_t rc_range = range;
+
+ // We will bail out of the walk on the first positive result.
+
+ column_block_callback_t cb = [&this_result, wait_policy](
+ col_t col, row_t row1, row_t row2, const column_block_shape_t& node)
+ {
+ assert(row1 <= row2);
+ row_t length = row2 - row1 + 1;
+
+ switch (node.type)
+ {
+ case column_block_t::empty:
+ case column_block_t::string:
+ case column_block_t::unknown:
+ // non-numeric blocks get skipped.
+ break;
+ case column_block_t::boolean:
+ {
+ auto blk_range = detail::make_element_range<column_block_t::boolean>{}(node, length);
+ this_result = std::any_of(blk_range.begin(), blk_range.end(), [](bool v) { return v; });
+ break;
+ }
+ case column_block_t::numeric:
+ {
+ auto blk_range = detail::make_element_range<column_block_t::numeric>{}(node, length);
+ this_result = std::any_of(blk_range.begin(), blk_range.end(), [](double v) { return v != 0.0; });
+ break;
+ }
+ case column_block_t::formula:
+ {
+ auto blk_range = detail::make_element_range<column_block_t::formula>{}(node, length);
+
+ for (const formula_cell* fc : blk_range)
+ {
+ formula_result res = fc->get_result_cache(wait_policy);
+ switch (res.get_type())
+ {
+ case formula_result::result_type::boolean:
+ this_result = res.get_boolean();
+ break;
+ case formula_result::result_type::value:
+ this_result = res.get_value() != 0.0;
+ break;
+ default:;
+ }
+
+ if (this_result)
+ break;
+ }
+
+ break;
+ }
+ }
+
+ return !this_result; // returning false will end the walk.
+ };
+
+ m_context.walk(sheet, rc_range, cb);
+ break;
+ }
+ default:
+ throw formula_error(formula_error_t::general_error);
+ }
+
+ if (this_result)
+ {
+ final_result = true;
+ break;
+ }
+ }
+
+ args.clear();
+ args.push_boolean(final_result);
+}
+
+void formula_functions::fnc_if(formula_value_stack& args) const
+{
+ if (args.size() != 3)
+ throw formula_functions::invalid_arg("IF requires exactly 3 arguments.");
+
+ formula_value_stack::iterator pos = args.begin();
+ bool eval = args.get_value(0) != 0.0;
+ if (eval)
+ std::advance(pos, 1);
+ else
+ std::advance(pos, 2);
+
+ formula_value_stack ret(m_context);
+ ret.push_back(args.release(pos));
+ args.swap(ret);
+}
+
+void formula_functions::fnc_true(formula_value_stack& args) const
+{
+ if (!args.empty())
+ throw formula_functions::invalid_arg("TRUE takes no arguments.");
+
+ args.push_boolean(true);
+}
+
+void formula_functions::fnc_false(formula_value_stack& args) const
+{
+ if (!args.empty())
+ throw formula_functions::invalid_arg("FALSE takes no arguments.");
+
+ args.push_boolean(false);
+}
+
+void formula_functions::fnc_not(formula_value_stack& args) const
+{
+ if (args.size() != 1)
+ throw formula_functions::invalid_arg("NOT requires exactly one argument.");
+
+ args.push_boolean(!args.pop_boolean());
+}
+
+void formula_functions::fnc_isblank(formula_value_stack& args) const
+{
+ if (args.size() != 1)
+ throw formula_functions::invalid_arg("ISBLANK requires exactly one argument.");
+
+ switch (args.get_type())
+ {
+ case stack_value_t::single_ref:
+ {
+ abs_address_t addr = args.pop_single_ref();
+ bool res = m_context.get_celltype(addr) == celltype_t::empty;
+ args.push_boolean(res);
+ break;
+ }
+ case stack_value_t::range_ref:
+ {
+ abs_range_t range = args.pop_range_ref();
+ bool res = m_context.is_empty(range);
+ args.push_boolean(res);
+ break;
+ }
+ default:
+ {
+ args.clear();
+ args.push_boolean(false);
+ }
+ }
+}
+
+void formula_functions::fnc_iserror(formula_value_stack& args) const
+{
+ if (args.size() != 1)
+ throw formula_functions::invalid_arg("ISERROR requires exactly one argument.");
+
+ switch (args.get_type())
+ {
+ case stack_value_t::single_ref:
+ {
+ abs_address_t addr = args.pop_single_ref();
+ bool res = m_context.get_cell_value_type(addr) == cell_value_t::error;
+ args.push_boolean(res);
+ break;
+ }
+ case stack_value_t::error:
+ {
+ args.clear();
+ args.push_boolean(true);
+ break;
+ }
+ default:
+ {
+ args.clear();
+ args.push_boolean(false);
+ }
+ }
+}
+
+void formula_functions::fnc_iseven(formula_value_stack& args) const
+{
+ if (args.size() != 1)
+ throw formula_functions::invalid_arg("ISEVEN requires exactly one argument.");
+
+ bool odd = pop_and_check_for_odd_value(args);
+ args.push_boolean(!odd);
+}
+
+void formula_functions::fnc_isformula(formula_value_stack& args) const
+{
+ if (args.size() != 1)
+ throw formula_functions::invalid_arg("ISFORMULA requires exactly one argument.");
+
+ if (args.get_type() != stack_value_t::single_ref)
+ {
+ args.clear();
+ args.push_boolean(false);
+ return;
+ }
+
+ abs_address_t addr = args.pop_single_ref();
+ bool res = m_context.get_celltype(addr) == celltype_t::formula;
+ args.push_boolean(res);
+}
+
+void formula_functions::fnc_islogical(formula_value_stack& args) const
+{
+ if (args.size() != 1)
+ throw formula_functions::invalid_arg("ISLOGICAL requires exactly one argument.");
+
+ switch (args.get_type())
+ {
+ case stack_value_t::single_ref:
+ {
+ abs_address_t addr = args.pop_single_ref();
+ bool res = m_context.get_cell_value_type(addr) == cell_value_t::boolean;
+ args.push_boolean(res);
+ break;
+ }
+ case stack_value_t::boolean:
+ args.clear();
+ args.push_boolean(true);
+ break;
+ default:
+ args.clear();
+ args.push_boolean(false);
+ }
+}
+
+void formula_functions::fnc_isna(formula_value_stack& args) const
+{
+ if (args.size() != 1u)
+ throw formula_functions::invalid_arg("ISNA requires exactly one argument.");
+
+ switch (args.get_type())
+ {
+ case stack_value_t::single_ref:
+ {
+ abs_address_t addr = args.pop_single_ref();
+ auto ca = m_context.get_cell_access(addr);
+ formula_error_t err = ca.get_error_value();
+ args.push_boolean(err == formula_error_t::no_value_available);
+ break;
+ }
+ case stack_value_t::error:
+ {
+ bool res = args.pop_error() == formula_error_t::no_value_available;
+ args.push_boolean(res);
+ break;
+ }
+ default:
+ {
+ args.clear();
+ args.push_boolean(false);
+ }
+ }
+}
+
+void formula_functions::fnc_isnontext(formula_value_stack& args) const
+{
+ if (args.size() != 1)
+ throw formula_functions::invalid_arg("ISNONTEXT requires exactly one argument.");
+
+ switch (args.get_type())
+ {
+ case stack_value_t::single_ref:
+ {
+ abs_address_t addr = args.pop_single_ref();
+ bool res = m_context.get_cell_value_type(addr) != cell_value_t::string;
+ args.push_boolean(res);
+ break;
+ }
+ case stack_value_t::string:
+ args.clear();
+ args.push_boolean(false);
+ break;
+ default:
+ args.clear();
+ args.push_boolean(true);
+ }
+}
+
+void formula_functions::fnc_isnumber(formula_value_stack& args) const
+{
+ if (args.size() != 1)
+ throw formula_functions::invalid_arg("ISNUMBER requires exactly one argument.");
+
+ switch (args.get_type())
+ {
+ case stack_value_t::single_ref:
+ {
+ abs_address_t addr = args.pop_single_ref();
+ bool res = m_context.get_cell_value_type(addr) == cell_value_t::numeric;
+ args.push_boolean(res);
+ break;
+ }
+ case stack_value_t::value:
+ args.clear();
+ args.push_boolean(true);
+ break;
+ default:
+ args.clear();
+ args.push_boolean(false);
+ }
+}
+
+void formula_functions::fnc_isodd(formula_value_stack& args) const
+{
+ if (args.size() != 1)
+ throw formula_functions::invalid_arg("ISODD requires exactly one argument.");
+
+ bool odd = pop_and_check_for_odd_value(args);
+ args.push_boolean(odd);
+}
+
+void formula_functions::fnc_isref(formula_value_stack& args) const
+{
+ if (args.size() != 1)
+ throw formula_functions::invalid_arg("ISREF requires exactly one argument.");
+
+ switch (args.get_type())
+ {
+ case stack_value_t::single_ref:
+ case stack_value_t::range_ref:
+ args.clear();
+ args.push_boolean(true);
+ break;
+ default:
+ args.clear();
+ args.push_boolean(false);
+ }
+}
+
+void formula_functions::fnc_istext(formula_value_stack& args) const
+{
+ if (args.size() != 1)
+ throw formula_functions::invalid_arg("ISTEXT requires exactly one argument.");
+
+ switch (args.get_type())
+ {
+ case stack_value_t::single_ref:
+ {
+ abs_address_t addr = args.pop_single_ref();
+ bool res = m_context.get_cell_value_type(addr) == cell_value_t::string;
+ args.push_boolean(res);
+ break;
+ }
+ case stack_value_t::string:
+ args.clear();
+ args.push_boolean(true);
+ break;
+ default:
+ args.clear();
+ args.push_boolean(false);
+ }
+}
+
+void formula_functions::fnc_n(formula_value_stack& args) const
+{
+ if (args.size() != 1u)
+ throw formula_functions::invalid_arg("N takes exactly one argument.");
+
+ double v = args.pop_value();
+ args.push_value(v);
+}
+
+void formula_functions::fnc_na(formula_value_stack& args) const
+{
+ if (!args.empty())
+ throw formula_functions::invalid_arg("NA takes no arguments.");
+
+ args.push_error(formula_error_t::no_value_available);
+}
+
+void formula_functions::fnc_type(formula_value_stack& args) const
+{
+ if (args.size() != 1u)
+ throw formula_functions::invalid_arg("TYPE requires exactly one argument.");
+
+ switch (args.get_type())
+ {
+ case stack_value_t::boolean:
+ args.pop_back();
+ args.push_value(4);
+ break;
+ case stack_value_t::error:
+ args.pop_back();
+ args.push_value(16);
+ break;
+ case stack_value_t::matrix:
+ case stack_value_t::range_ref:
+ args.pop_back();
+ args.push_value(64);
+ break;
+ case stack_value_t::string:
+ args.pop_back();
+ args.push_value(2);
+ break;
+ case stack_value_t::value:
+ args.pop_back();
+ args.push_value(1);
+ break;
+ case stack_value_t::single_ref:
+ {
+ abs_address_t addr = args.pop_single_ref();
+ cell_access ca = m_context.get_cell_access(addr);
+
+ switch (ca.get_value_type())
+ {
+ case cell_value_t::boolean:
+ args.push_value(4);
+ break;
+ case cell_value_t::empty:
+ case cell_value_t::numeric:
+ args.push_value(1);
+ break;
+ case cell_value_t::error:
+ args.push_value(16);
+ break;
+ case cell_value_t::string:
+ args.push_value(2);
+ break;
+ case cell_value_t::unknown:
+ throw formula_error(formula_error_t::no_result_error);
+ }
+
+ break;
+ }
+ }
+}
+
+void formula_functions::fnc_len(formula_value_stack& args) const
+{
+ if (args.size() != 1)
+ throw formula_functions::invalid_arg("LEN requires exactly one argument.");
+
+ std::string s = args.pop_string();
+
+ auto positions = detail::calc_utf8_byte_positions(s);
+ args.push_value(positions.size());
+}
+
+void formula_functions::fnc_mid(formula_value_stack& args) const
+{
+ if (args.size() != 3)
+ throw formula_functions::invalid_arg("MID requires exactly 3 arguments.");
+
+ int len = std::floor(args.pop_value());
+ int start = std::floor(args.pop_value()); // 1-based
+
+ if (len < 0 || start < 1)
+ {
+ args.clear();
+ args.push_error(formula_error_t::invalid_value_type);
+ return;
+ }
+
+ std::string s = args.pop_string();
+
+ auto positions = detail::calc_utf8_byte_positions(s);
+
+ start -= 1; // convert to 0-based start position
+
+ if (std::size_t(start) >= positions.size())
+ {
+ args.push_string(std::string{});
+ return;
+ }
+
+ std::size_t skip_front = positions[start];
+ std::size_t skip_back = 0;
+
+ int max_length = positions.size() - start;
+ if (len < max_length)
+ skip_back = s.size() - positions[start + len];
+
+ auto it_head = s.cbegin() + skip_front;
+ auto it_end = s.cend() - skip_back;
+
+ std::string truncated;
+ std::copy(it_head, it_end, std::back_inserter(truncated));
+ args.push_string(truncated);
+}
+
+void formula_functions::fnc_concatenate(formula_value_stack& args) const
+{
+ std::string s;
+ while (!args.empty())
+ s = args.pop_string() + s;
+ args.push_string(std::move(s));
+}
+
+void formula_functions::fnc_exact(formula_value_stack& args) const
+{
+ if (args.size() != 2u)
+ throw formula_functions::invalid_arg("EXACT requires exactly 2 arguments.");
+
+ std::string right = args.pop_string();
+ std::string left = args.pop_string();
+
+ return args.push_boolean(right == left);
+}
+
+void formula_functions::fnc_find(formula_value_stack& args) const
+{
+ if (args.size() < 2u || args.size() > 3u)
+ throw formula_functions::invalid_arg("FIND requires at least 2 and no more than 3 arguments.");
+
+ int start_pos = 0;
+ if (args.size() == 3u)
+ start_pos = std::floor(args.pop_value()) - 1; // to 0-based
+
+ if (start_pos < 0)
+ {
+ args.clear();
+ args.push_error(formula_error_t::invalid_value_type);
+ return;
+ }
+
+ std::string content = args.pop_string();
+ std::string part = args.pop_string();
+
+ auto positions = detail::calc_utf8_byte_positions(content);
+
+ // convert the logical utf-8 start position to a corresponding byte start position
+
+ if (std::size_t(start_pos) >= positions.size())
+ {
+ args.push_error(formula_error_t::invalid_value_type);
+ return;
+ }
+
+ start_pos = positions[start_pos];
+ std::size_t pos = content.find(part, start_pos);
+
+ if (pos == std::string::npos)
+ {
+ args.push_error(formula_error_t::invalid_value_type);
+ return;
+ }
+
+ // convert the byte position to a logical utf-8 character position.
+ auto it = std::lower_bound(positions.begin(), positions.end(), pos);
+
+ if (it == positions.end() || *it != pos)
+ {
+ // perhaps invalid utf-8 string...
+ args.push_error(formula_error_t::invalid_value_type);
+ return;
+ }
+
+ pos = std::distance(positions.begin(), it);
+ args.push_value(pos + 1); // back to 1-based
+}
+
+void formula_functions::fnc_left(formula_value_stack& args) const
+{
+ if (args.empty() || args.size() > 2)
+ throw formula_functions::invalid_arg(
+ "LEFT requires at least one argument but no more than 2.");
+
+ int n = 1; // when the 2nd arg is skipped, it defaults to 1.
+ if (args.size() == 2)
+ n = std::floor(args.pop_value());
+
+ if (n < 0)
+ {
+ args.clear();
+ args.push_error(formula_error_t::invalid_value_type);
+ return;
+ }
+
+ std::string s = args.pop_string();
+
+ auto positions = detail::calc_utf8_byte_positions(s);
+
+ // Resize ONLY when the desired length is lower than the original string length.
+ if (std::size_t(n) < positions.size())
+ s.resize(positions[n]);
+
+ args.push_string(std::move(s));
+}
+
+void formula_functions::fnc_replace(formula_value_stack& args) const
+{
+ if (args.size() != 4u)
+ throw formula_functions::invalid_arg("REPLACE requires exactly 4 arguments.");
+
+ std::string new_text = args.pop_string();
+ int n_segment = std::floor(args.pop_value());
+ int pos_segment = std::floor(args.pop_value()) - 1; // to 0-based
+
+ if (n_segment < 0 || pos_segment < 0)
+ {
+ args.clear();
+ args.push_error(formula_error_t::invalid_value_type);
+ return;
+ }
+
+ std::string content = args.pop_string();
+
+ auto positions = detail::calc_utf8_byte_positions(content);
+
+ pos_segment = std::min<int>(pos_segment, positions.size());
+ n_segment = std::min<int>(n_segment, positions.size() - pos_segment);
+
+ // convert to its byte position.
+ std::size_t pos_bytes = std::size_t(pos_segment) < positions.size() ? positions[pos_segment] : content.size();
+
+ // copy the leading segment.
+ auto it = std::next(content.begin(), pos_bytes);
+ std::string content_new{content.begin(), it};
+
+ content_new += new_text;
+
+ // copy the trailing segment.
+ std::size_t pos_logical = pos_segment + n_segment;
+ pos_bytes = pos_logical < positions.size() ? positions[pos_logical] : content.size();
+ it = std::next(content.begin(), pos_bytes);
+ std::copy(it, content.end(), std::back_inserter(content_new));
+
+ args.push_string(content_new);
+}
+
+void formula_functions::fnc_rept(formula_value_stack& args) const
+{
+ if (args.size() != 2u)
+ throw formula_functions::invalid_arg("REPT requires 2 arguments.");
+
+ int count = args.pop_value();
+ if (count < 0)
+ {
+ args.clear();
+ args.push_error(formula_error_t::invalid_value_type);
+ return;
+ }
+
+ std::string s = args.pop_string();
+ std::ostringstream os;
+ for (int i = 0; i < count; ++i)
+ os << s;
+
+ args.push_string(os.str());
+}
+
+void formula_functions::fnc_right(formula_value_stack& args) const
+{
+ if (args.empty() || args.size() > 2)
+ throw formula_functions::invalid_arg(
+ "RIGHT requires at least one argument but no more than 2.");
+
+ int n = 1; // when the 2nd arg is skipped, it defaults to 1.
+ if (args.size() == 2)
+ n = std::floor(args.pop_value());
+
+ if (n < 0)
+ {
+ args.clear();
+ args.push_error(formula_error_t::invalid_value_type);
+ return;
+ }
+
+ if (n == 0)
+ {
+ args.clear();
+ args.push_string(std::string{});
+ return;
+ }
+
+ std::string s = args.pop_string();
+
+ auto positions = detail::calc_utf8_byte_positions(s);
+
+ // determine how many logical characters to skip.
+ n = int(positions.size()) - n;
+
+ if (n > 0)
+ {
+ assert(std::size_t(n) < positions.size());
+ auto it = std::next(s.begin(), positions[n]);
+ std::string s_skipped;
+ std::copy(it, s.end(), std::back_inserter(s_skipped));
+ s.swap(s_skipped);
+ }
+
+ args.push_string(std::move(s));
+}
+
+void formula_functions::fnc_substitute(formula_value_stack& args) const
+{
+ if (args.size() < 3 || args.size() > 4)
+ throw formula_functions::invalid_arg(
+ "SUBSTITUTE requires at least 3 arguments but no more than 4.");
+
+ constexpr int replace_all = -1;
+ int which = replace_all;
+
+ if (args.size() == 4)
+ {
+ // explicit which value provided.
+ which = std::floor(args.pop_value());
+
+ if (which < 1)
+ {
+ args.clear();
+ args.push_error(formula_error_t::invalid_value_type);
+ return;
+ }
+ }
+
+ const std::string text_new = args.pop_string();
+ const std::string text_old = args.pop_string();
+ const std::string content = args.pop_string();
+ std::string content_new;
+
+ std::size_t pos = 0;
+ int which_found = 0;
+
+ while (true)
+ {
+ std::size_t found_pos = content.find(text_old, pos);
+ if (found_pos == std::string::npos)
+ {
+ // Copy the rest of the string to the new buffer and exit.
+ content_new.append(content, pos, std::string::npos);
+ break;
+ }
+
+ ++which_found;
+ bool replace_this = which_found == which || which == replace_all;
+ content_new.append(content, pos, found_pos - pos);
+ content_new.append(replace_this ? text_new : text_old);
+ pos = found_pos + text_old.size();
+ }
+
+ args.clear();
+ args.push_string(std::move(content_new));
+}
+
+void formula_functions::fnc_t(formula_value_stack& args) const
+{
+ if (args.size() != 1u)
+ throw formula_functions::invalid_arg("T takes exactly one argument.");
+
+ switch (args.get_type())
+ {
+ case stack_value_t::string:
+ // Do nothing and reuse the string value as the return value.
+ break;
+ case stack_value_t::single_ref:
+ case stack_value_t::range_ref:
+ {
+ auto addr = args.pop_single_ref();
+ auto ca = m_context.get_cell_access(addr);
+
+ std::string s;
+
+ if (ca.get_value_type() == cell_value_t::string)
+ s = ca.get_string_value();
+
+ args.push_string(std::move(s));
+ break;
+ }
+ default:
+ {
+ args.pop_back();
+ args.push_string(std::string{});
+ }
+ }
+}
+
+void formula_functions::fnc_textjoin(formula_value_stack& args) const
+{
+ if (args.size() < 3u)
+ throw formula_functions::invalid_arg("TEXTJOIN requires at least 3 arguments.");
+
+ std::deque<abs_range_t> ranges;
+
+ while (args.size() > 2u)
+ ranges.push_front(args.pop_range_ref());
+
+ bool skip_empty = args.pop_boolean();
+ std::string delim = args.pop_string();
+ std::vector<std::string> tokens;
+
+ for (const abs_range_t& range : ranges)
+ {
+ for (sheet_t sheet = range.first.sheet; sheet <= range.last.sheet; ++sheet)
+ {
+ model_iterator miter = m_context.get_model_iterator(sheet, rc_direction_t::horizontal, range);
+
+ for (; miter.has(); miter.next())
+ {
+ auto& cell = miter.get();
+
+ switch (cell.type)
+ {
+ case celltype_t::string:
+ {
+ auto sid = std::get<string_id_t>(cell.value);
+ const std::string* s = m_context.get_string(sid);
+ assert(s);
+ tokens.emplace_back(*s);
+ break;
+ }
+ case celltype_t::numeric:
+ {
+ std::ostringstream os;
+ os << std::get<double>(cell.value);
+ tokens.emplace_back(os.str());
+ break;
+ }
+ case celltype_t::boolean:
+ {
+ std::ostringstream os;
+ os << std::boolalpha << std::get<bool>(cell.value);
+ tokens.emplace_back(os.str());
+ break;
+ }
+ case celltype_t::formula:
+ {
+ const auto* fc = std::get<const formula_cell*>(cell.value);
+ formula_result res = fc->get_result_cache(m_context.get_formula_result_wait_policy());
+ tokens.emplace_back(res.str(m_context));
+ break;
+ }
+ case celltype_t::empty:
+ {
+ if (!skip_empty)
+ tokens.emplace_back();
+ break;
+ }
+ case celltype_t::unknown:
+ // logic error - this should never happen!
+ throw formula_error(formula_error_t::no_result_error);
+ }
+ }
+ }
+ }
+
+ if (tokens.empty())
+ {
+ args.push_string(std::string{});
+ return;
+ }
+
+ std::string result = std::move(tokens.front());
+
+ for (auto it = std::next(tokens.begin()); it != tokens.end(); ++it)
+ {
+ result.append(delim);
+ result.append(*it);
+ }
+
+ args.push_string(std::move(result));
+}
+
+void formula_functions::fnc_trim(formula_value_stack& args) const
+{
+ if (args.size() != 1u)
+ throw formula_functions::invalid_arg("TRIM takes exactly one argument.");
+
+ std::string s = args.pop_string();
+ const char* p = s.data();
+ const char* p_end = p + s.size();
+ const char* p_head = nullptr;
+
+ std::vector<std::string> tokens;
+
+ for (; p != p_end; ++p)
+ {
+ if (*p == ' ')
+ {
+ if (p_head)
+ {
+ tokens.emplace_back(p_head, std::distance(p_head, p));
+ p_head = nullptr;
+ }
+
+ continue;
+ }
+
+ if (!p_head)
+ // keep track of the head of each token.
+ p_head = p;
+ }
+
+ if (p_head)
+ tokens.emplace_back(p_head, std::distance(p_head, p));
+
+ if (tokens.empty())
+ {
+ args.push_string(std::string{});
+ return;
+ }
+
+ std::ostringstream os;
+ std::copy(tokens.cbegin(), std::prev(tokens.cend()), std::ostream_iterator<std::string>(os, " "));
+ os << tokens.back();
+
+ args.push_string(os.str());
+}
+
+void formula_functions::fnc_now(formula_value_stack& args) const
+{
+ if (!args.empty())
+ throw formula_functions::invalid_arg("NOW takes no arguments.");
+
+ // TODO: this value is currently not accurate since we don't take into
+ // account the zero date yet.
+ double cur_time = get_current_time();
+ cur_time /= 86400.0; // convert seconds to days.
+ args.push_value(cur_time);
+}
+
+void formula_functions::fnc_wait(formula_value_stack& args) const
+{
+ std::this_thread::sleep_for(std::chrono::milliseconds(1000));
+ args.clear();
+ args.push_value(1);
+}
+
+void formula_functions::fnc_subtotal(formula_value_stack& args) const
+{
+ if (args.size() != 2)
+ throw formula_functions::invalid_arg("SUBTOTAL requires exactly 2 arguments.");
+
+ abs_range_t range = args.pop_range_ref();
+ int subtype = args.pop_value();
+ switch (subtype)
+ {
+ case 109:
+ {
+ // SUM
+ matrix mx = m_context.get_range_value(range);
+ args.push_value(sum_matrix_elements(mx));
+ break;
+ }
+ default:
+ {
+ std::ostringstream os;
+ os << "SUBTOTAL: function type " << subtype << " not implemented yet";
+ throw formula_functions::invalid_arg(os.str());
+ }
+ }
+}
+
+void formula_functions::fnc_column(formula_value_stack& args) const
+{
+ if (args.empty())
+ {
+ args.push_value(m_pos.column + 1);
+ return;
+ }
+
+ if (args.size() > 1)
+ throw formula_functions::invalid_arg("COLUMN requires 1 argument or less.");
+
+ switch (args.get_type())
+ {
+ case stack_value_t::single_ref:
+ case stack_value_t::range_ref:
+ {
+ abs_address_t addr = args.pop_single_ref();
+ args.push_value(addr.column + 1);
+ break;
+ }
+ default:
+ throw formula_error(formula_error_t::invalid_value_type);
+ }
+}
+
+void formula_functions::fnc_columns(formula_value_stack& args) const
+{
+ double res = 0.0;
+
+ while (!args.empty())
+ {
+ switch (args.get_type())
+ {
+ case stack_value_t::single_ref:
+ case stack_value_t::range_ref:
+ {
+ abs_range_t range = args.pop_range_ref();
+ res += range.last.column - range.first.column + 1;
+ break;
+ }
+ default:
+ throw formula_error(formula_error_t::invalid_value_type);
+ }
+ }
+
+ args.push_value(res);
+}
+
+void formula_functions::fnc_row(formula_value_stack& args) const
+{
+ if (args.empty())
+ {
+ args.push_value(m_pos.row + 1);
+ return;
+ }
+
+ if (args.size() > 1)
+ throw formula_functions::invalid_arg("ROW requires 1 argument or less.");
+
+ switch (args.get_type())
+ {
+ case stack_value_t::single_ref:
+ case stack_value_t::range_ref:
+ {
+ abs_address_t addr = args.pop_single_ref();
+ args.push_value(addr.row + 1);
+ break;
+ }
+ default:
+ throw formula_error(formula_error_t::invalid_value_type);
+ }
+}
+
+void formula_functions::fnc_rows(formula_value_stack& args) const
+{
+ double res = 0.0;
+
+ while (!args.empty())
+ {
+ switch (args.get_type())
+ {
+ case stack_value_t::single_ref:
+ case stack_value_t::range_ref:
+ {
+ abs_range_t range = args.pop_range_ref();
+ res += range.last.row - range.first.row + 1;
+ break;
+ }
+ default:
+ throw formula_error(formula_error_t::invalid_value_type);
+ }
+ }
+
+ args.push_value(res);
+}
+
+void formula_functions::fnc_sheet(formula_value_stack& args) const
+{
+ if (args.empty())
+ {
+ // Take the current sheet index.
+ args.push_value(m_pos.sheet + 1);
+ return;
+ }
+
+ if (args.size() > 1u)
+ throw formula_functions::invalid_arg("SHEET only takes one argument or less.");
+
+ switch (args.get_type())
+ {
+ case stack_value_t::single_ref:
+ case stack_value_t::range_ref:
+ {
+ abs_range_t range = args.pop_range_ref();
+ args.push_value(range.first.sheet + 1);
+ break;
+ }
+ case stack_value_t::string:
+ {
+ // TODO: we need to make this case insensitive.
+ std::string sheet_name = args.pop_string();
+ sheet_t sheet_id = m_context.get_sheet_index(sheet_name);
+ if (sheet_id == invalid_sheet)
+ throw formula_error(formula_error_t::no_value_available);
+
+ args.push_value(sheet_id + 1);
+ break;
+ }
+ default:
+ throw formula_error(formula_error_t::invalid_value_type);
+ }
+}
+
+void formula_functions::fnc_sheets(formula_value_stack& args) const
+{
+ if (args.empty())
+ {
+ args.push_value(m_context.get_sheet_count());
+ return;
+ }
+
+ if (args.size() != 1u)
+ throw formula_functions::invalid_arg("SHEETS only takes one argument or less.");
+
+ switch (args.get_type())
+ {
+ case stack_value_t::single_ref:
+ case stack_value_t::range_ref:
+ {
+ abs_range_t range = args.pop_range_ref();
+ sheet_t n = range.last.sheet - range.first.sheet + 1;
+ args.push_value(n);
+ break;
+ }
+ default:
+ throw formula_error(formula_error_t::no_value_available);
+ }
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/formula_functions.hpp b/src/libixion/formula_functions.hpp
new file mode 100644
index 0000000..c4d9d73
--- /dev/null
+++ b/src/libixion/formula_functions.hpp
@@ -0,0 +1,129 @@
+/* -*- 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/.
+ */
+
+#ifndef __IXION_FORMULA_FUNCTIONS_HPP__
+#define __IXION_FORMULA_FUNCTIONS_HPP__
+
+#include "ixion/global.hpp"
+#include "ixion/exceptions.hpp"
+#include "ixion/formula_function_opcode.hpp"
+
+#include "formula_value_stack.hpp"
+
+#include <string>
+#include <vector>
+
+namespace ixion {
+
+struct formula_token;
+
+/**
+ * Collection of built-in cell function implementations. Note that those
+ * functions that return a string result <i>may</i> modify the state of the
+ * model context when the result string is not yet in the shared string
+ * pool.
+ */
+class formula_functions
+{
+public:
+ class invalid_arg : public general_error
+ {
+ public:
+ invalid_arg(const ::std::string& msg);
+ };
+
+ formula_functions(model_context& cxt, const abs_address_t& pos);
+ ~formula_functions();
+
+ static formula_function_t get_function_opcode(const formula_token& token);
+ static formula_function_t get_function_opcode(std::string_view s);
+ static std::string_view get_function_name(formula_function_t oc);
+
+ void interpret(formula_function_t oc, formula_value_stack& args);
+
+private:
+
+ // category: statistical
+ void fnc_abs(formula_value_stack& args) const;
+ void fnc_average(formula_value_stack& args) const;
+ void fnc_count(formula_value_stack& args) const;
+ void fnc_counta(formula_value_stack& args) const;
+ void fnc_countblank(formula_value_stack& args) const;
+ void fnc_max(formula_value_stack& args) const;
+ void fnc_median(formula_value_stack& args) const;
+ void fnc_min(formula_value_stack& args) const;
+ void fnc_mode(formula_value_stack& args) const;
+ void fnc_pi(formula_value_stack& args) const;
+
+ // category: mathematical
+ void fnc_int(formula_value_stack& args) const;
+ void fnc_mmult(formula_value_stack& args) const;
+ void fnc_subtotal(formula_value_stack& args) const;
+ void fnc_sum(formula_value_stack& args) const;
+
+ // category: logical
+ void fnc_and(formula_value_stack& args) const;
+ void fnc_false(formula_value_stack& args) const;
+ void fnc_if(formula_value_stack& args) const;
+ void fnc_not(formula_value_stack& args) const;
+ void fnc_or(formula_value_stack& args) const;
+ void fnc_true(formula_value_stack& args) const;
+
+ // category: information
+ void fnc_isblank(formula_value_stack& args) const;
+ void fnc_iserror(formula_value_stack& args) const;
+ void fnc_iseven(formula_value_stack& args) const;
+ void fnc_isformula(formula_value_stack& args) const;
+ void fnc_islogical(formula_value_stack& args) const;
+ void fnc_isna(formula_value_stack& args) const;
+ void fnc_isnontext(formula_value_stack& args) const;
+ void fnc_isnumber(formula_value_stack& args) const;
+ void fnc_isodd(formula_value_stack& args) const;
+ void fnc_isref(formula_value_stack& args) const;
+ void fnc_istext(formula_value_stack& args) const;
+ void fnc_n(formula_value_stack& args) const;
+ void fnc_na(formula_value_stack& args) const;
+ void fnc_type(formula_value_stack& args) const;
+
+ // category: text
+ void fnc_concatenate(formula_value_stack& args) const;
+ void fnc_exact(formula_value_stack& args) const;
+ void fnc_find(formula_value_stack& args) const;
+ void fnc_left(formula_value_stack& args) const;
+ void fnc_len(formula_value_stack& args) const;
+ void fnc_mid(formula_value_stack& args) const;
+ void fnc_replace(formula_value_stack& args) const;
+ void fnc_rept(formula_value_stack& args) const;
+ void fnc_right(formula_value_stack& args) const;
+ void fnc_substitute(formula_value_stack& args) const;
+ void fnc_t(formula_value_stack& args) const;
+ void fnc_textjoin(formula_value_stack& args) const;
+ void fnc_trim(formula_value_stack& args) const;
+
+ // category: date & time
+ void fnc_now(formula_value_stack& args) const;
+
+ // cateogry: spreadsheet
+ void fnc_column(formula_value_stack& args) const;
+ void fnc_columns(formula_value_stack& args) const;
+ void fnc_row(formula_value_stack& args) const;
+ void fnc_rows(formula_value_stack& args) const;
+ void fnc_sheet(formula_value_stack& args) const;
+ void fnc_sheets(formula_value_stack& args) const;
+
+ // category: development
+ void fnc_wait(formula_value_stack& args) const;
+
+private:
+ model_context& m_context;
+ abs_address_t m_pos;
+};
+
+}
+
+#endif
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
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: */
diff --git a/src/libixion/formula_interpreter.hpp b/src/libixion/formula_interpreter.hpp
new file mode 100644
index 0000000..73721f6
--- /dev/null
+++ b/src/libixion/formula_interpreter.hpp
@@ -0,0 +1,125 @@
+/* -*- 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/.
+ */
+
+#ifndef INCLUDED_IXION_FORMULA_INTERPRETER_HPP
+#define INCLUDED_IXION_FORMULA_INTERPRETER_HPP
+
+#include "ixion/global.hpp"
+#include "ixion/formula_tokens.hpp"
+#include "ixion/formula_result.hpp"
+
+#include "formula_value_stack.hpp"
+
+#include <sstream>
+#include <unordered_set>
+#include <deque>
+
+namespace ixion {
+
+class formula_cell;
+
+namespace iface {
+
+class session_handler;
+
+}
+
+/**
+ * The formula interpreter parses a series of formula tokens representing a
+ * formula expression, and calculates the result of that expression.
+ *
+ * <p>Intermediate result of each handler is pushed onto the stack and
+ * popped from it for the calling method to retrieve. By the end of the
+ * interpretation there should only be one result left on the stack which is
+ * the final result of the interpretation of the expression. The number of
+ * intermediate results (or stack values) on the stack is normally one at
+ * the end of each handler, except for the function handler where the number
+ * of stack values may be more than one when the function may take more than
+ * one argument.</p>
+ */
+class formula_interpreter
+{
+ using name_set = std::unordered_set<std::string>;
+ using fv_stacks_type = std::deque<formula_value_stack>;
+
+public:
+ typedef ::std::vector<const formula_token*> local_tokens_type;
+
+ formula_interpreter() = delete;
+ formula_interpreter(const formula_interpreter&) = delete;
+ formula_interpreter& operator= (formula_interpreter) = delete;
+
+ formula_interpreter(const formula_cell* cell, model_context& cxt);
+ ~formula_interpreter();
+
+ void set_origin(const abs_address_t& pos);
+ bool interpret();
+ formula_result transfer_result();
+ formula_error_t get_error() const;
+
+private:
+ /**
+ * Expand all named expressions into a flat set of tokens. This is also
+ * where we detect circular referencing of named expressions.
+ */
+ void init_tokens();
+
+ void pop_result();
+
+ void expand_named_expression(const named_expression_t* expr, name_set& used_names);
+
+ void ensure_token_exists() const;
+ bool has_token() const;
+ void next();
+ const formula_token& token() const;
+ const formula_token& token_or_throw() const;
+ const formula_token& next_token();
+ const std::string& string_or_throw() const;
+
+ // The following methods are handlers. In each handler, the initial
+ // position is always set to the first unprocessed token. Each handler is
+ // responsible for setting the token position to the next unprocessed
+ // position when it finishes.
+
+ void expression();
+ void term();
+ void factor();
+ bool sign();
+ void paren();
+ void single_ref();
+ void range_ref();
+ void table_ref();
+ void constant();
+ void literal();
+ void array();
+ void function();
+
+ void clear_stacks();
+ void push_stack();
+ void pop_stack();
+
+ formula_value_stack& get_stack();
+
+private:
+ const formula_cell* m_parent_cell;
+ model_context& m_context;
+ std::unique_ptr<iface::session_handler> mp_handler;
+ abs_address_t m_pos;
+
+ fv_stacks_type m_stacks;
+ local_tokens_type m_tokens;
+ local_tokens_type::const_iterator m_cur_token_itr;
+ local_tokens_type::const_iterator m_end_token_pos;
+
+ formula_result m_result;
+ formula_error_t m_error;
+};
+
+}
+
+#endif
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/formula_lexer.cpp b/src/libixion/formula_lexer.cpp
new file mode 100644
index 0000000..7928d82
--- /dev/null
+++ b/src/libixion/formula_lexer.cpp
@@ -0,0 +1,345 @@
+/* -*- 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_lexer.hpp"
+#include "debug.hpp"
+#include "ixion/global.hpp"
+
+#include <cassert>
+#include <iostream>
+#include <sstream>
+#include <cctype>
+#include <unordered_map>
+
+namespace ixion {
+
+namespace {
+
+const std::unordered_map<char, lexer_opcode_t> ops_map = {
+ { '&', lexer_opcode_t::concat },
+ { '(', lexer_opcode_t::open },
+ { ')', lexer_opcode_t::close },
+ { '*', lexer_opcode_t::multiply },
+ { '+', lexer_opcode_t::plus },
+ { '-', lexer_opcode_t::minus },
+ { '/', lexer_opcode_t::divide },
+ { '<', lexer_opcode_t::less },
+ { '=', lexer_opcode_t::equal },
+ { '>', lexer_opcode_t::greater },
+ { '^', lexer_opcode_t::exponent },
+ { '{', lexer_opcode_t::array_open },
+ { '}', lexer_opcode_t::array_close },
+};
+
+} // anonymous namespace
+
+class tokenizer
+{
+ enum buffer_type {
+ buf_numeral,
+ buf_name
+ };
+
+public:
+ tokenizer() = delete;
+ tokenizer(const tokenizer&) = delete;
+ tokenizer& operator= (tokenizer) = delete;
+
+ explicit tokenizer(lexer_tokens_t& tokens, const char* p, size_t n) :
+ m_tokens(tokens),
+ m_sep_arg(','),
+ m_sep_array_row(';'),
+ m_sep_decimal('.'),
+ mp_first(p),
+ mp_char(NULL),
+ m_size(n),
+ m_pos(0),
+ mp_char_stored(nullptr),
+ m_pos_stored(0)
+ {
+ }
+
+ void run();
+
+ void set_sep_arg(char c);
+
+private:
+ bool is_arg_sep(char c) const;
+ bool is_array_row_sep(char c) const;
+ bool is_decimal_sep(char c) const;
+ bool is_op(char c) const;
+
+ void init();
+
+ void numeral();
+ void space();
+ void name();
+ void op(lexer_opcode_t oc);
+ void string();
+
+ bool has_char() const;
+ void next();
+ void push_pos();
+ void pop_pos();
+
+private:
+ lexer_tokens_t& m_tokens;
+
+ char m_sep_arg;
+ char m_sep_array_row;
+ char m_sep_decimal;
+
+ const char* mp_first;
+ const char* mp_char;
+ const size_t m_size;
+ size_t m_pos;
+
+ const char* mp_char_stored;
+ size_t m_pos_stored;
+};
+
+void tokenizer::init()
+{
+ m_tokens.clear();
+ mp_char = mp_first;
+ m_pos = 0;
+}
+
+void tokenizer::run()
+{
+ if (!m_size)
+ // Nothing to do.
+ return;
+
+ init();
+
+ while (has_char())
+ {
+ if (std::isdigit(*mp_char))
+ {
+ numeral();
+ continue;
+ }
+
+ if (auto it = ops_map.find(*mp_char); it != ops_map.end())
+ {
+ op(it->second);
+ continue;
+ }
+
+ switch (*mp_char)
+ {
+ case ' ':
+ space();
+ continue;
+ case '"':
+ string();
+ continue;
+ }
+
+ if (is_arg_sep(*mp_char))
+ {
+ op(lexer_opcode_t::sep);
+ continue;
+ }
+
+ if (is_array_row_sep(*mp_char))
+ {
+ op(lexer_opcode_t::array_row_sep);
+ continue;
+ }
+
+ name();
+ }
+}
+
+void tokenizer::set_sep_arg(char c)
+{
+ m_sep_arg = c;
+}
+
+bool tokenizer::is_arg_sep(char c) const
+{
+ return c == m_sep_arg;
+}
+
+bool tokenizer::is_array_row_sep(char c) const
+{
+ return c == m_sep_array_row;
+}
+
+bool tokenizer::is_decimal_sep(char c) const
+{
+ return c == m_sep_decimal;
+}
+
+bool tokenizer::is_op(char c) const
+{
+ if (is_arg_sep(c))
+ return true;
+
+ if (ops_map.count(c) > 0)
+ return true;
+
+ switch (*mp_char)
+ {
+ case ' ':
+ case '"':
+ return true;
+ }
+ return false;
+}
+
+void tokenizer::numeral()
+{
+ const char* p = mp_char;
+ push_pos();
+
+ size_t len = 1;
+ size_t sep_count = 0;
+ for (next(); has_char(); next(), ++len)
+ {
+ if (*mp_char == ':')
+ {
+ // Treat this as a name. This may be a part of a row-only range (e.g. 3:3).
+ pop_pos();
+ name();
+ return;
+ }
+
+ if (std::isdigit(*mp_char))
+ continue;
+ if (is_decimal_sep(*mp_char) && ++sep_count <= 1)
+ continue;
+
+ break;
+ }
+
+ if (sep_count > 1)
+ {
+ // failed to parse this as a numeral. Treat this as a name.
+ IXION_TRACE("error parsing '" << std::string(p, len) << "' as a numeral, treating it as a name.");
+ pop_pos();
+ name();
+ return;
+ }
+ double val = to_double({p, len});
+ m_tokens.emplace_back(val);
+}
+
+void tokenizer::space()
+{
+ // space is ignored for now.
+ next();
+}
+
+void tokenizer::name()
+{
+ std::vector<char> scopes;
+
+ const char* p = mp_char;
+ size_t len = 0;
+
+ for (; has_char(); next(), ++len)
+ {
+ char c = *mp_char;
+
+ if (!scopes.empty() && scopes.back() == c)
+ {
+ scopes.pop_back();
+ continue;
+ }
+
+ switch (c)
+ {
+ case '[':
+ scopes.push_back(']');
+ continue;
+ case '\'':
+ scopes.push_back('\'');
+ continue;
+ }
+
+ if (!scopes.empty())
+ continue;
+
+ if (is_op(c))
+ break;
+ }
+
+ m_tokens.emplace_back(lexer_opcode_t::name, std::string_view{p, len});
+}
+
+void tokenizer::op(lexer_opcode_t oc)
+{
+ m_tokens.emplace_back(oc);
+ next();
+}
+
+void tokenizer::string()
+{
+ next();
+ const char* p = mp_char;
+ size_t len = 0;
+ for (; *mp_char != '"' && has_char(); ++len)
+ next();
+
+ m_tokens.emplace_back(lexer_opcode_t::string, std::string_view{p, len});
+
+ if (*mp_char == '"')
+ next();
+}
+
+void tokenizer::next()
+{
+ ++mp_char;
+ ++m_pos;
+}
+
+void tokenizer::push_pos()
+{
+ mp_char_stored = mp_char;
+ m_pos_stored = m_pos;
+}
+
+void tokenizer::pop_pos()
+{
+ mp_char = mp_char_stored;
+ m_pos = m_pos_stored;
+
+ mp_char_stored = NULL;
+ m_pos_stored = 0;
+}
+
+bool tokenizer::has_char() const
+{
+ return m_pos < m_size;
+}
+
+// ============================================================================
+
+formula_lexer::tokenize_error::tokenize_error(const std::string& msg) : general_error(msg) {}
+
+formula_lexer::formula_lexer(const config& config, const char* p, size_t n) :
+ m_config(config), mp_first(p), m_size(n) {}
+
+formula_lexer::~formula_lexer() {}
+
+void formula_lexer::tokenize()
+{
+ tokenizer tkr(m_tokens, mp_first, m_size);
+ tkr.set_sep_arg(m_config.sep_function_arg);
+ tkr.run();
+}
+
+void formula_lexer::swap_tokens(lexer_tokens_t& tokens)
+{
+ m_tokens.swap(tokens);
+}
+
+}
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/formula_lexer.hpp b/src/libixion/formula_lexer.hpp
new file mode 100644
index 0000000..b61eb0b
--- /dev/null
+++ b/src/libixion/formula_lexer.hpp
@@ -0,0 +1,55 @@
+/* -*- 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/.
+ */
+
+#ifndef INCLUDED_IXION_FORMULA_LEXER_HPP
+#define INCLUDED_IXION_FORMULA_LEXER_HPP
+
+#include <string>
+#include <exception>
+
+#include "ixion/exceptions.hpp"
+#include "ixion/config.hpp"
+
+#include "lexer_tokens.hpp"
+
+namespace ixion {
+
+class formula_lexer
+{
+ formula_lexer() = delete;
+ formula_lexer(const formula_lexer&) = delete;
+ formula_lexer& operator= (const formula_lexer&) = delete;
+public:
+ class tokenize_error : public general_error
+ {
+ public:
+ tokenize_error(const std::string& msg);
+ };
+
+ formula_lexer(const config& config, const char* p, size_t n);
+ ~formula_lexer();
+
+ void tokenize();
+
+ /**
+ * Note that this will empty the tokens stored inside the lexer instance.
+ *
+ * @param tokens token container to move the tokens to.
+ */
+ void swap_tokens(lexer_tokens_t& tokens);
+
+private:
+ const config& m_config;
+ lexer_tokens_t m_tokens;
+ const char* mp_first;
+ size_t m_size;
+};
+
+}
+
+#endif
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/formula_name_resolver.cpp b/src/libixion/formula_name_resolver.cpp
new file mode 100644
index 0000000..9e11401
--- /dev/null
+++ b/src/libixion/formula_name_resolver.cpp
@@ -0,0 +1,2057 @@
+/* -*- 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 <ixion/formula_name_resolver.hpp>
+#include <ixion/table.hpp>
+
+#include "formula_functions.hpp"
+#include "debug.hpp"
+
+#include <cassert>
+#include <iostream>
+#include <sstream>
+#include <vector>
+#include <limits>
+#include <algorithm>
+#include <cctype>
+#include <optional>
+
+namespace ixion {
+
+namespace {
+
+bool check_address_by_sheet_bounds(const model_context* cxt, const address_t& pos)
+{
+ rc_size_t ss(row_upper_bound, column_upper_bound);
+
+ if (cxt && pos.sheet >= 0 && size_t(pos.sheet) < cxt->get_sheet_count())
+ {
+ // Make sure the address is within the sheet size.
+ ss = cxt->get_sheet_size();
+ }
+
+ row_t row_check = pos.row >= 0 ? pos.row : -pos.row;
+
+ if (pos.row != row_unset && row_check >= ss.row)
+ return false;
+
+ col_t col_check = pos.column >= 0 ? pos.column : -pos.column;
+
+ if (pos.column != column_unset && col_check >= ss.column)
+ return false;
+
+ return true;
+}
+
+bool resolve_function(const char* p, size_t n, formula_name_t& ret)
+{
+ formula_function_t func_oc = formula_functions::get_function_opcode({p, n});
+ if (func_oc != formula_function_t::func_unknown)
+ {
+ // This is a built-in function.
+ ret.type = formula_name_t::function;
+ ret.value = func_oc;
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Table reference can be either one of:
+ *
+ * <ul>
+ * <li>Table[Column]</li>
+ * <li>[Column]</li>
+ * <li>Table[[#Area],[Column]]</li>
+ * <li>Table[[#Area1],[#Area2],[Column]]</li>
+ * </ul>
+ *
+ * where the #Area (area specifier) can be one or more of
+ *
+ * <ul>
+ * <li>#Header</li>
+ * <li>#Data</li>
+ * <li>#Totals</li>
+ * <li>#All</li>
+ * </ul>
+ */
+bool resolve_table(const model_context* cxt, const char* p, size_t n, formula_name_t& ret)
+{
+ if (!cxt)
+ return false;
+
+ short scope = 0;
+ size_t last_column_pos = std::numeric_limits<size_t>::max();
+ std::string_view buf;
+ std::string_view table_name;
+ std::vector<std::string_view> names;
+
+ bool table_detected = false;
+
+ const char* p_end = p + n;
+ for (; p != p_end; ++p)
+ {
+ switch (*p)
+ {
+ case '[':
+ {
+ if (scope >= 2)
+ return false;
+
+ table_detected = true;
+ if (!buf.empty())
+ {
+ if (scope != 0)
+ return false;
+
+ table_name = buf;
+ buf = std::string_view{};
+ }
+
+ ++scope;
+ }
+ break;
+ case ']':
+ {
+ if (scope <= 0)
+ // non-matching brace.
+ return false;
+
+ if (!buf.empty())
+ {
+ names.push_back(buf);
+ buf = std::string_view{};
+ }
+
+ --scope;
+ }
+ break;
+ case ',':
+ {
+ if (!buf.empty())
+ return false;
+ }
+ break;
+ case ':':
+ {
+ if (scope != 1)
+ // allowed only inside the first scope.
+ return false;
+
+ if (!buf.empty())
+ return false;
+
+ if (names.empty())
+ return false;
+
+ last_column_pos = names.size();
+ }
+ break;
+ default:
+ if (buf.empty())
+ buf = std::string_view{p, 1u};
+ else
+ buf = std::string_view{buf.data(), buf.size() + 1u};
+ }
+ }
+
+ if (!buf.empty())
+ return false;
+
+ if (!table_detected)
+ return false;
+
+ if (names.empty())
+ return false;
+
+ formula_name_t::table_type table;
+ table.areas = table_area_none;
+ table.name = table_name;
+ table.column_first = std::string_view();
+ table.column_last = std::string_view();
+
+ for (std::size_t i = 0; i < names.size(); ++i)
+ {
+ std::string_view name = names[i];
+ assert(!name.empty());
+
+ if (name[0] == '#')
+ {
+ // area specifier.
+ name.remove_prefix(1);
+ if (name == "Headers")
+ table.areas |= table_area_headers;
+ else if (name == "Data")
+ table.areas |= table_area_data;
+ else if (name == "Totals")
+ table.areas |= table_area_totals;
+ else if (name == "All")
+ table.areas = table_area_all;
+ }
+ else if (!table.column_first.empty())
+ {
+ // This is a second column name.
+ if (!table.column_last.empty())
+ return false;
+
+ if (i != last_column_pos)
+ return false;
+
+ table.column_last = name;
+ }
+ else
+ {
+ // first column name.
+ if (!table.areas)
+ table.areas = table_area_data;
+
+ table.column_first = name;
+ }
+ }
+
+ ret.type = formula_name_t::table_reference;
+ ret.value = table;
+
+ return true;
+}
+
+/**
+ * Check if the name is a built-in function, or else it's considered a named
+ * expression.
+ *
+ * @param name name to be resolved
+ * @param ret resolved name type
+ */
+void resolve_function_or_name(const char* p, size_t n, formula_name_t& ret)
+{
+ if (resolve_function(p, n, ret))
+ return;
+
+ // Everything else is assumed to be a named expression.
+ ret.type = formula_name_t::named_expression;
+}
+
+void set_cell_reference(formula_name_t& ret, const address_t& addr)
+{
+ ret.type = formula_name_t::cell_reference;
+ ret.value = addr;
+}
+
+enum class resolver_parse_mode { column, row };
+
+void append_sheet_name(std::ostringstream& os, const ixion::model_context& cxt, sheet_t sheet)
+{
+ if (!is_valid_sheet(sheet))
+ {
+ IXION_DEBUG("invalid sheet index (" << sheet << ")");
+ return;
+ }
+
+ std::string sheet_name = cxt.get_sheet_name(sheet);
+ std::string buffer; // used only when the sheet name contains at least one single quote.
+
+ const char* p = sheet_name.data();
+ const char* p_end = p + sheet_name.size();
+
+ bool quote = false;
+ const char* p0 = nullptr;
+
+ for (; p != p_end; ++p)
+ {
+ if (!p0)
+ p0 = p;
+
+ switch (*p)
+ {
+ case ' ':
+ case '"':
+ quote = true;
+ break;
+ case '\'':
+ quote = true;
+ buffer += std::string(p0, p-p0);
+ buffer.push_back(*p);
+ buffer.push_back(*p);
+ p0 = nullptr;
+ break;
+ }
+ }
+
+ if (quote)
+ os << '\'';
+
+ if (buffer.empty())
+ os << sheet_name;
+ else
+ {
+ if (p0)
+ buffer += std::string(p0, p-p0);
+ os << buffer;
+ }
+
+ if (quote)
+ os << '\'';
+}
+
+void append_sheet_name_calc_a1(
+ std::ostringstream& os, const ixion::model_context* cxt, const address_t& addr, const abs_address_t& origin)
+{
+ if (!cxt)
+ return;
+
+ sheet_t sheet = addr.sheet;
+ if (addr.abs_sheet)
+ os << '$';
+ else
+ sheet += origin.sheet;
+ append_sheet_name(os, *cxt, sheet);
+ os << '.';
+}
+
+void append_sheet_name_odf_cra(
+ std::ostringstream& os, const ixion::model_context* cxt, const address_t& addr, const abs_address_t& origin)
+{
+ if (cxt)
+ {
+ sheet_t sheet = addr.sheet;
+ if (addr.abs_sheet)
+ os << '$';
+ else
+ sheet += origin.sheet;
+ append_sheet_name(os, *cxt, sheet);
+ }
+ os << '.';
+}
+
+void append_column_name_a1(std::ostringstream& os, col_t col)
+{
+ const col_t div = 26;
+ std::string col_name;
+ while (true)
+ {
+ col_t rem = col % div;
+ char c = 'A' + rem;
+ col_name.push_back(c);
+ if (col < div)
+ break;
+
+ col -= rem;
+ col /= div;
+ col -= 1;
+ }
+
+ std::reverse(col_name.begin(), col_name.end());
+ os << col_name;
+}
+
+void append_column_address_a1(std::ostringstream& os, col_t col, col_t origin, bool absolute)
+{
+ if (col == column_unset)
+ return;
+
+ if (absolute)
+ os << '$';
+ else
+ col += origin;
+
+ append_column_name_a1(os, col);
+}
+
+void append_row_address_a1(std::ostringstream& os, row_t row, row_t origin, bool absolute)
+{
+ if (row == row_unset)
+ return;
+
+ if (absolute)
+ os << '$';
+ else
+ row += origin;
+
+ os << (row + 1);
+}
+
+void append_address_a1(
+ std::ostringstream& os, const ixion::model_context* cxt,
+ const address_t& addr, const abs_address_t& pos, char sheet_name_sep)
+{
+ assert(sheet_name_sep);
+
+ col_t col = addr.column;
+ row_t row = addr.row;
+ sheet_t sheet = addr.sheet;
+ if (!addr.abs_column)
+ col += pos.column;
+ if (!addr.abs_row)
+ row += pos.row;
+ if (!addr.abs_sheet)
+ sheet += pos.sheet;
+
+ if (cxt)
+ {
+ append_sheet_name(os, *cxt, sheet);
+ os << sheet_name_sep;
+ }
+
+ if (addr.abs_column)
+ os << '$';
+ append_column_name_a1(os, col);
+
+ if (addr.abs_row)
+ os << '$';
+ os << (row + 1);
+}
+
+/**
+ * Almost identical to append_address_a1, but it always appends a sheet name
+ * separator even if a sheet name is not appended.
+ */
+void append_address_a1_with_sheet_name_sep(
+ std::ostringstream& os, const ixion::model_context* cxt,
+ const address_t& addr, const abs_address_t& pos, char sheet_name_sep)
+{
+ if (!cxt)
+ os << sheet_name_sep;
+
+ append_address_a1(os, cxt, addr, pos, sheet_name_sep);
+}
+
+void append_address_r1c1(
+ std::ostringstream& os, const address_t& addr, const abs_address_t& pos)
+{
+ if (addr.row != row_unset)
+ {
+ os << 'R';
+ if (addr.abs_row)
+ // absolute row address.
+ os << (addr.row+1);
+ else if (addr.row)
+ {
+ // relative row address different from origin.
+ os << '[';
+ os << addr.row;
+ os << ']';
+ }
+
+ }
+ if (addr.column != column_unset)
+ {
+ os << 'C';
+ if (addr.abs_column)
+ os << (addr.column+1);
+ else if (addr.column)
+ {
+ os << '[';
+ os << addr.column;
+ os << ']';
+ }
+ }
+}
+
+void append_name_string(std::ostringstream& os, const model_context* cxt, string_id_t sid)
+{
+ if (!cxt)
+ return;
+
+ const std::string* p = cxt->get_string(sid);
+ if (p)
+ os << *p;
+}
+
+char append_table_areas(std::ostringstream& os, const table_t& table)
+{
+ if (table.areas == table_area_all)
+ {
+ os << "[#All]";
+ return 1;
+ }
+
+ bool headers = (table.areas & table_area_headers);
+ bool data = (table.areas & table_area_data);
+ bool totals = (table.areas & table_area_totals);
+
+ char count = 0;
+ if (headers)
+ {
+ os << "[#Headers]";
+ ++count;
+ }
+
+ if (data)
+ {
+ if (count > 0)
+ os << ',';
+ os << "[#Data]";
+ ++count;
+ }
+
+ if (totals)
+ {
+ if (count > 0)
+ os << ',';
+ os << "[#Totals]";
+ ++count;
+ }
+
+ return count;
+}
+
+enum parse_address_result_type
+{
+ invalid = 0,
+ valid_address,
+ range_expected /// valid address followed by a ':'.
+};
+
+struct parse_address_result
+{
+ parse_address_result_type result;
+ bool sheet_name = false;
+};
+
+#if IXION_LOGGING
+
+[[maybe_unused]] std::ostream& operator<< (std::ostream& os, parse_address_result_type rt)
+{
+ static const char* names[] = {
+ "invalid",
+ "valid address",
+ "range expected"
+ };
+
+ os << names[rt];
+ return os;
+}
+
+[[maybe_unused]] std::string to_string(parse_address_result_type rt)
+{
+ std::ostringstream os;
+ os << rt;
+ return os.str();
+}
+
+#endif
+
+/**
+ * Upon successful parsing, the position should be on the separator character.
+ */
+std::optional<sheet_t> parse_sheet_name_quoted(
+ const ixion::model_context& cxt, const char sep, const char*& p, const char* p_end)
+{
+ assert(*p == '\'');
+
+ ++p; // skip the open quote.
+ std::size_t len = 0;
+ std::string buffer; // used only when the name contains at least one single quote.
+ const char* p1 = p;
+
+ // parse until the closing quote is reached.
+ for (; p < p_end; ++p)
+ {
+ if (*p == '\'')
+ {
+ ++p; // skip the quote
+ if (p == p_end)
+ break;
+
+ if (*p == '\'')
+ {
+ // next character is a quote too. Store the parsed string
+ // segment to the buffer and move on.
+ ++len;
+ buffer += std::string(p1, len);
+ p1 = p + 1;
+ len = 0;
+ continue;
+ }
+
+ if (*p != sep)
+ // the next char must be the separator. Parse failed.
+ break;
+
+ std::optional<sheet_t> sheet;
+
+ if (buffer.empty())
+ // Name contains no single quotes.
+ sheet = cxt.get_sheet_index({p1, len});
+ else
+ {
+ buffer += std::string(p1, len);
+ sheet = cxt.get_sheet_index({buffer.data(), buffer.size()});
+ }
+
+ return sheet;
+ }
+
+ ++len;
+ }
+
+ return std::optional<sheet_t>{};
+}
+
+/**
+ * Try to parse a sheet name prefix in the string. If this fails, revert
+ * the current position back to the original position prior to the call.
+ *
+ * Upon successful parsing, the position should be on the separator character.
+ *
+ * @param cxt model context used to query sheet names.
+ * @param sep separator character between sheet name and the cell address.
+ * @param p pointer to the first character. When the parsing is succesful,
+ * this will point to the separator character, but if the parsing
+ * fails, it will point to the same character it pointed to prior to
+ * calling this function.
+ * @param p_end end position that is one character past the last parsable
+ * character.
+ *
+ * @return value containing sheet index upon success, or empty upon failure.
+ */
+std::optional<sheet_t> parse_sheet_name(
+ const ixion::model_context& cxt, const char sep, const char*& p, const char* p_end)
+{
+ assert(p < p_end);
+
+ const char* p_old = p; // old position to revert to in case we fail to parse a sheet name.
+
+ if (*p == '$')
+ ++p;
+
+ if (*p == '\'')
+ {
+ auto sheet = parse_sheet_name_quoted(cxt, sep, p, p_end);
+ if (!sheet)
+ p = p_old;
+ return sheet;
+ }
+
+ const char* p0 = p;
+ size_t len = 0;
+
+ // parse until we hit the sheet-address separator.
+ for (; p < p_end; ++p, ++len)
+ {
+ if (*p == sep)
+ return cxt.get_sheet_index({p0, len});
+ }
+
+ p = p_old;
+ return std::optional<sheet_t>{};
+}
+
+struct sheet_range_t
+{
+ bool present = false; // whether or not the address contains sheet name segment
+ sheet_t sheet1 = invalid_sheet;
+ sheet_t sheet2 = invalid_sheet;
+
+ bool valid() const
+ {
+ return sheet1 != invalid_sheet || sheet2 != invalid_sheet;
+ }
+};
+
+sheet_range_t parse_excel_sheet_name_quoted(const ixion::model_context& cxt, const char*& p, const char* p_end)
+{
+ assert(*p == '\'');
+ const char* p_old = p; // old position to revert to in case we fail to parse a sheet name.
+
+ ++p; // skip the quote
+
+ sheet_range_t ret;
+ bool was_quote = false; // record if the last char was a quote, but only when it followed a non-quote char.
+ std::string buf; // for name containing quote(s)
+
+ for (const char* p0 = nullptr; p < p_end; ++p)
+ {
+ if (!p0)
+ p0 = p;
+
+ switch (*p)
+ {
+ case '!':
+ {
+ if (!was_quote)
+ {
+ // Fail
+ p = p_end;
+ break;
+ }
+
+ assert(ret.sheet2 == invalid_sheet);
+ std::size_t n = std::distance(p0, p) - 1u;
+ buf += std::string_view{p0, n};
+ ret.sheet2 = cxt.get_sheet_index(buf);
+ ret.present = true;
+ return ret;
+ }
+ case ':':
+ {
+ if (ret.sheet1 != invalid_sheet)
+ {
+ // likely the second range separator, which is not allowed. Fail.
+ p = p_end;
+ break;
+ }
+
+ std::size_t n = std::distance(p0, p);
+ buf += std::string_view{p0, n};
+ ret.sheet1 = cxt.get_sheet_index(buf);
+ p0 = nullptr;
+ buf.clear();
+ was_quote = false;
+ break;
+ }
+ case '\'':
+ {
+ if (was_quote)
+ {
+ // two consequtive quotes are treated as a single quote.
+ std::size_t n = std::distance(p0, p);
+ buf += std::string_view{p0, n};
+ p0 = nullptr;
+ }
+
+ // if more than two quotes occur, set the flag only on the 1st, 3rd, 5th etc.
+ was_quote = !was_quote;
+ break;
+ }
+ default:
+ was_quote = false;
+ }
+ }
+
+ p = p_old;
+ ret.sheet1 = ret.sheet2 = invalid_sheet;
+ return ret;
+}
+
+/**
+ * Parse an Excel sheet name string. An Excel name can be a range of sheets
+ * separated by a ':' e.g. Sheet1:Sheet2!A1:B2.
+ *
+ * @return sheet range values. It can either contain 1) two valid sheet
+ * indices for a range of sheets, 2) sheet1 invalid and sheet2 valid
+ * for a single sheet name, or 3) both sheet indices are invalid.
+ */
+sheet_range_t parse_excel_sheet_name(const ixion::model_context& cxt, const char*& p, const char* p_end)
+{
+ assert(p < p_end);
+ if (*p == '\'')
+ return parse_excel_sheet_name_quoted(cxt, p, p_end);
+
+ sheet_range_t ret;
+
+ const char* p_old = p; // old position to revert to in case we fail to parse a sheet name.
+
+ for (const char* p0 = nullptr; p < p_end; ++p)
+ {
+ if (!p0)
+ p0 = p;
+
+ switch (*p)
+ {
+ case '!':
+ {
+ assert(ret.sheet2 == invalid_sheet);
+ std::size_t n = std::distance(p0, p);
+ std::string_view name{p0, n};
+ ret.sheet2 = cxt.get_sheet_index(name);
+ ret.present = true;
+ return ret;
+ }
+ case ':':
+ {
+ if (ret.sheet1 != invalid_sheet)
+ {
+ // likely the second range separator, which is not allowed. Fail.
+ p = p_end;
+ break;
+ }
+
+ std::size_t n = std::distance(p0, p);
+ std::string_view name{p0, n};
+ ret.sheet1 = cxt.get_sheet_index(name);
+ p0 = nullptr;
+ break;
+ }
+ case ' ':
+ case '\'':
+ case '"':
+ {
+ // invalid char. Check if a '!' occurs at a later position.
+ for (++p; p < p_end; ++p)
+ {
+ if (*p == '!')
+ {
+ ret.present = true;
+ p = p_end;
+ break;
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ p = p_old;
+ ret.sheet1 = ret.sheet2 = invalid_sheet;
+ return ret;
+}
+
+/**
+ * If there is no number to parse, it returns 0 and the p will not
+ * increment. Otherwise, p will point to the position past the last parsed
+ * digit character.
+ */
+template<typename T>
+T parse_number(const char*&p, const char* p_end)
+{
+ assert(p < p_end);
+
+ T num = 0;
+
+ bool sign = false;
+ if (*p == '+')
+ ++p;
+ else if (*p == '-')
+ {
+ ++p;
+ sign = true;
+ }
+
+ for (; p < p_end && std::isdigit(*p); ++p)
+ {
+ // Parse number.
+ num *= 10;
+ num += *p - '0';
+ }
+
+ if (sign)
+ num *= -1;
+
+ return num;
+}
+
+/**
+ * Parse A1-style single cell address.
+ *
+ * @param p it must point to the first character of a cell address.
+ * @param p_end end position that is one character past the last character of
+ * a parsable character sequence.
+ * @param addr resolved cell address.
+ *
+ * @return parsing result.
+ */
+parse_address_result_type parse_address_a1(const char*& p, const char* p_end, address_t& addr)
+{
+ // NOTE: Row and column IDs are 1-based during parsing, while 0 is used as
+ // the state of a value-not-set. They are subtracted by one before
+ // returning.
+
+ resolver_parse_mode mode = resolver_parse_mode::column;
+
+ for (; p < p_end; ++p)
+ {
+ char c = *p;
+ if ('a' <= c && c <= 'z')
+ {
+ // Convert to upper case.
+ c -= 'a' - 'A';
+ }
+
+ if ('A' <= c && c <= 'Z')
+ {
+ // Column digit
+ if (mode != resolver_parse_mode::column)
+ return invalid;
+
+ if (addr.column)
+ addr.column *= 26;
+ addr.column += static_cast<col_t>(c - 'A' + 1);
+
+ if (addr.column > column_upper_bound)
+ return invalid;
+ }
+ else if (std::isdigit(c))
+ {
+ if (mode == resolver_parse_mode::column)
+ {
+ // First digit of a row.
+ if (c == '0')
+ // Leading zeros not allowed.
+ return invalid;
+
+ mode = resolver_parse_mode::row;
+ }
+
+ if (addr.row)
+ addr.row *= 10;
+
+ addr.row += static_cast<row_t>(c - '0');
+ }
+ else if (c == ':')
+ {
+ if (mode == resolver_parse_mode::row)
+ {
+ if (!addr.row)
+ return invalid;
+
+ --addr.row;
+
+ if (addr.column)
+ --addr.column;
+ else
+ addr.column = column_unset;
+
+ return range_expected;
+ }
+ else if (mode == resolver_parse_mode::column)
+ {
+ // row number is not given.
+ if (addr.column)
+ --addr.column;
+ else
+ // neither row number nor column number is given.
+ return invalid;
+
+ addr.row = row_unset;
+ return range_expected;
+ }
+ else
+ return invalid;
+ }
+ else if (c == '$')
+ {
+ // Absolute position.
+ if (mode == resolver_parse_mode::column)
+ {
+ if (addr.column)
+ {
+ // Column position has been already parsed.
+ mode = resolver_parse_mode::row;
+ addr.abs_row = true;
+ }
+ else
+ {
+ // Column position has not yet been parsed.
+ addr.abs_column = true;
+ }
+ }
+ else
+ return invalid;
+ }
+ else
+ return invalid;
+ }
+
+ if (mode == resolver_parse_mode::row)
+ {
+ if (!addr.row)
+ return invalid;
+
+ --addr.row;
+
+ if (addr.column)
+ --addr.column;
+ else
+ addr.column = column_unset;
+ }
+ else if (mode == resolver_parse_mode::column)
+ {
+ // row number is not given.
+ if (addr.column)
+ --addr.column;
+ else
+ // neither row number nor column number is given.
+ return invalid;
+
+ addr.row = row_unset;
+ }
+ return valid_address;
+}
+
+parse_address_result_type parse_address_r1c1(const char*& p, const char* p_end, address_t& addr)
+{
+ assert(p < p_end);
+
+ addr.row = row_unset;
+ addr.column = column_unset;
+
+ if (*p == 'R' || *p == 'r')
+ {
+ addr.row = 0;
+ addr.abs_row = false;
+ ++p;
+
+ if (p == p_end)
+ // Just 'R'. Not sure if this is valid or invalid, but let's call it invalid for now.
+ return parse_address_result_type::invalid;
+
+ if (*p != 'C' && *p != 'c')
+ {
+ addr.abs_row = (*p != '[');
+ if (!addr.abs_row)
+ {
+ // Relative row address.
+ ++p;
+ if (!std::isdigit(*p) && *p != '-' && *p != '+')
+ return parse_address_result_type::invalid;
+
+ addr.row = parse_number<row_t>(p, p_end);
+ if (p + 1 == p_end)
+ return (*p == ']') ? parse_address_result_type::valid_address : parse_address_result_type::invalid;
+ ++p;
+ }
+ else if (std::isdigit(*p))
+ {
+ // Absolute row address.
+ addr.row = parse_number<row_t>(p, p_end);
+ if (addr.row <= 0)
+ // absolute address with 0 or negative value is invalid.
+ return parse_address_result_type::invalid;
+
+ --addr.row; // 1-based to 0-based.
+
+ if (p == p_end)
+ // 'R' followed by a number without 'C' is valid.
+ return parse_address_result_type::valid_address;
+ }
+ }
+ }
+
+ if (*p == 'C' || *p == 'c')
+ {
+ addr.column = 0;
+ addr.abs_column = false;
+
+ ++p;
+ if (p == p_end)
+ {
+ if (addr.row == row_unset)
+ // Just 'C'. Row must be set.
+ return parse_address_result_type::invalid;
+
+ if (!addr.abs_row && addr.row == 0)
+ // 'RC' is invalid as it references itself.
+ return parse_address_result_type::invalid;
+
+ return parse_address_result_type::valid_address;
+ }
+
+ if (*p == '[')
+ {
+ // Relative column address.
+ ++p;
+ if (p == p_end)
+ return parse_address_result_type::invalid;
+
+ if (!std::isdigit(*p) && *p != '-' && *p != '+')
+ return parse_address_result_type::invalid;
+
+ addr.column = parse_number<col_t>(p, p_end);
+ if (p + 1 == p_end)
+ return (*p == ']') ? parse_address_result_type::valid_address : parse_address_result_type::invalid;
+
+ ++p;
+ }
+ else if (std::isdigit(*p))
+ {
+ // Absolute column address.
+ addr.abs_column = true;
+ addr.column = parse_number<col_t>(p, p_end);
+ if (addr.column <= 0)
+ // absolute address with 0 or negative value is invalid.
+ return parse_address_result_type::invalid;
+
+ --addr.column; // 1-based to 0-based.
+
+ if (p == p_end)
+ return parse_address_result_type::valid_address;
+ }
+ }
+
+ if (*p == ':')
+ return (p + 1 == p_end) ? parse_address_result_type::invalid : parse_address_result_type::range_expected;
+
+ return parse_address_result_type::invalid;
+}
+
+parse_address_result parse_address_calc_a1(
+ const ixion::model_context* cxt, const char*& p, const char* p_last, address_t& addr)
+{
+ parse_address_result res;
+
+ addr.row = 0;
+ addr.column = 0;
+ addr.abs_row = false;
+ addr.abs_column = false;
+
+ if (cxt)
+ {
+ // Overwrite the sheet index *only when* the sheet name is parsed successfully.
+ const char* p0 = p;
+ auto sheet = parse_sheet_name(*cxt, '.', p, p_last + 1);
+ res.sheet_name = sheet.has_value();
+ if (sheet)
+ {
+ ++p; // skip the separator
+ addr.sheet = *sheet;
+ addr.abs_sheet = (*p0 == '$');
+ }
+ }
+
+ res.result = parse_address_a1(p, ++p_last, addr);
+ return res;
+}
+
+/**
+ * Parse a single cell address in ODF cell-range-address syntax. This is
+ * almost identical to Calc A1 except that it allows a leading '.' as in
+ * '.E1' as opposed to just 'E1'.
+ */
+parse_address_result parse_address_odf_cra(
+ const ixion::model_context* cxt, const char*& p, const char* p_last, address_t& addr)
+{
+ if (*p == '.')
+ {
+ // Skip the '.', and assume absence of sheet name.
+ ++p;
+ cxt = nullptr;
+ }
+
+ return parse_address_calc_a1(cxt, p, p_last, addr);
+}
+
+parse_address_result_type parse_address_excel_a1(const char*& p, const char* p_end, address_t& addr)
+{
+ addr.row = 0;
+ addr.column = 0;
+ addr.abs_sheet = true; // Excel's sheet position is always absolute.
+ addr.abs_row = false;
+ addr.abs_column = false;
+
+ return parse_address_a1(p, p_end, addr);
+}
+
+parse_address_result_type parse_address_excel_r1c1(const char*& p, const char* p_end, address_t& addr)
+{
+ addr.row = 0;
+ addr.column = 0;
+ addr.abs_sheet = true; // Excel's sheet position is always absolute.
+ addr.abs_row = false;
+ addr.abs_column = false;
+
+ return parse_address_r1c1(p, p_end, addr);
+}
+
+parse_address_result parse_address_odff(
+ const ixion::model_context* cxt, const char*& p, const char* p_last, address_t& addr)
+{
+ parse_address_result res;
+ assert(p <= p_last);
+
+ addr.row = 0;
+ addr.column = 0;
+ addr.abs_row = false;
+ addr.abs_column = false;
+
+ if (*p == '.')
+ {
+ // This address doesn't contain a sheet name.
+ ++p;
+ }
+ else if (cxt)
+ {
+ // This address DOES contain a sheet name.
+ res.sheet_name = true;
+ addr.abs_sheet = false;
+ addr.sheet = invalid_sheet;
+
+ // Overwrite the sheet index *only when* the sheet name is parsed successfully.
+ if (*p == '$')
+ {
+ addr.abs_sheet = true;
+ ++p;
+ }
+
+ if (p <= p_last)
+ {
+ auto sheet = parse_sheet_name(*cxt, '.', p, p_last + 1);
+ if (sheet)
+ {
+ ++p; // skip the separator
+ addr.sheet = *sheet;
+ }
+ }
+ }
+
+ res.result = parse_address_a1(p, ++p_last, addr);
+ return res;
+}
+
+void to_relative_address(address_t& addr, const abs_address_t& pos, bool sheet)
+{
+ if (!addr.abs_sheet && sheet)
+ addr.sheet -= pos.sheet;
+ if (!addr.abs_row && addr.row <= row_upper_bound)
+ addr.row -= pos.row;
+ if (!addr.abs_column && addr.column <= column_upper_bound)
+ addr.column -= pos.column;
+}
+
+std::string to_string(const model_context* cxt, const table_t& table)
+{
+ std::ostringstream os;
+ append_name_string(os, cxt, table.name);
+
+ if (table.column_first == empty_string_id)
+ {
+ // Area specifier(s) only.
+ bool headers = (table.areas & table_area_headers);
+ bool data = (table.areas & table_area_data);
+ bool totals = (table.areas & table_area_totals);
+
+ short count = 0;
+ if (headers)
+ ++count;
+ if (data)
+ ++count;
+ if (totals)
+ ++count;
+
+ bool multiple = count == 2;
+ if (multiple)
+ os << '[';
+
+ append_table_areas(os, table);
+
+ if (multiple)
+ os << ']';
+ }
+ else if (table.column_last == empty_string_id)
+ {
+ // single column.
+ os << '[';
+
+ bool multiple = false;
+ if (table.areas && table.areas != table_area_data)
+ {
+ if (append_table_areas(os, table))
+ {
+ os << ',';
+ multiple = true;
+ }
+ }
+
+ if (multiple)
+ os << '[';
+
+ append_name_string(os, cxt, table.column_first);
+
+ if (multiple)
+ os << ']';
+
+ os << ']';
+ }
+ else
+ {
+ // column range.
+ os << '[';
+
+ if (table.areas && table.areas != table_area_data)
+ {
+ if (append_table_areas(os, table))
+ os << ',';
+ }
+
+ os << '[';
+ append_name_string(os, cxt, table.column_first);
+ os << "]:[";
+ append_name_string(os, cxt, table.column_last);
+ os << "]]";
+ }
+
+ return os.str();
+}
+
+} // anonymous namespace
+
+formula_name_t::formula_name_t() :
+ type(invalid), value(formula_function_t::func_unknown) {}
+
+std::string formula_name_t::to_string() const
+{
+ std::ostringstream os;
+
+ switch (type)
+ {
+ case cell_reference:
+ os << "cell reference: " << std::get<address_t>(value);
+ break;
+ case function:
+ {
+ auto v = std::get<formula_function_t>(value);
+ os << "function: " << get_formula_function_name(v);
+ break;
+ }
+ case invalid:
+ os << "invalid";
+ break;
+ case named_expression:
+ os << "named expression";
+ break;
+ case range_reference:
+ os << "range raference: " << std::get<range_t>(value);
+ break;
+ case table_reference:
+ os << "table reference";
+ break;
+ default:
+ os << "unknown foromula name type";
+ }
+
+ return os.str();
+}
+
+formula_name_resolver::formula_name_resolver() {}
+formula_name_resolver::~formula_name_resolver() {}
+
+namespace {
+
+std::string get_column_name_a1(col_t col)
+{
+ std::ostringstream os;
+ append_column_name_a1(os, col);
+ return os.str();
+}
+
+class excel_a1 : public formula_name_resolver
+{
+public:
+ excel_a1(const model_context* cxt) : formula_name_resolver(), mp_cxt(cxt) {}
+ virtual ~excel_a1() {}
+
+ virtual formula_name_t resolve(std::string_view s, const abs_address_t& pos) const
+ {
+ const char* p = s.data();
+ std::size_t n = s.size();
+
+ formula_name_t ret;
+ if (!n)
+ return ret;
+
+ if (resolve_function(p, n, ret))
+ return ret;
+
+ if (resolve_table(mp_cxt, p, n, ret))
+ return ret;
+
+ const char* p_end = p + n;
+
+ sheet_range_t sheets;
+
+ if (mp_cxt)
+ {
+ sheets = parse_excel_sheet_name(*mp_cxt, p, p_end);
+ if (sheets.present && sheets.sheet2 == invalid_sheet)
+ // Sheet name(s) given but is not found in the model.
+ return ret;
+
+ if (sheets.present)
+ {
+ assert(*p == '!');
+ ++p; // skip the '!'
+ }
+ }
+
+ // Use the sheet where the cell is unless sheet name is explicitly given.
+ address_t parsed_addr(pos.sheet, 0, 0, false, false, false);
+
+ parse_address_result_type parse_res = parse_address_excel_a1(p, p_end, parsed_addr);
+
+ if (parse_res != invalid)
+ {
+ // This is a valid A1-style address syntax-wise.
+
+ if (!check_address_by_sheet_bounds(mp_cxt, parsed_addr))
+ parse_res = invalid;
+ }
+
+ // prevent for example H to be recognized as column address
+ if (parse_res == valid_address && parsed_addr.row != row_unset)
+ {
+ // This is a single cell address.
+ to_relative_address(parsed_addr, pos, false);
+
+ if (sheets.present)
+ {
+ if (sheets.sheet1 != invalid_sheet)
+ {
+ // range of sheets is given. Switch to a range.
+ range_t v{parsed_addr, parsed_addr};
+ v.first.sheet = sheets.sheet1;
+ v.last.sheet = sheets.sheet2;
+
+ ret.value = v;
+ ret.type = formula_name_t::range_reference;
+ }
+ else
+ {
+ // single sheet is given.
+ parsed_addr.sheet = sheets.sheet2;
+ set_cell_reference(ret, parsed_addr);
+ }
+ }
+ else
+ set_cell_reference(ret, parsed_addr);
+
+ return ret;
+ }
+
+ if (parse_res == range_expected)
+ {
+ assert(*p == ':');
+ ++p; // skip ':'
+
+ if (p == p_end)
+ // ':' occurs as the last character. This is not allowed.
+ return ret;
+
+ range_t v;
+ to_relative_address(parsed_addr, pos, false);
+ v.first = parsed_addr;
+
+ // For now, we assume the sheet index of the end address is identical
+ // to that of the begin address.
+ parse_res = parse_address_excel_a1(p, p_end, parsed_addr);
+ if (parse_res != valid_address)
+ {
+ // The 2nd part after the ':' is not valid.
+ ret.value = v;
+ return ret;
+ }
+
+ to_relative_address(parsed_addr, pos, false);
+ v.last = parsed_addr;
+ v.last.sheet = v.first.sheet; // re-use the sheet index of the begin address.
+
+ if (sheets.present)
+ {
+ if (sheets.sheet1 != invalid_sheet)
+ {
+ // range of sheets is given
+ v.first.sheet = sheets.sheet1;
+ v.last.sheet = sheets.sheet2;
+ }
+ else
+ {
+ // single sheet is given
+ v.first.sheet = v.last.sheet = sheets.sheet2;
+ }
+ }
+
+ ret.value = v;
+ ret.type = formula_name_t::range_reference;
+ return ret;
+ }
+
+ resolve_function_or_name(p, n, ret);
+ return ret;
+ }
+
+ virtual std::string get_name(const address_t& addr, const abs_address_t& pos, bool sheet_name) const
+ {
+ std::ostringstream os;
+ append_address_a1(os, sheet_name ? mp_cxt : nullptr, addr, pos, '!');
+ return os.str();
+ }
+
+ virtual std::string get_name(const range_t& range, const abs_address_t& pos, bool sheet_name) const
+ {
+ // For now, sheet index of the end-range address is ignored.
+
+ std::ostringstream os;
+ col_t col = range.first.column;
+ row_t row = range.first.row;
+ sheet_t sheet = range.first.sheet;
+
+ if (!range.first.abs_sheet)
+ sheet += pos.sheet;
+
+ if (sheet_name && mp_cxt)
+ {
+ append_sheet_name(os, *mp_cxt, sheet);
+ os << '!';
+ }
+
+ append_column_address_a1(os, col, pos.column, range.first.abs_column);
+ append_row_address_a1(os, row, pos.row, range.first.abs_row);
+
+ os << ":";
+ col = range.last.column;
+ row = range.last.row;
+
+ append_column_address_a1(os, col, pos.column, range.last.abs_column);
+
+ if (row != row_unset)
+ {
+ if (range.last.abs_row)
+ os << '$';
+ else
+ row += pos.row;
+ os << (row + 1);
+ }
+
+ return os.str();
+ }
+
+ virtual std::string get_name(const table_t& table) const
+ {
+ return to_string(mp_cxt, table);
+ }
+
+ virtual std::string get_column_name(col_t col) const
+ {
+ return get_column_name_a1(col);
+ }
+
+private:
+ const model_context* mp_cxt;
+};
+
+class excel_r1c1 : public formula_name_resolver
+{
+ const model_context* mp_cxt;
+
+ void write_sheet_name(std::ostringstream& os, const address_t& addr, const abs_address_t& pos) const
+ {
+ if (mp_cxt)
+ {
+ sheet_t sheet = addr.sheet;
+ if (!addr.abs_sheet)
+ sheet += pos.sheet;
+
+ append_sheet_name(os, *mp_cxt, sheet);
+ os << '!';
+ }
+ }
+
+public:
+ excel_r1c1(const model_context* cxt) : mp_cxt(cxt) {}
+
+ virtual formula_name_t resolve(std::string_view s, const abs_address_t& pos) const
+ {
+ const char* p = s.data();
+ std::size_t n = s.size();
+
+ formula_name_t ret;
+ if (!n)
+ return ret;
+
+ if (resolve_function(p, n, ret))
+ return ret;
+
+ const char* p_end = p + n;
+
+ sheet_range_t sheets;
+
+ if (mp_cxt)
+ {
+ sheets = parse_excel_sheet_name(*mp_cxt, p, p_end);
+ if (sheets.present && sheets.sheet2 == invalid_sheet)
+ // Sheet name(s) given but is not found in the model.
+ return ret;
+
+ if (sheets.present)
+ {
+ assert(*p == '!');
+ ++p; // skip the '!'
+ }
+ }
+
+ // Use the sheet where the cell is unless sheet name is explicitly given.
+ address_t parsed_addr(pos.sheet, 0, 0);
+ parse_address_result_type parse_res = parse_address_excel_r1c1(p, p_end, parsed_addr);
+
+ switch (parse_res)
+ {
+ case parse_address_result_type::valid_address:
+ {
+ if (sheets.present)
+ {
+ if (sheets.sheet1 != invalid_sheet)
+ {
+ // range of sheets is given. Switch to a range.
+ range_t v{parsed_addr, parsed_addr};
+ v.first.sheet = sheets.sheet1;
+ v.last.sheet = sheets.sheet2;
+
+ ret.value = v;
+ ret.type = formula_name_t::range_reference;
+ }
+ else
+ {
+ // single sheet is given.
+ parsed_addr.sheet = sheets.sheet2;
+ set_cell_reference(ret, parsed_addr);
+ }
+ }
+ else
+ set_cell_reference(ret, parsed_addr);
+
+ return ret;
+ }
+ case parse_address_result_type::range_expected:
+ {
+ ++p; // skip ':'
+ if (p == p_end)
+ return ret;
+
+ range_t v;
+ v.first = parsed_addr;
+
+ parse_address_result_type parse_res2 = parse_address_excel_r1c1(p, p_end, parsed_addr);
+ if (parse_res2 != parse_address_result_type::valid_address)
+ return ret;
+
+ v.last = parsed_addr;
+
+ if (sheets.present)
+ {
+ if (sheets.sheet1 != invalid_sheet)
+ {
+ // range of sheets is given
+ v.first.sheet = sheets.sheet1;
+ v.last.sheet = sheets.sheet2;
+ }
+ else
+ {
+ // single sheet is given
+ v.first.sheet = v.last.sheet = sheets.sheet2;
+ }
+ }
+
+ ret.type = formula_name_t::range_reference;
+ ret.value = v;
+
+ return ret;
+ }
+ default:
+ ;
+ }
+
+ resolve_function_or_name(p, n, ret);
+ return ret;
+ }
+
+ virtual std::string get_name(const address_t& addr, const abs_address_t& pos, bool sheet_name) const
+ {
+ std::ostringstream os;
+
+ if (sheet_name)
+ write_sheet_name(os, addr, pos);
+
+ append_address_r1c1(os, addr, pos);
+ return os.str();
+ }
+
+ virtual std::string get_name(const range_t& range, const abs_address_t& pos, bool sheet_name) const
+ {
+ std::ostringstream os;
+
+ if (sheet_name)
+ write_sheet_name(os, range.first, pos);
+
+ append_address_r1c1(os, range.first, pos);
+ os << ':';
+ append_address_r1c1(os, range.last, pos);
+ return os.str();
+ }
+
+ virtual std::string get_name(const table_t& table) const
+ {
+ return to_string(mp_cxt, table);
+ }
+
+ virtual std::string get_column_name(col_t col) const
+ {
+ std::ostringstream os;
+ os << (col+1);
+ return os.str();
+ }
+};
+
+class dot_a1_resolver : public formula_name_resolver
+{
+ using func_parse_address_type =
+ std::function<parse_address_result(
+ const ixion::model_context*,
+ const char*&, const char*, address_t&)>;
+
+ using func_append_address_type =
+ std::function<void(
+ std::ostringstream&, const ixion::model_context*,
+ const address_t&, const abs_address_t&, char)>;
+
+ using func_append_sheet_name_type =
+ std::function<void(
+ std::ostringstream&, const ixion::model_context*,
+ const address_t&, const abs_address_t&)>;
+
+ const model_context* mp_cxt;
+ func_parse_address_type m_func_parse_address;
+ func_append_address_type m_func_append_address;
+ func_append_sheet_name_type m_func_append_sheet_name;
+
+ static bool display_last_sheet(const range_t& range, const abs_address_t& pos)
+ {
+ if (range.first.abs_sheet != range.last.abs_sheet)
+ return true;
+
+ abs_range_t abs = range.to_abs(pos);
+ return abs.first.sheet != abs.last.sheet;
+ }
+
+public:
+ dot_a1_resolver(
+ const model_context* cxt,
+ func_parse_address_type func_parse_address,
+ func_append_address_type func_append_address,
+ func_append_sheet_name_type func_append_sheet_name) :
+ formula_name_resolver(),
+ mp_cxt(cxt),
+ m_func_parse_address(func_parse_address),
+ m_func_append_address(func_append_address),
+ m_func_append_sheet_name(func_append_sheet_name) {}
+
+ virtual formula_name_t resolve(std::string_view s, const abs_address_t &pos) const override
+ {
+ const char* p = s.data();
+ std::size_t n = s.size();
+
+ formula_name_t ret;
+ if (!n)
+ return ret;
+
+ if (resolve_function(p, n, ret))
+ return ret;
+
+ const char* p_last = p + n -1;
+
+ // Use the sheet where the cell is unless sheet name is explicitly given.
+ address_t parsed_addr(pos.sheet, 0, 0, false, false, false);
+
+ parse_address_result parse_res = m_func_parse_address(mp_cxt, p, p_last, parsed_addr);
+ IXION_TRACE("parse address result on 1st address (" << to_string(parse_res.result) << ")");
+
+ if (parse_res.result != invalid)
+ {
+ // This is a valid A1-style address syntax-wise.
+
+ if (parsed_addr.sheet == invalid_sheet)
+ {
+ IXION_DEBUG("Sheet name is not found in the model context.");
+ return ret;
+ }
+
+ if (!check_address_by_sheet_bounds(mp_cxt, parsed_addr))
+ {
+ IXION_DEBUG("Address is outside the sheet bounds.");
+ parse_res.result = invalid;
+ }
+ }
+
+ // prevent for example H to be recognized as column address
+ if (parse_res.result == valid_address && parsed_addr.row != row_unset)
+ {
+ // This is a single cell address.
+ to_relative_address(parsed_addr, pos, true);
+ set_cell_reference(ret, parsed_addr);
+
+ return ret;
+ }
+
+ if (parse_res.result == range_expected)
+ {
+ if (p == p_last)
+ {
+ IXION_DEBUG("':' occurs as the last character. This is not allowed.");
+ return ret;
+ }
+
+ ++p; // skip ':'
+
+ to_relative_address(parsed_addr, pos, true);
+ range_t v;
+ v.first = parsed_addr;
+
+ parse_res = m_func_parse_address(mp_cxt, p, p_last, parsed_addr);
+ IXION_TRACE("parse address result on 2nd address (" << to_string(parse_res.result) << ")");
+
+ if (parse_res.result != valid_address)
+ {
+ IXION_DEBUG("2nd part after the ':' is not valid.");
+ return ret;
+ }
+
+ to_relative_address(parsed_addr, pos, parse_res.sheet_name);
+ v.last = parsed_addr;
+ ret.type = formula_name_t::range_reference;
+ ret.value = v;
+ return ret;
+ }
+
+ resolve_function_or_name(p, n, ret);
+ return ret;
+ }
+
+ virtual std::string get_name(const address_t &addr, const abs_address_t &pos, bool sheet_name) const override
+ {
+ std::ostringstream os;
+ if (sheet_name && addr.abs_sheet)
+ os << '$';
+ m_func_append_address(os, sheet_name ? mp_cxt : nullptr, addr, pos, '.');
+ return os.str();
+ }
+
+ virtual std::string get_name(const range_t& range, const abs_address_t& pos, bool sheet_name) const override
+ {
+ std::ostringstream os;
+ col_t col = range.first.column;
+ row_t row = range.first.row;
+
+ m_func_append_sheet_name(os, sheet_name ? mp_cxt : nullptr, range.first, pos);
+
+ append_column_address_a1(os, col, pos.column, range.first.abs_column);
+ append_row_address_a1(os, row, pos.row, range.first.abs_row);
+
+ os << ":";
+
+ if (!display_last_sheet(range, pos))
+ sheet_name = false;
+
+ m_func_append_sheet_name(os, sheet_name ? mp_cxt : nullptr, range.last, pos);
+
+ col = range.last.column;
+ row = range.last.row;
+
+ append_column_address_a1(os, col, pos.column, range.last.abs_column);
+ append_row_address_a1(os, row, pos.row, range.last.abs_row);
+
+ return os.str();
+ }
+
+ virtual std::string get_name(const table_t &table) const override
+ {
+ // TODO : find out how Calc A1 handles table reference.
+ return std::string();
+ }
+
+ virtual std::string get_column_name(col_t col) const override
+ {
+ return get_column_name_a1(col);
+ }
+};
+
+/**
+ * Name resolver for ODFF formula expressions.
+ */
+class odff_resolver : public formula_name_resolver
+{
+public:
+ odff_resolver(const model_context* cxt) : formula_name_resolver(), mp_cxt(cxt) {}
+ virtual ~odff_resolver() {}
+
+ virtual formula_name_t resolve(std::string_view s, const abs_address_t& pos) const
+ {
+ const char* p = s.data();
+ std::size_t n = s.size();
+
+ formula_name_t ret;
+
+ if (resolve_function(p, n, ret))
+ return ret;
+
+ if (!n)
+ // Empty string.
+ return ret;
+
+ // First character must be '[' for this to be a reference.
+ if (*p != '[')
+ {
+ ret.type = formula_name_t::named_expression;
+ return ret;
+ }
+
+ ++p;
+ const char* p_last = p;
+ std::advance(p_last, n-2);
+
+ // Last character must be ']'.
+ if (*p_last != ']')
+ return ret;
+
+ --p_last;
+
+ // Use the sheet where the cell is unless sheet name is explicitly given.
+ address_t parsed_addr(pos.sheet, 0, 0, true, false, false);
+
+ parse_address_result parse_res = parse_address_odff(mp_cxt, p, p_last, parsed_addr);
+
+ // prevent for example H to be recognized as column address
+ if (parse_res.result == valid_address && parsed_addr.row != row_unset)
+ {
+ // This is a single cell address.
+ to_relative_address(parsed_addr, pos, true);
+ set_cell_reference(ret, parsed_addr);
+ return ret;
+ }
+
+ if (parse_res.result == range_expected)
+ {
+ if (p == p_last)
+ // ':' occurs as the last character. This is not allowed.
+ return ret;
+
+ ++p; // skip ':'
+
+ range_t v;
+ to_relative_address(parsed_addr, pos, true);
+ v.first = parsed_addr;
+
+ parse_res = parse_address_odff(mp_cxt, p, p_last, parsed_addr);
+ if (parse_res.result != valid_address)
+ // The 2nd part after the ':' is not valid.
+ return ret;
+
+ to_relative_address(parsed_addr, pos, parse_res.sheet_name);
+
+ v.last = parsed_addr;
+ ret.type = formula_name_t::range_reference;
+ ret.value = v;
+
+ return ret;
+ }
+
+ resolve_function_or_name(p, n, ret);
+
+ return ret;
+ }
+
+ virtual std::string get_name(const address_t& addr, const abs_address_t& pos, bool sheet_name) const
+ {
+ if (!mp_cxt)
+ sheet_name = false;
+
+ std::ostringstream os;
+ os << '[';
+ if (sheet_name)
+ {
+ if (addr.abs_sheet)
+ os << '$';
+ append_address_a1(os, mp_cxt, addr, pos, '.');
+ }
+ else
+ {
+ os << '.';
+ append_address_a1(os, nullptr, addr, pos, '.');
+ }
+ os << ']';
+ return os.str();
+ }
+
+ virtual std::string get_name(const range_t& range, const abs_address_t& pos, bool sheet_name) const
+ {
+ if (!mp_cxt)
+ sheet_name = false;
+
+ std::ostringstream os;
+ os << '[';
+
+ if (sheet_name)
+ {
+ const model_context* cxt = mp_cxt;
+
+ if (range.first.abs_sheet)
+ os << '$';
+ append_address_a1(os, cxt, range.first, pos, '.');
+
+ os << ':';
+
+ if (range.last.sheet != range.first.sheet || range.last.abs_sheet != range.first.abs_sheet)
+ {
+ // sheet indices differ between the first and last addresses.
+ if (range.last.abs_sheet)
+ os << '$';
+ }
+ else
+ {
+ // sheet indices are identical.
+ cxt = nullptr;
+ os << '.';
+ }
+
+ append_address_a1(os, cxt, range.last, pos, '.');
+ }
+ else
+ {
+ os << '.';
+ append_address_a1(os, nullptr, range.first, pos, '.');
+ os << ":.";
+ append_address_a1(os, nullptr, range.last, pos, '.');
+ }
+
+ os << ']';
+ return os.str();
+ }
+
+ virtual std::string get_name(const table_t& table) const
+ {
+ // TODO : ODF doesn't support table reference yet.
+ return std::string();
+ }
+
+ virtual std::string get_column_name(col_t col) const
+ {
+ return get_column_name_a1(col);
+ }
+
+private:
+ const model_context* mp_cxt;
+};
+
+}
+
+std::unique_ptr<formula_name_resolver> formula_name_resolver::get(
+ formula_name_resolver_t type, const model_context* cxt)
+{
+
+ switch (type)
+ {
+ case formula_name_resolver_t::excel_a1:
+ return std::unique_ptr<formula_name_resolver>(new excel_a1(cxt));
+ case formula_name_resolver_t::excel_r1c1:
+ return std::unique_ptr<formula_name_resolver>(new excel_r1c1(cxt));
+ case formula_name_resolver_t::odff:
+ return std::unique_ptr<formula_name_resolver>(new odff_resolver(cxt));
+ case formula_name_resolver_t::calc_a1:
+ return std::unique_ptr<formula_name_resolver>(
+ new dot_a1_resolver(
+ cxt, parse_address_calc_a1, append_address_a1, append_sheet_name_calc_a1));
+ case formula_name_resolver_t::odf_cra:
+ return std::unique_ptr<formula_name_resolver>(
+ new dot_a1_resolver(
+ cxt, parse_address_odf_cra, append_address_a1_with_sheet_name_sep, append_sheet_name_odf_cra));
+ case formula_name_resolver_t::unknown:
+ default:
+ ;
+ }
+
+ return std::unique_ptr<formula_name_resolver>();
+}
+
+}
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/formula_parser.cpp b/src/libixion/formula_parser.cpp
new file mode 100644
index 0000000..4a8e6f5
--- /dev/null
+++ b/src/libixion/formula_parser.cpp
@@ -0,0 +1,268 @@
+/* -*- 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_parser.hpp"
+#include "ixion/formula_name_resolver.hpp"
+
+#include "formula_functions.hpp"
+
+#include <iostream>
+#include <sstream>
+
+namespace ixion {
+
+namespace {
+
+class ref_error : public general_error
+{
+public:
+ ref_error(const std::string& msg) :
+ general_error(msg) {}
+};
+
+}
+
+// ----------------------------------------------------------------------------
+
+formula_parser::parse_error::parse_error(const std::string& msg) :
+ general_error(msg) {}
+
+// ----------------------------------------------------------------------------
+
+formula_parser::formula_parser(
+ const lexer_tokens_t& tokens, model_context& cxt, const formula_name_resolver& resolver) :
+ m_itr_cur(tokens.end()),
+ m_itr_end(tokens.end()),
+ m_tokens(tokens),
+ m_context(cxt),
+ m_resolver(resolver)
+{
+}
+
+formula_parser::~formula_parser()
+{
+}
+
+void formula_parser::set_origin(const abs_address_t& pos)
+{
+ m_pos = pos;
+}
+
+void formula_parser::parse()
+{
+ for (m_itr_cur = m_tokens.begin(); has_token(); next())
+ {
+ switch (get_token().opcode)
+ {
+ case lexer_opcode_t::array_open:
+ case lexer_opcode_t::array_close:
+ case lexer_opcode_t::open:
+ case lexer_opcode_t::close:
+ case lexer_opcode_t::plus:
+ case lexer_opcode_t::minus:
+ case lexer_opcode_t::multiply:
+ case lexer_opcode_t::exponent:
+ case lexer_opcode_t::concat:
+ case lexer_opcode_t::equal:
+ case lexer_opcode_t::divide:
+ case lexer_opcode_t::sep:
+ case lexer_opcode_t::array_row_sep:
+ primitive();
+ break;
+ case lexer_opcode_t::name:
+ name();
+ break;
+ case lexer_opcode_t::string:
+ literal();
+ break;
+ case lexer_opcode_t::value:
+ value();
+ break;
+ case lexer_opcode_t::less:
+ less();
+ break;
+ case lexer_opcode_t::greater:
+ greater();
+ break;
+ default:
+ ;
+ }
+ }
+}
+
+formula_tokens_t& formula_parser::get_tokens()
+{
+ return m_formula_tokens;
+}
+
+void formula_parser::primitive()
+{
+ fopcode_t foc = fop_unknown;
+ switch (get_token().opcode)
+ {
+ case lexer_opcode_t::close:
+ foc = fop_close;
+ break;
+ case lexer_opcode_t::divide:
+ foc = fop_divide;
+ break;
+ case lexer_opcode_t::minus:
+ foc = fop_minus;
+ break;
+ case lexer_opcode_t::multiply:
+ foc = fop_multiply;
+ break;
+ case lexer_opcode_t::exponent:
+ foc = fop_exponent;
+ break;
+ case lexer_opcode_t::concat:
+ foc = fop_concat;
+ break;
+ case lexer_opcode_t::equal:
+ foc = fop_equal;
+ break;
+ case lexer_opcode_t::open:
+ foc = fop_open;
+ break;
+ case lexer_opcode_t::plus:
+ foc = fop_plus;
+ break;
+ case lexer_opcode_t::sep:
+ foc = fop_sep;
+ break;
+ case lexer_opcode_t::array_row_sep:
+ foc = fop_array_row_sep;
+ break;
+ case lexer_opcode_t::array_open:
+ foc = fop_array_open;
+ break;
+ case lexer_opcode_t::array_close:
+ foc = fop_array_close;
+ break;
+ default:
+ throw parse_error("unknown primitive token received");
+ }
+ m_formula_tokens.emplace_back(foc);
+}
+
+void formula_parser::name()
+{
+ std::string_view name = std::get<std::string_view>(get_token().value);
+
+ formula_name_t fn = m_resolver.resolve(name, m_pos);
+
+ switch (fn.type)
+ {
+ case formula_name_t::cell_reference:
+ m_formula_tokens.emplace_back(std::get<address_t>(fn.value));
+ break;
+ case formula_name_t::range_reference:
+ {
+ m_formula_tokens.emplace_back(std::get<range_t>(fn.value));
+ break;
+ }
+ case formula_name_t::table_reference:
+ {
+ table_t table;
+ formula_name_t::table_type src_table = std::get<formula_name_t::table_type>(fn.value);
+ table.name = m_context.add_string(src_table.name);
+ table.column_first = m_context.add_string(src_table.column_first);
+ table.column_last = m_context.add_string(src_table.column_last);
+ table.areas = src_table.areas;
+ m_formula_tokens.emplace_back(table);
+ break;
+ }
+ case formula_name_t::function:
+ m_formula_tokens.emplace_back(std::get<formula_function_t>(fn.value));
+ break;
+ case formula_name_t::named_expression:
+ m_formula_tokens.emplace_back(std::string{name});
+ break;
+ default:
+ {
+ std::ostringstream os;
+ os << "failed to resolve a name token '" << name << "'.";
+ throw parse_error(os.str());
+ }
+ }
+}
+
+void formula_parser::literal()
+{
+ string_id_t sid = m_context.add_string(std::get<std::string_view>(get_token().value));
+ m_formula_tokens.emplace_back(sid);
+}
+
+void formula_parser::value()
+{
+ m_formula_tokens.emplace_back(std::get<double>(get_token().value));
+}
+
+void formula_parser::less()
+{
+ if (has_next())
+ {
+ next();
+ switch (get_token().opcode)
+ {
+ case lexer_opcode_t::equal:
+ m_formula_tokens.emplace_back(fop_less_equal);
+ return;
+ case lexer_opcode_t::greater:
+ m_formula_tokens.emplace_back(fop_not_equal);
+ return;
+ default:
+ ;
+ }
+ prev();
+ }
+ m_formula_tokens.emplace_back(fop_less);
+}
+
+void formula_parser::greater()
+{
+ if (has_next())
+ {
+ next();
+ if (get_token().opcode == lexer_opcode_t::equal)
+ {
+ m_formula_tokens.emplace_back(fop_greater_equal);
+ return;
+ }
+ prev();
+ }
+ m_formula_tokens.emplace_back(fop_greater);
+
+}
+
+const lexer_token& formula_parser::get_token() const
+{
+ return *m_itr_cur;
+}
+
+bool formula_parser::has_token() const
+{
+ return m_itr_cur != m_itr_end;
+}
+
+bool formula_parser::has_next() const
+{
+ return (m_itr_cur+1) != m_itr_end;
+}
+
+void formula_parser::next()
+{
+ ++m_itr_cur;
+}
+
+void formula_parser::prev()
+{
+ --m_itr_cur;
+}
+
+}
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/formula_parser.hpp b/src/libixion/formula_parser.hpp
new file mode 100644
index 0000000..3c28b67
--- /dev/null
+++ b/src/libixion/formula_parser.hpp
@@ -0,0 +1,80 @@
+/* -*- 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/.
+ */
+
+#ifndef INCLUDED_IXION_FORMULA_PARSER_HPP
+#define INCLUDED_IXION_FORMULA_PARSER_HPP
+
+#include "ixion/exceptions.hpp"
+#include "ixion/formula_tokens.hpp"
+
+#include "lexer_tokens.hpp"
+
+#include <string>
+
+namespace ixion {
+
+class formula_name_resolver;
+class model_context;
+
+/**
+ * Class formula_parser parses a series of primitive (or lexer) tokens
+ * passed on from the lexer, and turn them into a series of formula tokens.
+ * It also picks up a list of cells that it depends on.
+ */
+class formula_parser
+{
+ formula_parser() = delete;
+ formula_parser(const formula_parser&) = delete;
+ formula_parser& operator=(const formula_parser&) = delete;
+
+public:
+ class parse_error : public general_error
+ {
+ public:
+ parse_error(const ::std::string& msg);
+ };
+
+ formula_parser(const lexer_tokens_t& tokens, model_context& cxt, const formula_name_resolver& resolver);
+ ~formula_parser();
+
+ void set_origin(const abs_address_t& pos);
+ void parse();
+
+ formula_tokens_t& get_tokens();
+
+private:
+
+ void primitive();
+ void name();
+ void literal();
+ void value();
+ void less();
+ void greater();
+
+ const lexer_token& get_token() const;
+ bool has_token() const;
+ bool has_next() const;
+ void next();
+ void prev();
+
+private:
+ lexer_tokens_t::const_iterator m_itr_cur;
+ lexer_tokens_t::const_iterator m_itr_end;
+
+ const lexer_tokens_t& m_tokens; // lexer tokens of this expression
+ model_context& m_context;
+ formula_tokens_t m_formula_tokens;
+ abs_address_t m_pos; // reference position (usually current cell). always absolute.
+
+ const formula_name_resolver& m_resolver;
+};
+
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/formula_result.cpp b/src/libixion/formula_result.cpp
new file mode 100644
index 0000000..f20395a
--- /dev/null
+++ b/src/libixion/formula_result.cpp
@@ -0,0 +1,421 @@
+/* -*- 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 <ixion/formula_result.hpp>
+#include <ixion/exceptions.hpp>
+#include <ixion/config.hpp>
+#include <ixion/matrix.hpp>
+#include <ixion/model_context.hpp>
+
+#include <cassert>
+#include <sstream>
+#include <iomanip>
+#include <ostream>
+#include <variant>
+
+#include "debug.hpp"
+
+namespace ixion {
+
+struct formula_result::impl
+{
+ using result_value_type = std::variant<bool, double, formula_error_t, matrix, std::string>;
+
+ result_type type;
+ result_value_type value;
+
+ impl() : type(result_type::value), value(0.0) {}
+ impl(bool b) : type(result_type::boolean), value(b) {}
+ impl(double v) : type(result_type::value), value(v) {}
+ impl(std::string str) : type(result_type::string), value(std::move(str)) {}
+ impl(formula_error_t e) : type(result_type::error), value(e) {}
+ impl(matrix mtx) : type(result_type::matrix), value(std::move(mtx)) {}
+ impl(const impl& other) : type(other.type), value(other.value) {}
+
+ void reset()
+ {
+ type = result_type::value;
+ value = 0.0;
+ }
+
+ void set_boolean(bool b)
+ {
+ type = result_type::boolean;
+ value = b;
+ }
+
+ void set_value(double v)
+ {
+ IXION_TRACE("v=" << v);
+ type = result_type::value;
+ value = v;
+ }
+
+ void set_string_value(std::string str)
+ {
+ type = result_type::string;
+ value = std::move(str);
+ }
+
+ void set_error(formula_error_t e)
+ {
+ type = result_type::error;
+ value = e;
+ }
+
+ void set_matrix(matrix mtx)
+ {
+ type = result_type::matrix;
+ value = std::move(mtx);
+ }
+
+ bool get_boolean() const
+ {
+ assert(type == result_type::boolean);
+ return std::get<bool>(value);
+ }
+
+ double get_value() const
+ {
+ assert(type == result_type::value);
+ return std::get<double>(value);
+ }
+
+ const std::string& get_string_value() const
+ {
+ assert(type == result_type::string);
+ return std::get<std::string>(value);
+ }
+
+ formula_error_t get_error() const
+ {
+ assert(type == result_type::error);
+ return std::get<formula_error_t>(value);
+ }
+
+ const matrix& get_matrix() const
+ {
+ assert(type == result_type::matrix);
+ return std::get<matrix>(value);
+ }
+
+ matrix& get_matrix()
+ {
+ assert(type == result_type::matrix);
+ return std::get<matrix>(value);
+ }
+
+ result_type get_type() const
+ {
+ return type;
+ }
+
+ std::string str(const model_context& cxt) const
+ {
+ switch (type)
+ {
+ case result_type::error:
+ {
+ std::string_view s = get_formula_error_name(std::get<formula_error_t>(value));
+ return std::string(s);
+ }
+ case result_type::string:
+ return std::get<std::string>(value);
+ case result_type::boolean:
+ {
+ std::ostringstream os;
+ os << std::boolalpha << std::get<bool>(value);
+ return os.str();
+ }
+ case result_type::value:
+ {
+ std::ostringstream os;
+ if (cxt.get_config().output_precision >= 0)
+ os << std::fixed << std::setprecision(cxt.get_config().output_precision);
+ os << std::get<double>(value);
+ return os.str();
+ }
+ case result_type::matrix:
+ {
+ const matrix& m = std::get<matrix>(value);
+
+ std::ostringstream os;
+
+ os << '{';
+
+ for (size_t row = 0; row < m.row_size(); ++row)
+ {
+ if (row > 0)
+ os << cxt.get_config().sep_matrix_row;
+
+ for (size_t col = 0; col < m.col_size(); ++col)
+ {
+ if (col > 0)
+ os << cxt.get_config().sep_matrix_column;
+
+ matrix::element e = m.get(row, col);
+
+ 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::error:
+ {
+ os << get_formula_error_name(std::get<formula_error_t>(e.value));
+ break;
+ }
+ case matrix::element_type::boolean:
+ {
+ os << std::get<bool>(e.value);
+ break;
+ }
+ default:
+ ;
+ }
+ }
+ }
+
+ os << '}';
+
+ return os.str();
+ }
+ default:
+ assert(!"unknown formula result type!");
+ }
+ return std::string{};
+ }
+
+ void parse(std::string_view s)
+ {
+ if (s.empty())
+ return;
+
+ switch (s[0])
+ {
+ case '#':
+ {
+ parse_error(s);
+ break;
+ }
+ case '"':
+ {
+ parse_string(s);
+ break;
+ }
+ case 't':
+ case 'f':
+ {
+ // parse this as a boolean value.
+ value = to_bool(s);
+ type = result_type::boolean;
+ break;
+ }
+ default:
+ {
+ // parse this as a number.
+ value = to_double(s);
+ type = result_type::value;
+ }
+ }
+ }
+
+ void move_from(formula_result&& r)
+ {
+ type = r.mp_impl->type;
+ value = std::move(r.mp_impl->value);
+ }
+
+ bool equals(const formula_result& r) const
+ {
+ if (type != r.mp_impl->type)
+ return false;
+
+ return value == r.mp_impl->value;
+ }
+
+ void parse_error(std::string_view s)
+ {
+ assert(!s.empty());
+ assert(s[0] == '#');
+
+ formula_error_t err = to_formula_error_type(s);
+
+ if (err == formula_error_t::no_error)
+ {
+ std::ostringstream os;
+ os << "malformed error string: " << s;
+ throw general_error(os.str());
+ }
+
+ value = err;
+ type = result_type::error;
+ }
+
+ void parse_string(std::string_view s)
+ {
+ if (s.size() < 2u)
+ // It needs to at least have the opening and closing quotes.
+ return;
+
+ assert(s[0] == '"');
+ auto pos = s.find_first_of('"', 1);
+ if (pos == std::string_view::npos)
+ throw general_error("failed to parse string result.");
+
+ type = result_type::string;
+ value = std::string(&s[1], pos - 1);
+ }
+};
+
+formula_result::formula_result() :
+ mp_impl(std::make_unique<impl>()) {}
+
+formula_result::formula_result(const formula_result& r) :
+ mp_impl(std::make_unique<impl>(*r.mp_impl)) {}
+
+formula_result::formula_result(formula_result&& r) : mp_impl(std::move(r.mp_impl)) {}
+
+formula_result::formula_result(bool b) : mp_impl(std::make_unique<impl>(b)) {}
+
+formula_result::formula_result(double v) : mp_impl(std::make_unique<impl>(v)) {}
+
+formula_result::formula_result(std::string str) : mp_impl(std::make_unique<impl>(std::move(str))) {}
+
+formula_result::formula_result(formula_error_t e) : mp_impl(std::make_unique<impl>(e)) {}
+
+formula_result::formula_result(matrix mtx) : mp_impl(std::make_unique<impl>(std::move(mtx))) {}
+
+formula_result::~formula_result() {}
+
+void formula_result::reset()
+{
+ mp_impl->reset();
+}
+
+void formula_result::set_boolean(bool b)
+{
+ mp_impl->set_boolean(b);
+}
+
+void formula_result::set_value(double v)
+{
+ mp_impl->set_value(v);
+}
+
+void formula_result::set_string_value(std::string str)
+{
+ mp_impl->set_string_value(std::move(str));
+}
+
+void formula_result::set_error(formula_error_t e)
+{
+ mp_impl->set_error(e);
+}
+
+void formula_result::set_matrix(matrix mtx)
+{
+ mp_impl->set_matrix(std::move(mtx));
+}
+
+bool formula_result::get_boolean() const
+{
+ return mp_impl->get_boolean();
+}
+
+double formula_result::get_value() const
+{
+ return mp_impl->get_value();
+}
+
+const std::string& formula_result::get_string() const
+{
+ return mp_impl->get_string_value();
+}
+
+formula_error_t formula_result::get_error() const
+{
+ return mp_impl->get_error();
+}
+
+const matrix& formula_result::get_matrix() const
+{
+ return mp_impl->get_matrix();
+}
+
+matrix& formula_result::get_matrix()
+{
+ return mp_impl->get_matrix();
+}
+
+formula_result::result_type formula_result::get_type() const
+{
+ return mp_impl->get_type();
+}
+
+std::string formula_result::str(const model_context& cxt) const
+{
+ return mp_impl->str(cxt);
+}
+
+void formula_result::parse(std::string_view s)
+{
+ mp_impl->parse(s);
+}
+
+formula_result& formula_result::operator= (formula_result r)
+{
+ mp_impl->move_from(std::move(r));
+ return *this;
+}
+
+bool formula_result::operator== (const formula_result& r) const
+{
+ return mp_impl->equals(r);
+}
+
+bool formula_result::operator!= (const formula_result& r) const
+{
+ return !operator== (r);
+}
+
+std::ostream& operator<< (std::ostream& os, formula_result::result_type v)
+{
+ switch (v)
+ {
+ case formula_result::result_type::error:
+ os << "error";
+ break;
+ case formula_result::result_type::matrix:
+ os << "matrix";
+ break;
+ case formula_result::result_type::string:
+ os << "string";
+ break;
+ case formula_result::result_type::value:
+ os << "value";
+ break;
+ case formula_result::result_type::boolean:
+ os << "boolean";
+ break;
+ default:
+ ;
+ }
+
+ return os;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/formula_tokens.cpp b/src/libixion/formula_tokens.cpp
new file mode 100644
index 0000000..ea432e8
--- /dev/null
+++ b/src/libixion/formula_tokens.cpp
@@ -0,0 +1,303 @@
+/* -*- 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 <ixion/formula_tokens.hpp>
+#include <ixion/exceptions.hpp>
+#include <ixion/global.hpp>
+#include <ixion/macros.hpp>
+
+#include <sstream>
+
+namespace ixion {
+
+std::string_view get_opcode_name(fopcode_t oc)
+{
+ // Make sure the names are ordered identically to the ordering of the enum members.
+ static constexpr std::string_view names[] = {
+ "unknown", // fop_unknown
+ "single-ref", // fop_single_ref
+ "range-ref", // fop_range_ref
+ "table-ref", // fop_table_ref
+ "named-expression", // fop_named_expression
+ "string", // fop_string
+ "value", // fop_value
+ "function", // fop_function
+ "plus", // fop_plus
+ "minus", // fop_minus
+ "divide", // fop_divide
+ "multiply", // fop_multiply
+ "exponent", // fop_exponent
+ "concat", // fop_concat
+ "equal", // fop_equal
+ "not-equal", // fop_not_equal
+ "less", // fop_less
+ "greater", // fop_greater
+ "less-equal", // fop_less_equal
+ "greater-equal", // fop_greater_equal
+ "open", // fop_open
+ "close", // fop_close
+ "sep", // fop_sep
+ "array-row-sep", // fop_array_row_sep
+ "array-open", // fop_array_open
+ "array-close", // fop_array_close
+ "error", // fop_error
+ };
+
+ if (std::size_t(oc) >= std::size(names))
+ return "???";
+
+ return names[oc];
+}
+
+std::string_view get_formula_opcode_string(fopcode_t oc)
+{
+ static constexpr std::string_view empty = "";
+
+ // Make sure the names are ordered identically to the ordering of the enum members.
+ static constexpr std::string_view names[] = {
+ empty, // fop_unknown
+ empty, // fop_single_ref
+ empty, // fop_range_ref
+ empty, // fop_table_ref
+ empty, // fop_named_expression
+ empty, // fop_string
+ empty, // fop_value
+ empty, // fop_function
+ "+", // fop_plus
+ "-", // fop_minus
+ "/", // fop_divide
+ "*", // fop_multiply
+ "^", // fop_exponent
+ "&", // fop_concat
+ "=", // fop_equal
+ "<>", // fop_not_equal
+ "<", // fop_less
+ ">", // fop_greater
+ "<=", // fop_less_equal
+ ">=", // fop_greater_equal
+ "(", // fop_open
+ ")", // fop_close
+ empty, // fop_sep
+ empty, // fop_array_row_sep
+ "{", // fop_array_open
+ "}", // fop_array_close
+ empty, // fop_error
+ };
+
+ if (std::size_t(oc) >= std::size(names))
+ return empty;
+
+ return names[oc];
+}
+
+// ============================================================================
+
+formula_token::formula_token(fopcode_t op) :
+ opcode(op)
+{
+ switch (opcode)
+ {
+ case fop_single_ref:
+ case fop_range_ref:
+ case fop_table_ref:
+ case fop_named_expression:
+ case fop_string:
+ case fop_value:
+ case fop_function:
+ {
+ std::ostringstream os;
+ os << "this opcode named '" << get_opcode_name(op) << "' cannot be instantiated by this constructor";
+ throw std::invalid_argument(os.str());
+ }
+ default:;
+ }
+}
+
+formula_token::formula_token(const address_t& addr) :
+ opcode(fop_single_ref), value(addr)
+{
+}
+
+formula_token::formula_token(const range_t& range) :
+ opcode(fop_range_ref), value(range)
+{
+}
+
+formula_token::formula_token(const table_t& table) :
+ opcode(fop_table_ref), value(table)
+{
+}
+
+formula_token::formula_token(formula_function_t func) :
+ opcode(fop_function), value(func)
+{
+}
+
+formula_token::formula_token(double v) :
+ opcode(fop_value), value(v)
+{
+}
+
+formula_token::formula_token(string_id_t sid) :
+ opcode(fop_string), value(sid)
+{
+}
+
+formula_token::formula_token(std::string name) :
+ opcode(fop_named_expression), value(std::move(name))
+{
+}
+
+
+formula_token::formula_token(const formula_token& r) = default;
+formula_token::formula_token(formula_token&& r) = default;
+formula_token::~formula_token() = default;
+
+bool formula_token::operator== (const formula_token& r) const
+{
+ return opcode == r.opcode && value == r.value;
+}
+
+bool formula_token::operator!= (const formula_token& r) const
+{
+ return !operator== (r);
+}
+
+struct formula_tokens_store::impl
+{
+ formula_tokens_t m_tokens;
+ size_t m_refcount;
+
+ impl() : m_refcount(0) {}
+};
+
+formula_tokens_store::formula_tokens_store() :
+ mp_impl(std::make_unique<impl>())
+{
+}
+
+formula_tokens_store::~formula_tokens_store()
+{
+}
+
+formula_tokens_store_ptr_t formula_tokens_store::create()
+{
+ return formula_tokens_store_ptr_t(new formula_tokens_store);
+}
+
+void formula_tokens_store::add_ref()
+{
+ ++mp_impl->m_refcount;
+}
+
+void formula_tokens_store::release_ref()
+{
+ if (--mp_impl->m_refcount == 0)
+ delete this;
+}
+
+size_t formula_tokens_store::get_reference_count() const
+{
+ return mp_impl->m_refcount;
+}
+
+formula_tokens_t& formula_tokens_store::get()
+{
+ return mp_impl->m_tokens;
+}
+
+const formula_tokens_t& formula_tokens_store::get() const
+{
+ return mp_impl->m_tokens;
+}
+
+named_expression_t::named_expression_t() {}
+named_expression_t::named_expression_t(const abs_address_t& _origin, formula_tokens_t _tokens) :
+ origin(_origin), tokens(std::move(_tokens)) {}
+
+named_expression_t::named_expression_t(named_expression_t&& other) :
+ origin(other.origin), tokens(std::move(other.tokens)) {}
+
+named_expression_t::~named_expression_t() {}
+
+std::ostream& operator<< (std::ostream& os, const formula_token& ft)
+{
+ switch (ft.opcode)
+ {
+ case fop_string:
+ {
+ os << "string token: (identifier=" << std::get<string_id_t>(ft.value) << ")";
+ break;
+ }
+ case fop_value:
+ {
+ os << "value token: " << std::get<double>(ft.value);
+ break;
+ }
+ case fop_single_ref:
+ {
+ os << "single ref token: " << std::get<address_t>(ft.value);
+ break;
+ }
+ case fop_range_ref:
+ {
+ os << "range ref token: " << std::get<range_t>(ft.value);
+ break;
+ }
+ case fop_table_ref:
+ {
+ os << "table ref token: " << std::get<table_t>(ft.value);
+ break;
+ }
+ case fop_named_expression:
+ {
+ os << "named expression token: '" << std::get<std::string>(ft.value) << "'";
+ break;
+ }
+ case fop_function:
+ {
+ using _int_type = std::underlying_type_t<formula_function_t>;
+
+ formula_function_t v = std::get<formula_function_t>(ft.value);
+ os << "function token: (opcode=" << _int_type(v) << "; name='" << get_formula_function_name(v) << "')";
+ break;
+ }
+ case fop_error:
+ {
+ os << "invalid error token: (count=" << std::get<string_id_t>(ft.value) << ")";
+ break;
+ }
+ case fop_plus:
+ case fop_minus:
+ case fop_divide:
+ case fop_multiply:
+ case fop_exponent:
+ case fop_concat:
+ case fop_equal:
+ case fop_not_equal:
+ case fop_less:
+ case fop_greater:
+ case fop_less_equal:
+ case fop_greater_equal:
+ case fop_open:
+ case fop_close:
+ case fop_sep:
+ case fop_array_row_sep:
+ case fop_array_open:
+ case fop_array_close:
+ case fop_unknown:
+ os << "opcode token: (name=" << get_opcode_name(ft.opcode) << "; s='"
+ << get_formula_opcode_string(ft.opcode) << "')";
+ break;
+ }
+
+ return os;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/formula_value_stack.cpp b/src/libixion/formula_value_stack.cpp
new file mode 100644
index 0000000..9985c77
--- /dev/null
+++ b/src/libixion/formula_value_stack.cpp
@@ -0,0 +1,625 @@
+/* -*- 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_value_stack.hpp"
+#include "debug.hpp"
+
+#include <ixion/address.hpp>
+#include <ixion/cell.hpp>
+#include <ixion/cell_access.hpp>
+#include <ixion/matrix.hpp>
+#include <ixion/formula_result.hpp>
+#include <ixion/config.hpp>
+#include <ixion/exceptions.hpp>
+
+#include <string>
+#include <sstream>
+
+namespace ixion {
+
+namespace {
+
+bool get_boolean_value(const model_context& cxt, const stack_value& v)
+{
+ switch (v.get_type())
+ {
+ case stack_value_t::boolean:
+ return v.get_boolean();
+ case stack_value_t::value:
+ case stack_value_t::matrix:
+ return v.get_value() != 0.0;
+ case stack_value_t::single_ref:
+ {
+ // reference to a single cell.
+ const abs_address_t& addr = v.get_address();
+ auto ca = cxt.get_cell_access(addr);
+ switch (ca.get_value_type())
+ {
+ case cell_value_t::numeric:
+ case cell_value_t::boolean:
+ return ca.get_boolean_value();
+ case cell_value_t::empty:
+ return false;
+ default:;
+ }
+ break;
+ }
+ default:;
+ }
+
+ throw formula_error(formula_error_t::invalid_value_type);
+}
+
+double get_numeric_value(const model_context& cxt, const stack_value& v)
+{
+ switch (v.get_type())
+ {
+ case stack_value_t::boolean:
+ return v.get_boolean() ? 1.0 : 0.0;
+ case stack_value_t::value:
+ case stack_value_t::matrix:
+ return v.get_value();
+ case stack_value_t::string:
+ return 0.0;
+ case stack_value_t::single_ref:
+ {
+ // reference to a single cell.
+ const abs_address_t& addr = v.get_address();
+ return cxt.get_numeric_value(addr);
+ }
+ default:;
+ }
+
+ throw formula_error(formula_error_t::invalid_value_type);
+}
+
+} // anonymous namespace
+
+std::ostream& operator<<(std::ostream& os, stack_value_t sv)
+{
+ static constexpr std::string_view names[] = {
+ "boolean",
+ "error",
+ "value",
+ "string",
+ "single_ref",
+ "range_ref",
+ "matrix",
+ };
+
+ auto pos = static_cast<std::size_t>(sv);
+ if (pos < std::size(names))
+ os << names[pos];
+ else
+ os << "???";
+
+ return os;
+}
+
+stack_value::stack_value(bool b) :
+ m_type(stack_value_t::boolean), m_value(b) {}
+
+stack_value::stack_value(double val) :
+ m_type(stack_value_t::value), m_value(val) {}
+
+stack_value::stack_value(std::string str) :
+ m_type(stack_value_t::string), m_value(std::move(str)) {}
+
+stack_value::stack_value(const abs_address_t& val) :
+ m_type(stack_value_t::single_ref), m_value(val) {}
+
+stack_value::stack_value(const abs_range_t& val) :
+ m_type(stack_value_t::range_ref), m_value(val) {}
+
+stack_value::stack_value(formula_error_t err) :
+ m_type(stack_value_t::error), m_value(err) {}
+
+stack_value::stack_value(matrix mtx) :
+ m_type(stack_value_t::matrix), m_value(std::move(mtx)) {}
+
+stack_value::stack_value(stack_value&& other) :
+ m_type(other.m_type), m_value(std::move(other.m_value)) {}
+
+stack_value::~stack_value() = default;
+
+stack_value& stack_value::operator= (stack_value&& other)
+{
+ m_type = other.m_type;
+ m_value = std::move(other.m_value);
+ return *this;
+}
+
+stack_value_t stack_value::get_type() const
+{
+ return m_type;
+}
+
+bool stack_value::get_boolean() const
+{
+ switch (m_type)
+ {
+ case stack_value_t::boolean:
+ return std::get<bool>(m_value);
+ case stack_value_t::value:
+ return std::get<double>(m_value) != 0.0;
+ case stack_value_t::matrix:
+ return std::get<matrix>(m_value).get_boolean(0, 0);
+ default:;
+ }
+
+ return false;
+}
+
+double stack_value::get_value() const
+{
+ switch (m_type)
+ {
+ case stack_value_t::boolean:
+ return std::get<bool>(m_value) ? 1.0 : 0.0;
+ case stack_value_t::value:
+ return std::get<double>(m_value);
+ case stack_value_t::matrix:
+ return std::get<matrix>(m_value).get_numeric(0, 0);
+ default:
+ ;
+ }
+
+ return 0.0;
+}
+
+const std::string& stack_value::get_string() const
+{
+ return std::get<std::string>(m_value);
+}
+
+const abs_address_t& stack_value::get_address() const
+{
+ return std::get<abs_address_t>(m_value);
+}
+
+const abs_range_t& stack_value::get_range() const
+{
+ return std::get<abs_range_t>(m_value);
+}
+
+formula_error_t stack_value::get_error() const
+{
+ return std::get<formula_error_t>(m_value);
+}
+
+const matrix& stack_value::get_matrix() const
+{
+ return std::get<matrix>(m_value);
+}
+
+matrix stack_value::pop_matrix()
+{
+ switch (m_type)
+ {
+ case stack_value_t::boolean:
+ {
+ matrix mtx(1, 1);
+ mtx.set(0, 0, std::get<bool>(m_value));
+ return mtx;
+ }
+ case stack_value_t::value:
+ {
+ matrix mtx(1, 1);
+ mtx.set(0, 0, std::get<double>(m_value));
+ return mtx;
+ }
+ case stack_value_t::matrix:
+ {
+ matrix mtx;
+ mtx.swap(std::get<matrix>(m_value));
+ return mtx;
+ }
+ default:
+ throw formula_error(formula_error_t::stack_error);
+ }
+}
+
+formula_value_stack::formula_value_stack(const model_context& cxt) : m_context(cxt) {}
+
+formula_value_stack::iterator formula_value_stack::begin()
+{
+ return m_stack.begin();
+}
+
+formula_value_stack::iterator formula_value_stack::end()
+{
+ return m_stack.end();
+}
+
+formula_value_stack::const_iterator formula_value_stack::begin() const
+{
+ return m_stack.begin();
+}
+
+formula_value_stack::const_iterator formula_value_stack::end() const
+{
+ return m_stack.end();
+}
+
+formula_value_stack::value_type formula_value_stack::release(iterator pos)
+{
+ auto tmp = std::move(*pos);
+ m_stack.erase(pos);
+ return tmp;
+}
+
+formula_value_stack::value_type formula_value_stack::release_back()
+{
+ assert(!m_stack.empty());
+ auto tmp = std::move(m_stack.back());
+ m_stack.pop_back();
+ return tmp;
+}
+
+bool formula_value_stack::empty() const
+{
+ return m_stack.empty();
+}
+
+size_t formula_value_stack::size() const
+{
+ return m_stack.size();
+}
+
+void formula_value_stack::clear()
+{
+ return m_stack.clear();
+}
+
+void formula_value_stack::swap(formula_value_stack& other)
+{
+ m_stack.swap(other.m_stack);
+}
+
+stack_value& formula_value_stack::back()
+{
+ return m_stack.back();
+}
+
+const stack_value& formula_value_stack::back() const
+{
+ return m_stack.back();
+}
+
+const stack_value& formula_value_stack::operator[](size_t pos) const
+{
+ return m_stack[pos];
+}
+
+double formula_value_stack::get_value(size_t pos) const
+{
+ const stack_value& v = m_stack[pos];
+ return get_numeric_value(m_context, v);
+}
+
+void formula_value_stack::push_back(value_type&& val)
+{
+ IXION_TRACE("push_back");
+ m_stack.push_back(std::move(val));
+}
+
+void formula_value_stack::push_boolean(bool b)
+{
+ IXION_TRACE("b=" << std::boolalpha << b);
+ m_stack.emplace_back(b);
+}
+
+void formula_value_stack::push_value(double val)
+{
+ IXION_TRACE("val=" << val);
+ m_stack.emplace_back(val);
+}
+
+void formula_value_stack::push_string(std::string str)
+{
+ IXION_TRACE("str='" << str << "'");
+ m_stack.emplace_back(std::move(str));
+}
+
+void formula_value_stack::push_single_ref(const abs_address_t& val)
+{
+ IXION_TRACE("val=" << val.get_name());
+ m_stack.emplace_back(val);
+}
+
+void formula_value_stack::push_range_ref(const abs_range_t& val)
+{
+ assert(val.valid());
+ IXION_TRACE("start=" << val.first.get_name() << "; end=" << val.last.get_name());
+ m_stack.emplace_back(val);
+}
+
+void formula_value_stack::push_matrix(matrix mtx)
+{
+ IXION_TRACE("push_matrix");
+ m_stack.emplace_back(std::move(mtx));
+}
+
+void formula_value_stack::push_error(formula_error_t err)
+{
+ IXION_TRACE("err=" << short(err) << " (" << get_formula_error_name(err) << ")");
+ m_stack.emplace_back(err);
+}
+
+bool formula_value_stack::pop_boolean()
+{
+ if (m_stack.empty())
+ throw formula_error(formula_error_t::stack_error);
+
+ const stack_value& v = m_stack.back();
+ bool ret = get_boolean_value(m_context, v);
+ m_stack.pop_back();
+ IXION_TRACE("ret=" << std::boolalpha << ret);
+ return ret;
+}
+
+double formula_value_stack::pop_value()
+{
+ double ret = 0.0;
+ if (m_stack.empty())
+ throw formula_error(formula_error_t::stack_error);
+
+ const stack_value& v = m_stack.back();
+ ret = get_numeric_value(m_context, v);
+ m_stack.pop_back();
+ IXION_TRACE("ret=" << ret);
+ return ret;
+}
+
+std::string formula_value_stack::pop_string()
+{
+ IXION_TRACE("pop_string");
+
+ if (m_stack.empty())
+ throw formula_error(formula_error_t::stack_error);
+
+ const stack_value& v = m_stack.back();
+ switch (v.get_type())
+ {
+ case stack_value_t::string:
+ {
+ const std::string str = v.get_string();
+ m_stack.pop_back();
+ return str;
+ }
+ case stack_value_t::boolean:
+ {
+ std::ostringstream os;
+ os << std::boolalpha << v.get_boolean();
+ m_stack.pop_back();
+ return os.str();
+ }
+ case stack_value_t::value:
+ {
+ std::ostringstream os;
+ os << v.get_value();
+ m_stack.pop_back();
+ return os.str();
+ }
+ case stack_value_t::single_ref:
+ {
+ // reference to a single cell.
+ abs_address_t addr = v.get_address();
+ m_stack.pop_back();
+
+ switch (m_context.get_celltype(addr))
+ {
+ case celltype_t::empty:
+ return std::string();
+ case celltype_t::formula:
+ {
+ formula_result res = m_context.get_formula_result(addr);
+
+ switch (res.get_type())
+ {
+ case formula_result::result_type::error:
+ throw formula_error(res.get_error());
+ case formula_result::result_type::string:
+ return res.get_string();
+ case formula_result::result_type::boolean:
+ {
+ std::ostringstream os;
+ os << std::boolalpha << res.get_boolean();
+ return os.str();
+ }
+ case formula_result::result_type::value:
+ {
+ std::ostringstream os;
+ os << res.get_value();
+ return os.str();
+ }
+ default:
+ throw formula_error(formula_error_t::stack_error);
+ }
+ }
+ break;
+ case celltype_t::numeric:
+ {
+ std::ostringstream os;
+ os << m_context.get_numeric_value(addr);
+ return os.str();
+ }
+ case celltype_t::string:
+ {
+ const std::string* ps = m_context.get_string(m_context.get_string_identifier(addr));
+ if (!ps)
+ throw formula_error(formula_error_t::stack_error);
+ return *ps;
+ }
+ break;
+ default:
+ throw formula_error(formula_error_t::stack_error);
+ }
+
+ break;
+ }
+ default:
+ {
+ IXION_DEBUG("unhandled type: " << v.get_type());
+ }
+ }
+ throw formula_error(formula_error_t::stack_error);
+}
+
+matrix formula_value_stack::pop_matrix()
+{
+ if (auto mtx = maybe_pop_matrix(); mtx)
+ return *mtx;
+
+ throw formula_error(formula_error_t::stack_error);
+}
+
+std::optional<matrix> formula_value_stack::maybe_pop_matrix()
+{
+ if (m_stack.empty())
+ throw formula_error(formula_error_t::stack_error);
+
+ stack_value& v = m_stack.back();
+ switch (v.get_type())
+ {
+ case stack_value_t::matrix:
+ {
+ auto mtx = v.pop_matrix();
+ m_stack.pop_back();
+ return mtx;
+ }
+ case stack_value_t::range_ref:
+ return pop_range_value();
+ default:;
+ }
+
+ return {};
+}
+
+abs_address_t formula_value_stack::pop_single_ref()
+{
+ IXION_TRACE("pop_single_ref");
+
+ if (m_stack.empty())
+ throw formula_error(formula_error_t::stack_error);
+
+ const stack_value& v = m_stack.back();
+
+ switch (v.get_type())
+ {
+ case stack_value_t::single_ref:
+ {
+ abs_address_t addr = v.get_address();
+ m_stack.pop_back();
+ return addr;
+ }
+ case stack_value_t::range_ref:
+ {
+ abs_range_t range = v.get_range();
+ m_stack.pop_back();
+ return range.first;
+ }
+ default:;
+ }
+
+ throw formula_error(formula_error_t::stack_error);
+}
+
+abs_range_t formula_value_stack::pop_range_ref()
+{
+ IXION_TRACE("pop_range_ref");
+
+ if (m_stack.empty())
+ throw formula_error(formula_error_t::stack_error);
+
+ const stack_value& v = m_stack.back();
+
+ switch (v.get_type())
+ {
+ case stack_value_t::single_ref:
+ {
+ abs_address_t addr = v.get_address();
+ m_stack.pop_back();
+ return addr;
+ }
+ case stack_value_t::range_ref:
+ {
+ abs_range_t range = v.get_range();
+ m_stack.pop_back();
+ return range;
+ }
+ default:
+ throw formula_error(formula_error_t::stack_error);
+ }
+}
+
+matrix formula_value_stack::pop_range_value()
+{
+ IXION_TRACE("pop_range_value");
+
+ if (m_stack.empty())
+ throw formula_error(formula_error_t::stack_error);
+
+ const stack_value& v = m_stack.back();
+ if (v.get_type() != stack_value_t::range_ref)
+ throw formula_error(formula_error_t::stack_error);
+
+ matrix ret = m_context.get_range_value(v.get_range());
+ m_stack.pop_back();
+ return ret;
+}
+
+formula_error_t formula_value_stack::pop_error()
+{
+ IXION_TRACE("pop_error");
+
+ if (m_stack.empty())
+ throw formula_error(formula_error_t::stack_error);
+
+ const stack_value& v = m_stack.back();
+ if (v.get_type() != stack_value_t::error)
+ throw formula_error(formula_error_t::stack_error);
+
+ formula_error_t ret = v.get_error();
+ m_stack.pop_back();
+ return ret;
+}
+
+resolved_stack_value formula_value_stack::pop_matrix_or_numeric()
+{
+ if (auto mtx = maybe_pop_matrix(); mtx)
+ return *mtx;
+
+ // fall back to numeric value
+ return pop_value();
+}
+
+resolved_stack_value formula_value_stack::pop_matrix_or_string()
+{
+ if (auto mtx = maybe_pop_matrix(); mtx)
+ return *mtx;
+
+ // fall back to string value
+ return pop_string();
+}
+
+void formula_value_stack::pop_back()
+{
+ m_stack.pop_back();
+}
+
+stack_value_t formula_value_stack::get_type() const
+{
+ if (m_stack.empty())
+ throw formula_error(formula_error_t::stack_error);
+
+ return m_stack.back().get_type();
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/formula_value_stack.hpp b/src/libixion/formula_value_stack.hpp
new file mode 100644
index 0000000..91c8fb0
--- /dev/null
+++ b/src/libixion/formula_value_stack.hpp
@@ -0,0 +1,153 @@
+/* -*- 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/.
+ */
+
+#ifndef INCLUDED_IXION_FORMULA_VALUE_STACK_HPP
+#define INCLUDED_IXION_FORMULA_VALUE_STACK_HPP
+
+#include <ixion/address.hpp>
+#include <ixion/global.hpp>
+#include <ixion/matrix.hpp>
+#include <ixion/model_context.hpp>
+
+#include "impl_types.hpp"
+
+#include <deque>
+#include <variant>
+#include <ostream>
+#include <optional>
+
+namespace ixion {
+
+/**
+ * Type of stack value which can be used as intermediate value during
+ * formula interpretation.
+ */
+enum class stack_value_t
+{
+ boolean,
+ error,
+ value,
+ string,
+ single_ref,
+ range_ref,
+ matrix,
+};
+
+std::ostream& operator<<(std::ostream& os, stack_value_t sv);
+
+/**
+ * Individual stack value storage.
+ */
+class stack_value
+{
+ using stored_value_type = std::variant<bool, double, abs_address_t, abs_range_t, formula_error_t, matrix, std::string>;
+
+ stack_value_t m_type;
+ stored_value_type m_value;
+
+public:
+ stack_value() = delete;
+ stack_value(const stack_value&) = delete;
+ stack_value& operator= (const stack_value&) = delete;
+
+ explicit stack_value(bool b);
+ explicit stack_value(double val);
+ explicit stack_value(std::string str);
+ explicit stack_value(const abs_address_t& val);
+ explicit stack_value(const abs_range_t& val);
+ explicit stack_value(formula_error_t err);
+ explicit stack_value(matrix mtx);
+ stack_value(stack_value&& other);
+ ~stack_value();
+
+ stack_value& operator= (stack_value&& other);
+
+ stack_value_t get_type() const;
+ bool get_boolean() const;
+ double get_value() const;
+ const std::string& get_string() const;
+ const abs_address_t& get_address() const;
+ const abs_range_t& get_range() const;
+ formula_error_t get_error() const;
+
+ const matrix& get_matrix() const;
+
+ /**
+ * Move the matrix value out from storage. The internal matrix content
+ * will be empty after this call.
+ */
+ matrix pop_matrix();
+};
+
+/**
+ * FILO stack of values; last pushed value gets popped first.
+ */
+class formula_value_stack
+{
+ typedef std::deque<stack_value> store_type;
+ store_type m_stack;
+ const model_context& m_context;
+
+public:
+ formula_value_stack() = delete;
+ formula_value_stack(const formula_value_stack&) = delete;
+ formula_value_stack& operator= (const formula_value_stack&) = delete;
+
+ explicit formula_value_stack(const model_context& cxt);
+
+ typedef store_type::value_type value_type;
+ typedef store_type::iterator iterator;
+ typedef store_type::const_iterator const_iterator;
+ iterator begin();
+ iterator end();
+ const_iterator begin() const;
+ const_iterator end() const;
+ value_type release(iterator pos);
+ value_type release_back();
+ bool empty() const;
+ size_t size() const;
+ void clear();
+ void swap(formula_value_stack& other);
+
+ stack_value& back();
+ const stack_value& back() const;
+ const stack_value& operator[](size_t pos) const;
+
+ double get_value(size_t pos) const;
+
+ void push_back(value_type&& val);
+ void push_boolean(bool b);
+ void push_value(double val);
+ void push_string(std::string str);
+ void push_single_ref(const abs_address_t& val);
+ void push_range_ref(const abs_range_t& val);
+ void push_matrix(matrix mtx);
+ void push_error(formula_error_t err);
+
+ bool pop_boolean();
+ double pop_value();
+ std::string pop_string();
+ matrix pop_matrix();
+ std::optional<matrix> maybe_pop_matrix();
+ abs_address_t pop_single_ref();
+ abs_range_t pop_range_ref();
+ matrix pop_range_value();
+ formula_error_t pop_error();
+
+ resolved_stack_value pop_matrix_or_numeric();
+ resolved_stack_value pop_matrix_or_string();
+
+ void pop_back();
+
+ stack_value_t get_type() const;
+};
+
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/general_test.cpp b/src/libixion/general_test.cpp
new file mode 100644
index 0000000..4409205
--- /dev/null
+++ b/src/libixion/general_test.cpp
@@ -0,0 +1,1495 @@
+/* -*- 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 "test_global.hpp" // This must be the first header to be included.
+
+#include <ixion/formula_name_resolver.hpp>
+#include <ixion/address.hpp>
+#include <ixion/formula.hpp>
+#include <ixion/model_context.hpp>
+#include <ixion/model_iterator.hpp>
+#include <ixion/named_expressions_iterator.hpp>
+#include <ixion/global.hpp>
+#include <ixion/macros.hpp>
+#include <ixion/interface/table_handler.hpp>
+#include <ixion/config.hpp>
+#include <ixion/matrix.hpp>
+#include <ixion/cell.hpp>
+#include <ixion/cell_access.hpp>
+#include <ixion/formula_result.hpp>
+#include <ixion/exceptions.hpp>
+
+#include <string>
+#include <cstring>
+#include <sstream>
+#include <thread>
+
+using namespace std;
+using namespace ixion;
+
+namespace {
+
+void test_size()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ cout << "test size" << endl;
+ cout << "* int: " << sizeof(int) << endl;
+ cout << "* long: " << sizeof(long) << endl;
+ cout << "* double: " << sizeof(double) << endl;
+ cout << "* size_t: " << sizeof(size_t) << endl;
+ cout << "* string_id_t: " << sizeof(string_id_t)
+ << " (min:" << std::numeric_limits<string_id_t>::min()
+ << "; max:" << std::numeric_limits<string_id_t>::max() << ")" << endl;
+ cout << "* celltype_t: " << sizeof(celltype_t) << endl;
+ cout << "* formula_cell: " << sizeof(formula_cell) << endl;
+ cout << "* formula_tokens_t: " << sizeof(formula_tokens_t) << endl;
+}
+
+void test_string_to_double()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ struct { const char* s; double v; } tests[] = {
+ { "12", 12.0 },
+ { "0", 0.0 },
+ { "1.3", 1.3 },
+ { "1234.00983", 1234.00983 },
+ { "-123.3", -123.3 }
+ };
+
+ size_t n = sizeof(tests) / sizeof(tests[0]);
+ for (size_t i = 0; i < n; ++i)
+ {
+ double v = to_double(tests[i].s);
+ assert(v == tests[i].v);
+ }
+}
+
+void test_string_pool()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ model_context cxt;
+
+ string_id_t s_table1 = cxt.append_string("Table1");
+ string_id_t s_table2 = cxt.append_string("Table2");
+ string_id_t s_cat = cxt.append_string("Category");
+ string_id_t s_val = cxt.append_string("Value");
+
+ cxt.dump_strings();
+
+ // Make sure these work correctly before proceeding further with the test.
+ assert(s_table1 == cxt.get_identifier_from_string("Table1"));
+ assert(s_table2 == cxt.get_identifier_from_string("Table2"));
+ assert(s_cat == cxt.get_identifier_from_string("Category"));
+ assert(s_val == cxt.get_identifier_from_string("Value"));
+}
+
+void test_formula_tokens_store()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ formula_tokens_store_ptr_t p = formula_tokens_store::create();
+ assert(p->get_reference_count() == 1);
+ auto p2 = p;
+
+ assert(p->get_reference_count() == 2);
+ assert(p2->get_reference_count() == 2);
+
+ auto p3(p);
+
+ assert(p->get_reference_count() == 3);
+ assert(p2->get_reference_count() == 3);
+ assert(p3->get_reference_count() == 3);
+
+ p3.reset();
+ assert(p->get_reference_count() == 2);
+ assert(p2->get_reference_count() == 2);
+
+ p2.reset();
+ assert(p->get_reference_count() == 1);
+ p.reset();
+}
+
+void test_matrix()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ struct check
+ {
+ size_t row;
+ size_t col;
+ double val;
+ };
+
+ std::vector<check> checks =
+ {
+ { 0, 0, 1.0 },
+ { 0, 1, 2.0 },
+ { 1, 0, 3.0 },
+ { 1, 1, 4.0 },
+ };
+
+ numeric_matrix num_mtx(2, 2);
+
+ for (const check& c : checks)
+ num_mtx(c.row, c.col) = c.val;
+
+ for (const check& c : checks)
+ assert(num_mtx(c.row, c.col) == c.val);
+
+ matrix mtx(num_mtx);
+
+ for (const check& c : checks)
+ {
+ matrix::element e = mtx.get(c.row, c.col);
+ assert(e.type == matrix::element_type::numeric);
+ assert(std::get<double>(e.value) == c.val);
+ }
+}
+
+void test_matrix_non_numeric_values()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ matrix mtx(2, 2);
+ mtx.set(0, 0, 1.1);
+ mtx.set(1, 0, formula_error_t::division_by_zero);
+ mtx.set(0, 1, std::string("foo"));
+ mtx.set(1, 1, true);
+
+ assert(mtx.get_numeric(0, 0) == 1.1);
+
+ matrix::element elem = mtx.get(1, 0);
+ assert(elem.type == matrix::element_type::error);
+ assert(std::get<formula_error_t>(elem.value) == formula_error_t::division_by_zero);
+
+ elem = mtx.get(0, 1);
+ assert(elem.type == matrix::element_type::string);
+ assert(std::get<std::string_view>(elem.value) == "foo");
+
+ elem = mtx.get(1, 1);
+ assert(elem.type == matrix::element_type::boolean);
+ assert(std::get<bool>(elem.value) == true);
+}
+
+void test_address()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ {
+ address_t addr(-1, 0, 0, false, false, false);
+ abs_address_t pos(1, 0, 0);
+ abs_address_t abs_addr = addr.to_abs(pos);
+ assert(abs_addr.sheet == 0 && abs_addr.row == 0 && abs_addr.column == 0);
+
+ abs_address_t pos_invalid_sheet(invalid_sheet, 2, 3);
+ auto test = addr.to_abs(pos_invalid_sheet);
+ assert(test.sheet == invalid_sheet);
+ assert(test.row == 2);
+ assert(test.column == 3);
+ }
+
+
+ // Default constructor makes valid address.
+ assert(abs_address_t().valid());
+ assert(abs_range_t().valid());
+
+ // These are invalid addresses.
+ assert(!abs_address_t(abs_address_t::invalid).valid());
+ assert(!abs_range_t(abs_range_t::invalid).valid());
+
+ {
+ abs_range_t range(1, 1, 2, 3, 3);
+ assert(range.first.sheet == 1);
+ assert(range.first.row == 1);
+ assert(range.first.column == 2);
+ assert(range.last.sheet == 1);
+ assert(range.last.row == 3);
+ assert(range.last.column == 4);
+
+ abs_range_t range2(range);
+ assert(range2 == range);
+
+ abs_rc_range_t rc_range(range);
+ assert(rc_range.first.row == 1);
+ assert(rc_range.first.column == 2);
+ assert(rc_range.last.row == 3);
+ assert(rc_range.last.column == 4);
+ }
+}
+
+bool check_formula_expression(
+ model_context& cxt, const formula_name_resolver& resolver, const char* p)
+{
+ size_t n = strlen(p);
+ cout << "testing formula expression '" << p << "'" << endl;
+
+ formula_tokens_t tokens = parse_formula_string(cxt, abs_address_t(), resolver, {p, n});
+ std::string expression = print_formula_tokens(cxt, abs_address_t(), resolver, tokens);
+
+ int res = strcmp(p, expression.data());
+ if (res)
+ {
+ cout << "formula expressions differ: '" << p << "' (before) -> '" << expression << "' (after)" << endl;
+ return false;
+ }
+
+ std::ostringstream os;
+ for (const auto& t : tokens)
+ os << print_formula_token(cxt, abs_address_t(), resolver, t);
+ std::string individual_tokens = os.str();
+
+ if (expression != individual_tokens)
+ {
+ cout << "whole expression differs from individual token strings:" << endl
+ << " * expression='" << expression << "'" << endl
+ << " * individual-tokens='" << individual_tokens << "'" << endl;
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Make sure the public API works as advertized.
+ */
+void test_parse_and_print_expressions()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ // Excel A1
+
+ std::vector<const char*> exps = {
+ "\" \"",
+ "1/3*1.4",
+ "2.3*(1+2)/(34*(3-2))",
+ "SUM(1,2,3)",
+ "A1",
+ "B10",
+ "XFD1048576",
+ "C10:D20",
+ "A1:XFD1048576",
+ "H:H",
+ "B:D",
+ "AB:AD",
+ "2:2",
+ "3:5",
+ "34:36",
+ "1>2",
+ "1>=2",
+ "1<2",
+ "1<=2",
+ "1<>2",
+ "1=2",
+ "Table1[Category]",
+ "Table1[Value]",
+ "Table1[#Headers]",
+ "Table1[[#Headers],[Category]:[Value]]",
+ "Table1[[#Headers],[#Data],[Category]:[Value]]",
+ "IF(A1=\"\",\"empty\",\"not empty\")",
+ "$'Ying & Yang'.$A$1:$H$54",
+ };
+
+ model_context cxt;
+ cxt.append_sheet("Test");
+ cxt.append_string("Table1");
+ cxt.append_string("Category");
+ cxt.append_string("Value");
+ cxt.append_sheet("Ying & Yang"); // name with '&'
+
+ auto resolver = formula_name_resolver::get(formula_name_resolver_t::excel_a1, &cxt);
+ assert(resolver);
+
+ for (const char* exp : exps)
+ {
+ bool result = check_formula_expression(cxt, *resolver, exp);
+ assert(result);
+ }
+
+ // Excel R1C1
+
+ exps = {
+ "SUM(R[-5]C:R[-1]C)",
+ };
+
+ resolver = formula_name_resolver::get(formula_name_resolver_t::excel_r1c1, &cxt);
+ assert(resolver);
+
+ for (const char* exp : exps)
+ {
+ bool result = check_formula_expression(cxt, *resolver, exp);
+ assert(result);
+ }
+
+ // ODFF
+
+ exps = {
+ "\" \"",
+ "SUM([.A1];[.B1])",
+ "CONCATENATE([.A6];\" \";[.B6])",
+ "IF(['Ying & Yang'.$A$1:.$O$200];2;0)",
+ };
+
+ resolver = formula_name_resolver::get(formula_name_resolver_t::odff, &cxt);
+ assert(resolver);
+
+ config cfg = cxt.get_config();
+ cfg.sep_function_arg = ';';
+ cxt.set_config(cfg);
+
+ for (const char* exp : exps)
+ {
+ bool result = check_formula_expression(cxt, *resolver, exp);
+ assert(result);
+ }
+}
+
+/**
+ * Function name must be resolved case-insensitively.
+ */
+void test_function_name_resolution()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ const char* valid_names[] = {
+ "SUM", "sum", "Sum", "Average", "max", "min"
+ };
+
+ const char* invalid_names[] = {
+ "suma", "foo", "", "su", "maxx", "minmin"
+ };
+
+ model_context cxt;
+ cxt.append_sheet("Test");
+ auto resolver = formula_name_resolver::get(ixion::formula_name_resolver_t::excel_a1, &cxt);
+ size_t n = std::size(valid_names);
+ for (size_t i = 0; i < n; ++i)
+ {
+ const char* name = valid_names[i];
+ cout << "valid name: " << name << endl;
+ formula_name_t t = resolver->resolve(name, abs_address_t());
+ assert(t.type == formula_name_t::function);
+ }
+
+ n = std::size(invalid_names);
+ for (size_t i = 0; i < n; ++i)
+ {
+ const char* name = invalid_names[i];
+ cout << "invalid name: " << name << endl;
+ formula_name_t t = resolver->resolve(name, abs_address_t());
+ assert(t.type != formula_name_t::function);
+ }
+}
+
+formula_cell* insert_formula(
+ model_context& cxt, const abs_address_t& pos, const char* exp,
+ const formula_name_resolver& resolver)
+{
+ formula_tokens_t tokens = parse_formula_string(cxt, pos, resolver, exp);
+ auto ts = formula_tokens_store::create();
+ ts->get() = std::move(tokens);
+ formula_cell* p_inserted = cxt.set_formula_cell(pos, ts);
+ assert(p_inserted);
+ register_formula_cell(cxt, pos);
+ formula_cell* p = cxt.get_formula_cell(pos);
+ assert(p);
+ assert(p == p_inserted);
+ return p;
+}
+
+void test_model_context_storage()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ {
+ model_context cxt;
+ auto resolver = formula_name_resolver::get(formula_name_resolver_t::excel_a1, &cxt);
+ assert(resolver);
+
+ cxt.append_sheet("test");
+
+ // Test empty cell access.
+ cell_access ca = cxt.get_cell_access(abs_address_t(0, 0, 0));
+ assert(ca.get_type() == celltype_t::empty);
+ assert(ca.get_value_type() == cell_value_t::empty);
+
+ // String value on an empty cell should be an empty string.
+ std::string_view s = ca.get_string_value();
+ assert(s.empty());
+
+ // Likewise...
+ s = cxt.get_string_value(abs_address_t(0, 0, 0));
+ assert(s.empty());
+
+ // Test storage of numeric values.
+ volatile double val = 0.1;
+ for (col_t col = 0; col < 3; ++col)
+ {
+ for (row_t row = 0; row < 3; ++row)
+ {
+ abs_address_t pos(0, row, col);
+ cxt.set_numeric_cell(pos, val);
+ double test = cxt.get_numeric_value(pos);
+ assert(test == val);
+
+ ca = cxt.get_cell_access(pos);
+ assert(ca.get_type() == celltype_t::numeric);
+ assert(ca.get_value_type() == cell_value_t::numeric);
+ test = ca.get_numeric_value();
+ assert(test == val);
+
+ val += 0.2;
+ }
+ }
+
+ // Test formula cells.
+ abs_address_t pos(0,3,0);
+ const char* exp = "SUM(1,2,3)";
+ formula_tokens_t tokens = parse_formula_string(cxt, pos, *resolver, exp);
+ auto ts = formula_tokens_store::create();
+ ts->get() = std::move(tokens);
+ formula_cell* p_inserted = cxt.set_formula_cell(pos, ts);
+ assert(p_inserted);
+ formula_cell* p = cxt.get_formula_cell(pos);
+ assert(p);
+ assert(p_inserted == p);
+ p->interpret(cxt, pos);
+
+ ca = cxt.get_cell_access(pos);
+ assert(ca.get_type() == celltype_t::formula);
+ assert(ca.get_value_type() == cell_value_t::numeric);
+ assert(ca.get_numeric_value() == 6.0);
+ }
+
+ {
+ model_context cxt;
+ auto resolver = formula_name_resolver::get(formula_name_resolver_t::excel_a1, &cxt);
+ assert(resolver);
+
+ cxt.append_sheet("test");
+ string exp = "1";
+ cxt.set_formula_cell(abs_address_t(0,0,0), parse_formula_string(cxt, abs_address_t(0,0,0), *resolver, exp));
+ cxt.set_formula_cell(abs_address_t(0,2,0), parse_formula_string(cxt, abs_address_t(0,2,0), *resolver, exp));
+ cxt.set_formula_cell(abs_address_t(0,1,0), parse_formula_string(cxt, abs_address_t(0,1,0), *resolver, exp));
+ }
+
+ {
+ // Test data area.
+ model_context cxt;
+ cxt.append_sheet("test");
+
+ abs_range_t area = cxt.get_data_range(0);
+ assert(!area.valid());
+
+ cxt.set_numeric_cell(abs_address_t(0, 6, 5), 1.1);
+ area = cxt.get_data_range(0);
+ assert(area.first == area.last);
+ assert(area.first.sheet == 0);
+ assert(area.first.row == 6);
+ assert(area.first.column == 5);
+
+ cxt.set_numeric_cell(abs_address_t(0, 2, 3), 1.1);
+ area = cxt.get_data_range(0);
+ assert(area.first.sheet == 0);
+ assert(area.first.row == 2);
+ assert(area.first.column == 3);
+ assert(area.last.sheet == 0);
+ assert(area.last.row == 6);
+ assert(area.last.column == 5);
+
+ cxt.set_numeric_cell(abs_address_t(0, 7, 1), 1.1);
+ area = cxt.get_data_range(0);
+ assert(area.first.sheet == 0);
+ assert(area.first.row == 2);
+ assert(area.first.column == 1);
+ assert(area.last.sheet == 0);
+ assert(area.last.row == 7);
+ assert(area.last.column == 5);
+
+ // This shouldn't change the data range.
+ cxt.set_numeric_cell(abs_address_t(0, 5, 5), 1.1);
+ abs_range_t test = cxt.get_data_range(0);
+ assert(test == area);
+ }
+
+ {
+ // Fill up the document model and make sure the data range is still
+ // correct.
+ const row_t row_size = 5;
+ const col_t col_size = 4;
+ model_context cxt({row_size, col_size});
+ cxt.append_sheet("test");
+ for (row_t row = 0; row < row_size; ++row)
+ for (col_t col = 0; col < col_size; ++col)
+ cxt.set_numeric_cell(abs_address_t(0,row,col), 1.0);
+
+ abs_range_t test = cxt.get_data_range(0);
+
+ assert(test.first.sheet == 0);
+ assert(test.first.row == 0);
+ assert(test.first.column == 0);
+ assert(test.last.sheet == 0);
+ assert(test.last.row == row_size-1);
+ assert(test.last.column == col_size-1);
+ }
+
+ {
+ const row_t row_size = 5;
+ const col_t col_size = 4;
+ model_context cxt({row_size, col_size});
+ cxt.append_sheet("test");
+ cxt.set_numeric_cell(abs_address_t(0,0,0), 1.0);
+ cxt.set_numeric_cell(abs_address_t(0,row_size-1,0), 1.0);
+ cxt.set_numeric_cell(abs_address_t(0,row_size/2,col_size/2), 1.0);
+
+ abs_range_t test = cxt.get_data_range(0);
+
+ assert(test.first.sheet == 0);
+ assert(test.first.row == 0);
+ assert(test.first.column == 0);
+ assert(test.last.sheet == 0);
+ assert(test.last.row == row_size-1);
+ assert(test.last.column == col_size/2);
+ }
+}
+
+void test_model_context_direct_string_access()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ model_context cxt{{400, 20}};
+ cxt.append_sheet("test");
+
+ // regular string cell
+ abs_address_t B2(0, 1, 1);
+ cxt.set_string_cell(B2, "string cell");
+ std::string_view s = cxt.get_string_value(B2);
+ assert(s == "string cell");
+
+ cell_access ca = cxt.get_cell_access(B2);
+ assert(ca.get_type() == celltype_t::string);
+ assert(ca.get_value_type() == cell_value_t::string);
+ s = ca.get_string_value();
+ assert(s == "string cell");
+
+ // formula cell containing a string result.
+ abs_address_t C4(0, 3, 2);
+ auto resolver = formula_name_resolver::get(formula_name_resolver_t::calc_a1, &cxt);
+ assert(resolver);
+
+ // Insert a formula containing one literal string token.
+ formula_tokens_t tokens = parse_formula_string(cxt, C4, *resolver, "\"string value in formula\"");
+ assert(tokens.size() == 1);
+ cxt.set_formula_cell(C4, std::move(tokens));
+ // no need to register formula cell since it does not reference other cells.
+
+ abs_range_set_t formula_cells{C4};
+ auto sorted = query_and_sort_dirty_cells(cxt, abs_range_set_t(), &formula_cells);
+ calculate_sorted_cells(cxt, sorted, 1);
+
+ s = cxt.get_string_value(C4);
+ assert(s == "string value in formula");
+
+ ca = cxt.get_cell_access(C4);
+ assert(ca.get_type() == celltype_t::formula);
+ assert(ca.get_value_type() == cell_value_t::string);
+ s = ca.get_string_value();
+ assert(s == "string value in formula");
+}
+
+void test_model_context_named_expression()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ model_context cxt{{400, 20}};
+ cxt.append_sheet("test");
+ auto resolver = formula_name_resolver::get(formula_name_resolver_t::calc_a1, &cxt);
+ assert(resolver);
+
+ abs_address_t B3(0, 2, 1);
+
+ struct test_case
+ {
+ std::string name;
+ std::string formula;
+ abs_address_t origin;
+ };
+
+ std::vector<test_case> tcs = {
+ { "LeftAndAbove", "A3+B2", B3 },
+ { "SumAboveRow", "SUM(A2:D2)", B3 },
+ };
+
+ for (const test_case& tc : tcs)
+ {
+ formula_tokens_t tokens = parse_formula_string(cxt, tc.origin, *resolver, tc.formula);
+ std::string test = print_formula_tokens(cxt, tc.origin, *resolver, tokens);
+ assert(test == tc.formula);
+
+ cxt.set_named_expression(tc.name, tc.origin, std::move(tokens));
+ }
+
+ for (const test_case& tc : tcs)
+ {
+ const named_expression_t* exp = cxt.get_named_expression(0, tc.name);
+ assert(exp);
+ assert(exp->origin == tc.origin);
+ std::string test = print_formula_tokens(cxt, exp->origin, *resolver, exp->tokens);
+ assert(test == tc.formula);
+ }
+
+ // invalid names should be rejected.
+ struct name_test_case
+ {
+ std::string name;
+ bool valid;
+ };
+
+ std::vector<name_test_case> invalid_names = {
+ { "Name 1", false },
+ { "Name_1", true },
+ { "123Name", false },
+ { "Name123", true },
+ { "", false },
+ { "Name.1", true },
+ { ".Name.2", false },
+ };
+
+ for (const name_test_case& tc : invalid_names)
+ {
+ abs_address_t origin;
+ std::string formula = "1+2";
+
+ if (tc.valid)
+ {
+ formula_tokens_t tokens = parse_formula_string(cxt, origin, *resolver, formula);
+ cxt.set_named_expression(tc.name, origin, std::move(tokens));
+
+ tokens = parse_formula_string(cxt, origin, *resolver, formula);
+ cxt.set_named_expression(0, tc.name, origin, std::move(tokens));
+ }
+ else
+ {
+ try
+ {
+ formula_tokens_t tokens = parse_formula_string(cxt, origin, *resolver, formula);
+ cxt.set_named_expression(tc.name, origin, std::move(tokens));
+ assert(!"named expression with invalid name should have been rejected!");
+ }
+ catch (const model_context_error& e)
+ {
+ assert(e.get_error_type() == model_context_error::invalid_named_expression);
+ }
+
+ try
+ {
+ formula_tokens_t tokens = parse_formula_string(cxt, origin, *resolver, formula);
+ cxt.set_named_expression(0, tc.name, origin, std::move(tokens));
+ assert(!"named expression with invalid name should have been rejected!");
+ }
+ catch (const model_context_error& e)
+ {
+ assert(e.get_error_type() == model_context_error::invalid_named_expression);
+ }
+ }
+ }
+}
+
+bool check_model_iterator_output(model_iterator& iter, const std::vector<model_iterator::cell>& checks)
+{
+ for (const model_iterator::cell& c : checks)
+ {
+ if (!iter.has())
+ {
+ cerr << "a cell value was expected, but none found." << endl;
+ return false;
+ }
+
+ if (iter.get() != c)
+ {
+ cerr << "unexpected cell value: expected=" << c << "; observed=" << iter.get() << endl;
+ return false;
+ }
+
+ iter.next();
+ }
+
+ if (iter.has())
+ {
+ cerr << "an additional cell value was found, but none was expected." << endl;
+ return false;
+ }
+
+ return true;
+}
+
+void test_model_context_iterator_horizontal()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ const row_t row_size = 5;
+ const col_t col_size = 2;
+ model_context cxt{{row_size, col_size}};
+ model_iterator iter;
+
+ abs_rc_range_t whole_range;
+ whole_range.set_all_columns();
+ whole_range.set_all_rows();
+
+ // It should not crash or throw an exception on empty model.
+ iter = cxt.get_model_iterator(0, rc_direction_t::horizontal, whole_range);
+ assert(!iter.has());
+
+ // Insert an actual sheet and try again.
+
+ cxt.append_sheet("empty sheet");
+ iter = cxt.get_model_iterator(0, rc_direction_t::horizontal, whole_range);
+
+ // Make sure the cell position iterates correctly.
+ size_t cell_count = 0;
+ for (row_t row = 0; row < row_size; ++row)
+ {
+ for (col_t col = 0; col < col_size; ++cell_count, ++col, iter.next())
+ {
+ assert(iter.has());
+ assert(iter.get().row == row);
+ assert(iter.get().col == col);
+ assert(iter.get().type == celltype_t::empty);
+ }
+ }
+
+ assert(!iter.has()); // There should be no more cells on this sheet.
+ assert(cell_count == 10);
+
+ cxt.append_sheet("values");
+ cxt.set_string_cell(abs_address_t(1, 0, 0), "F1");
+ cxt.set_string_cell(abs_address_t(1, 0, 1), "F2");
+ cxt.set_boolean_cell(abs_address_t(1, 1, 0), true);
+ cxt.set_boolean_cell(abs_address_t(1, 1, 1), false);
+ cxt.set_numeric_cell(abs_address_t(1, 2, 0), 3.14);
+ cxt.set_numeric_cell(abs_address_t(1, 2, 1), -12.5);
+
+ auto resolver = formula_name_resolver::get(formula_name_resolver_t::excel_a1, &cxt);
+ abs_range_set_t modified_cells;
+ abs_address_t pos(1, 3, 0);
+ formula_tokens_t tokens = parse_formula_string(cxt, pos, *resolver, "SUM(1, 2, 3)");
+ formula_cell* p = cxt.set_formula_cell(pos, std::move(tokens));
+ assert(p);
+ const formula_tokens_t& t = p->get_tokens()->get();
+ assert(t.size() == 8); // there should be 8 tokens.
+ register_formula_cell(cxt, pos, p);
+ modified_cells.insert(pos);
+
+ pos.column = 1;
+ tokens = parse_formula_string(cxt, pos, *resolver, "5 + 6 - 7");
+ p = cxt.set_formula_cell(pos, std::move(tokens));
+ register_formula_cell(cxt, pos, p);
+ modified_cells.insert(pos);
+
+ // Calculate the formula cells.
+ auto sorted = query_and_sort_dirty_cells(cxt, abs_range_set_t(), &modified_cells);
+ calculate_sorted_cells(cxt, sorted, 1);
+
+ std::vector<model_iterator::cell> checks =
+ {
+ // row, column, value
+ { 0, 0, cxt.get_identifier_from_string("F1") },
+ { 0, 1, cxt.get_identifier_from_string("F2") },
+ { 1, 0, true },
+ { 1, 1, false },
+ { 2, 0, 3.14 },
+ { 2, 1, -12.5 },
+ { 3, 0, cxt.get_formula_cell(abs_address_t(1, 3, 0)) },
+ { 3, 1, cxt.get_formula_cell(abs_address_t(1, 3, 1)) },
+ { 4, 0 },
+ { 4, 1 },
+ };
+
+ // Iterator and check the individual cell values.
+ iter = cxt.get_model_iterator(1, rc_direction_t::horizontal, whole_range);
+ assert(check_model_iterator_output(iter, checks));
+}
+
+void test_model_context_iterator_horizontal_range()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ nullptr_t empty = nullptr;
+ model_context cxt{{10, 5}};
+ cxt.append_sheet("Values");
+ cxt.set_cell_values(0, {
+ { "F1", "F2", "F3", "F4", "F5" },
+ { 1.0, true, "s1", empty, empty },
+ { 1.1, false, empty, "s2", empty },
+ { 1.2, false, empty, "s3", empty },
+ { 1.3, true, empty, "s4", empty },
+ { 1.4, false, empty, "s5", empty },
+ { 1.5, "NA", empty, "s6", empty },
+ { 1.6, 99.9, empty, "s7", empty },
+ { 1.7, 199.9, empty, "s8", empty },
+ { 1.8, 299.9, empty, "s9", "end" },
+ });
+
+ // Only iterate over the first two rows.
+ abs_rc_range_t range;
+ range.set_all_columns();
+ range.first.row = 0;
+ range.last.row = 1;
+
+ model_iterator iter = cxt.get_model_iterator(0, rc_direction_t::horizontal, range);
+
+ std::vector<model_iterator::cell> checks =
+ {
+ // row, column, value
+ { 0, 0, cxt.get_identifier_from_string("F1") },
+ { 0, 1, cxt.get_identifier_from_string("F2") },
+ { 0, 2, cxt.get_identifier_from_string("F3") },
+ { 0, 3, cxt.get_identifier_from_string("F4") },
+ { 0, 4, cxt.get_identifier_from_string("F5") },
+ { 1, 0, 1.0 },
+ { 1, 1, true },
+ { 1, 2, cxt.get_identifier_from_string("s1") },
+ { 1, 3 },
+ { 1, 4 },
+ };
+
+ assert(check_model_iterator_output(iter, checks));
+
+ // Only iterate over rows 2:4.
+ range.first.row = 2;
+ range.last.row = 4;
+ iter = cxt.get_model_iterator(0, rc_direction_t::horizontal, range);
+
+ checks =
+ {
+ // row, column, value
+ { 2, 0, 1.1 },
+ { 2, 1, false },
+ { 2, 2 },
+ { 2, 3, cxt.get_identifier_from_string("s2") },
+ { 2, 4 },
+ { 3, 0, 1.2 },
+ { 3, 1, false },
+ { 3, 2 },
+ { 3, 3, cxt.get_identifier_from_string("s3") },
+ { 3, 4 },
+ { 4, 0, 1.3 },
+ { 4, 1, true },
+ { 4, 2 },
+ { 4, 3, cxt.get_identifier_from_string("s4") },
+ { 4, 4 },
+ };
+
+ assert(check_model_iterator_output(iter, checks));
+
+ // Only iterate over columns 1:3 and only down to row 4.
+ range.set_all_rows();
+ range.first.column = 1;
+ range.last.column = 3;
+ range.last.row = 4;
+ iter = cxt.get_model_iterator(0, rc_direction_t::horizontal, range);
+
+ checks =
+ {
+ // row, column, value
+ { 0, 1, cxt.get_identifier_from_string("F2") },
+ { 0, 2, cxt.get_identifier_from_string("F3") },
+ { 0, 3, cxt.get_identifier_from_string("F4") },
+ { 1, 1, true },
+ { 1, 2, cxt.get_identifier_from_string("s1") },
+ { 1, 3 },
+ { 2, 1, false },
+ { 2, 2 },
+ { 2, 3, cxt.get_identifier_from_string("s2") },
+ { 3, 1, false },
+ { 3, 2 },
+ { 3, 3, cxt.get_identifier_from_string("s3") },
+ { 4, 1, true },
+ { 4, 2 },
+ { 4, 3, cxt.get_identifier_from_string("s4") },
+ };
+
+ assert(check_model_iterator_output(iter, checks));
+}
+
+void test_model_context_iterator_vertical()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ const row_t row_size = 5;
+ const col_t col_size = 2;
+ model_context cxt{{row_size, col_size}};
+ model_iterator iter;
+
+ abs_rc_range_t whole_range;
+ whole_range.set_all_columns();
+ whole_range.set_all_rows();
+
+ // It should not crash or throw an exception on empty model.
+ iter = cxt.get_model_iterator(0, rc_direction_t::vertical, whole_range);
+ assert(!iter.has());
+
+ // Insert an actual sheet and try again.
+
+ cxt.append_sheet("empty sheet");
+ iter = cxt.get_model_iterator(0, rc_direction_t::vertical, whole_range);
+
+ // Make sure the cell position iterates correctly.
+ size_t cell_count = 0;
+ for (col_t col = 0; col < col_size; ++col)
+ {
+ for (row_t row = 0; row < row_size; ++cell_count, ++row, iter.next())
+ {
+ const model_iterator::cell& cell = iter.get();
+ assert(iter.has());
+ assert(cell.row == row);
+ assert(cell.col == col);
+ assert(cell.type == celltype_t::empty);
+ }
+ }
+
+ assert(!iter.has()); // There should be no more cells on this sheet.
+ assert(cell_count == 10);
+
+ cxt.append_sheet("values");
+ cxt.set_string_cell(abs_address_t(1, 0, 0), "F1");
+ cxt.set_string_cell(abs_address_t(1, 0, 1), "F2");
+ cxt.set_boolean_cell(abs_address_t(1, 1, 0), true);
+ cxt.set_boolean_cell(abs_address_t(1, 1, 1), false);
+ cxt.set_numeric_cell(abs_address_t(1, 2, 0), 3.14);
+ cxt.set_numeric_cell(abs_address_t(1, 2, 1), -12.5);
+
+ auto resolver = formula_name_resolver::get(formula_name_resolver_t::excel_a1, &cxt);
+ abs_range_set_t modified_cells;
+ abs_address_t pos(1, 3, 0);
+ formula_tokens_t tokens = parse_formula_string(cxt, pos, *resolver, "SUM(1, 2, 3)");
+ cxt.set_formula_cell(pos, std::move(tokens));
+ register_formula_cell(cxt, pos);
+ modified_cells.insert(pos);
+
+ pos.column = 1;
+ tokens = parse_formula_string(cxt, pos, *resolver, "5 + 6 - 7");
+ cxt.set_formula_cell(pos, std::move(tokens));
+ register_formula_cell(cxt, pos);
+ modified_cells.insert(pos);
+
+ // Calculate the formula cells.
+ auto sorted = query_and_sort_dirty_cells(cxt, abs_range_set_t(), &modified_cells);
+ calculate_sorted_cells(cxt, sorted, 1);
+
+ std::vector<model_iterator::cell> checks =
+ {
+ // row, column, value
+ { 0, 0, cxt.get_identifier_from_string("F1") },
+ { 1, 0, true },
+ { 2, 0, 3.14 },
+ { 3, 0, cxt.get_formula_cell(abs_address_t(1, 3, 0)) },
+ { 4, 0 },
+
+ { 0, 1, cxt.get_identifier_from_string("F2") },
+ { 1, 1, false },
+ { 2, 1, -12.5 },
+ { 3, 1, cxt.get_formula_cell(abs_address_t(1, 3, 1)) },
+ { 4, 1 },
+ };
+
+ iter = cxt.get_model_iterator(1, rc_direction_t::vertical, whole_range);
+ assert(check_model_iterator_output(iter, checks));
+}
+
+void test_model_context_iterator_vertical_range()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ nullptr_t empty = nullptr;
+ model_context cxt{{10, 5}};
+ cxt.append_sheet("Values");
+ cxt.set_cell_values(0, {
+ { "F1", "F2", "F3", "F4", "F5" },
+ { 1.0, true, "s1", empty, empty },
+ { 1.1, false, empty, "s2", empty },
+ { 1.2, false, empty, "s3", empty },
+ { 1.3, true, empty, "s4", empty },
+ { 1.4, false, empty, "s5", empty },
+ { 1.5, "NA", empty, "s6", empty },
+ { 1.6, 99.9, empty, "s7", empty },
+ { 1.7, 199.9, empty, "s8", empty },
+ { 1.8, 299.9, empty, "s9", "end" },
+ });
+
+ // Iterate over the top 2 rows.
+ abs_rc_range_t range;
+ range.set_all_columns();
+ range.set_all_rows();
+ range.last.row = 1;
+
+ model_iterator iter = cxt.get_model_iterator(0, rc_direction_t::vertical, range);
+
+ std::vector<model_iterator::cell> checks =
+ {
+ // row, column, value
+ { 0, 0, cxt.get_identifier_from_string("F1") },
+ { 1, 0, 1.0 },
+ { 0, 1, cxt.get_identifier_from_string("F2") },
+ { 1, 1, true },
+ { 0, 2, cxt.get_identifier_from_string("F3") },
+ { 1, 2, cxt.get_identifier_from_string("s1") },
+ { 0, 3, cxt.get_identifier_from_string("F4") },
+ { 1, 3 },
+ { 0, 4, cxt.get_identifier_from_string("F5") },
+ { 1, 4 },
+ };
+
+ assert(check_model_iterator_output(iter, checks));
+
+ // Iterate over the bottom 2 rows.
+
+ range.set_all_rows();
+ range.first.row = 8;
+ iter = cxt.get_model_iterator(0, rc_direction_t::vertical, range);
+
+ checks =
+ {
+ // row, column, value
+ { 8, 0, 1.7 },
+ { 9, 0, 1.8 },
+ { 8, 1, 199.9 },
+ { 9, 1, 299.9 },
+ { 8, 2 },
+ { 9, 2 },
+ { 8, 3, cxt.get_identifier_from_string("s8") },
+ { 9, 3, cxt.get_identifier_from_string("s9") },
+ { 8, 4 },
+ { 9, 4, cxt.get_identifier_from_string("end") },
+ };
+
+ assert(check_model_iterator_output(iter, checks));
+
+ // Iterate over the bottom-left corners.
+ range.last.column = 2;
+ iter = cxt.get_model_iterator(0, rc_direction_t::vertical, range);
+
+ checks =
+ {
+ // row, column, value
+ { 8, 0, 1.7 },
+ { 9, 0, 1.8 },
+ { 8, 1, 199.9 },
+ { 9, 1, 299.9 },
+ { 8, 2 },
+ { 9, 2 },
+ };
+
+ assert(check_model_iterator_output(iter, checks));
+
+ // Iterate over the top-right corners.
+ range.first.column = 3;
+ range.last.column = column_unset;
+ range.first.row = row_unset;
+ range.last.row = 1;
+ iter = cxt.get_model_iterator(0, rc_direction_t::vertical, range);
+
+ checks =
+ {
+ { 0, 3, cxt.get_identifier_from_string("F4") },
+ { 1, 3 },
+ { 0, 4, cxt.get_identifier_from_string("F5") },
+ { 1, 4 },
+ };
+
+ assert(check_model_iterator_output(iter, checks));
+
+ // Iterate over only one cell in the middle.
+ range.first.row = 5;
+ range.last.row = 5;
+ range.first.column = 3;
+ range.last.column = 3;
+
+ iter = cxt.get_model_iterator(0, rc_direction_t::vertical, range);
+ checks =
+ {
+ { 5, 3, cxt.get_identifier_from_string("s5") },
+ };
+
+ assert(check_model_iterator_output(iter, checks));
+}
+
+void test_model_context_iterator_named_exps()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ struct check
+ {
+ std::string name;
+ const named_expression_t* exp;
+ };
+
+ model_context cxt{{100, 10}};
+ cxt.append_sheet("test1");
+ cxt.append_sheet("test2");
+
+ named_expressions_iterator iter;
+ assert(!iter.has());
+ assert(iter.size() == 0);
+
+ iter = cxt.get_named_expressions_iterator();
+ assert(!iter.has());
+ assert(iter.size() == 0);
+
+ auto resolver = formula_name_resolver::get(formula_name_resolver_t::calc_a1, &cxt);
+ assert(resolver);
+
+ auto tokenize = [&](const char* p) -> formula_tokens_t
+ {
+ return parse_formula_string(cxt, abs_address_t(), *resolver, p);
+ };
+
+ auto validate = [](named_expressions_iterator _iter, const std::vector<check>& _expected) -> bool
+ {
+ if (_iter.size() != _expected.size())
+ {
+ cout << "iterator's size() returns wrong value." << endl;
+ return false;
+ }
+
+ for (const check& c : _expected)
+ {
+ if (!_iter.has())
+ {
+ cout << "iterator has no more element, but it is expected to." << endl;
+ return false;
+ }
+
+ if (c.name != *_iter.get().name)
+ {
+ cout << "names differ: expected='" << c.name << "'; actual='" << *_iter.get().name << endl;
+ return false;
+ }
+
+ if (c.exp != _iter.get().expression)
+ {
+ cout << "expressions differ." << endl;
+ return false;
+ }
+
+ _iter.next();
+ }
+
+ if (_iter.has())
+ {
+ cout << "the iterator has more elements, but it is not expected to." << endl;
+ return false;
+ }
+
+ return true;
+ };
+
+ cxt.set_named_expression("MyCalc", tokenize("(1+2)/3"));
+
+ std::vector<check> expected =
+ {
+ { "MyCalc", cxt.get_named_expression(0, "MyCalc") },
+ };
+
+ iter = cxt.get_named_expressions_iterator();
+ assert(validate(iter, expected));
+
+ cxt.set_named_expression("RefToRight", tokenize("B1"));
+
+ expected =
+ {
+ { "MyCalc", cxt.get_named_expression(0, "MyCalc") },
+ { "RefToRight", cxt.get_named_expression(0, "RefToRight") },
+ };
+
+ iter = cxt.get_named_expressions_iterator();
+ assert(validate(iter, expected));
+
+ cxt.set_named_expression(1, "MyCalc2", tokenize("(B1+C1)/D1"));
+ cxt.set_named_expression(1, "MyCalc3", tokenize("B1/(PI()*2)"));
+
+ iter = cxt.get_named_expressions_iterator(0);
+ assert(!iter.has());
+
+ iter = cxt.get_named_expressions_iterator(1);
+
+ expected =
+ {
+ { "MyCalc2", cxt.get_named_expression(1, "MyCalc2") },
+ { "MyCalc3", cxt.get_named_expression(1, "MyCalc3") },
+ };
+
+ assert(validate(iter, expected));
+}
+
+void test_model_context_fill_down()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ nullptr_t empty = nullptr;
+ model_context cxt{{100, 10}};
+ cxt.append_sheet("test");
+ cxt.set_cell_values(0, {
+ { "numeric", "bool", "string", "empty" },
+ { 12.3, true, "foo", empty },
+ { empty, empty, empty, 1.1 },
+ { empty, empty, empty, 1.1 },
+ { empty, empty, empty, 1.1 },
+ { empty, empty, empty, 1.1 },
+ { empty, empty, empty, 1.1 },
+ });
+
+ abs_address_t pos(0, 1, 0);
+ cxt.fill_down_cells(pos, 2);
+
+ assert(cxt.get_numeric_value(abs_address_t(0, 1, 0)) == 12.3);
+ assert(cxt.get_numeric_value(abs_address_t(0, 2, 0)) == 12.3);
+ assert(cxt.get_numeric_value(abs_address_t(0, 3, 0)) == 12.3);
+ assert(cxt.is_empty(abs_address_t(0, 4, 0)));
+
+ pos.column = 1;
+ cxt.fill_down_cells(pos, 1);
+ assert(cxt.get_boolean_value(abs_address_t(0, 1, 1)) == true);
+ assert(cxt.get_boolean_value(abs_address_t(0, 2, 1)) == true);
+ assert(cxt.is_empty(abs_address_t(0, 3, 1)));
+
+ pos.column = 2;
+ string_id_t s_foo = cxt.get_string_identifier(pos);
+ const std::string* p = cxt.get_string(s_foo);
+ assert(p && *p == "foo");
+ cxt.fill_down_cells(pos, 3);
+ assert(cxt.get_string_identifier(abs_address_t(0, 2, 2)) == s_foo);
+ assert(cxt.get_string_identifier(abs_address_t(0, 3, 2)) == s_foo);
+ assert(cxt.get_string_identifier(abs_address_t(0, 4, 2)) == s_foo);
+ assert(cxt.is_empty(abs_address_t(0, 5, 2)));
+
+ pos.column = 3;
+ cxt.fill_down_cells(pos, 2);
+ assert(cxt.is_empty(pos));
+ assert(cxt.is_empty(abs_address_t(0, 2, 3)));
+ assert(cxt.is_empty(abs_address_t(0, 3, 3)));
+ assert(cxt.get_numeric_value(abs_address_t(0, 4, 3)) == 1.1);
+}
+
+void test_model_context_error_value()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ model_context cxt{{100, 10}};
+ cxt.append_sheet("test");
+
+ auto resolver = formula_name_resolver::get(formula_name_resolver_t::excel_a1, &cxt);
+ assert(resolver);
+
+ abs_address_t pos(0,3,0);
+ const char* exp = "10/0";
+ formula_tokens_t tokens = parse_formula_string(cxt, pos, *resolver, exp);
+ formula_cell* fc = cxt.set_formula_cell(pos, std::move(tokens));
+ fc->interpret(cxt, pos);
+
+ cell_access ca = cxt.get_cell_access(pos);
+ assert(ca.get_type() == celltype_t::formula);
+ assert(ca.get_value_type() == cell_value_t::error);
+ assert(ca.get_error_value() == formula_error_t::division_by_zero);
+}
+
+void test_model_context_rename_sheets()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ model_context cxt{{100, 10}};
+ cxt.append_sheet("sheet1");
+ cxt.append_sheet("sheet2");
+ cxt.append_sheet("sheet3");
+
+ assert(cxt.get_sheet_index("sheet1") == 0);
+ assert(cxt.get_sheet_index("sheet2") == 1);
+ assert(cxt.get_sheet_index("sheet3") == 2);
+
+ cxt.set_sheet_name(0, "sheet1"); // Setting it to the current name is a no-op.
+ try
+ {
+ cxt.set_sheet_name(0, "sheet3");
+ assert(!"exception should have been thrown!");
+ }
+ catch (const ixion::model_context_error& e)
+ {
+ assert(e.get_error_type() == ixion::model_context_error::sheet_name_conflict);
+ }
+ catch (...)
+ {
+ assert(!"wrong exception caught");
+ }
+
+ cxt.set_sheet_name(0, "one");
+ cxt.set_sheet_name(1, "two");
+ cxt.set_sheet_name(2, "three");
+
+ assert(cxt.get_sheet_index("one") == 0);
+ assert(cxt.get_sheet_index("two") == 1);
+ assert(cxt.get_sheet_index("three") == 2);
+}
+
+void test_volatile_function()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ model_context cxt{{1048576, 16384}};
+ auto resolver = formula_name_resolver::get(formula_name_resolver_t::excel_a1, &cxt);
+ assert(resolver);
+
+ cxt.append_sheet("test");
+
+ abs_range_set_t dirty_cells;
+ abs_range_set_t modified_cells;
+
+ // Set values into A1:A3.
+ cxt.set_numeric_cell(abs_address_t(0,0,0), 1.0);
+ cxt.set_numeric_cell(abs_address_t(0,1,0), 2.0);
+ cxt.set_numeric_cell(abs_address_t(0,2,0), 3.0);
+
+ // Set formula in A4 that references A1:A3.
+ formula_cell* p = insert_formula(cxt, abs_address_t(0,3,0), "SUM(A1:A3)", *resolver);
+ assert(p);
+ dirty_cells.insert(abs_address_t(0,3,0));
+
+ // Initial full calculation.
+ auto sorted = ixion::query_and_sort_dirty_cells(cxt, modified_cells, &dirty_cells);
+ ixion::calculate_sorted_cells(cxt, sorted, 0);
+
+ double val = cxt.get_numeric_value(abs_address_t(0,3,0));
+ assert(val == 6);
+
+ modified_cells.clear();
+ dirty_cells.clear();
+
+ // Modify the value of A2. This should flag A4 dirty.
+ cxt.set_numeric_cell(abs_address_t(0,1,0), 10.0);
+ modified_cells.insert(abs_address_t(0,1,0));
+ sorted = ixion::query_and_sort_dirty_cells(cxt, modified_cells, &dirty_cells);
+ assert(sorted.size() == 1);
+
+ // Partial recalculation.
+ ixion::calculate_sorted_cells(cxt, sorted, 0);
+
+ val = cxt.get_numeric_value(abs_address_t(0, 3, 0));
+ assert(val == 14);
+
+ modified_cells.clear();
+ dirty_cells.clear();
+
+ // Insert a volatile cell into B1. At this point B1 should be the only dirty cell.
+ p = insert_formula(cxt, abs_address_t(0,0,1), "NOW()", *resolver);
+ assert(p);
+ dirty_cells.insert(abs_address_t(0,0,1));
+ sorted = ixion::query_and_sort_dirty_cells(cxt, modified_cells, &dirty_cells);
+ assert(sorted.size() == 1);
+
+ // Partial recalc again.
+ ixion::calculate_sorted_cells(cxt, sorted, 0);
+ double t1 = cxt.get_numeric_value(abs_address_t(0,0,1));
+
+ // Pause for 0.2 second.
+ std::this_thread::sleep_for(std::chrono::milliseconds(200));
+
+ // No modification, but B1 should still be flagged dirty.
+ modified_cells.clear();
+ dirty_cells.clear();
+
+ sorted = ixion::query_and_sort_dirty_cells(cxt, modified_cells, &dirty_cells);
+ assert(sorted.size() == 1);
+ ixion::calculate_sorted_cells(cxt, sorted, 0);
+ double t2 = cxt.get_numeric_value(abs_address_t(0,0,1));
+ double delta = (t2-t1)*24*60*60;
+ cout << "delta = " << delta << endl;
+
+ // The delta should be close to 0.2. It may be a little larger depending
+ // on the CPU speed.
+ assert(0.2 <= delta && delta <= 0.3);
+}
+
+void test_invalid_formula_tokens()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ model_context cxt;
+ std::string_view invalid_formula("invalid formula");
+ std::string_view error_msg("failed to parse formula");
+
+ formula_tokens_t tokens = create_formula_error_tokens(cxt, invalid_formula, error_msg);
+
+ assert(tokens[0].opcode == fop_error);
+ assert(tokens.size() == (std::get<string_id_t>(tokens[0].value) + 1));
+
+ assert(tokens[1].opcode == fop_string);
+ string_id_t sid = std::get<string_id_t>(tokens[1].value);
+ const std::string* s = cxt.get_string(sid);
+ assert(invalid_formula == *s);
+
+ assert(tokens[2].opcode == fop_string);
+ sid = std::get<string_id_t>(tokens[2].value);
+ s = cxt.get_string(sid);
+ assert(error_msg == *s);
+}
+
+void test_grouped_formula_string_results()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ model_context cxt;
+ cxt.append_sheet("test");
+
+ auto resolver = formula_name_resolver::get(formula_name_resolver_t::excel_a1, &cxt);
+ assert(resolver);
+
+ abs_range_t A1B2(0, 0, 0, 2, 2);
+
+ formula_tokens_t tokens = parse_formula_string(cxt, A1B2.first, *resolver, "\"literal string\"");
+
+ matrix res_value(2, 2, std::string("literal string"));
+ formula_result res(std::move(res_value));
+ cxt.set_grouped_formula_cells(A1B2, std::move(tokens), std::move(res));
+
+ std::string_view s = cxt.get_string_value(A1B2.last);
+ assert(s == "literal string");
+}
+
+} // anonymous namespace
+
+int main()
+{
+ test_size();
+ test_string_to_double();
+ test_string_pool();
+ test_formula_tokens_store();
+ test_matrix();
+ test_matrix_non_numeric_values();
+
+ test_address();
+ test_parse_and_print_expressions();
+ test_function_name_resolution();
+ test_model_context_storage();
+ test_model_context_direct_string_access();
+ test_model_context_named_expression();
+ test_model_context_iterator_horizontal();
+ test_model_context_iterator_horizontal_range();
+ test_model_context_iterator_vertical();
+ test_model_context_iterator_vertical_range();
+ test_model_context_iterator_named_exps();
+ test_model_context_fill_down();
+ test_model_context_error_value();
+ test_model_context_rename_sheets();
+ test_volatile_function();
+ test_invalid_formula_tokens();
+ test_grouped_formula_string_results();
+
+ return EXIT_SUCCESS;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/global.cpp b/src/libixion/global.cpp
new file mode 100644
index 0000000..1100bcb
--- /dev/null
+++ b/src/libixion/global.cpp
@@ -0,0 +1,103 @@
+/* -*- 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 "ixion/global.hpp"
+#include "ixion/address.hpp"
+#include "ixion/matrix.hpp"
+#include "ixion/cell.hpp"
+#include "ixion/exceptions.hpp"
+#include "ixion/formula_result.hpp"
+
+#include <iostream>
+#include <cstdlib>
+#include <sstream>
+#include <fstream>
+#include <chrono>
+
+using namespace std;
+
+namespace ixion {
+
+double get_current_time()
+{
+ unsigned long usec_since_epoch =
+ std::chrono::duration_cast<std::chrono::microseconds>(
+ std::chrono::system_clock::now().time_since_epoch()).count();
+
+ return usec_since_epoch / 1000000.0;
+}
+
+double to_double(std::string_view s)
+{
+ const char* p = s.data();
+ std::size_t n = s.size();
+
+ if (!n)
+ return 0.0;
+
+ // First, use the standard C API.
+ const char* p_last_check = p + n;
+ char* p_last;
+ double val = strtod(p, &p_last);
+ if (p_last == p_last_check)
+ return val;
+
+ // If that fails, do the manual conversion, which may introduce rounding
+ // errors. Revise this to reduce the amount of rounding error.
+ bool dot = false;
+ double frac = 1.0;
+ double sign = 1.0;
+ for (size_t i = 0; i < n; ++i, ++p)
+ {
+ if (i == 0)
+ {
+ if (*p == '+')
+ // Skip this.
+ continue;
+
+ if (*p == '-')
+ {
+ sign = -1.0;
+ continue;
+ }
+ }
+ if (*p == '.')
+ {
+ if (dot)
+ // Second dot is not allowed.
+ break;
+ dot = true;
+ continue;
+ }
+
+ if (*p < '0' || '9' < *p)
+ // not a digit. End the parse.
+ break;
+
+ int digit = *p - '0';
+ if (dot)
+ {
+ frac *= 0.1;
+ val += digit * frac;
+ }
+ else
+ {
+ val *= 10.0;
+ val += digit;
+ }
+ }
+ return sign*val;
+}
+
+bool to_bool(std::string_view s)
+{
+ return s == "true";
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/impl_types.cpp b/src/libixion/impl_types.cpp
new file mode 100644
index 0000000..223f2c5
--- /dev/null
+++ b/src/libixion/impl_types.cpp
@@ -0,0 +1,40 @@
+/* -*- 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 "impl_types.hpp"
+
+#include <stdexcept>
+
+namespace ixion {
+
+resolved_stack_value::resolved_stack_value(matrix v) : m_value(std::move(v)) {}
+resolved_stack_value::resolved_stack_value(double v) : m_value(v) {}
+resolved_stack_value::resolved_stack_value(std::string v) : m_value(std::move(v)) {}
+
+resolved_stack_value::value_type resolved_stack_value::type() const
+{
+ return value_type(m_value.index());
+}
+
+const matrix& resolved_stack_value::get_matrix() const
+{
+ return std::get<matrix>(m_value);
+}
+
+double resolved_stack_value::get_numeric() const
+{
+ return std::get<double>(m_value);
+}
+
+const std::string& resolved_stack_value::get_string() const
+{
+ return std::get<std::string>(m_value);
+}
+
+} // namespace ixion
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/impl_types.hpp b/src/libixion/impl_types.hpp
new file mode 100644
index 0000000..89ead71
--- /dev/null
+++ b/src/libixion/impl_types.hpp
@@ -0,0 +1,69 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include <ixion/matrix.hpp>
+#include <ixion/types.hpp>
+
+#include <variant>
+#include <string>
+
+namespace ixion {
+
+class matrix;
+
+/**
+ * Similar to stack_value but does not store a reference; it only stores a
+ * static value.
+ */
+class resolved_stack_value
+{
+ // Keep the type ordering in sync with value_type's.
+ using store_type = std::variant<matrix, double, std::string>;
+ store_type m_value;
+public:
+
+ enum class value_type { matrix, numeric, string };
+
+ resolved_stack_value(matrix v);
+ resolved_stack_value(double v);
+ resolved_stack_value(std::string v);
+
+ value_type type() const;
+
+ const matrix& get_matrix() const;
+ double get_numeric() const;
+ const std::string& get_string() const;
+};
+
+template<typename T>
+class formula_op_result
+{
+ using store_type = std::variant<T, formula_error_t>;
+ store_type m_value;
+
+public:
+ formula_op_result(T v) : m_value(std::move(v)) {}
+ formula_op_result(formula_error_t err) : m_value(err) {}
+
+ operator bool() const { return m_value.index() == 0; }
+
+ T operator*() const
+ {
+ return std::get<T>(m_value);
+ }
+
+ formula_error_t error() const
+ {
+ return std::get<formula_error_t>(m_value);
+ }
+};
+
+} // namespace ixion
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/info.cpp b/src/libixion/info.cpp
new file mode 100644
index 0000000..0a117ad
--- /dev/null
+++ b/src/libixion/info.cpp
@@ -0,0 +1,41 @@
+/* -*- 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 "ixion/info.hpp"
+
+#include "constants.inl"
+
+namespace ixion {
+
+int get_version_major()
+{
+ return IXION_MAJOR_VERSION;
+}
+
+int get_version_minor()
+{
+ return IXION_MINOR_VERSION;
+}
+
+int get_version_micro()
+{
+ return IXION_MICRO_VERSION;
+}
+
+int get_api_version_major()
+{
+ return IXION_MAJOR_API_VERSION;
+}
+
+int get_api_version_minor()
+{
+ return IXION_MINOR_API_VERSION;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/interface.cpp b/src/libixion/interface.cpp
new file mode 100644
index 0000000..5bf19a1
--- /dev/null
+++ b/src/libixion/interface.cpp
@@ -0,0 +1,19 @@
+/* -*- 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 "ixion/interface/table_handler.hpp"
+#include "ixion/interface/session_handler.hpp"
+
+namespace ixion { namespace iface {
+
+table_handler::~table_handler() {}
+
+session_handler::~session_handler() {}
+
+}}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/ixion_test_track_deps.cpp b/src/libixion/ixion_test_track_deps.cpp
new file mode 100644
index 0000000..95875f6
--- /dev/null
+++ b/src/libixion/ixion_test_track_deps.cpp
@@ -0,0 +1,155 @@
+/* -*- 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 "test_global.hpp" // This must be the first header to be included.
+
+#include <ixion/model_context.hpp>
+#include <ixion/macros.hpp>
+#include <ixion/formula_name_resolver.hpp>
+#include <ixion/formula.hpp>
+
+#include <cassert>
+#include <iostream>
+
+using namespace ixion;
+using namespace std;
+
+void test_single_cell_dependency()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ model_context cxt{{400, 200}};
+ cxt.append_sheet("One");
+
+ auto resolver = formula_name_resolver::get(formula_name_resolver_t::excel_a1, &cxt);
+
+ cxt.set_numeric_cell(abs_address_t(0,0,0), 1.0); // A1
+
+ // A2
+ abs_address_t pos(0,1,0);
+ formula_tokens_t tokens = parse_formula_string(cxt, pos, *resolver, "A1*2");
+ formula_tokens_store_ptr_t store = formula_tokens_store::create();
+ store->get() = std::move(tokens);
+ cxt.set_formula_cell(pos, store);
+ register_formula_cell(cxt, pos);
+
+ // A3
+ pos.row = 2;
+ tokens = parse_formula_string(cxt, pos, *resolver, "A2*2");
+ store = formula_tokens_store::create();
+ store->get() = std::move(tokens);
+ cxt.set_formula_cell(pos, store);
+ register_formula_cell(cxt, pos);
+
+ // If A1 is modified, then both A2 and A3 should get updated.
+ abs_address_set_t mod_cells = {
+ { 0, 0, 0 }
+ };
+
+ abs_address_set_t cells = query_dirty_cells(cxt, mod_cells);
+
+ assert(cells.size() == 2);
+ assert(cells.count(abs_address_t(0,1,0)) == 1);
+ assert(cells.count(abs_address_t(0,2,0)) == 1);
+}
+
+void test_range_dependency()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ model_context cxt{{400, 200}};
+ cxt.append_sheet("One");
+
+ cxt.set_numeric_cell(abs_address_t(0,0,0), 1.0); // A1
+ cxt.set_numeric_cell(abs_address_t(0,0,0), 2.0); // A2
+ cxt.set_numeric_cell(abs_address_t(0,0,0), 3.0); // A3
+
+ cxt.set_numeric_cell(abs_address_t(0,0,2), 4.0); // C1
+ cxt.set_numeric_cell(abs_address_t(0,0,2), 5.0); // D1
+ cxt.set_numeric_cell(abs_address_t(0,0,2), 6.0); // E1
+
+ auto resolver = formula_name_resolver::get(formula_name_resolver_t::excel_a1, &cxt);
+
+ // C5
+ abs_address_t pos(0,4,2);
+ formula_tokens_t tokens = parse_formula_string(cxt, pos, *resolver, "SUM(A1:A3,C1:E1)");
+ auto ts = formula_tokens_store::create();
+ ts->get() = std::move(tokens);
+ cxt.set_formula_cell(pos, ts);
+ register_formula_cell(cxt, pos);
+
+ // A10
+ pos.row = 9;
+ pos.column = 0;
+ tokens = parse_formula_string(cxt, pos, *resolver, "C5*2");
+ ts = formula_tokens_store::create();
+ ts->get() = std::move(tokens);
+ cxt.set_formula_cell(pos, ts);
+ register_formula_cell(cxt, pos);
+
+ // If A1 is modified, both C5 and A10 should get updated.
+ abs_address_set_t addrs = { abs_address_t(0,0,0) };
+ abs_address_set_t cells = query_dirty_cells(cxt, addrs);
+
+ assert(cells.count(abs_address_t(0,4,2)) == 1);
+ assert(cells.count(abs_address_t(0,9,0)) == 1);
+}
+
+void test_matrix_dependency()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ model_context cxt{{400, 200}};
+ cxt.append_sheet("One");
+
+ cxt.set_numeric_cell(abs_address_t(0,0,0), 1.0); // A1
+ cxt.set_numeric_cell(abs_address_t(0,0,0), 2.0); // A2
+ cxt.set_numeric_cell(abs_address_t(0,0,0), 3.0); // A3
+
+ cxt.set_numeric_cell(abs_address_t(0,0,2), 4.0); // C1
+ cxt.set_numeric_cell(abs_address_t(0,0,2), 5.0); // D1
+ cxt.set_numeric_cell(abs_address_t(0,0,2), 6.0); // E1
+
+ abs_range_t range;
+ range.first = abs_address_t(0,4,2); // C5
+ range.last = abs_address_t(0,6,4); // E7
+
+ auto resolver = formula_name_resolver::get(formula_name_resolver_t::excel_a1, &cxt);
+
+ // C5:E7
+ formula_tokens_t tokens = parse_formula_string(
+ cxt, range.first, *resolver, "MMULT(A1:A3,C1:E1)");
+
+ cxt.set_grouped_formula_cells(range, std::move(tokens));
+ register_formula_cell(cxt, range.first); // Register only the top-left cell.
+
+ // A10
+ abs_address_t pos(0,9,0);
+ tokens = parse_formula_string(cxt, pos, *resolver, "C5*2");
+ auto ts = formula_tokens_store::create();
+ ts->get() = std::move(tokens);
+ cxt.set_formula_cell(pos, ts);
+ register_formula_cell(cxt, pos);
+
+ // If A1 is modified, both C5 and A10 should get updated.
+ abs_address_set_t addrs = { abs_address_t(0,0,0) };
+ abs_address_set_t cells = query_dirty_cells(cxt, addrs);
+
+ assert(cells.count(abs_address_t(0,4,2)) == 1);
+ assert(cells.count(abs_address_t(0,9,0)) == 1);
+}
+
+int main()
+{
+ test_single_cell_dependency();
+ test_range_dependency();
+ test_matrix_dependency();
+
+ return EXIT_SUCCESS;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/lexer_tokens.cpp b/src/libixion/lexer_tokens.cpp
new file mode 100644
index 0000000..2f3bef3
--- /dev/null
+++ b/src/libixion/lexer_tokens.cpp
@@ -0,0 +1,156 @@
+/* -*- 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 "lexer_tokens.hpp"
+
+#include <iostream>
+#include <sstream>
+#include <algorithm>
+
+using namespace std;
+
+namespace ixion {
+
+std::string print_tokens(const lexer_tokens_t& tokens, bool verbose)
+{
+ std::ostringstream os;
+
+ for (const auto& t : tokens)
+ {
+ if (verbose)
+ os << "(" << get_opcode_name(t.opcode) << ")'" << t << "' ";
+ else
+ os << t;
+ }
+
+ return os.str();
+}
+
+const char* get_opcode_name(lexer_opcode_t oc)
+{
+ switch (oc)
+ {
+ case lexer_opcode_t::value:
+ return "value";
+ case lexer_opcode_t::string:
+ return "string";
+ case lexer_opcode_t::name:
+ return "name";
+ case lexer_opcode_t::divide:
+ return "divide";
+ case lexer_opcode_t::minus:
+ return "minus";
+ case lexer_opcode_t::multiply:
+ return "multiply";
+ case lexer_opcode_t::exponent:
+ return "exponent";
+ case lexer_opcode_t::concat:
+ return "concat";
+ case lexer_opcode_t::equal:
+ return "equal";
+ case lexer_opcode_t::less:
+ return "less";
+ case lexer_opcode_t::greater:
+ return "greater";
+ case lexer_opcode_t::plus:
+ return "plus";
+ case lexer_opcode_t::open:
+ return "open";
+ case lexer_opcode_t::close:
+ return "close";
+ case lexer_opcode_t::sep:
+ return "sep";
+ case lexer_opcode_t::array_row_sep:
+ return "array-row-sep";
+ case lexer_opcode_t::array_open:
+ return "array-open";
+ case lexer_opcode_t::array_close:
+ return "array-close";
+ }
+ return "";
+}
+
+lexer_token::lexer_token(lexer_opcode_t _opcode) :
+ opcode(_opcode)
+{
+}
+
+lexer_token::lexer_token(lexer_opcode_t _opcode, std::string_view _value) :
+ opcode(_opcode), value(_value)
+{
+}
+
+lexer_token::lexer_token(double _value) :
+ opcode(lexer_opcode_t::value), value(_value)
+{
+}
+
+
+std::ostream& operator<<(std::ostream& os, const lexer_token& t)
+{
+ switch (t.opcode)
+ {
+ case lexer_opcode_t::plus:
+ os << '+';
+ break;
+ case lexer_opcode_t::minus:
+ os << '-';
+ break;
+ case lexer_opcode_t::divide:
+ os << '/';
+ break;
+ case lexer_opcode_t::multiply:
+ os << '*';
+ break;
+ case lexer_opcode_t::exponent:
+ os << '^';
+ break;
+ case lexer_opcode_t::concat:
+ os << '&';
+ break;
+ case lexer_opcode_t::equal:
+ os << '=';
+ break;
+ case lexer_opcode_t::less:
+ os << '<';
+ break;
+ case lexer_opcode_t::greater:
+ os << '>';
+ break;
+ case lexer_opcode_t::open:
+ os << '(';
+ break;
+ case lexer_opcode_t::close:
+ os << ')';
+ break;
+ case lexer_opcode_t::array_open:
+ os << '{';
+ break;
+ case lexer_opcode_t::array_close:
+ os << '}';
+ break;
+ case lexer_opcode_t::sep:
+ os << ',';
+ break;
+ case lexer_opcode_t::array_row_sep:
+ os << ';';
+ break;
+ case lexer_opcode_t::name:
+ case lexer_opcode_t::string:
+ os << std::get<std::string_view>(t.value);
+ break;
+ case lexer_opcode_t::value:
+ os << std::get<double>(t.value);
+ break;
+ }
+
+ return os;
+}
+
+} // namespace ixion
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/lexer_tokens.hpp b/src/libixion/lexer_tokens.hpp
new file mode 100644
index 0000000..61109a6
--- /dev/null
+++ b/src/libixion/lexer_tokens.hpp
@@ -0,0 +1,75 @@
+/* -*- 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/.
+ */
+
+#ifndef INCLUDED_IXION_LEXER_TOKENS_HPP
+#define INCLUDED_IXION_LEXER_TOKENS_HPP
+
+#include <ixion/env.hpp>
+
+#include <vector>
+#include <memory>
+#include <variant>
+#include <string_view>
+
+namespace ixion {
+
+enum class lexer_opcode_t
+{
+ // data types
+ value,
+ string,
+ name,
+
+ // arithmetic operators
+ plus,
+ minus,
+ divide,
+ multiply,
+ exponent,
+
+ // string operators
+ concat,
+
+ // relational operators
+ equal,
+ less,
+ greater,
+
+ // parentheses, separators
+ open,
+ close,
+ sep,
+ array_open,
+ array_close,
+ array_row_sep,
+};
+
+const char* get_opcode_name(lexer_opcode_t oc);
+
+struct lexer_token
+{
+ using value_type = std::variant<double, std::string_view>;
+
+ lexer_opcode_t opcode;
+ value_type value;
+
+ lexer_token(lexer_opcode_t _opcode);
+ lexer_token(lexer_opcode_t _opcode, std::string_view _value);
+ lexer_token(double _value);
+};
+
+std::ostream& operator<<(std::ostream& os, const lexer_token& t);
+
+using lexer_tokens_t = std::vector<lexer_token>;
+
+std::string print_tokens(const lexer_tokens_t& tokens, bool verbose);
+
+} // namespace ixion
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/matrix.cpp b/src/libixion/matrix.cpp
new file mode 100644
index 0000000..14d1769
--- /dev/null
+++ b/src/libixion/matrix.cpp
@@ -0,0 +1,331 @@
+/* -*- 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 "ixion/matrix.hpp"
+#include "ixion/global.hpp"
+#include "column_store_type.hpp"
+
+#include <limits>
+#include <cstring>
+#include <functional>
+
+namespace ixion {
+
+const double nan = std::numeric_limits<double>::quiet_NaN();
+
+struct matrix::impl
+{
+ matrix_store_t m_data;
+
+ impl() {}
+
+ impl(size_t rows, size_t cols) : m_data(rows, cols) {}
+
+ impl(size_t rows, size_t cols, double numeric) :
+ m_data(rows, cols, numeric) {}
+
+ impl(size_t rows, size_t cols, bool boolean) :
+ m_data(rows, cols, boolean) {}
+
+ impl(size_t rows, size_t cols, const std::string& str) :
+ m_data(rows, cols, str) {}
+
+ impl(size_t rows, size_t cols, formula_error_t error) :
+ m_data(rows, cols, -static_cast<int64_t>(error)) {}
+
+ impl(const std::vector<double>& array, size_t rows, size_t cols) :
+ m_data(rows, cols, array.begin(), array.end()) {}
+
+ impl(const impl& other) : m_data(other.m_data) {}
+};
+
+struct numeric_matrix::impl
+{
+ std::vector<double> m_array;
+ size_t m_rows;
+ size_t m_cols;
+
+ impl() : m_rows(0), m_cols(0) {}
+
+ impl(size_t rows, size_t cols) :
+ m_array(rows * cols, 0.0), m_rows(rows), m_cols(cols) {}
+
+ impl(std::vector<double> array, size_t rows, size_t cols) :
+ m_array(std::move(array)), m_rows(rows), m_cols(cols) {}
+
+ size_t to_array_pos(size_t row, size_t col) const
+ {
+ return m_rows * col + row;
+ }
+};
+
+matrix::matrix() :
+ mp_impl(std::make_unique<impl>()) {}
+
+matrix::matrix(size_t rows, size_t cols) :
+ mp_impl(std::make_unique<impl>(rows, cols)) {}
+
+matrix::matrix(size_t rows, size_t cols, double numeric) :
+ mp_impl(std::make_unique<impl>(rows, cols, numeric)) {}
+
+matrix::matrix(size_t rows, size_t cols, bool boolean) :
+ mp_impl(std::make_unique<impl>(rows, cols, boolean)) {}
+
+matrix::matrix(size_t rows, size_t cols, const std::string& str) :
+ mp_impl(std::make_unique<impl>(rows, cols, str)) {}
+
+matrix::matrix(size_t rows, size_t cols, formula_error_t error) :
+ mp_impl(std::make_unique<impl>(rows, cols, error)) {}
+
+matrix::matrix(const matrix& other) :
+ mp_impl(std::make_unique<impl>(*other.mp_impl))
+{
+}
+
+matrix::matrix(matrix&& other) :
+ mp_impl(std::move(other.mp_impl))
+{
+}
+
+matrix::matrix(const numeric_matrix& other) :
+ mp_impl(std::make_unique<impl>(
+ other.mp_impl->m_array, other.row_size(), other.col_size()))
+{
+}
+
+matrix::~matrix() = default;
+
+matrix& matrix::operator= (matrix other)
+{
+ matrix t(std::move(other));
+ swap(t);
+ return *this;
+}
+
+bool matrix::is_numeric() const
+{
+ return mp_impl->m_data.numeric();
+}
+
+bool matrix::get_boolean(size_t row, size_t col) const
+{
+ return mp_impl->m_data.get_boolean(row, col);
+}
+
+bool matrix::is_numeric(size_t row, size_t col) const
+{
+ switch (mp_impl->m_data.get_type(row, col))
+ {
+ case mdds::mtm::element_numeric:
+ case mdds::mtm::element_boolean:
+ return true;
+ default:
+ ;
+ }
+
+ return false;
+}
+
+double matrix::get_numeric(size_t row, size_t col) const
+{
+ return mp_impl->m_data.get_numeric(row, col);
+}
+
+void matrix::set(size_t row, size_t col, double val)
+{
+ mp_impl->m_data.set(row, col, val);
+}
+
+void matrix::set(size_t row, size_t col, bool val)
+{
+ mp_impl->m_data.set(row, col, val);
+}
+
+void matrix::set(size_t row, size_t col, const std::string& str)
+{
+ mp_impl->m_data.set(row, col, str);
+}
+
+void matrix::set(size_t row, size_t col, formula_error_t val)
+{
+ int64_t encoded = -static_cast<uint8_t>(val);
+ mp_impl->m_data.set(row, col, encoded);
+}
+
+matrix::element matrix::get(size_t row, size_t col) const
+{
+ element me;
+ me.type = element_type::empty;
+
+ switch (mp_impl->m_data.get_type(row, col))
+ {
+ case mdds::mtm::element_numeric:
+ me.type = element_type::numeric;
+ me.value = mp_impl->m_data.get_numeric(row, col);
+ break;
+ case mdds::mtm::element_integer:
+ {
+ // This is an error value, which must be negative.
+ auto v = mp_impl->m_data.get_integer(row, col);
+ if (v >= 0)
+ break;
+
+ me.type = element_type::error;
+ me.value = static_cast<formula_error_t>(-v);
+ break;
+ }
+ case mdds::mtm::element_string:
+ {
+ me.type = element_type::string;
+ me.value = mp_impl->m_data.get_string(row, col);
+ break;
+ }
+ case mdds::mtm::element_boolean:
+ {
+ me.type = element_type::boolean;
+ me.value = mp_impl->m_data.get_boolean(row, col);
+ break;
+ }
+ default:
+ ;
+ }
+
+ return me;
+}
+
+size_t matrix::row_size() const
+{
+ return mp_impl->m_data.size().row;
+}
+
+size_t matrix::col_size() const
+{
+ return mp_impl->m_data.size().column;
+}
+
+void matrix::swap(matrix& r)
+{
+ mp_impl.swap(r.mp_impl);
+}
+
+numeric_matrix matrix::as_numeric() const
+{
+ matrix_store_t::size_pair_type mtx_size = mp_impl->m_data.size();
+
+ std::vector<double> num_array(mtx_size.row*mtx_size.column, nan);
+ double* dest = num_array.data();
+
+ std::function<void(const matrix_store_t::element_block_node_type&)> f =
+ [&](const matrix_store_t::element_block_node_type& node)
+ {
+ assert(node.offset == 0);
+
+ switch (node.type)
+ {
+ case mdds::mtm::element_integer:
+ {
+ // String and error values will be handled as numeric values of 0.0.
+#ifndef __STDC_IEC_559__
+ throw std::runtime_error("IEEE 754 is not fully supported.");
+#endif
+ std::memset(dest, 0, sizeof(double)*node.size); // IEEE 754 defines 0.0 to be 8 zero bytes.
+ std::advance(dest, node.size);
+ break;
+ }
+ case mdds::mtm::element_boolean:
+ {
+ using block_type = matrix_store_t::boolean_block_type;
+ auto it = block_type::begin(*node.data);
+ auto ite = block_type::end(*node.data);
+
+ for (; it != ite; ++it)
+ *dest++ = *it ? 1.0 : 0.0;
+ break;
+ }
+ case mdds::mtm::element_numeric:
+ {
+ using block_type = matrix_store_t::numeric_block_type;
+ const double* src = &block_type::at(*node.data, 0);
+ std::memcpy(dest, src, sizeof(double)*node.size);
+ std::advance(dest, node.size);
+ break;
+ }
+ case mdds::mtm::element_string:
+ {
+ // Skip string blocks.
+ std::advance(dest, node.size);
+ break;
+ }
+ default:
+ ;
+ }
+ };
+
+ mp_impl->m_data.walk(f);
+
+ return numeric_matrix(std::move(num_array), mtx_size.row, mtx_size.column);
+}
+
+bool matrix::operator== (const matrix& r) const
+{
+ return mp_impl->m_data == r.mp_impl->m_data;
+}
+
+bool matrix::operator!= (const matrix& r) const
+{
+ return !operator==(r);
+}
+
+numeric_matrix::numeric_matrix() : mp_impl(std::make_unique<impl>()) {}
+numeric_matrix::numeric_matrix(size_t rows, size_t cols) :
+ mp_impl(std::make_unique<impl>(rows, cols)) {}
+numeric_matrix::numeric_matrix(std::vector<double> array, size_t rows, size_t cols) :
+ mp_impl(std::make_unique<impl>(std::move(array), rows, cols)) {}
+
+numeric_matrix::numeric_matrix(numeric_matrix&& r) : mp_impl(std::move(r.mp_impl)) {}
+
+numeric_matrix::~numeric_matrix() {}
+
+numeric_matrix& numeric_matrix::operator= (numeric_matrix other)
+{
+ numeric_matrix t(std::move(other));
+ swap(t);
+
+ return *this;
+}
+
+double& numeric_matrix::operator() (size_t row, size_t col)
+{
+ size_t pos = mp_impl->to_array_pos(row, col);
+ return mp_impl->m_array[pos];
+}
+
+const double& numeric_matrix::operator() (size_t row, size_t col) const
+{
+ size_t pos = mp_impl->to_array_pos(row, col);
+ return mp_impl->m_array[pos];
+}
+
+void numeric_matrix::swap(numeric_matrix& r)
+{
+ mp_impl.swap(r.mp_impl);
+}
+
+size_t numeric_matrix::row_size() const
+{
+ return mp_impl->m_rows;
+}
+
+size_t numeric_matrix::col_size() const
+{
+ return mp_impl->m_cols;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
+
diff --git a/src/libixion/model_context.cpp b/src/libixion/model_context.cpp
new file mode 100644
index 0000000..b5ece1a
--- /dev/null
+++ b/src/libixion/model_context.cpp
@@ -0,0 +1,425 @@
+/* -*- 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 <ixion/model_context.hpp>
+#include <ixion/formula_result.hpp>
+#include <ixion/matrix.hpp>
+#include <ixion/model_iterator.hpp>
+#include <ixion/interface/session_handler.hpp>
+#include <ixion/named_expressions_iterator.hpp>
+#include <ixion/cell_access.hpp>
+#include <ixion/exceptions.hpp>
+
+#include "model_context_impl.hpp"
+
+namespace ixion {
+
+model_context::input_cell::input_cell(std::nullptr_t) : type(celltype_t::empty) {}
+model_context::input_cell::input_cell(bool b) : type(celltype_t::boolean)
+{
+ value = b;
+}
+
+model_context::input_cell::input_cell(const char* s) : type(celltype_t::string)
+{
+ value = std::string_view(s);
+}
+
+model_context::input_cell::input_cell(double v) : type(celltype_t::numeric)
+{
+ value = v;
+}
+
+model_context::input_cell::input_cell(const input_cell& other) :
+ type(other.type), value(other.value)
+{
+}
+
+model_context::input_row::input_row(std::initializer_list<input_cell> cells) :
+ m_cells(std::move(cells)) { }
+
+const std::initializer_list<model_context::input_cell>& model_context::input_row::cells() const
+{
+ return m_cells;
+}
+
+std::unique_ptr<iface::session_handler> model_context::session_handler_factory::create()
+{
+ return std::unique_ptr<iface::session_handler>();
+}
+
+model_context::session_handler_factory::~session_handler_factory() {}
+
+model_context::model_context() :
+ mp_impl(new detail::model_context_impl(*this, {1048576, 16384})) {}
+
+model_context::model_context(const rc_size_t& sheet_size) :
+ mp_impl(new detail::model_context_impl(*this, sheet_size)) {}
+
+model_context::~model_context()
+{
+}
+
+formula_result_wait_policy_t model_context::get_formula_result_wait_policy() const
+{
+ return mp_impl->get_formula_result_wait_policy();
+}
+
+void model_context::notify(formula_event_t event)
+{
+ mp_impl->notify(event);
+}
+
+const config& model_context::get_config() const
+{
+ return mp_impl->get_config();
+}
+
+dirty_cell_tracker& model_context::get_cell_tracker()
+{
+ return mp_impl->get_cell_tracker();
+}
+
+const dirty_cell_tracker& model_context::get_cell_tracker() const
+{
+ return mp_impl->get_cell_tracker();
+}
+
+void model_context::empty_cell(const abs_address_t& addr)
+{
+ mp_impl->empty_cell(addr);
+}
+
+void model_context::set_numeric_cell(const abs_address_t& addr, double val)
+{
+ mp_impl->set_numeric_cell(addr, val);
+}
+
+void model_context::set_boolean_cell(const abs_address_t& addr, bool val)
+{
+ mp_impl->set_boolean_cell(addr, val);
+}
+
+void model_context::set_string_cell(const abs_address_t& addr, std::string_view s)
+{
+ mp_impl->set_string_cell(addr, s);
+}
+
+cell_access model_context::get_cell_access(const abs_address_t& addr) const
+{
+ return cell_access(*this, addr);
+}
+
+void model_context::fill_down_cells(const abs_address_t& src, size_t n_dst)
+{
+ mp_impl->fill_down_cells(src, n_dst);
+}
+
+void model_context::set_string_cell(const abs_address_t& addr, string_id_t identifier)
+{
+ mp_impl->set_string_cell(addr, identifier);
+}
+
+formula_cell* model_context::set_formula_cell(const abs_address_t& addr, formula_tokens_t tokens)
+{
+ formula_tokens_store_ptr_t ts = formula_tokens_store::create();
+ ts->get() = std::move(tokens);
+
+ return mp_impl->set_formula_cell(addr, ts);
+}
+
+formula_cell* model_context::set_formula_cell(
+ const abs_address_t& addr, const formula_tokens_store_ptr_t& tokens)
+{
+ return mp_impl->set_formula_cell(addr, tokens);
+}
+
+formula_cell* model_context::set_formula_cell(
+ const abs_address_t& addr, const formula_tokens_store_ptr_t& tokens, formula_result result)
+{
+ return mp_impl->set_formula_cell(addr, tokens, std::move(result));
+}
+
+void model_context::set_grouped_formula_cells(
+ const abs_range_t& group_range, formula_tokens_t tokens)
+{
+ mp_impl->set_grouped_formula_cells(group_range, std::move(tokens));
+}
+
+void model_context::set_grouped_formula_cells(
+ const abs_range_t& group_range, formula_tokens_t tokens, formula_result result)
+{
+ mp_impl->set_grouped_formula_cells(group_range, std::move(tokens), std::move(result));
+}
+
+abs_range_t model_context::get_data_range(sheet_t sheet) const
+{
+ return mp_impl->get_data_range(sheet);
+}
+
+bool model_context::is_empty(const abs_address_t& addr) const
+{
+ return mp_impl->is_empty(addr);
+}
+
+bool model_context::is_empty(const abs_range_t& range) const
+{
+ return mp_impl->is_empty(range);
+}
+
+celltype_t model_context::get_celltype(const abs_address_t& addr) const
+{
+ return mp_impl->get_celltype(addr);
+}
+
+cell_value_t model_context::get_cell_value_type(const abs_address_t& addr) const
+{
+ return mp_impl->get_cell_value_type(addr);
+}
+
+double model_context::get_numeric_value(const abs_address_t& addr) const
+{
+ return mp_impl->get_numeric_value(addr);
+}
+
+bool model_context::get_boolean_value(const abs_address_t& addr) const
+{
+ return mp_impl->get_boolean_value(addr);
+}
+
+void model_context::set_sheet_size(const rc_size_t& sheet_size)
+{
+ mp_impl->set_sheet_size(sheet_size);
+}
+
+void model_context::set_config(const config& cfg)
+{
+ mp_impl->set_config(cfg);
+}
+
+string_id_t model_context::get_string_identifier(const abs_address_t& addr) const
+{
+ return mp_impl->get_string_identifier(addr);
+}
+
+std::string_view model_context::get_string_value(const abs_address_t& addr) const
+{
+ return mp_impl->get_string_value(addr);
+}
+
+const formula_cell* model_context::get_formula_cell(const abs_address_t& addr) const
+{
+ return mp_impl->get_formula_cell(addr);
+}
+
+formula_cell* model_context::get_formula_cell(const abs_address_t& addr)
+{
+ return mp_impl->get_formula_cell(addr);
+}
+
+formula_result model_context::get_formula_result(const abs_address_t& addr) const
+{
+ return mp_impl->get_formula_result(addr);
+}
+
+double model_context::count_range(const abs_range_t& range, values_t values_type) const
+{
+ return mp_impl->count_range(range, values_type);
+}
+
+matrix model_context::get_range_value(const abs_range_t& range) const
+{
+ if (range.first.sheet != range.last.sheet)
+ throw general_error("multi-sheet range is not allowed.");
+
+ if (!range.valid())
+ {
+ std::ostringstream os;
+ os << "invalid range: " << range;
+ throw std::invalid_argument(os.str());
+ }
+
+ rc_size_t sheet_size = get_sheet_size();
+ abs_range_t range_clipped = range;
+ if (range_clipped.all_rows())
+ {
+ range_clipped.first.row = 0;
+ range_clipped.last.row = sheet_size.row - 1;
+ }
+ if (range_clipped.all_columns())
+ {
+ range_clipped.first.column = 0;
+ range_clipped.last.column = sheet_size.column - 1;
+ }
+
+ row_t rows = range_clipped.last.row - range_clipped.first.row + 1;
+ col_t cols = range_clipped.last.column - range_clipped.first.column + 1;
+
+ matrix ret(rows, cols);
+ for (row_t i = 0; i < rows; ++i)
+ {
+ for (col_t j = 0; j < cols; ++j)
+ {
+ row_t row = i + range_clipped.first.row;
+ col_t col = j + range_clipped.first.column;
+ double val = get_numeric_value(abs_address_t(range_clipped.first.sheet, row, col));
+
+ // TODO: we need to handle string types when that becomes available.
+ ret.set(i, j, val);
+ }
+ }
+ return ret;
+}
+
+std::unique_ptr<iface::session_handler> model_context::create_session_handler()
+{
+ return mp_impl->create_session_handler();
+}
+
+iface::table_handler* model_context::get_table_handler()
+{
+ return mp_impl->get_table_handler();
+}
+
+const iface::table_handler* model_context::get_table_handler() const
+{
+ return mp_impl->get_table_handler();
+}
+
+string_id_t model_context::append_string(std::string_view s)
+{
+ return mp_impl->append_string(s);
+}
+
+string_id_t model_context::add_string(std::string_view s)
+{
+ return mp_impl->add_string(s);
+}
+
+const std::string* model_context::get_string(string_id_t identifier) const
+{
+ return mp_impl->get_string(identifier);
+}
+
+sheet_t model_context::get_sheet_index(std::string_view name) const
+{
+ return mp_impl->get_sheet_index(name);
+}
+
+std::string model_context::get_sheet_name(sheet_t sheet) const
+{
+ return mp_impl->get_sheet_name(sheet);
+}
+
+void model_context::set_sheet_name(sheet_t sheet, std::string name)
+{
+ mp_impl->set_sheet_name(sheet, std::move(name));
+}
+
+rc_size_t model_context::get_sheet_size() const
+{
+ return mp_impl->get_sheet_size();
+}
+
+size_t model_context::get_sheet_count() const
+{
+ return mp_impl->get_sheet_count();
+}
+
+void model_context::set_named_expression(std::string name, formula_tokens_t expr)
+{
+ abs_address_t origin(0, 0, 0);
+ mp_impl->set_named_expression(std::move(name), origin, std::move(expr));
+}
+
+void model_context::set_named_expression(std::string name, const abs_address_t& origin, formula_tokens_t expr)
+{
+ mp_impl->set_named_expression(std::move(name), origin, std::move(expr));
+}
+
+void model_context::set_named_expression(sheet_t sheet, std::string name, formula_tokens_t expr)
+{
+ abs_address_t origin(0, 0, 0);
+ mp_impl->set_named_expression(sheet, std::move(name), origin, std::move(expr));
+}
+
+void model_context::set_named_expression(
+ sheet_t sheet, std::string name, const abs_address_t& origin, formula_tokens_t expr)
+{
+ mp_impl->set_named_expression(sheet, std::move(name), origin, std::move(expr));
+}
+
+const named_expression_t* model_context::get_named_expression(sheet_t sheet, std::string_view name) const
+{
+ return mp_impl->get_named_expression(sheet, name);
+}
+
+sheet_t model_context::append_sheet(std::string name)
+{
+ return mp_impl->append_sheet(std::move(name));
+}
+
+void model_context::set_cell_values(sheet_t sheet, std::initializer_list<input_row> rows)
+{
+ mp_impl->set_cell_values(sheet, std::move(rows));
+}
+
+void model_context::set_session_handler_factory(session_handler_factory* factory)
+{
+ mp_impl->set_session_handler_factory(factory);
+}
+
+void model_context::set_table_handler(iface::table_handler* handler)
+{
+ mp_impl->set_table_handler(handler);
+}
+
+size_t model_context::get_string_count() const
+{
+ return mp_impl->get_string_count();
+}
+
+void model_context::dump_strings() const
+{
+ mp_impl->dump_strings();
+}
+
+string_id_t model_context::get_identifier_from_string(std::string_view s) const
+{
+ return mp_impl->get_identifier_from_string(s);
+}
+
+model_iterator model_context::get_model_iterator(
+ sheet_t sheet, rc_direction_t dir, const abs_rc_range_t& range) const
+{
+ return mp_impl->get_model_iterator(sheet, dir, range);
+}
+
+named_expressions_iterator model_context::get_named_expressions_iterator() const
+{
+ return named_expressions_iterator(*this, -1);
+}
+
+named_expressions_iterator model_context::get_named_expressions_iterator(sheet_t sheet) const
+{
+ return named_expressions_iterator(*this, sheet);
+}
+
+void model_context::walk(
+ sheet_t sheet, const abs_rc_range_t& range, column_block_callback_t cb) const
+{
+ mp_impl->walk(sheet, range, std::move(cb));
+}
+
+bool model_context::empty() const
+{
+ return mp_impl->empty();
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/model_context_impl.cpp b/src/libixion/model_context_impl.cpp
new file mode 100644
index 0000000..fdd6714
--- /dev/null
+++ b/src/libixion/model_context_impl.cpp
@@ -0,0 +1,1126 @@
+/* -*- 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_context_impl.hpp"
+
+#include <ixion/address.hpp>
+#include <ixion/cell.hpp>
+#include <ixion/formula_result.hpp>
+#include <ixion/matrix.hpp>
+#include <ixion/interface/session_handler.hpp>
+#include <ixion/model_iterator.hpp>
+#include <ixion/exceptions.hpp>
+
+#include "calc_status.hpp"
+#include "model_types.hpp"
+#include "utils.hpp"
+#include "debug.hpp"
+
+#include <sstream>
+#include <iostream>
+#include <cstring>
+
+using std::cout;
+using std::endl;
+
+namespace ixion { namespace detail {
+
+string_id_t safe_string_pool::append_string_unsafe(std::string_view s)
+{
+ assert(!s.empty());
+
+ string_id_t str_id = m_strings.size();
+ m_strings.push_back(std::string{s});
+ s = m_strings.back();
+ m_string_map.insert(string_map_type::value_type(s, str_id));
+ return str_id;
+}
+
+string_id_t safe_string_pool::append_string(std::string_view s)
+{
+ if (s.empty())
+ // Never add an empty or invalid string.
+ return empty_string_id;
+
+ std::unique_lock<std::mutex> lock(m_mtx);
+ return append_string_unsafe(s);
+}
+
+string_id_t safe_string_pool::add_string(std::string_view s)
+{
+ if (s.empty())
+ // Never add an empty or invalid string.
+ return empty_string_id;
+
+ std::unique_lock<std::mutex> lock(m_mtx);
+ string_map_type::iterator itr = m_string_map.find(s);
+ if (itr != m_string_map.end())
+ return itr->second;
+
+ return append_string_unsafe(s);
+}
+
+const std::string* safe_string_pool::get_string(string_id_t identifier) const
+{
+ if (identifier == empty_string_id)
+ return &m_empty_string;
+
+ if (identifier >= m_strings.size())
+ return nullptr;
+
+ return &m_strings[identifier];
+}
+
+size_t safe_string_pool::size() const
+{
+ return m_strings.size();
+}
+
+void safe_string_pool::dump_strings() const
+{
+ {
+ cout << "string count: " << m_strings.size() << endl;
+ auto it = m_strings.begin(), ite = m_strings.end();
+ for (string_id_t sid = 0; it != ite; ++it, ++sid)
+ {
+ const std::string& s = *it;
+ cout << "* " << sid << ": '" << s << "' (" << (void*)s.data() << ")" << endl;
+ }
+ }
+
+ {
+ cout << "string map count: " << m_string_map.size() << endl;
+ auto it = m_string_map.begin(), ite = m_string_map.end();
+ for (; it != ite; ++it)
+ {
+ std::string_view key = it->first;
+ cout << "* key: '" << key << "' (" << (void*)key.data() << "; " << key.size() << "), value: " << it->second << endl;
+ }
+ }
+}
+
+string_id_t safe_string_pool::get_identifier_from_string(std::string_view s) const
+{
+ string_map_type::const_iterator it = m_string_map.find(s);
+ return it == m_string_map.end() ? empty_string_id : it->second;
+}
+
+namespace {
+
+model_context::session_handler_factory dummy_session_handler_factory;
+
+rc_size_t to_group_size(const abs_range_t& group_range)
+{
+ rc_size_t group_size;
+ group_size.row = group_range.last.row - group_range.first.row + 1;
+ group_size.column = group_range.last.column - group_range.first.column + 1;
+ return group_size;
+}
+
+void set_grouped_formula_cells_to_workbook(
+ workbook& wb, const abs_address_t& top_left, const rc_size_t& group_size,
+ const calc_status_ptr_t& cs, const formula_tokens_store_ptr_t& ts)
+{
+ worksheet& sheet = wb.at(top_left.sheet);
+
+ for (col_t col_offset = 0; col_offset < group_size.column; ++col_offset)
+ {
+ col_t col = top_left.column + col_offset;
+ column_store_t& col_store = sheet.at(col);
+ column_store_t::iterator& pos_hint = sheet.get_pos_hint(col);
+
+ for (row_t row_offset = 0; row_offset < group_size.row; ++row_offset)
+ {
+ row_t row = top_left.row + row_offset;
+ pos_hint = col_store.set(pos_hint, row, new formula_cell(row_offset, col_offset, cs, ts));
+ }
+ }
+}
+
+/**
+ * The name of a named expression can only contain letters, numbers or an
+ * underscore character.
+ */
+void check_named_exp_name_or_throw(const char* p, size_t n)
+{
+ const char* p_end = p + n;
+
+ if (p == p_end)
+ throw model_context_error(
+ "empty name is not allowed", model_context_error::invalid_named_expression);
+
+ if ('0' <= *p && *p <= '9')
+ throw model_context_error(
+ "name cannot start with a numeric character", model_context_error::invalid_named_expression);
+
+ if (*p == '.')
+ throw model_context_error(
+ "name cannot start with a dot", model_context_error::invalid_named_expression);
+
+ for (; p != p_end; ++p)
+ {
+ char c = *p;
+ if ('a' <= c && c <= 'z')
+ continue;
+
+ if ('A' <= c && c <= 'Z')
+ continue;
+
+ if ('0' <= c && c <= '9')
+ continue;
+
+ if (c == '_' || c == '.')
+ continue;
+
+ std::ostringstream os;
+ os << "name contains invalid character '" << c << "'";
+ throw model_context_error(os.str(), model_context_error::invalid_named_expression);
+ }
+}
+
+void clip_range(abs_range_t& range, const rc_size_t& sheet_size)
+{
+ if (range.first.row == row_unset)
+ range.first.row = 0;
+ if (range.last.row == row_unset)
+ range.last.row = sheet_size.row - 1;
+}
+
+void throw_sheet_name_conflict(const std::string& name)
+{
+ // This sheet name is already taken.
+ std::ostringstream os;
+ os << "Sheet name '" << name << "' already exists.";
+ throw model_context_error(os.str(), model_context_error::sheet_name_conflict);
+}
+
+} // anonymous namespace
+
+model_context_impl::model_context_impl(model_context& parent, const rc_size_t& sheet_size) :
+ m_parent(parent),
+ m_sheet_size(sheet_size),
+ m_tracker(),
+ mp_table_handler(nullptr),
+ mp_session_factory(&dummy_session_handler_factory),
+ m_formula_res_wait_policy(formula_result_wait_policy_t::throw_exception)
+{
+}
+
+model_context_impl::~model_context_impl() {}
+
+void model_context_impl::notify(formula_event_t event)
+{
+ switch (event)
+ {
+ case formula_event_t::calculation_begins:
+ m_formula_res_wait_policy = formula_result_wait_policy_t::block_until_done;
+ break;
+ case formula_event_t::calculation_ends:
+ m_formula_res_wait_policy = formula_result_wait_policy_t::throw_exception;
+ break;
+ }
+}
+
+void model_context_impl::set_named_expression(
+ std::string name, const abs_address_t& origin, formula_tokens_t&& expr)
+{
+ check_named_exp_name_or_throw(name.data(), name.size());
+
+ IXION_TRACE("named expression: name='" << name << "'");
+ m_named_expressions.insert(
+ detail::named_expressions_t::value_type(
+ std::move(name),
+ named_expression_t(origin, std::move(expr))
+ )
+ );
+}
+
+void model_context_impl::set_named_expression(
+ sheet_t sheet, std::string name, const abs_address_t& origin, formula_tokens_t&& expr)
+{
+ check_named_exp_name_or_throw(name.data(), name.size());
+
+ detail::named_expressions_t& ns = m_sheets.at(sheet).get_named_expressions();
+ IXION_TRACE("named expression: name='" << name << "'");
+ ns.insert(
+ detail::named_expressions_t::value_type(
+ std::move(name),
+ named_expression_t(origin, std::move(expr))
+ )
+ );
+}
+
+const named_expression_t* model_context_impl::get_named_expression(std::string_view name) const
+{
+ named_expressions_t::const_iterator itr = m_named_expressions.find(std::string(name));
+ return itr == m_named_expressions.end() ? nullptr : &itr->second;
+}
+
+const named_expression_t* model_context_impl::get_named_expression(sheet_t sheet, std::string_view name) const
+{
+ const worksheet* ws = fetch_sheet(sheet);
+
+ if (ws)
+ {
+ const named_expressions_t& ns = ws->get_named_expressions();
+ auto it = ns.find(std::string(name));
+ if (it != ns.end())
+ return &it->second;
+ }
+
+ // Search the global scope if not found in the sheet local scope.
+ return get_named_expression(name);
+}
+
+sheet_t model_context_impl::get_sheet_index(std::string_view name) const
+{
+ strings_type::const_iterator itr_beg = m_sheet_names.begin(), itr_end = m_sheet_names.end();
+ for (strings_type::const_iterator itr = itr_beg; itr != itr_end; ++itr)
+ {
+ const std::string& s = *itr;
+ if (s.empty())
+ continue;
+
+ if (s == name)
+ return static_cast<sheet_t>(std::distance(itr_beg, itr));
+ }
+ return invalid_sheet;
+}
+
+std::string model_context_impl::get_sheet_name(sheet_t sheet) const
+{
+ if (sheet < 0 || m_sheet_names.size() <= std::size_t(sheet))
+ return std::string();
+
+ return m_sheet_names[sheet];
+}
+
+void model_context_impl::set_sheet_name(sheet_t sheet, std::string name)
+{
+ if (sheet < 0 || m_sheet_names.size() <= std::size_t(sheet))
+ {
+ std::ostringstream os;
+ os << "invalid sheet index: " << sheet;
+ throw std::invalid_argument(os.str());
+ }
+
+ for (std::size_t i = 0; i < m_sheet_names.size(); ++i)
+ {
+ if (m_sheet_names[i] == name)
+ {
+ if (i == std::size_t(sheet))
+ // Same sheet name is given. No point updating it.
+ return;
+ else
+ throw_sheet_name_conflict(name);
+ }
+ }
+
+ m_sheet_names[sheet] = std::move(name);
+}
+
+rc_size_t model_context_impl::get_sheet_size() const
+{
+ return m_sheet_size;
+}
+
+size_t model_context_impl::get_sheet_count() const
+{
+ return m_sheets.size();
+}
+
+sheet_t model_context_impl::append_sheet(std::string&& name)
+{
+ IXION_TRACE("name='" << name << "'");
+
+ // Check if the new sheet name already exists.
+ strings_type::const_iterator it =
+ std::find(m_sheet_names.begin(), m_sheet_names.end(), name);
+ if (it != m_sheet_names.end())
+ throw_sheet_name_conflict(name);
+
+ // index of the new sheet.
+ sheet_t sheet_index = m_sheets.size();
+
+ m_sheet_names.push_back(std::move(name));
+ m_sheets.push_back(m_sheet_size.row, m_sheet_size.column);
+ return sheet_index;
+}
+
+void model_context_impl::set_cell_values(sheet_t sheet, std::initializer_list<model_context::input_row>&& rows)
+{
+ abs_address_t pos;
+ pos.sheet = sheet;
+ pos.row = 0;
+ pos.column = 0;
+
+ // TODO : This function is not optimized for speed as it is mainly for
+ // convenience. Decide if we need to optimize this later.
+
+ for (const model_context::input_row& row : rows)
+ {
+ pos.column = 0;
+
+ for (const model_context::input_cell& c : row.cells())
+ {
+ switch (c.type)
+ {
+ case celltype_t::numeric:
+ set_numeric_cell(pos, std::get<double>(c.value));
+ break;
+ case celltype_t::string:
+ {
+ auto s = std::get<std::string_view>(c.value);
+ set_string_cell(pos, s);
+ break;
+ }
+ case celltype_t::boolean:
+ set_boolean_cell(pos, std::get<bool>(c.value));
+ break;
+ default:
+ ;
+ }
+
+ ++pos.column;
+ }
+
+ ++pos.row;
+ }
+}
+
+string_id_t model_context_impl::append_string(std::string_view s)
+{
+ return m_str_pool.append_string(s);
+}
+
+string_id_t model_context_impl::add_string(std::string_view s)
+{
+ return m_str_pool.add_string(s);
+}
+
+const std::string* model_context_impl::get_string(string_id_t identifier) const
+{
+ return m_str_pool.get_string(identifier);
+}
+
+size_t model_context_impl::get_string_count() const
+{
+ return m_str_pool.size();
+}
+
+void model_context_impl::dump_strings() const
+{
+ m_str_pool.dump_strings();
+}
+
+const column_store_t* model_context_impl::get_column(sheet_t sheet, col_t col) const
+{
+ if (static_cast<size_t>(sheet) >= m_sheets.size())
+ return nullptr;
+
+ const worksheet& sh = m_sheets[sheet];
+
+ if (static_cast<size_t>(col) >= sh.size())
+ return nullptr;
+
+ return &sh[col];
+}
+
+const column_stores_t* model_context_impl::get_columns(sheet_t sheet) const
+{
+ if (static_cast<size_t>(sheet) >= m_sheets.size())
+ return nullptr;
+
+ const worksheet& sh = m_sheets[sheet];
+ return &sh.get_columns();
+}
+
+namespace {
+
+double count_formula_block(
+ formula_result_wait_policy_t wait_policy, const column_store_t::const_iterator& itb, size_t offset, size_t len, const values_t& vt)
+{
+ double ret = 0.0;
+
+ // Inspect each formula cell individually.
+ formula_cell** pp = &formula_element_block::at(*itb->data, offset);
+ formula_cell** pp_end = pp + len;
+ for (; pp != pp_end; ++pp)
+ {
+ const formula_cell& fc = **pp;
+ formula_result res = fc.get_result_cache(wait_policy);
+
+ switch (res.get_type())
+ {
+ case formula_result::result_type::boolean:
+ if (vt.is_boolean())
+ ++ret;
+ break;
+ case formula_result::result_type::value:
+ if (vt.is_numeric())
+ ++ret;
+ break;
+ case formula_result::result_type::string:
+ if (vt.is_string())
+ ++ret;
+ break;
+ case formula_result::result_type::error:
+ // TODO : how do we handle error formula cells?
+ break;
+ case formula_result::result_type::matrix:
+ // TODO : ditto
+ break;
+ }
+ }
+
+ return ret;
+}
+
+column_block_t map_column_block_type(const mdds::mtv::element_t mtv_type)
+{
+ static const std::map<mdds::mtv::element_t, column_block_t> rules = {
+ { element_type_empty, column_block_t::empty }, // -1
+ { element_type_boolean, column_block_t::boolean }, // 0
+ { element_type_string, column_block_t::string }, // 6
+ { element_type_numeric, column_block_t::numeric }, // 10
+ { element_type_formula, column_block_t::formula }, // user-start (50)
+ };
+
+ auto it = rules.find(mtv_type);
+ return it == rules.end() ? column_block_t::unknown : it->second;
+}
+
+} // anonymous namespace
+
+double model_context_impl::count_range(abs_range_t range, values_t values_type) const
+{
+ if (m_sheets.empty())
+ return 0.0;
+
+ clip_range(range, m_sheet_size);
+
+ double ret = 0.0;
+ sheet_t last_sheet = range.last.sheet;
+ if (static_cast<size_t>(last_sheet) >= m_sheets.size())
+ last_sheet = m_sheets.size() - 1;
+
+ for (sheet_t sheet = range.first.sheet; sheet <= last_sheet; ++sheet)
+ {
+ const worksheet& ws = m_sheets.at(sheet);
+ for (col_t col = range.first.column; col <= range.last.column; ++col)
+ {
+ const column_store_t& cs = ws.at(col);
+ row_t cur_row = range.first.row;
+ column_store_t::const_position_type pos = cs.position(cur_row);
+ column_store_t::const_iterator itb = pos.first; // block iterator
+ column_store_t::const_iterator itb_end = cs.end();
+ size_t offset = pos.second;
+ if (itb == itb_end)
+ continue;
+
+ bool cont = true;
+ while (cont)
+ {
+ // remaining length of current block.
+ size_t len = itb->size - offset;
+ row_t last_row = cur_row + len - 1;
+
+ if (last_row >= range.last.row)
+ {
+ last_row = range.last.row;
+ len = last_row - cur_row + 1;
+ cont = false;
+ }
+
+ bool match = false;
+
+ switch (itb->type)
+ {
+ case element_type_numeric:
+ match = values_type.is_numeric();
+ break;
+ case element_type_boolean:
+ match = values_type.is_boolean();
+ break;
+ case element_type_string:
+ match = values_type.is_string();
+ break;
+ case element_type_empty:
+ match = values_type.is_empty();
+ break;
+ case element_type_formula:
+ ret += count_formula_block(m_formula_res_wait_policy, itb, offset, len, values_type);
+ break;
+ default:
+ {
+ std::ostringstream os;
+ os << __FUNCTION__ << ": unhandled block type (" << itb->type << ")";
+ throw general_error(os.str());
+ }
+ }
+
+ if (match)
+ ret += len;
+
+ // Move to the next block.
+ cur_row = last_row + 1;
+ ++itb;
+ offset = 0;
+ if (itb == itb_end)
+ cont = false;
+ }
+ }
+ }
+
+ return ret;
+}
+
+void model_context_impl::walk(sheet_t sheet, const abs_rc_range_t& range, column_block_callback_t cb) const
+{
+ const worksheet& sh = m_sheets.at(sheet);
+
+ for (col_t ic = range.first.column; ic <= range.last.column; ++ic)
+ {
+ row_t cur_row = range.first.row;
+
+ while (cur_row <= range.last.row)
+ {
+ const column_store_t& col = sh.at(ic);
+ auto pos = col.position(cur_row);
+ auto blk = pos.first;
+
+ column_block_shape_t shape;
+ shape.position = blk->position;
+ shape.size = blk->size;
+ shape.offset = pos.second;
+ shape.type = map_column_block_type(blk->type);
+ shape.data = blk->data;
+
+ // last row specified by the caller, or row corresponding to the
+ // last element of the block, whichever comes first.
+ row_t last_row = std::min<row_t>(blk->size - pos.second - 1 + cur_row, range.last.row);
+
+ if (!cb(ic, cur_row, last_row, shape))
+ return;
+
+ assert(blk->size > pos.second);
+ cur_row += blk->size - pos.second;
+ }
+ }
+}
+
+bool model_context_impl::empty() const
+{
+ return m_sheets.empty();
+}
+
+const worksheet* model_context_impl::fetch_sheet(sheet_t sheet_index) const
+{
+ if (sheet_index < 0 || m_sheets.size() <= size_t(sheet_index))
+ return nullptr;
+
+ return &m_sheets[sheet_index];
+}
+
+column_store_t::const_position_type model_context_impl::get_cell_position(const abs_address_t& addr) const
+{
+ const column_store_t& col_store = m_sheets.at(addr.sheet).at(addr.column);
+ return col_store.position(addr.row);
+}
+
+const detail::named_expressions_t& model_context_impl::get_named_expressions() const
+{
+ return m_named_expressions;
+}
+
+const detail::named_expressions_t& model_context_impl::get_named_expressions(sheet_t sheet) const
+{
+ const worksheet& sh = m_sheets.at(sheet);
+ return sh.get_named_expressions();
+}
+
+model_iterator model_context_impl::get_model_iterator(
+ sheet_t sheet, rc_direction_t dir, const abs_rc_range_t& range) const
+{
+ return model_iterator(*this, sheet, range, dir);
+}
+
+void model_context_impl::set_sheet_size(const rc_size_t& sheet_size)
+{
+ if (!m_sheets.empty())
+ throw model_context_error(
+ "You cannot change the sheet size if you already have at least one existing sheet.",
+ model_context_error::sheet_size_locked);
+
+ m_sheet_size = sheet_size;
+}
+
+std::unique_ptr<iface::session_handler> model_context_impl::create_session_handler()
+{
+ return mp_session_factory->create();
+}
+
+void model_context_impl::empty_cell(const abs_address_t& addr)
+{
+ worksheet& sheet = m_sheets.at(addr.sheet);
+ column_store_t& col_store = sheet.at(addr.column);
+ column_store_t::iterator& pos_hint = sheet.get_pos_hint(addr.column);
+ pos_hint = col_store.set_empty(addr.row, addr.row);
+}
+
+void model_context_impl::set_numeric_cell(const abs_address_t& addr, double val)
+{
+ worksheet& sheet = m_sheets.at(addr.sheet);
+ column_store_t& col_store = sheet.at(addr.column);
+ column_store_t::iterator& pos_hint = sheet.get_pos_hint(addr.column);
+ pos_hint = col_store.set(pos_hint, addr.row, val);
+}
+
+void model_context_impl::set_boolean_cell(const abs_address_t& addr, bool val)
+{
+ worksheet& sheet = m_sheets.at(addr.sheet);
+ column_store_t& col_store = sheet.at(addr.column);
+ column_store_t::iterator& pos_hint = sheet.get_pos_hint(addr.column);
+ pos_hint = col_store.set(pos_hint, addr.row, val);
+}
+
+void model_context_impl::set_string_cell(const abs_address_t& addr, std::string_view s)
+{
+ worksheet& sheet = m_sheets.at(addr.sheet);
+ string_id_t str_id = add_string(s);
+ column_store_t& col_store = sheet.at(addr.column);
+ column_store_t::iterator& pos_hint = sheet.get_pos_hint(addr.column);
+ pos_hint = col_store.set(pos_hint, addr.row, str_id);
+}
+
+void model_context_impl::fill_down_cells(const abs_address_t& src, size_t n_dst)
+{
+ if (!n_dst)
+ // Destination cell length is 0. Nothing to copy to.
+ return;
+
+ worksheet& sheet = m_sheets.at(src.sheet);
+ column_store_t& col_store = sheet.at(src.column);
+ column_store_t::iterator& pos_hint = sheet.get_pos_hint(src.column);
+
+ column_store_t::const_position_type pos = col_store.position(pos_hint, src.row);
+ auto it = pos.first; // block iterator
+
+ switch (it->type)
+ {
+ case element_type_numeric:
+ {
+ double v = col_store.get<numeric_element_block>(pos);
+ std::vector<double> vs(n_dst, v);
+ pos_hint = col_store.set(pos_hint, src.row+1, vs.begin(), vs.end());
+ break;
+ }
+ case element_type_boolean:
+ {
+ bool b = col_store.get<boolean_element_block>(pos);
+ std::deque<bool> vs(n_dst, b);
+ pos_hint = col_store.set(pos_hint, src.row+1, vs.begin(), vs.end());
+ break;
+ }
+ case element_type_string:
+ {
+ string_id_t sid = col_store.get<string_element_block>(pos);
+ std::vector<string_id_t> vs(n_dst, sid);
+ pos_hint = col_store.set(pos_hint, src.row+1, vs.begin(), vs.end());
+ break;
+ }
+ case element_type_empty:
+ {
+ size_t start_pos = src.row + 1;
+ size_t end_pos = start_pos + n_dst - 1;
+ pos_hint = col_store.set_empty(pos_hint, start_pos, end_pos);
+ break;
+ }
+ case element_type_formula:
+ // TODO : support this.
+ throw not_implemented_error("filling down of a formula cell is not yet supported.");
+ default:
+ {
+ std::ostringstream os;
+ os << __FUNCTION__ << ": unhandled block type (" << it->type << ")";
+ throw general_error(os.str());
+ }
+ }
+}
+
+void model_context_impl::set_string_cell(const abs_address_t& addr, string_id_t identifier)
+{
+ worksheet& sheet = m_sheets.at(addr.sheet);
+ column_store_t& col_store = sheet.at(addr.column);
+ column_store_t::iterator& pos_hint = sheet.get_pos_hint(addr.column);
+ pos_hint = col_store.set(pos_hint, addr.row, identifier);
+}
+
+formula_cell* model_context_impl::set_formula_cell(
+ const abs_address_t& addr, const formula_tokens_store_ptr_t& tokens)
+{
+ std::unique_ptr<formula_cell> fcell = std::make_unique<formula_cell>(tokens);
+
+ worksheet& sheet = m_sheets.at(addr.sheet);
+ column_store_t& col_store = sheet.at(addr.column);
+ column_store_t::iterator& pos_hint = sheet.get_pos_hint(addr.column);
+ formula_cell* p = fcell.release();
+ pos_hint = col_store.set(pos_hint, addr.row, p);
+ return p;
+}
+
+formula_cell* model_context_impl::set_formula_cell(
+ const abs_address_t& addr, const formula_tokens_store_ptr_t& tokens, formula_result result)
+{
+ std::unique_ptr<formula_cell> fcell = std::make_unique<formula_cell>(tokens);
+
+ worksheet& sheet = m_sheets.at(addr.sheet);
+ column_store_t& col_store = sheet.at(addr.column);
+ column_store_t::iterator& pos_hint = sheet.get_pos_hint(addr.column);
+ formula_cell* p = fcell.release();
+ p->set_result_cache(std::move(result));
+ pos_hint = col_store.set(pos_hint, addr.row, p);
+ return p;
+}
+
+void model_context_impl::set_grouped_formula_cells(
+ const abs_range_t& group_range, formula_tokens_t tokens)
+{
+ formula_tokens_store_ptr_t ts = formula_tokens_store::create();
+ ts->get() = std::move(tokens);
+
+ rc_size_t group_size = to_group_size(group_range);
+ calc_status_ptr_t cs(new calc_status(group_size));
+ set_grouped_formula_cells_to_workbook(m_sheets, group_range.first, group_size, cs, ts);
+}
+
+void model_context_impl::set_grouped_formula_cells(
+ const abs_range_t& group_range, formula_tokens_t tokens, formula_result result)
+{
+ formula_tokens_store_ptr_t ts = formula_tokens_store::create();
+ ts->get() = std::move(tokens);
+
+ rc_size_t group_size = to_group_size(group_range);
+
+ if (result.get_type() != formula_result::result_type::matrix)
+ throw std::invalid_argument("cached result for grouped formula cells must be of matrix type.");
+
+ if (row_t(result.get_matrix().row_size()) != group_size.row || col_t(result.get_matrix().col_size()) != group_size.column)
+ throw std::invalid_argument("dimension of the cached result differs from the size of the group.");
+
+ calc_status_ptr_t cs(new calc_status(group_size));
+ cs->result = std::make_unique<formula_result>(std::move(result));
+ set_grouped_formula_cells_to_workbook(m_sheets, group_range.first, group_size, cs, ts);
+}
+
+abs_range_t model_context_impl::get_data_range(sheet_t sheet) const
+{
+ const worksheet& cols = m_sheets.at(sheet);
+ size_t col_size = cols.size();
+ if (!col_size)
+ return abs_range_t(abs_range_t::invalid);
+
+ row_t row_size = cols[0].size();
+ if (!row_size)
+ return abs_range_t(abs_range_t::invalid);
+
+ abs_range_t range;
+ range.first.column = 0;
+ range.first.row = row_size-1;
+ range.first.sheet = sheet;
+ range.last.column = -1; // if this stays -1 all columns are empty.
+ range.last.row = 0;
+ range.last.sheet = sheet;
+
+ for (size_t i = 0; i < col_size; ++i)
+ {
+ const column_store_t& col = cols[i];
+ if (col.empty())
+ {
+ if (range.last.column < 0)
+ ++range.first.column;
+ continue;
+ }
+
+ if (range.first.row > 0)
+ {
+ // First non-empty row.
+
+ column_store_t::const_iterator it = col.begin(), it_end = col.end();
+ assert(it != it_end);
+ if (it->type == element_type_empty)
+ {
+ // First block is empty.
+ row_t offset = it->size;
+ ++it;
+ if (it == it_end)
+ {
+ // The whole column is empty.
+ if (range.last.column < 0)
+ ++range.first.column;
+ continue;
+ }
+
+ assert(it->type != element_type_empty);
+ if (range.first.row > offset)
+ range.first.row = offset;
+ }
+ else
+ // Set the first row to 0, and lock it.
+ range.first.row = 0;
+ }
+
+ if (range.last.row < (row_size-1))
+ {
+ // Last non-empty row.
+
+ column_store_t::const_reverse_iterator it = col.rbegin(), it_end = col.rend();
+ assert(it != it_end);
+ if (it->type == element_type_empty)
+ {
+ // Last block is empty.
+ size_t size_last_block = it->size;
+ ++it;
+ if (it == it_end)
+ {
+ // The whole column is empty.
+ if (range.last.column < 0)
+ ++range.first.column;
+ continue;
+ }
+
+ assert(it->type != element_type_empty);
+ row_t last_data_row = static_cast<row_t>(col.size() - size_last_block - 1);
+ if (range.last.row < last_data_row)
+ range.last.row = last_data_row;
+ }
+ else
+ // Last block is not empty.
+ range.last.row = row_size - 1;
+ }
+
+ // Check if the column contains at least one non-empty cell.
+ if (col.block_size() > 1 || !col.is_empty(0))
+ range.last.column = i;
+ }
+
+ if (range.last.column < 0)
+ // No data column found. The whole sheet is empty.
+ return abs_range_t(abs_range_t::invalid);
+
+ return range;
+}
+
+bool model_context_impl::is_empty(const abs_address_t& addr) const
+{
+ return m_sheets.at(addr.sheet).at(addr.column).is_empty(addr.row);
+}
+
+bool model_context_impl::is_empty(abs_range_t range) const
+{
+ range = shrink_to_workbook(range);
+
+ for (sheet_t sh = range.first.sheet; sh <= range.last.sheet; ++sh)
+ {
+ for (col_t col = range.first.column; col <= range.last.column; ++col)
+ {
+ const column_store_t& col_store = m_sheets[sh][col];
+ auto pos = col_store.position(range.first.row);
+ if (pos.first->type != element_type_empty)
+ // The top block is non-empty.
+ return false;
+
+ // See if this block covers the entire row range.
+ row_t last_empty_row = range.first.row + pos.first->size - pos.second - 1;
+ if (last_empty_row < range.last.row)
+ return false;
+ }
+ }
+
+ return true;
+}
+
+celltype_t model_context_impl::get_celltype(const abs_address_t& addr) const
+{
+ mdds::mtv::element_t gmcell_type =
+ m_sheets.at(addr.sheet).at(addr.column).get_type(addr.row);
+
+ return detail::to_celltype(gmcell_type);
+}
+
+cell_value_t model_context_impl::get_cell_value_type(const abs_address_t& addr) const
+{
+ const column_store_t& col_store = m_sheets.at(addr.sheet).at(addr.column);
+ auto pos = col_store.position(addr.row);
+ return detail::to_cell_value_type(pos, get_formula_result_wait_policy());
+}
+
+double model_context_impl::get_numeric_value(const abs_address_t& addr) const
+{
+ const column_store_t& col_store = m_sheets.at(addr.sheet).at(addr.column);
+ auto pos = col_store.position(addr.row);
+
+ switch (pos.first->type)
+ {
+ case element_type_numeric:
+ return numeric_element_block::at(*pos.first->data, pos.second);
+ case element_type_boolean:
+ {
+ auto it = boolean_element_block::cbegin(*pos.first->data);
+ std::advance(it, pos.second);
+ return *it ? 1.0 : 0.0;
+ }
+ case element_type_formula:
+ {
+ const formula_cell* p = formula_element_block::at(*pos.first->data, pos.second);
+ return p->get_value(m_formula_res_wait_policy);
+ }
+ default:
+ ;
+ }
+ return 0.0;
+}
+
+bool model_context_impl::get_boolean_value(const abs_address_t& addr) const
+{
+ const column_store_t& col_store = m_sheets.at(addr.sheet).at(addr.column);
+ auto pos = col_store.position(addr.row);
+
+ switch (pos.first->type)
+ {
+ case element_type_numeric:
+ return numeric_element_block::at(*pos.first->data, pos.second) != 0.0 ? true : false;
+ case element_type_boolean:
+ {
+ auto it = boolean_element_block::cbegin(*pos.first->data);
+ std::advance(it, pos.second);
+ return *it;
+ }
+ case element_type_formula:
+ {
+ const formula_cell* p = formula_element_block::at(*pos.first->data, pos.second);
+ return p->get_value(m_formula_res_wait_policy) == 0.0 ? false : true;
+ }
+ default:
+ ;
+ }
+ return false;
+}
+
+string_id_t model_context_impl::get_string_identifier(const abs_address_t& addr) const
+{
+ const column_store_t& col_store = m_sheets.at(addr.sheet).at(addr.column);
+ auto pos = col_store.position(addr.row);
+
+ switch (pos.first->type)
+ {
+ case element_type_string:
+ return string_element_block::at(*pos.first->data, pos.second);
+ default:
+ ;
+ }
+ return empty_string_id;
+}
+
+std::string_view model_context_impl::get_string_value(const abs_address_t& addr) const
+{
+ const column_store_t& col_store = m_sheets.at(addr.sheet).at(addr.column);
+ auto pos = col_store.position(addr.row);
+
+ switch (pos.first->type)
+ {
+ case element_type_string:
+ {
+ string_id_t sid = string_element_block::at(*pos.first->data, pos.second);
+ const std::string* p = m_str_pool.get_string(sid);
+ return p ? *p : std::string_view{};
+ }
+ case element_type_formula:
+ {
+ const formula_cell* p = formula_element_block::at(*pos.first->data, pos.second);
+ return p->get_string(m_formula_res_wait_policy);
+ }
+ case element_type_empty:
+ return empty_string;
+ default:
+ ;
+ }
+
+ return std::string_view{};
+}
+
+string_id_t model_context_impl::get_identifier_from_string(std::string_view s) const
+{
+ return m_str_pool.get_identifier_from_string(s);
+}
+
+const formula_cell* model_context_impl::get_formula_cell(const abs_address_t& addr) const
+{
+ const column_store_t& col_store = m_sheets.at(addr.sheet).at(addr.column);
+ auto pos = col_store.position(addr.row);
+
+ if (pos.first->type != element_type_formula)
+ return nullptr;
+
+ return formula_element_block::at(*pos.first->data, pos.second);
+}
+
+formula_cell* model_context_impl::get_formula_cell(const abs_address_t& addr)
+{
+ column_store_t& col_store = m_sheets.at(addr.sheet).at(addr.column);
+ auto pos = col_store.position(addr.row);
+
+ if (pos.first->type != element_type_formula)
+ return nullptr;
+
+ return formula_element_block::at(*pos.first->data, pos.second);
+}
+
+formula_result model_context_impl::get_formula_result(const abs_address_t& addr) const
+{
+ const formula_cell* fc = get_formula_cell(addr);
+ if (!fc)
+ throw general_error("not a formula cell.");
+
+ return fc->get_result_cache(m_formula_res_wait_policy);
+}
+
+abs_range_t model_context_impl::shrink_to_workbook(abs_range_t range) const
+{
+ range.reorder();
+
+ if (m_sheets.empty())
+ return range;
+
+ if (range.first.sheet >= sheet_t(m_sheets.size()))
+ throw general_error("out-of-bound sheet ranges");
+
+ range.last.sheet = std::min<sheet_t>(range.last.sheet, m_sheets.size()-1);
+ const worksheet& ws = m_sheets[range.last.sheet];
+ const column_stores_t& cols = ws.get_columns();
+
+ if (cols.empty())
+ return range;
+
+ if (range.first.column >= col_t(cols.size()))
+ throw general_error("out-of-bound column ranges");
+
+ range.last.column = std::min<col_t>(range.last.column, cols.size()-1);
+
+ const column_store_t& col = cols[0];
+
+ if (range.first.row >= row_t(col.size()))
+ throw general_error("out-of-bound row ranges");
+
+ range.last.row = std::min<row_t>(range.last.row, col.size()-1);
+
+ return range;
+}
+
+}}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/model_context_impl.hpp b/src/libixion/model_context_impl.hpp
new file mode 100644
index 0000000..57b73d8
--- /dev/null
+++ b/src/libixion/model_context_impl.hpp
@@ -0,0 +1,206 @@
+/* -*- 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/.
+ */
+
+#ifndef INCLUDED_MODEL_CONTEXT_IMPL_HPP
+#define INCLUDED_MODEL_CONTEXT_IMPL_HPP
+
+#include "ixion/model_context.hpp"
+#include "ixion/types.hpp"
+#include "ixion/config.hpp"
+#include "ixion/dirty_cell_tracker.hpp"
+
+#include "workbook.hpp"
+#include "column_store_type.hpp"
+
+#include <vector>
+#include <string>
+#include <unordered_map>
+#include <mutex>
+#include <deque>
+
+namespace ixion { namespace detail {
+
+class safe_string_pool
+{
+ using string_pool_type = std::deque<std::string>;
+ using string_map_type = std::unordered_map<std::string_view, string_id_t>;
+
+ std::mutex m_mtx;
+ string_pool_type m_strings;
+ string_map_type m_string_map;
+ std::string m_empty_string;
+
+ string_id_t append_string_unsafe(std::string_view s);
+
+public:
+ string_id_t append_string(std::string_view s);
+ string_id_t add_string(std::string_view s);
+ const std::string* get_string(string_id_t identifier) const;
+
+ size_t size() const;
+ void dump_strings() const;
+ string_id_t get_identifier_from_string(std::string_view s) const;
+};
+
+class model_context_impl
+{
+ typedef std::vector<std::string> strings_type;
+
+public:
+ model_context_impl() = delete;
+ model_context_impl(const model_context_impl&) = delete;
+ model_context_impl& operator= (model_context_impl) = delete;
+
+ model_context_impl(model_context& parent, const rc_size_t& sheet_size);
+ ~model_context_impl();
+
+ formula_result_wait_policy_t get_formula_result_wait_policy() const
+ {
+ return m_formula_res_wait_policy;
+ }
+
+ void notify(formula_event_t event);
+
+ const config& get_config() const
+ {
+ return m_config;
+ }
+
+ void set_config(const config& cfg)
+ {
+ m_config = cfg;
+ }
+
+ void set_sheet_size(const rc_size_t& sheet_size);
+
+ dirty_cell_tracker& get_cell_tracker()
+ {
+ return m_tracker;
+ }
+
+ const dirty_cell_tracker& get_cell_tracker() const
+ {
+ return m_tracker;
+ }
+
+ std::unique_ptr<iface::session_handler> create_session_handler();
+
+ void set_session_handler_factory(model_context::session_handler_factory* factory)
+ {
+ mp_session_factory = factory;
+ }
+
+ iface::table_handler* get_table_handler()
+ {
+ return mp_table_handler;
+ }
+
+ const iface::table_handler* get_table_handler() const
+ {
+ return mp_table_handler;
+ }
+
+ void set_table_handler(iface::table_handler* handler)
+ {
+ mp_table_handler = handler;
+ }
+
+ void empty_cell(const abs_address_t& addr);
+ void set_numeric_cell(const abs_address_t& addr, double val);
+ void set_boolean_cell(const abs_address_t& addr, bool val);
+ void set_string_cell(const abs_address_t& addr, std::string_view s);
+ void set_string_cell(const abs_address_t& addr, string_id_t identifier);
+ void fill_down_cells(const abs_address_t& src, size_t n_dst);
+ formula_cell* set_formula_cell(const abs_address_t& addr, const formula_tokens_store_ptr_t& tokens);
+ formula_cell* set_formula_cell(const abs_address_t& addr, const formula_tokens_store_ptr_t& tokens, formula_result result);
+ void set_grouped_formula_cells(const abs_range_t& group_range, formula_tokens_t tokens);
+ void set_grouped_formula_cells(const abs_range_t& group_range, formula_tokens_t tokens, formula_result result);
+
+ abs_range_t get_data_range(sheet_t sheet) const;
+
+ bool is_empty(const abs_address_t& addr) const;
+ bool is_empty(abs_range_t range) const;
+ celltype_t get_celltype(const abs_address_t& addr) const;
+ cell_value_t get_cell_value_type(const abs_address_t& addr) const;
+ double get_numeric_value(const abs_address_t& addr) const;
+ bool get_boolean_value(const abs_address_t& addr) const;
+ string_id_t get_string_identifier(const abs_address_t& addr) const;
+ std::string_view get_string_value(const abs_address_t& addr) const;
+ string_id_t get_identifier_from_string(std::string_view s) const;
+ const formula_cell* get_formula_cell(const abs_address_t& addr) const;
+ formula_cell* get_formula_cell(const abs_address_t& addr);
+
+ formula_result get_formula_result(const abs_address_t& addr) const;
+
+ void set_named_expression(std::string name, const abs_address_t& origin, formula_tokens_t&& expr);
+ void set_named_expression(sheet_t sheet, std::string name, const abs_address_t& origin, formula_tokens_t&& expr);
+
+ const named_expression_t* get_named_expression(std::string_view name) const;
+ const named_expression_t* get_named_expression(sheet_t sheet, std::string_view name) const;
+
+ sheet_t get_sheet_index(std::string_view name) const;
+ std::string get_sheet_name(sheet_t sheet) const;
+ void set_sheet_name(sheet_t sheet, std::string name);
+ rc_size_t get_sheet_size() const;
+ size_t get_sheet_count() const;
+ sheet_t append_sheet(std::string&& name);
+
+ void set_cell_values(sheet_t sheet, std::initializer_list<model_context::input_row>&& rows);
+
+ string_id_t append_string(std::string_view s);
+ string_id_t add_string(std::string_view s);
+ const std::string* get_string(string_id_t identifier) const;
+ size_t get_string_count() const;
+ void dump_strings() const;
+
+ const column_store_t* get_column(sheet_t sheet, col_t col) const;
+ const column_stores_t* get_columns(sheet_t sheet) const;
+
+ double count_range(abs_range_t range, values_t values_type) const;
+
+ void walk(sheet_t sheet, const abs_rc_range_t& range, column_block_callback_t cb) const;
+
+ bool empty() const;
+
+ const worksheet* fetch_sheet(sheet_t sheet_index) const;
+
+ column_store_t::const_position_type get_cell_position(const abs_address_t& addr) const;
+
+ const detail::named_expressions_t& get_named_expressions() const;
+ const detail::named_expressions_t& get_named_expressions(sheet_t sheet) const;
+
+ model_iterator get_model_iterator(
+ sheet_t sheet, rc_direction_t dir, const abs_rc_range_t& range) const;
+
+private:
+ abs_range_t shrink_to_workbook(abs_range_t range) const;
+
+private:
+ model_context& m_parent;
+
+ rc_size_t m_sheet_size;
+ workbook m_sheets;
+
+ config m_config;
+ dirty_cell_tracker m_tracker;
+ iface::table_handler* mp_table_handler;
+ detail::named_expressions_t m_named_expressions;
+
+ model_context::session_handler_factory* mp_session_factory;
+
+ strings_type m_sheet_names; ///< index to sheet name map.
+
+ safe_string_pool m_str_pool;
+
+ formula_result_wait_policy_t m_formula_res_wait_policy;
+};
+
+}}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/model_iterator.cpp b/src/libixion/model_iterator.cpp
new file mode 100644
index 0000000..fb54b70
--- /dev/null
+++ b/src/libixion/model_iterator.cpp
@@ -0,0 +1,408 @@
+/* -*- 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 "ixion/model_iterator.hpp"
+#include "ixion/global.hpp"
+#include "ixion/exceptions.hpp"
+#include "model_context_impl.hpp"
+
+#include <mdds/multi_type_vector/collection.hpp>
+#include <sstream>
+#include <ostream>
+
+namespace ixion {
+
+model_iterator::cell::cell() : row(0), col(0), type(celltype_t::empty), value(false) {}
+
+model_iterator::cell::cell(row_t _row, col_t _col) :
+ row(_row), col(_col), type(celltype_t::empty), value(false) {}
+
+model_iterator::cell::cell(row_t _row, col_t _col, bool _b) :
+ row(_row), col(_col), type(celltype_t::boolean), value(_b) {}
+
+model_iterator::cell::cell(row_t _row, col_t _col, string_id_t _s) :
+ row(_row), col(_col), type(celltype_t::string), value(_s) {}
+
+model_iterator::cell::cell(row_t _row, col_t _col, double _v) :
+ row(_row), col(_col), type(celltype_t::numeric), value(_v) {}
+
+model_iterator::cell::cell(row_t _row, col_t _col, const formula_cell* _f) :
+ row(_row), col(_col), type(celltype_t::formula), value(_f) {}
+
+bool model_iterator::cell::operator== (const cell& other) const
+{
+ if (type != other.type || row != other.row || col != other.col)
+ return false;
+
+ return value == other.value;
+}
+
+bool model_iterator::cell::operator!= (const cell& other) const
+{
+ return !operator==(other);
+}
+
+class model_iterator::impl
+{
+public:
+ virtual bool has() const = 0;
+ virtual void next() = 0;
+ virtual const model_iterator::cell& get() const = 0;
+ virtual ~impl() {}
+};
+
+namespace {
+
+class iterator_core_empty : public model_iterator::impl
+{
+ model_iterator::cell m_cell;
+public:
+ bool has() const override { return false; }
+
+ void next() override {}
+
+ const model_iterator::cell& get() const override
+ {
+ return m_cell;
+ }
+};
+
+
+class iterator_core_horizontal : public model_iterator::impl
+{
+ using collection_type = mdds::mtv::collection<column_store_t>;
+
+ collection_type m_collection;
+ mutable model_iterator::cell m_current_cell;
+ mutable bool m_update_current_cell;
+ collection_type::const_iterator m_current_pos;
+ collection_type::const_iterator m_end;
+
+ void update_current() const
+ {
+ m_current_cell.col = m_current_pos->index;
+ m_current_cell.row = m_current_pos->position;
+
+ switch (m_current_pos->type)
+ {
+ case element_type_boolean:
+ m_current_cell.type = celltype_t::boolean;
+ m_current_cell.value = m_current_pos->get<boolean_element_block>();
+ break;
+ case element_type_numeric:
+ m_current_cell.type = celltype_t::numeric;
+ m_current_cell.value = m_current_pos->get<numeric_element_block>();
+ break;
+ case element_type_string:
+ m_current_cell.type = celltype_t::string;
+ m_current_cell.value = m_current_pos->get<string_element_block>();
+ break;
+ case element_type_formula:
+ m_current_cell.type = celltype_t::formula;
+ m_current_cell.value = m_current_pos->get<formula_element_block>();
+ break;
+ case element_type_empty:
+ m_current_cell.type = celltype_t::empty;
+ m_current_cell.value = false;
+ default:
+ ;
+ }
+
+ m_update_current_cell = false;
+ }
+public:
+ iterator_core_horizontal(const detail::model_context_impl& cxt, sheet_t sheet, const abs_rc_range_t& range) :
+ m_update_current_cell(true)
+ {
+ const column_stores_t* cols = cxt.get_columns(sheet);
+ if (cols && !cols->empty())
+ {
+ collection_type c = mdds::mtv::collection<column_store_t>(cols->begin(), cols->end());
+
+ if (range.valid())
+ {
+ if (!range.all_columns())
+ {
+ col_t c1 = range.first.column == column_unset ? 0 : range.first.column;
+ col_t c2 = range.last.column == column_unset ? (cols->size() - 1) : range.last.column;
+ assert(c1 >= 0);
+ assert(c1 <= c2);
+ size_t start = c1;
+ size_t size = c2 - c1 + 1;
+ c.set_collection_range(start, size);
+ }
+
+ if (!range.all_rows())
+ {
+ const column_store_t& col = (*cols)[0];
+ row_t r1 = range.first.row == row_unset ? 0 : range.first.row;
+ row_t r2 = range.last.row == row_unset ? (col.size() - 1) : range.last.row;
+ assert(r1 >= 0);
+ assert(r1 <= r2);
+ size_t start = r1;
+ size_t size = r2 - r1 + 1;
+ c.set_element_range(start, size);
+ }
+ }
+
+ m_collection.swap(c);
+ }
+
+ m_current_pos = m_collection.begin();
+ m_end = m_collection.end();
+ }
+
+ virtual bool has() const override
+ {
+ return m_current_pos != m_end;
+ }
+
+ virtual void next() override
+ {
+ ++m_current_pos;
+ m_update_current_cell = true;
+ }
+
+ virtual const model_iterator::cell& get() const override
+ {
+ if (m_update_current_cell)
+ update_current();
+ return m_current_cell;
+ }
+};
+
+class iterator_core_vertical : public model_iterator::impl
+{
+ const column_stores_t* m_cols;
+ mutable model_iterator::cell m_current_cell;
+ mutable bool m_update_current_cell;
+
+ column_stores_t::const_iterator m_it_cols;
+ column_stores_t::const_iterator m_it_cols_begin;
+ column_stores_t::const_iterator m_it_cols_end;
+
+ column_store_t::const_position_type m_current_pos;
+ column_store_t::const_position_type m_end_pos;
+
+ row_t m_row_first;
+ row_t m_row_last;
+
+ void update_current() const
+ {
+ column_store_t::const_iterator blk_pos = m_current_pos.first;
+
+ switch (blk_pos->type)
+ {
+ case element_type_empty:
+ m_current_cell.type = celltype_t::empty;
+ m_current_cell.value = false;
+ break;
+ case element_type_boolean:
+ m_current_cell.type = celltype_t::boolean;
+ m_current_cell.value = column_store_t::get<boolean_element_block>(m_current_pos);
+ break;
+ case element_type_numeric:
+ m_current_cell.type = celltype_t::numeric;
+ m_current_cell.value = column_store_t::get<numeric_element_block>(m_current_pos);
+ break;
+ case element_type_string:
+ m_current_cell.type = celltype_t::string;
+ m_current_cell.value = column_store_t::get<string_element_block>(m_current_pos);
+ break;
+ case element_type_formula:
+ m_current_cell.type = celltype_t::formula;
+ m_current_cell.value = column_store_t::get<formula_element_block>(m_current_pos);
+ break;
+ default:
+ throw std::logic_error("unhandled element type.");
+ }
+
+ m_current_cell.row = column_store_t::logical_position(m_current_pos);
+ m_current_cell.col = std::distance(m_it_cols_begin, m_it_cols);
+ m_update_current_cell = false;
+ }
+
+public:
+ iterator_core_vertical(const detail::model_context_impl& cxt, sheet_t sheet, const abs_rc_range_t& range) :
+ m_update_current_cell(true),
+ m_row_first(0),
+ m_row_last(row_unset)
+ {
+ m_cols = cxt.get_columns(sheet);
+ if (!m_cols)
+ return;
+
+ m_it_cols_begin = m_cols->begin();
+ m_it_cols = m_it_cols_begin;
+ m_it_cols_end = m_cols->end();
+ if (m_it_cols_begin == m_it_cols_end)
+ return;
+
+ m_row_last = (*m_cols)[0].size() - 1;
+
+ if (range.valid())
+ {
+ col_t last_col = m_cols->size() - 1;
+
+ if (range.last.column != column_unset && range.last.column < last_col)
+ {
+ // Shrink the tail end.
+ col_t diff = range.last.column - last_col;
+ assert(diff < 0);
+ std::advance(m_it_cols_end, diff);
+
+ last_col += diff;
+ }
+
+ if (range.first.column != column_unset)
+ {
+ if (range.first.column <= last_col)
+ std::advance(m_it_cols, range.first.column);
+ else
+ {
+ // First column is past the last column. Nothing to parse.
+ m_it_cols_begin = m_it_cols_end;
+ return;
+ }
+ }
+
+ if (range.last.row != row_unset && range.last.row < m_row_last)
+ {
+ // Shrink the tail end.
+ m_row_last = range.last.row;
+ }
+
+ if (range.first.row != row_unset)
+ {
+ if (range.first.row <= m_row_last)
+ m_row_first = range.first.row;
+ else
+ {
+ // First row is past the last row. Set it to an empty
+ // range and bail out.
+ m_it_cols_begin = m_it_cols_end;
+ return;
+ }
+ }
+ }
+
+ const column_store_t& col = *m_it_cols;
+ m_current_pos = col.position(m_row_first);
+ m_end_pos = col.position(m_row_last+1);
+ }
+
+ bool has() const override
+ {
+ if (!m_cols)
+ return false;
+
+ return m_it_cols != m_it_cols_end;
+ }
+
+ void next() override
+ {
+ m_update_current_cell = true;
+ m_current_pos = column_store_t::next_position(m_current_pos);
+
+ const column_store_t* col = &*m_it_cols;
+ if (m_current_pos != m_end_pos)
+ // It hasn't reached the end of the current column yet.
+ return;
+
+ ++m_it_cols; // Move to the next column.
+ if (m_it_cols == m_it_cols_end)
+ return;
+
+ // Reset the position to the first cell in the new column.
+ col = &*m_it_cols;
+ m_current_pos = col->position(m_row_first);
+ m_end_pos = col->position(m_row_last+1);
+ }
+
+ const model_iterator::cell& get() const override
+ {
+ if (m_update_current_cell)
+ update_current();
+ return m_current_cell;
+ }
+};
+
+} // anonymous namespace
+
+model_iterator::model_iterator() : mp_impl(std::make_unique<iterator_core_empty>()) {}
+model_iterator::model_iterator(const detail::model_context_impl& cxt, sheet_t sheet, const abs_rc_range_t& range, rc_direction_t dir)
+{
+ switch (dir)
+ {
+ case rc_direction_t::horizontal:
+ mp_impl = std::make_unique<iterator_core_horizontal>(cxt, sheet, range);
+ break;
+ case rc_direction_t::vertical:
+ mp_impl = std::make_unique<iterator_core_vertical>(cxt, sheet, range);
+ break;
+ }
+}
+
+model_iterator::model_iterator(model_iterator&& other) : mp_impl(std::move(other.mp_impl)) {}
+
+model_iterator::~model_iterator() {}
+
+model_iterator& model_iterator::operator= (model_iterator&& other)
+{
+ mp_impl = std::move(other.mp_impl);
+ other.mp_impl = std::make_unique<iterator_core_empty>();
+ return *this;
+}
+
+bool model_iterator::has() const
+{
+ return mp_impl->has();
+}
+
+void model_iterator::next()
+{
+ mp_impl->next();
+}
+
+const model_iterator::cell& model_iterator::get() const
+{
+ return mp_impl->get();
+}
+
+std::ostream& operator<< (std::ostream& os, const model_iterator::cell& c)
+{
+ os << "(row=" << c.row << "; col=" << c.col << "; type=" << short(c.type);
+
+ switch (c.type)
+ {
+ case celltype_t::boolean:
+ os << "; boolean=" << std::get<bool>(c.value);
+ break;
+ case celltype_t::formula:
+ os << "; formula=" << std::get<const formula_cell*>(c.value);
+ break;
+ case celltype_t::numeric:
+ os << "; numeric=" << std::get<double>(c.value);
+ break;
+ case celltype_t::string:
+ os << "; string=" << std::get<string_id_t>(c.value);
+ break;
+ case celltype_t::empty:
+ os << "; empty";
+ break;
+ case celltype_t::unknown:
+ default:
+ ;
+ }
+
+ os << ')';
+ return os;
+}
+
+} // namespace ixion
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/model_types.cpp b/src/libixion/model_types.cpp
new file mode 100644
index 0000000..ee46119
--- /dev/null
+++ b/src/libixion/model_types.cpp
@@ -0,0 +1,16 @@
+/* -*- 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_types.hpp"
+
+namespace ixion { namespace detail {
+
+const std::string empty_string = "";
+
+}}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/model_types.hpp b/src/libixion/model_types.hpp
new file mode 100644
index 0000000..3884621
--- /dev/null
+++ b/src/libixion/model_types.hpp
@@ -0,0 +1,31 @@
+/* -*- 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/.
+ */
+
+#ifndef INCLUDED_IXION_MODEL_TYPES_HPP
+#define INCLUDED_IXION_MODEL_TYPES_HPP
+
+#include <string>
+#include <map>
+#include <memory>
+
+#include "ixion/formula_tokens.hpp"
+
+namespace ixion {
+
+class formula_cell;
+
+namespace detail {
+
+typedef std::map<std::string, named_expression_t> named_expressions_t;
+
+extern const std::string empty_string;
+
+}}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/module.cpp b/src/libixion/module.cpp
new file mode 100644
index 0000000..b6a4dff
--- /dev/null
+++ b/src/libixion/module.cpp
@@ -0,0 +1,111 @@
+/* -*- 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 "ixion/module.hpp"
+#include "ixion/info.hpp"
+#include "ixion/compute_engine.hpp"
+#include <sstream>
+#include <vector>
+#include <boost/filesystem.hpp>
+
+#ifdef _WIN32
+#include <windows.h>
+#else
+#include <dlfcn.h>
+#endif
+
+namespace fs = boost::filesystem;
+
+namespace ixion { namespace draft {
+
+namespace {
+
+const char* mod_names[] = { "vulkan" };
+
+std::string get_module_prefix()
+{
+ std::ostringstream os;
+ os << "ixion-" << get_api_version_major() << "." << get_api_version_minor() << "-";
+ return os.str();
+}
+
+typedef module_def* (*fp_register_module_type)(void);
+
+void register_module(void* mod_handler, std::string_view mod_name, fp_register_module_type fp_register_module)
+{
+ if (!fp_register_module)
+ return;
+
+ module_def* md = fp_register_module();
+ compute_engine::add_class(
+ mod_handler, mod_name, md->create_compute_engine, md->destroy_compute_engine);
+}
+
+} // anonymous namespace
+
+#ifdef _WIN32
+
+void init_modules()
+{
+ std::string mod_prefix = get_module_prefix();
+
+ for (const char* mod_name : mod_names)
+ {
+ std::ostringstream os;
+ os << mod_prefix << mod_name << ".dll";
+
+ std::string modfile_name = os.str();
+
+ HMODULE hdl = LoadLibrary(modfile_name.c_str());
+ if (!hdl)
+ continue;
+
+ fp_register_module_type fp_register_module;
+ *(void**)(&fp_register_module) =
+ reinterpret_cast<void **>(GetProcAddress(hdl, "register_module"));
+
+ register_module(hdl, mod_name, fp_register_module);
+ }
+}
+
+void unload_module(void* handler)
+{
+ FreeLibrary((HMODULE)handler);
+}
+
+#else
+
+void init_modules()
+{
+ std::string mod_prefix = get_module_prefix();
+
+ for (const char* mod_name : mod_names)
+ {
+ std::ostringstream os;
+ os << mod_prefix << mod_name << ".so";
+
+ void* hdl = dlopen(os.str().data(), RTLD_NOW | RTLD_GLOBAL);
+ if (!hdl)
+ continue;
+
+ fp_register_module_type fp_register_module;
+ *(void**)(&fp_register_module) = dlsym(hdl, "register_module");
+
+ register_module(hdl, mod_name, fp_register_module);
+ }
+}
+
+void unload_module(void* handler)
+{
+ dlclose(handler);
+}
+
+#endif
+
+}}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/name_resolver_test.cpp b/src/libixion/name_resolver_test.cpp
new file mode 100644
index 0000000..82cea0f
--- /dev/null
+++ b/src/libixion/name_resolver_test.cpp
@@ -0,0 +1,1131 @@
+/* -*- 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 "test_global.hpp" // This must be the first header to be included.
+
+#include <ixion/model_context.hpp>
+#include <ixion/formula_name_resolver.hpp>
+#include <ixion/address.hpp>
+#include <ixion/table.hpp>
+
+#include <string>
+
+using namespace ixion;
+
+namespace {
+
+struct ref_name_entry
+{
+ const char* name;
+ bool sheet_name;
+};
+
+void test_calc_a1()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ model_context cxt;
+ cxt.append_sheet("One");
+ cxt.append_sheet("Two");
+ cxt.append_sheet("Three");
+ cxt.append_sheet("A B C"); // name with space
+ auto resolver = formula_name_resolver::get(formula_name_resolver_t::calc_a1, &cxt);
+ assert(resolver);
+
+ {
+ // Parse single cell addresses.
+ ref_name_entry names[] =
+ {
+ { "A1", false },
+ { "$A1", false },
+ { "A$1", false },
+ { "$A$1", false },
+ { "Z1", false },
+ { "AA23", false },
+ { "AB23", false },
+ { "$AB23", false },
+ { "AB$23", false },
+ { "$AB$23", false },
+ { "BA1", false },
+ { "AAA2", false },
+ { "ABA1", false },
+ { "BAA1", false },
+ { "XFD1048576", false },
+ { "One.A1", true },
+ { "One.XFD1048576", true },
+ { "Two.B10", true },
+ { "Two.$B10", true },
+ { "Two.B$10", true },
+ { "Two.$B$10", true },
+ { "Three.CFD234", true },
+ { "$Three.CFD234", true },
+ { "'A B C'.Z12", true },
+ { "$'A B C'.Z12", true },
+ { 0, false }
+ };
+
+ for (size_t i = 0; names[i].name; ++i)
+ {
+ const char* p = names[i].name;
+ std::string name_a1(p);
+ std::cout << "single cell address: " << name_a1 << std::endl;
+ formula_name_t res = resolver->resolve(name_a1, abs_address_t());
+ if (res.type != formula_name_t::cell_reference)
+ {
+ std::cerr << "failed to resolve cell address: " << name_a1 << std::endl;
+ assert(false);
+ }
+
+ address_t addr = std::get<address_t>(res.value);
+ std::string test_name = resolver->get_name(addr, abs_address_t(), names[i].sheet_name);
+
+ if (name_a1 != test_name)
+ {
+ std::cerr << "failed to compile name from address: (name expected: " << name_a1 << "; actual name created: " << test_name << ")" << std::endl;
+ assert(false);
+ }
+ }
+ }
+
+ {
+ // Parse range addresses.
+
+ ref_name_entry names[] =
+ {
+ { "A1:B2", false },
+ { "$D10:G$24", false },
+ { "One.C$1:Z$400", true },
+ { "Two.$C1:$Z400", true },
+ { "Three.$C1:Z$400", true },
+ { "$Three.$C1:Z$400", true },
+ { "'A B C'.$C4:$Z256", true },
+ { "$'A B C'.$C4:$Z256", true },
+ { "One.C4:Three.Z100", true },
+ { "One.C4:$Three.Z100", true },
+ { "$One.C4:Three.Z100", true },
+ { "$One.C4:$Three.Z100", true },
+ { 0, false },
+ };
+
+ for (sheet_t sheet = 0; sheet <= 3; ++sheet)
+ {
+ for (size_t i = 0; names[i].name; ++i)
+ {
+ abs_address_t pos{sheet, 0, 0};
+ std::string name_a1(names[i].name);
+ std::cout << "range address: " << name_a1 << std::endl;
+ formula_name_t res = resolver->resolve(name_a1, pos);
+ if (res.type != formula_name_t::range_reference)
+ {
+ std::cerr << "failed to resolve range address: " << name_a1 << std::endl;
+ assert(false);
+ }
+
+ std::string test_name = resolver->get_name(
+ std::get<range_t>(res.value), pos, names[i].sheet_name);
+
+ if (name_a1 != test_name)
+ {
+ std::cerr << "failed to compile name from range: (pos: " << pos << "; name expected: " << name_a1 << "; actual name created: " << test_name << ")" << std::endl;
+ assert(false);
+ }
+ }
+ }
+ }
+
+ struct {
+ const char* name; sheet_t sheet1; row_t row1; col_t col1; sheet_t sheet2; row_t row2; col_t col2;
+ } range_tests[] = {
+ { "A1:B2", 0, 0, 0, 0, 1, 1 },
+ { "D10:G24", 0, 9, 3, 0, 23, 6 },
+ { "One.C1:Z400", 0, 0, 2, 0, 399, 25 },
+ { "Two.C1:Z400", 1, 0, 2, 1, 399, 25 },
+ { "Three.C1:Z400", 2, 0, 2, 2, 399, 25 },
+ { 0, 0, 0, 0, 0, 0, 0 }
+ };
+
+ for (size_t i = 0; range_tests[i].name; ++i)
+ {
+ std::string name_a1(range_tests[i].name);
+ std::cout << "range address: " << name_a1 << std::endl;
+ formula_name_t res = resolver->resolve(name_a1, abs_address_t());
+ auto range = std::get<range_t>(res.value);
+ assert(res.type == formula_name_t::range_reference);
+ assert(range.first.sheet == range_tests[i].sheet1);
+ assert(range.first.row == range_tests[i].row1);
+ assert(range.first.column == range_tests[i].col1);
+ assert(range.last.sheet == range_tests[i].sheet2);
+ assert(range.last.row == range_tests[i].row2);
+ assert(range.last.column == range_tests[i].col2);
+ }
+
+ {
+ formula_name_t res = resolver->resolve("B1", abs_address_t(0,1,1));
+ auto addr = std::get<address_t>(res.value);
+ assert(res.type == formula_name_t::cell_reference);
+ assert(addr.sheet == 0);
+ assert(addr.row == -1);
+ assert(addr.column == 0);
+ }
+
+ {
+ formula_name_t res = resolver->resolve("B2:B4", abs_address_t(0,0,3));
+ auto range = std::get<range_t>(res.value);
+ assert(res.type == formula_name_t::range_reference);
+ assert(range.first.sheet == 0);
+ assert(range.first.row == 1);
+ assert(range.first.column == -2);
+ assert(range.last.sheet == 0);
+ assert(range.last.row == 3);
+ assert(range.last.column == -2);
+ }
+
+ {
+ formula_name_t res = resolver->resolve("Three.B2", abs_address_t(2,0,0));
+ auto addr = std::get<address_t>(res.value);
+ assert(res.type == formula_name_t::cell_reference);
+ assert(!addr.abs_sheet);
+ assert(!addr.abs_row);
+ assert(!addr.abs_column);
+ assert(addr.sheet == 0);
+ assert(addr.row == 1);
+ assert(addr.column == 1);
+ }
+
+ {
+ abs_address_t pos(2, 0, 0);
+ std::string name("One.B2:Three.C4");
+ formula_name_t res = resolver->resolve(name, pos);
+ auto range = std::get<range_t>(res.value);
+ assert(res.type == formula_name_t::range_reference);
+ assert(!range.first.abs_sheet);
+ assert(!range.first.abs_row);
+ assert(!range.first.abs_column);
+ assert(range.first.sheet == -2);
+ assert(range.first.row == 1);
+ assert(range.first.column == 1);
+ assert(!range.last.abs_sheet);
+ assert(!range.last.abs_row);
+ assert(!range.last.abs_column);
+ assert(range.last.sheet == 0);
+ assert(range.last.row == 3);
+ assert(range.last.column == 2);
+
+ std::string s = resolver->get_name(range, pos, true);
+ assert(s == name);
+ }
+}
+
+void test_excel_a1()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ model_context cxt;
+ cxt.append_sheet("One");
+ cxt.append_sheet("Two");
+ cxt.append_sheet("Three");
+ cxt.append_sheet("A B C"); // name with space
+ cxt.append_sheet("A'B"); // name with quote
+ cxt.append_sheet("C'''D"); // name with multiple consecutive quotes
+ cxt.append_sheet("\"Quoted\""); // name with double quotes
+ auto resolver = formula_name_resolver::get(formula_name_resolver_t::excel_a1, &cxt);
+ assert(resolver);
+
+ {
+ // Parse single cell addresses.
+ ref_name_entry names[] =
+ {
+ { "A1", false },
+ { "$A1", false },
+ { "A$1", false },
+ { "$A$1", false },
+ { "Z1", false },
+ { "AA23", false },
+ { "AB23", false },
+ { "$AB23", false },
+ { "AB$23", false },
+ { "$AB$23", false },
+ { "BA1", false },
+ { "AAA2", false },
+ { "ABA1", false },
+ { "BAA1", false },
+ { "XFD1048576", false },
+ { "One!A1", true },
+ { "One!XFD1048576", true },
+ { "Two!B10", true },
+ { "Two!$B10", true },
+ { "Two!B$10", true },
+ { "Two!$B$10", true },
+ { "Three!CFD234", true },
+ { "'A B C'!Z12", true },
+ { "'A''B'!Z12", true },
+ { "'C''''''D'!BB23", true },
+ { "'\"Quoted\"'!A2", true },
+ { 0, false }
+ };
+
+ for (size_t i = 0; names[i].name; ++i)
+ {
+ const char* p = names[i].name;
+ std::string name_a1(p);
+ formula_name_t res = resolver->resolve(name_a1, abs_address_t());
+ if (res.type != formula_name_t::cell_reference)
+ {
+ std::cerr << "failed to resolve cell address: " << name_a1 << std::endl;
+ assert(false);
+ }
+
+ address_t addr = std::get<address_t>(res.value);
+ std::string test_name = resolver->get_name(addr, abs_address_t(), names[i].sheet_name);
+
+ if (name_a1 != test_name)
+ {
+ std::cerr << "failed to compile name from address: (name expected: " << name_a1 << "; actual name created: " << test_name << ")" << std::endl;
+ assert(false);
+ }
+ }
+ }
+
+ {
+ // Parse range addresses.
+
+ ref_name_entry names[] =
+ {
+ { "A1:B2", false },
+ { "$D10:G$24", false },
+ { "One!C$1:Z$400", true },
+ { "Two!$C1:$Z400", true },
+ { "Three!$C1:Z$400", true },
+ { "'A B C'!$C4:$Z256", true },
+ { 0, false },
+ };
+
+ for (size_t i = 0; names[i].name; ++i)
+ {
+ const char* p = names[i].name;
+ std::string name_a1(p);
+ std::cout << "range address: " << name_a1 << std::endl;
+ formula_name_t res = resolver->resolve(name_a1, abs_address_t());
+ if (res.type != formula_name_t::range_reference)
+ {
+ std::cerr << "failed to resolve range address: " << name_a1 << std::endl;
+ assert(false);
+ }
+
+ range_t range = std::get<range_t>(res.value);
+ std::string test_name = resolver->get_name(range, abs_address_t(), names[i].sheet_name);
+
+ if (name_a1 != test_name)
+ {
+ std::cerr << "failed to compile name from range: (name expected: " << name_a1 << "; actual name created: " << test_name << ")" << std::endl;
+ assert(false);
+ }
+ }
+ }
+
+ // Parse range addresses.
+ struct {
+ const char* name; sheet_t sheet1; row_t row1; col_t col1; sheet_t sheet2; row_t row2; col_t col2;
+ } range_tests[] = {
+ { "A1:B2", 0, 0, 0, 0, 1, 1 },
+ { "D10:G24", 0, 9, 3, 0, 23, 6 },
+ { "One!C1:Z400", 0, 0, 2, 0, 399, 25 },
+ { "Two!C1:Z400", 1, 0, 2, 1, 399, 25 },
+ { "Three!C1:Z400", 2, 0, 2, 2, 399, 25 },
+ { 0, 0, 0, 0, 0, 0, 0 }
+ };
+
+ for (size_t i = 0; range_tests[i].name; ++i)
+ {
+ std::string name_a1(range_tests[i].name);
+ formula_name_t res = resolver->resolve(name_a1, abs_address_t());
+ auto range = std::get<range_t>(res.value);
+ assert(res.type == formula_name_t::range_reference);
+ assert(range.first.sheet == range_tests[i].sheet1);
+ assert(range.first.row == range_tests[i].row1);
+ assert(range.first.column == range_tests[i].col1);
+ assert(range.last.sheet == range_tests[i].sheet2);
+ assert(range.last.row == range_tests[i].row2);
+ assert(range.last.column == range_tests[i].col2);
+ }
+
+ {
+ formula_name_t res = resolver->resolve("B1", abs_address_t(0,1,1));
+ auto addr = std::get<address_t>(res.value);
+ assert(res.type == formula_name_t::cell_reference);
+ assert(addr.sheet == 0);
+ assert(addr.row == -1);
+ assert(addr.column == 0);
+ }
+
+ {
+ formula_name_t res = resolver->resolve("B2:B4", abs_address_t(0,0,3));
+ auto range = std::get<range_t>(res.value);
+ assert(res.type == formula_name_t::range_reference);
+ assert(range.first.sheet == 0);
+ assert(range.first.row == 1);
+ assert(range.first.column == -2);
+ assert(range.last.sheet == 0);
+ assert(range.last.row == 3);
+ assert(range.last.column == -2);
+ }
+
+ // Parse name without row index.
+ struct {
+ const char* name; formula_name_t::name_type type;
+ } name_tests[] = {
+ { "H:H", formula_name_t::range_reference },
+ { "ABC", formula_name_t::named_expression },
+ { "H", formula_name_t::named_expression },
+ { "MAX", formula_name_t::function },
+ { 0, formula_name_t::invalid }
+ };
+
+ for (size_t i = 0; name_tests[i].name; ++i)
+ {
+ std::string name_a1(name_tests[i].name);
+ formula_name_t res = resolver->resolve(name_a1, abs_address_t());
+ assert(res.type == name_tests[i].type);
+ }
+
+ std::string_view invalid_names[] = {
+ "NotExists!A1", // non-existing sheet name
+ "A B C!B2", // sheet name with space not being quoted
+ "A''B!D10", // sheet name with quote must be quoted
+ "\"Quoted\"!E12", // sheet name with double quotes must be quoted
+ };
+
+ for (const auto& name: invalid_names)
+ {
+ std::cout << "invalid name: " << name << std::endl;
+ formula_name_t res = resolver->resolve(name, abs_address_t());
+ std::cout << "parsed name: " << res.to_string() << std::endl;
+ assert(res.type == formula_name_t::invalid);
+ }
+}
+
+void test_excel_a1_multisheet()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ model_context cxt;
+ cxt.append_sheet("One");
+ cxt.append_sheet("Two");
+ cxt.append_sheet("Three");
+ cxt.append_sheet("A B C"); // name with space
+ cxt.append_sheet("A'B"); // name with quote
+ cxt.append_sheet("C'D''E"); // name with two quotes
+ cxt.append_sheet("\"Quoted\""); // name with double quotes
+
+ auto resolver = formula_name_resolver::get(formula_name_resolver_t::excel_a1, &cxt);
+ assert(resolver);
+
+ struct check_type
+ {
+ std::string_view name;
+ address_t address1;
+ address_t address2;
+ };
+
+ // NB: sheet positions are always absolute in Excel.
+ const check_type checks[] = {
+ { "One:Two!C4", {0, 3, 2, true, false, false}, {1, 3, 2, true, false, false} },
+ { "One:Three!A1:B2", {0, 0, 0, true, false, false}, {2, 1, 1, true, false, false} },
+ { "'Two:A''B'!$F$10", {1, 9, 5, true, true, true}, {4, 9, 5, true, true, true} },
+ { "'A''B:C''D''''E'!B$2:D$10", {4, 1, 1, true, true, false}, {5, 9, 3, true, true, false} },
+ { "'Three:\"Quoted\"'!B10", {2, 9, 1, true, false, false}, {6, 9, 1, true, false, false} },
+ };
+
+ for (const auto& c : checks)
+ {
+ std::cout << "name: " << c.name << std::endl;
+ formula_name_t res = resolver->resolve(c.name, abs_address_t());
+ assert(res.type == formula_name_t::name_type::range_reference);
+
+ auto v = std::get<range_t>(res.value);
+ assert(v.first == c.address1);
+ assert(v.last == c.address2);
+ }
+}
+
+void test_named_expression()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ model_context cxt;
+ cxt.append_sheet("Sheet");
+
+ const std::vector<formula_name_resolver_t> resolver_types = {
+ formula_name_resolver_t::excel_a1,
+ formula_name_resolver_t::excel_r1c1,
+ // TODO : add more resolver types.
+ };
+
+ const std::vector<std::string> names = {
+ "MyRange",
+ "MyRange2",
+ };
+
+ for (formula_name_resolver_t rt : resolver_types)
+ {
+ std::cout << "formula resolver type: " << int(rt) << std::endl; // TODO : map the enum value to std::string name.
+
+ auto resolver = formula_name_resolver::get(rt, &cxt);
+ assert(resolver);
+
+ for (const std::string& name : names)
+ {
+ std::cout << "parsing '" << name << "'..." << std::endl;
+ formula_name_t res = resolver->resolve(name, abs_address_t(0,0,0));
+ assert(res.type == formula_name_t::name_type::named_expression);
+ }
+ }
+}
+
+void test_table_excel_a1()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ model_context cxt;
+ cxt.append_sheet("Sheet");
+ string_id_t s_table1 = cxt.append_string("Table1");
+ string_id_t s_table2 = cxt.append_string("Table2");
+ string_id_t s_cat = cxt.append_string("Category");
+ string_id_t s_val = cxt.append_string("Value");
+
+ // Make sure these work correctly before proceeding further with the test.
+ assert(s_table1 == cxt.get_identifier_from_string("Table1"));
+ assert(s_table2 == cxt.get_identifier_from_string("Table2"));
+ assert(s_cat == cxt.get_identifier_from_string("Category"));
+ assert(s_val == cxt.get_identifier_from_string("Value"));
+
+ auto resolver = formula_name_resolver::get(formula_name_resolver_t::excel_a1, &cxt);
+ assert(resolver);
+
+ struct {
+ std::string_view exp;
+ sheet_t sheet;
+ row_t row;
+ col_t col;
+ string_id_t table_name;
+ string_id_t column_first;
+ string_id_t column_last;
+ table_areas_t areas;
+ } tests[] = {
+ { "[Value]", 0, 9, 2, empty_string_id, s_val, empty_string_id, table_area_data },
+ { "Table1[Category]", 0, 9, 2, s_table1, s_cat, empty_string_id, table_area_data },
+ { "Table1[Value]", 0, 9, 2, s_table1, s_val, empty_string_id, table_area_data },
+ { "Table1[[#Headers],[Value]]", 0, 9, 2, s_table1, s_val, empty_string_id, table_area_headers },
+ { "Table1[[#Headers],[#Data],[Value]]", 0, 9, 2, s_table1, s_val, empty_string_id, table_area_headers | table_area_data },
+ { "Table1[[#All],[Category]]", 0, 9, 2, s_table1, s_cat, empty_string_id, table_area_all },
+ { "Table1[[#Totals],[Category]]", 0, 9, 2, s_table1, s_cat, empty_string_id, table_area_totals },
+ { "Table1[[#Data],[#Totals],[Value]]", 0, 9, 2, s_table1, s_val, empty_string_id, table_area_data | table_area_totals },
+ { "Table1[#All]", 0, 9, 2, s_table1, empty_string_id, empty_string_id, table_area_all },
+ { "Table1[#Headers]", 0, 9, 2, s_table1, empty_string_id, empty_string_id, table_area_headers },
+ { "Table1[#Data]", 0, 9, 2, s_table1, empty_string_id, empty_string_id, table_area_data },
+ { "Table1[#Totals]", 0, 9, 2, s_table1, empty_string_id, empty_string_id, table_area_totals },
+ { "Table1[[#Headers],[#Data]]", 0, 9, 2, s_table1, empty_string_id, empty_string_id, table_area_headers | table_area_data },
+ { "Table1[[#Totals],[Category]:[Value]]", 0, 9, 2, s_table1, s_cat, s_val, table_area_totals },
+ { "Table1[[#Data],[#Totals],[Category]:[Value]]", 0, 9, 2, s_table1, s_cat, s_val, table_area_data | table_area_totals },
+ };
+
+ for (size_t i = 0, n = std::size(tests); i < n; ++i)
+ {
+ std::cout << "* table reference: " << tests[i].exp << std::endl;
+ abs_address_t pos(tests[i].sheet, tests[i].row, tests[i].col);
+ formula_name_t res = resolver->resolve(tests[i].exp, pos);
+ if (res.type != formula_name_t::table_reference)
+ assert(!"table reference expected.");
+
+ auto table = std::get<formula_name_t::table_type>(res.value);
+ string_id_t table_name = cxt.get_identifier_from_string(table.name);
+ string_id_t column_first = cxt.get_identifier_from_string(table.column_first);
+ string_id_t column_last = cxt.get_identifier_from_string(table.column_last);
+ assert(table_name == tests[i].table_name);
+ assert(column_first == tests[i].column_first);
+ assert(column_last == tests[i].column_last);
+ assert(table.areas == tests[i].areas);
+
+ // Make sure we get the same name back.
+ table_t tb;
+ tb.name = table_name;
+ tb.column_first = column_first;
+ tb.column_last = column_last;
+ tb.areas = table.areas;
+ std::string original(tests[i].exp);
+ std::string returned = resolver->get_name(tb);
+ std::cout << " original: " << original << std::endl;
+ std::cout << " returned: " << returned << std::endl;
+ assert(original == returned);
+ }
+}
+
+void test_excel_r1c1()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ model_context cxt;
+ cxt.append_sheet("One");
+ cxt.append_sheet("Two");
+ cxt.append_sheet("A B C"); // name with space
+ cxt.append_sheet("80's Music");
+
+ auto resolver = formula_name_resolver::get(formula_name_resolver_t::excel_r1c1, &cxt);
+ assert(resolver);
+
+ // Parse single cell addresses for round-tripping.
+ ref_name_entry single_ref_names[] =
+ {
+ { "R2", false },
+ { "R[3]", false },
+ { "R[-10]", false },
+ { "C2", false },
+ { "C[3]", false },
+ { "C[-10]", false },
+ { "R1C1", false },
+ { "R[1]C2", false },
+ { "R2C[-2]", false },
+ { "R1C", false },
+ { "R[-3]C", false },
+ { "RC2", false },
+ { "One!R10C", true },
+ { "Two!C[-2]", true },
+ { "'A B C'!R100", true },
+ { 0, false }
+ };
+
+ for (size_t i = 0; single_ref_names[i].name; ++i)
+ {
+ const char* p = single_ref_names[i].name;
+ std::string name_r1c1(p);
+ std::cout << "Parsing " << name_r1c1 << std::endl;
+ formula_name_t res = resolver->resolve(name_r1c1, abs_address_t());
+ if (res.type != formula_name_t::cell_reference)
+ {
+ std::cerr << "failed to resolve cell address: " << name_r1c1 << std::endl;
+ assert(false);
+ }
+
+ address_t addr = std::get<address_t>(res.value);
+ std::string test_name = resolver->get_name(addr, abs_address_t(), single_ref_names[i].sheet_name);
+
+ if (name_r1c1 != test_name)
+ {
+ std::cerr << "failed to compile name from address: (name expected: "
+ << name_r1c1 << "; actual name created: " << test_name << ")" << std::endl;
+ assert(false);
+ }
+ }
+
+ // These are supposed to be all invalid or named expression.
+ const char* invalid_address[] = {
+ "F",
+ "RR",
+ "RC",
+ "R",
+ "C",
+ "R0C1",
+ "R[-2]C-1",
+ "R1C2:",
+ "R:",
+ "R2:",
+ "R[-3]:",
+ "C:",
+ "C3:",
+ "C[-4]:",
+ 0
+ };
+
+ for (size_t i = 0; invalid_address[i]; ++i)
+ {
+ const char* p = invalid_address[i];
+ std::string name_r1c1(p);
+ formula_name_t res = resolver->resolve(name_r1c1, abs_address_t());
+ if (res.type != formula_name_t::invalid && res.type != formula_name_t::named_expression)
+ {
+ std::cerr << "address " << name_r1c1 << " is expected to be invalid." << std::endl;
+ assert(false);
+ }
+ }
+
+ // These are supposed to be all valid.
+ const char* valid_address[] = {
+ "r1c2",
+ "r[-2]",
+ "c10",
+ 0
+ };
+
+ for (size_t i = 0; valid_address[i]; ++i)
+ {
+ const char* p = valid_address[i];
+ std::string name_r1c1(p);
+ formula_name_t res = resolver->resolve(name_r1c1, abs_address_t());
+ if (res.type != formula_name_t::cell_reference)
+ {
+ std::cerr << "address " << name_r1c1 << " is expected to be valid." << std::endl;
+ assert(false);
+ }
+ }
+
+ // Parse range addresses.
+ struct {
+ const char* name;
+ sheet_t sheet1;
+ row_t row1;
+ col_t col1;
+ sheet_t sheet2;
+ row_t row2;
+ col_t col2;
+ bool abs_sheet1;
+ bool abs_row1;
+ bool abs_col1;
+ bool abs_sheet2;
+ bool abs_row2;
+ bool abs_col2;
+ } range_tests[] = {
+ { "R1C1:R2C2", 0, 0, 0, 0, 1, 1, true, true, true, true, true, true },
+ { "R[-3]C[2]:R[1]C[4]", 0, -3, 2, 0, 1, 4, true, false, false, true, false, false },
+ { "R2:R4", 0, 1, column_unset, 0, 3, column_unset, true, true, false, true, true, false },
+ { "R[2]:R[4]", 0, 2, column_unset, 0, 4, column_unset, true, false, false, true, false, false },
+ { "C3:C6", 0, row_unset, 2, 0, row_unset, 5, true, false, true, true, false, true },
+ { "C[3]:C[6]", 0, row_unset, 3, 0, row_unset, 6, true, false, false, true, false, false },
+ { "Two!R2C2:R2C[100]", 1, 1, 1, 1, 1, 100, true, true, true, true, true, false },
+ { "'A B C'!R[2]:R[4]", 2, 2, column_unset, 2, 4, column_unset, true, false, false, true, false, false },
+ { 0, 0, 0, 0, 0, 0, 0, false, false, false, false, false, false }
+ };
+
+ for (size_t i = 0; range_tests[i].name; ++i)
+ {
+ std::string name_r1c1(range_tests[i].name);
+ std::cout << "Parsing " << name_r1c1 << std::endl;
+ formula_name_t res = resolver->resolve(name_r1c1, abs_address_t());
+ auto range = std::get<range_t>(res.value);
+
+ assert(res.type == formula_name_t::range_reference);
+ assert(range.first.sheet == range_tests[i].sheet1);
+ assert(range.first.row == range_tests[i].row1);
+ assert(range.first.column == range_tests[i].col1);
+ assert(range.first.abs_sheet == range_tests[i].abs_sheet1);
+ if (range.first.row != row_unset)
+ // When row is unset, whether it's relative or absolute is not relevant.
+ assert(range.first.abs_row == range_tests[i].abs_row1);
+ if (range.first.column != column_unset)
+ // Same with unset column.
+ assert(range.first.abs_column == range_tests[i].abs_col1);
+
+ assert(range.last.sheet == range_tests[i].sheet2);
+ assert(range.last.row == range_tests[i].row2);
+ assert(range.last.column == range_tests[i].col2);
+ assert(range.last.abs_sheet == range_tests[i].abs_sheet2);
+ if (range.last.row != row_unset)
+ assert(range.last.abs_row == range_tests[i].abs_row2);
+ if (range.last.column != column_unset)
+ assert(range.last.abs_column == range_tests[i].abs_col2);
+ }
+
+ ref_name_entry range_ref_names[] =
+ {
+ { "R2C2:R3C3", false },
+ { "R[-3]C2:R[-1]C3", false },
+ { "R[-5]C:R[-1]C", false },
+ { "'A B C'!R2:R4", true },
+ { "'80''s Music'!C[2]:C[4]", true },
+ { 0, false },
+ };
+
+ for (size_t i = 0; range_ref_names[i].name; ++i)
+ {
+ const char* p = range_ref_names[i].name;
+ std::string name_r1c1(p);
+ std::cout << "Parsing " << name_r1c1 << std::endl;
+ formula_name_t res = resolver->resolve(name_r1c1, abs_address_t());
+ if (res.type != formula_name_t::range_reference)
+ {
+ std::cerr << "failed to resolve range address: " << name_r1c1 << std::endl;
+ assert(false);
+ }
+
+ auto range = std::get<range_t>(res.value);
+ std::string test_name = resolver->get_name(range, abs_address_t(), range_ref_names[i].sheet_name);
+
+ if (name_r1c1 != test_name)
+ {
+ std::cerr << "failed to compile name from range: (name expected: " << name_r1c1 << "; actual name created: " << test_name << ")" << std::endl;
+ assert(false);
+ }
+ }
+
+ struct {
+ col_t col;
+ std::string name;
+ } colnames[] = {
+ { 0, "1" },
+ { 1, "2" },
+ { 10, "11" },
+ { 123, "124" },
+ };
+
+ for (size_t i = 0, n = std::size(colnames); i < n; ++i)
+ {
+ std::string colname = resolver->get_column_name(colnames[i].col);
+ if (colname != colnames[i].name)
+ {
+ std::cerr << "column name: expected='" << colnames[i].name << "', actual='" << colname << "'" << std::endl;
+ assert(false);
+ }
+ }
+}
+
+void test_excel_r1c1_multisheet()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ model_context cxt;
+ cxt.append_sheet("One");
+ cxt.append_sheet("Two");
+ cxt.append_sheet("Three");
+ cxt.append_sheet("A B C"); // name with space
+ cxt.append_sheet("A'B"); // name with quote
+ cxt.append_sheet("C'D''E"); // name with two quotes
+ cxt.append_sheet("\"Quoted\""); // name with double quotes
+
+ auto resolver = formula_name_resolver::get(formula_name_resolver_t::excel_r1c1, &cxt);
+ assert(resolver);
+
+ struct check_type
+ {
+ std::string_view name;
+ address_t address1;
+ address_t address2;
+ };
+
+ // NB: sheet positions are always absolute in Excel.
+ const check_type checks[] = {
+ { "One:Two!R[3]C[2]", {0, 3, 2, true, false, false}, {1, 3, 2, true, false, false} },
+ { "One:Three!RC:R[1]C[1]", {0, 0, 0, true, false, false}, {2, 1, 1, true, false, false} },
+ { "'Two:A''B'!R10C6", {1, 9, 5, true, true, true}, {4, 9, 5, true, true, true} },
+ { "'A''B:C''D''''E'!R2C[1]:R10C[3]", {4, 1, 1, true, true, false}, {5, 9, 3, true, true, false} },
+ { "'Three:\"Quoted\"'!R[9]C[1]", {2, 9, 1, true, false, false}, {6, 9, 1, true, false, false} },
+ };
+
+ for (const auto& c : checks)
+ {
+ std::cout << "name: " << c.name << std::endl;
+ formula_name_t res = resolver->resolve(c.name, abs_address_t());
+ assert(res.type == formula_name_t::name_type::range_reference);
+
+ auto v = std::get<range_t>(res.value);
+ assert(v.first == c.address1);
+ assert(v.last == c.address2);
+ }
+}
+
+void test_odff()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ model_context cxt;
+ cxt.append_sheet("One");
+ cxt.append_sheet("Two");
+ cxt.append_sheet("A B C"); // name with space
+ cxt.append_sheet("80's Music");
+
+ auto resolver = formula_name_resolver::get(formula_name_resolver_t::odff, &cxt);
+ assert(resolver);
+
+ // Parse single cell addresses.
+ ref_name_entry single_ref_names[] =
+ {
+ { "[.A1]", false },
+ { "[.$A1]", false },
+ { "[.A$1]", false },
+ { "[.$A$1]", false },
+ { 0, false }
+ };
+
+ for (size_t i = 0; single_ref_names[i].name; ++i)
+ {
+ const char* p = single_ref_names[i].name;
+ std::string name_a1(p);
+ formula_name_t res = resolver->resolve(name_a1, abs_address_t());
+ if (res.type != formula_name_t::cell_reference)
+ {
+ std::cerr << "failed to resolve cell address: " << name_a1 << std::endl;
+ assert(false);
+ }
+
+ address_t addr = std::get<address_t>(res.value);
+ std::string test_name = resolver->get_name(addr, abs_address_t(), single_ref_names[i].sheet_name);
+
+ if (name_a1 != test_name)
+ {
+ std::cerr << "failed to compile name from address: (name expected: " << name_a1 << "; actual name created: " << test_name << ")" << std::endl;
+ assert(false);
+ }
+ }
+
+ // Parse cell range addresses.
+ ref_name_entry range_ref_names[] =
+ {
+ { "[.A1:.A3]", false },
+ { "[.$B5:.$D10]", false },
+ { 0, false }
+ };
+
+ for (size_t i = 0; range_ref_names[i].name; ++i)
+ {
+ const char* p = range_ref_names[i].name;
+ std::string name_a1(p);
+ formula_name_t res = resolver->resolve(name_a1, abs_address_t());
+ if (res.type != formula_name_t::range_reference)
+ {
+ std::cerr << "failed to resolve range address: " << name_a1 << std::endl;
+ assert(false);
+ }
+
+ auto range = std::get<range_t>(res.value);
+ std::string test_name = resolver->get_name(range, abs_address_t(), range_ref_names[i].sheet_name);
+
+ if (name_a1 != test_name)
+ {
+ std::cerr << "failed to compile name from range: (name expected: " << name_a1 << "; actual name created: " << test_name << ")" << std::endl;
+ assert(false);
+ }
+ }
+
+ // single cell addresses with sheet names
+ ref_name_entry addr_with_sheet_names[] =
+ {
+ { "[One.$B$1]", true },
+ { "[$One.$B$1]", true },
+ { "[Two.$B$2]", true },
+ { "[$Two.$B$4]", true },
+ { "['A B C'.$B$4]", true },
+ { "[$'A B C'.$B$4]", true },
+ { 0, false },
+ };
+
+ for (size_t i = 0; addr_with_sheet_names[i].name; ++i)
+ {
+ const char* p = addr_with_sheet_names[i].name;
+ std::string name_a1(p);
+
+ formula_name_t res = resolver->resolve(name_a1, abs_address_t());
+ if (res.type != formula_name_t::cell_reference)
+ {
+ std::cerr << "failed to resolve cell address: " << name_a1 << std::endl;
+ assert(false);
+ }
+
+ address_t addr = std::get<address_t>(res.value);
+ std::string test_name = resolver->get_name(addr, abs_address_t(), addr_with_sheet_names[i].sheet_name);
+
+ if (name_a1 != test_name)
+ {
+ std::cerr << "failed to compile name from cell address: (name expected: " << name_a1 << "; actual name created: " << test_name << ")" << std::endl;
+ assert(false);
+ }
+ }
+
+ // range addresses with sheet names
+ ref_name_entry ref_with_sheet_names[] =
+ {
+ { "[One.$B$1:.$B$30]", true },
+ { "[$One.$B$1:.$B$30]", true },
+ { "[Two.$B$2:.$D30]", true },
+ { "[$Two.$B$4:.F$35]", true },
+ { "['A B C'.$B$4:.F$35]", true },
+ { "[$'A B C'.$B$4:.F$35]", true },
+ { "[$One.B$4:$Two.F35]", true },
+ { "[$One.B$4:'A B C'.F35]", true },
+ { "[One.B$4:$'A B C'.F35]", true },
+ { 0, false },
+ };
+
+ for (size_t i = 0; ref_with_sheet_names[i].name; ++i)
+ {
+ const char* p = ref_with_sheet_names[i].name;
+ std::string name_a1(p);
+
+ formula_name_t res = resolver->resolve(name_a1, abs_address_t());
+ if (res.type != formula_name_t::range_reference)
+ {
+ std::cerr << "failed to resolve range address: " << name_a1 << std::endl;
+ assert(false);
+ }
+
+ auto range = std::get<range_t>(res.value);
+ std::string test_name = resolver->get_name(range, abs_address_t(), ref_with_sheet_names[i].sheet_name);
+
+ if (name_a1 != test_name)
+ {
+ std::cerr << "failed to compile name from range: (name expected: " << name_a1 << "; actual name created: " << test_name << ")" << std::endl;
+ assert(false);
+ }
+ }
+
+ {
+ std::string name = "[.H2:.I2]";
+ abs_address_t pos(2, 1, 9);
+ formula_name_t res = resolver->resolve(name, pos);
+ assert(res.type == formula_name_t::range_reference);
+ abs_range_t range = std::get<range_t>(res.value).to_abs(pos);
+ abs_range_t range_expected(abs_address_t(pos.sheet, 1, 7), 1, 2);
+ assert(range == range_expected);
+ }
+
+ {
+ std::string name = "[Two.$B$2:.$B$10]";
+ abs_address_t pos(3, 2, 1);
+ formula_name_t res = resolver->resolve(name, pos);
+ assert(res.type == formula_name_t::range_reference);
+ abs_range_t range = std::get<range_t>(res.value).to_abs(pos);
+ abs_range_t range_expected(abs_address_t(1, 1, 1), 9, 1);
+ assert(range == range_expected);
+ }
+
+ {
+ std::string name = "MyRange";
+ abs_address_t pos(3, 2, 1);
+ formula_name_t res = resolver->resolve(name, pos);
+ assert(res.type == formula_name_t::named_expression);
+ }
+}
+
+void test_odf_cra()
+{
+ IXION_TEST_FUNC_SCOPE;
+
+ model_context cxt;
+ cxt.append_sheet("One");
+ cxt.append_sheet("Two");
+ cxt.append_sheet("Three");
+ cxt.append_sheet("A B C"); // name with space
+ auto resolver = formula_name_resolver::get(formula_name_resolver_t::odf_cra, &cxt);
+ assert(resolver);
+
+ {
+ // Parse single cell addresses.
+ ref_name_entry names[] =
+ {
+ { ".A1", false },
+ { ".$A1", false },
+ { ".A$1", false },
+ { ".$A$1", false },
+ { ".Z1", false },
+ { ".AA23", false },
+ { ".AB23", false },
+ { ".$AB23", false },
+ { ".AB$23", false },
+ { ".$AB$23", false },
+ { ".BA1", false },
+ { ".AAA2", false },
+ { ".ABA1", false },
+ { ".BAA1", false },
+ { ".XFD1048576", false },
+ { "One.A1", true },
+ { "One.XFD1048576", true },
+ { "Two.B10", true },
+ { "Two.$B10", true },
+ { "Two.B$10", true },
+ { "Two.$B$10", true },
+ { "Three.CFD234", true },
+ { "$Three.CFD234", true },
+ { "'A B C'.Z12", true },
+ { "$'A B C'.Z12", true },
+ { 0, false }
+ };
+
+ for (size_t i = 0; names[i].name; ++i)
+ {
+ const char* p = names[i].name;
+ std::string name_a1(p);
+ std::cout << "single cell address: " << name_a1 << std::endl;
+ formula_name_t res = resolver->resolve(name_a1, abs_address_t());
+ if (res.type != formula_name_t::cell_reference)
+ {
+ std::cerr << "failed to resolve cell address: " << name_a1 << std::endl;
+ assert(false);
+ }
+
+ address_t addr = std::get<address_t>(res.value);
+ std::string test_name = resolver->get_name(addr, abs_address_t(), names[i].sheet_name);
+
+ if (name_a1 != test_name)
+ {
+ std::cerr << "failed to compile name from address: (name expected: " << name_a1 << "; actual name created: " << test_name << ")" << std::endl;
+ assert(false);
+ }
+ }
+ }
+
+ {
+ // Parse range addresses.
+
+ ref_name_entry names[] =
+ {
+ { ".A1:.B2", false },
+ { ".$D10:.G$24", false },
+ { "One.C$1:.Z$400", true },
+ { "Two.$C1:.$Z400", true },
+ { "Three.$C1:.Z$400", true },
+ { "$Three.$C1:.Z$400", true },
+ { "'A B C'.$C4:.$Z256", true },
+ { "$'A B C'.$C4:.$Z256", true },
+ { "One.C4:Three.Z100", true },
+ { "One.C4:$Three.Z100", true },
+ { "$One.C4:Three.Z100", true },
+ { "$One.C4:$Three.Z100", true },
+ { 0, false },
+ };
+
+ for (sheet_t sheet = 0; sheet <= 3; ++sheet)
+ {
+ for (size_t i = 0; names[i].name; ++i)
+ {
+ abs_address_t pos{sheet, 0, 0};
+ std::string name_a1(names[i].name);
+ std::cout << "range address: " << name_a1 << std::endl;
+ formula_name_t res = resolver->resolve(name_a1, pos);
+ if (res.type != formula_name_t::range_reference)
+ {
+ std::cerr << "failed to resolve range address: " << name_a1 << std::endl;
+ assert(false);
+ }
+
+ auto range = std::get<range_t>(res.value);
+ std::string test_name = resolver->get_name(range, pos, names[i].sheet_name);
+
+ if (name_a1 != test_name)
+ {
+ std::cerr << "failed to compile name from range: (pos: " << pos << "; name expected: " << name_a1 << "; actual name created: " << test_name << ")" << std::endl;
+ assert(false);
+ }
+ }
+ }
+ }
+}
+
+} // anonymous namespace
+
+int main()
+{
+ test_calc_a1();
+ test_excel_a1();
+ test_excel_a1_multisheet();
+ test_named_expression();
+ test_table_excel_a1();
+ test_excel_r1c1();
+ test_excel_r1c1_multisheet();
+ test_odff();
+ test_odf_cra();
+
+ return EXIT_SUCCESS;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/named_expressions_iterator.cpp b/src/libixion/named_expressions_iterator.cpp
new file mode 100644
index 0000000..77ce457
--- /dev/null
+++ b/src/libixion/named_expressions_iterator.cpp
@@ -0,0 +1,85 @@
+/* -*- 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 "ixion/named_expressions_iterator.hpp"
+#include "ixion/global.hpp"
+#include "model_types.hpp"
+#include "model_context_impl.hpp"
+
+namespace ixion {
+
+struct named_expressions_iterator::impl
+{
+ const detail::named_expressions_t* named_exps;
+ detail::named_expressions_t::const_iterator it;
+ detail::named_expressions_t::const_iterator it_end;
+
+ impl() : named_exps(nullptr) {}
+
+ impl(const model_context& cxt, sheet_t scope) :
+ named_exps(scope >=0 ? &cxt.mp_impl->get_named_expressions(scope) : &cxt.mp_impl->get_named_expressions()),
+ it(named_exps->cbegin()),
+ it_end(named_exps->cend()) {}
+
+ impl(const impl& other) :
+ named_exps(other.named_exps),
+ it(other.it),
+ it_end(other.it_end) {}
+};
+
+named_expressions_iterator::named_expressions_iterator() :
+ mp_impl(std::make_unique<impl>()) {}
+
+named_expressions_iterator::named_expressions_iterator(const model_context& cxt, sheet_t scope) :
+ mp_impl(std::make_unique<impl>(cxt, scope)) {}
+
+named_expressions_iterator::named_expressions_iterator(const named_expressions_iterator& other) :
+ mp_impl(std::make_unique<impl>(*other.mp_impl)) {}
+
+named_expressions_iterator::named_expressions_iterator(named_expressions_iterator&& other) :
+ mp_impl(std::move(other.mp_impl)) {}
+
+named_expressions_iterator::~named_expressions_iterator() = default;
+
+size_t named_expressions_iterator::size() const
+{
+ if (!mp_impl->named_exps)
+ return 0;
+
+ return mp_impl->named_exps->size();
+}
+
+bool named_expressions_iterator::has() const
+{
+ if (!mp_impl->named_exps)
+ return false;
+
+ return mp_impl->it != mp_impl->it_end;
+}
+
+void named_expressions_iterator::next()
+{
+ ++mp_impl->it;
+}
+
+named_expressions_iterator::named_expression named_expressions_iterator::get() const
+{
+ named_expression ret;
+ ret.name = &mp_impl->it->first;
+ ret.expression = &mp_impl->it->second;
+ return ret;
+}
+
+named_expressions_iterator& named_expressions_iterator::operator= (const named_expressions_iterator& other)
+{
+ mp_impl = std::make_unique<impl>(*other.mp_impl);
+ return *this;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/queue_entry.cpp b/src/libixion/queue_entry.cpp
new file mode 100644
index 0000000..3051f8f
--- /dev/null
+++ b/src/libixion/queue_entry.cpp
@@ -0,0 +1,17 @@
+/* -*- 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 "queue_entry.hpp"
+
+namespace ixion {
+
+queue_entry::queue_entry(formula_cell* _p, const abs_address_t& _pos) :
+ p(_p), pos(_pos) {}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/queue_entry.hpp b/src/libixion/queue_entry.hpp
new file mode 100644
index 0000000..eb5e663
--- /dev/null
+++ b/src/libixion/queue_entry.hpp
@@ -0,0 +1,29 @@
+/* -*- 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/.
+ */
+
+#ifndef INCLUDED_QUEUE_ENTRY_HPP
+#define INCLUDED_QUEUE_ENTRY_HPP
+
+#include "ixion/address.hpp"
+
+namespace ixion {
+
+class formula_cell;
+
+struct queue_entry
+{
+ formula_cell* p;
+ abs_address_t pos;
+
+ queue_entry(formula_cell* _p, const abs_address_t& _pos);
+};
+
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/table.cpp b/src/libixion/table.cpp
new file mode 100644
index 0000000..d182715
--- /dev/null
+++ b/src/libixion/table.cpp
@@ -0,0 +1,40 @@
+/* -*- 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 <ixion/table.hpp>
+
+#include <iomanip>
+
+namespace ixion {
+
+table_t::table_t() :
+ name(empty_string_id),
+ column_first(empty_string_id),
+ column_last(empty_string_id),
+ areas(table_area_none) {}
+
+bool table_t::operator== (const table_t& r) const
+{
+ return name == r.name && column_first == r.column_first && column_last == r.column_first && areas == r.areas;
+}
+
+bool table_t::operator!= (const table_t& r) const
+{
+ return !operator==(r);
+}
+
+std::ostream& operator<<(std::ostream& os, const table_t& table)
+{
+ os << "(name:" << table.name << "; column-first:" << table.column_first
+ << "; column-last:" << table.column_last << "; areas:0x" << std::hex
+ << std::setw(2) << std::setfill('0') << table.areas << ")";
+ return os;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/types.cpp b/src/libixion/types.cpp
new file mode 100644
index 0000000..32d996c
--- /dev/null
+++ b/src/libixion/types.cpp
@@ -0,0 +1,133 @@
+/* -*- 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 <ixion/types.hpp>
+#include <ixion/macros.hpp>
+
+#include "column_store_type.hpp"
+
+#include <limits>
+#include <vector>
+
+namespace ixion {
+
+namespace {
+
+constexpr std::string_view formula_error_names[] = {
+ "", // 0: no error
+ "#REF!", // 1: result not available
+ "#DIV/0!", // 2: division by zero
+ "#NUM!", // 3: invalid expression
+ "#NAME?", // 4: name not found
+ "#NULL!", // 5: no range intersection
+ "#VALUE!", // 6: invalid value type
+ "#N/A", // 7: no value available
+};
+
+} // anonymous namespace
+
+const sheet_t global_scope = -1;
+const sheet_t invalid_sheet = -2;
+
+const string_id_t empty_string_id = std::numeric_limits<string_id_t>::max();
+
+bool is_valid_sheet(sheet_t sheet)
+{
+ return sheet >= 0;
+}
+
+rc_size_t::rc_size_t() : row(0), column(0) {}
+rc_size_t::rc_size_t(const rc_size_t& other) : row(other.row), column(other.column) {}
+rc_size_t::rc_size_t(row_t _row, col_t _column) : row(_row), column(_column) {}
+rc_size_t::~rc_size_t() {}
+
+rc_size_t& rc_size_t::operator= (const rc_size_t& other)
+{
+ row = other.row;
+ column = other.column;
+ return *this;
+}
+
+formula_group_t::formula_group_t() : size(), identity(0), grouped(false) {}
+formula_group_t::formula_group_t(const formula_group_t& r) :
+ size(r.size), identity(r.identity), grouped(r.grouped) {}
+formula_group_t::formula_group_t(const rc_size_t& _group_size, uintptr_t _identity, bool _grouped) :
+ size(_group_size), identity(_identity), grouped(_grouped) {}
+formula_group_t::~formula_group_t() {}
+
+formula_group_t& formula_group_t::operator= (const formula_group_t& other)
+{
+ size = other.size;
+ identity = other.identity;
+ return *this;
+}
+
+std::string_view get_formula_error_name(formula_error_t fe)
+{
+ constexpr std::string_view default_err_name = "#ERR!";
+
+ if (std::size_t(fe) < std::size(formula_error_names))
+ return formula_error_names[std::size_t(fe)];
+
+ return default_err_name;
+}
+
+formula_error_t to_formula_error_type(std::string_view s)
+{
+ const auto* p = formula_error_names;
+ const auto* p_end = p + std::size(formula_error_names);
+
+ p = std::find(p, p_end, s);
+
+ if (p == p_end)
+ return formula_error_t::no_error;
+
+ return formula_error_t(std::distance(formula_error_names, p));
+}
+
+column_block_shape_t::column_block_shape_t() :
+ position(0), size(0), offset(0), type(column_block_t::unknown), data(nullptr)
+{
+}
+
+column_block_shape_t::column_block_shape_t(
+ std::size_t _position, std::size_t _size, std::size_t _offset,
+ column_block_t _type, column_block_handle _data) :
+ position(_position), size(_size), offset(_offset), type(_type), data(_data)
+{
+}
+
+column_block_shape_t::column_block_shape_t(const column_block_shape_t& other) :
+ position(other.position), size(other.size), offset(other.offset),
+ type(other.type), data(other.data)
+{
+}
+
+column_block_shape_t& column_block_shape_t::operator=(const column_block_shape_t& other)
+{
+ position = other.position;
+ size = other.size;
+ offset = other.offset;
+ type = other.type;
+ data = other.data;
+ return *this;
+}
+
+std::ostream& operator<< (std::ostream& os, const column_block_shape_t& v)
+{
+ os << "(column_block_shape_t: position=" << v.position << "; size=" << v.size << "; offset=" << v.offset
+ << "; type=" << int(v.type) << "; data=" << v.data << ")";
+ return os;
+}
+
+print_config::print_config() = default;
+print_config::print_config(const print_config&) = default;
+print_config::~print_config() = default;
+
+} // namespace ixion
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/utf8.cpp b/src/libixion/utf8.cpp
new file mode 100644
index 0000000..a833ada
--- /dev/null
+++ b/src/libixion/utf8.cpp
@@ -0,0 +1,72 @@
+/* -*- 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 "utf8.hpp"
+
+#include <ixion/exceptions.hpp>
+#include <sstream>
+#include <limits>
+
+namespace ixion { namespace detail {
+
+namespace {
+
+constexpr uint8_t invalid_utf8_byte_length = std::numeric_limits<uint8_t>::max();
+
+uint8_t calc_utf8_byte_length(uint8_t c1)
+{
+ if ((c1 & 0x80) == 0x00)
+ // highest bit is not set.
+ return 1;
+
+ if ((c1 & 0xE0) == 0xC0)
+ // highest 3 bits are 110.
+ return 2;
+
+ if ((c1 & 0xF0) == 0xE0)
+ // highest 4 bits are 1110.
+ return 3;
+
+ if ((c1 & 0xFC) == 0xF0)
+ // highest 5 bits are 11110.
+ return 4;
+
+ return invalid_utf8_byte_length;
+}
+
+}
+
+std::vector<std::size_t> calc_utf8_byte_positions(const std::string& s)
+{
+ const char* p = s.data();
+ const char* p0 = p; // head position
+ const char* p_end = p + s.size();
+
+ std::vector<std::size_t> positions;
+
+ while (p < p_end)
+ {
+ positions.push_back(std::distance(p0, p));
+
+ uint8_t n = calc_utf8_byte_length(*p);
+
+ if (n == invalid_utf8_byte_length)
+ {
+ std::ostringstream os;
+ os << "invalid utf8 byte length in string '" << s << "'";
+ throw general_error(os.str());
+ }
+
+ p += n;
+ }
+
+ return positions;
+}
+
+}}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/utf8.hpp b/src/libixion/utf8.hpp
new file mode 100644
index 0000000..56962b9
--- /dev/null
+++ b/src/libixion/utf8.hpp
@@ -0,0 +1,27 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include <vector>
+#include <string>
+
+namespace ixion { namespace detail {
+
+/**
+ * Obtains the positions of the first bytes of the unicode characters.
+ *
+ * @param s input string encoded in utf-8.
+ *
+ * @return a sequence of the positions. The size of the sequence equals the
+ * logical length of the utf-8 string.
+ */
+std::vector<std::size_t> calc_utf8_byte_positions(const std::string& s);
+
+}}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/utils.cpp b/src/libixion/utils.cpp
new file mode 100644
index 0000000..24d78ec
--- /dev/null
+++ b/src/libixion/utils.cpp
@@ -0,0 +1,70 @@
+/* -*- 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 "utils.hpp"
+#include <ixion/exceptions.hpp>
+#include <ixion/formula_result.hpp>
+
+#include <sstream>
+
+namespace ixion { namespace detail {
+
+celltype_t to_celltype(mdds::mtv::element_t mtv_type)
+{
+ switch (mtv_type)
+ {
+ case element_type_empty:
+ return celltype_t::empty;
+ case element_type_numeric:
+ return celltype_t::numeric;
+ case element_type_boolean:
+ return celltype_t::boolean;
+ case element_type_string:
+ return celltype_t::string;
+ case element_type_formula:
+ return celltype_t::formula;
+ default:
+ ;
+ }
+
+ std::ostringstream os;
+ os << "unknown cell type (" << mtv_type << ")";
+ throw general_error(os.str());
+}
+
+cell_value_t to_cell_value_type(
+ const column_store_t::const_position_type& pos, formula_result_wait_policy_t policy)
+{
+ celltype_t raw_type = to_celltype(pos.first->type);
+
+ if (raw_type != celltype_t::formula)
+ // celltype_t and cell_result_t are numerically equivalent except for the formula slot.
+ return static_cast<cell_value_t>(raw_type);
+
+ const formula_cell* fc = formula_element_block::at(*pos.first->data, pos.second);
+ formula_result res = fc->get_result_cache(policy); // by calling this we should not get a matrix result.
+
+ switch (res.get_type())
+ {
+ case formula_result::result_type::boolean:
+ return cell_value_t::boolean;
+ case formula_result::result_type::value:
+ return cell_value_t::numeric;
+ case formula_result::result_type::string:
+ return cell_value_t::string;
+ case formula_result::result_type::error:
+ return cell_value_t::error;
+ case formula_result::result_type::matrix:
+ throw std::logic_error("we shouldn't be getting a matrix result type here.");
+ }
+
+ return cell_value_t::unknown;
+}
+
+}}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/utils.hpp b/src/libixion/utils.hpp
new file mode 100644
index 0000000..aff5c4d
--- /dev/null
+++ b/src/libixion/utils.hpp
@@ -0,0 +1,111 @@
+/* -*- 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/.
+ */
+
+#ifndef INCLUDED_IXION_DETAIL_UTILS_HPP
+#define INCLUDED_IXION_DETAIL_UTILS_HPP
+
+#include "ixion/types.hpp"
+#include "column_store_type.hpp"
+
+#include <sstream>
+
+namespace ixion { namespace detail {
+
+celltype_t to_celltype(mdds::mtv::element_t mtv_type);
+
+cell_value_t to_cell_value_type(
+ const column_store_t::const_position_type& pos, formula_result_wait_policy_t policy);
+
+template<std::size_t S, typename T>
+void ensure_max_size(const T& v)
+{
+ static_assert(sizeof(T) <= S, "The size of the value exceeded allowed size limit.");
+}
+
+template<typename T>
+class const_element_block_range
+{
+ const T* m_begin;
+ const T* m_end;
+
+public:
+ const_element_block_range(const T* begin, const T* end) : m_begin(begin), m_end(end) {}
+
+ const T* begin() const { return m_begin; }
+ const T* end() const { return m_end; }
+};
+
+/* Specialization for bool in order to handle std::vector<bool>. */
+template<>
+class const_element_block_range<bool>
+{
+ using iterator_type = boolean_element_block::const_iterator;
+ iterator_type m_begin;
+ iterator_type m_end;
+
+public:
+ const_element_block_range(const iterator_type& begin, const iterator_type& end) :
+ m_begin(begin), m_end(end) {}
+
+ iterator_type begin() const { return m_begin; }
+ iterator_type end() const { return m_end; }
+};
+
+template<column_block_t BlockT>
+struct make_element_range;
+
+template<>
+struct make_element_range<column_block_t::boolean>
+{
+ const_element_block_range<bool> operator()(const column_block_shape_t& node, std::size_t length) const
+ {
+ // NB: special treatment for std::vector<bool> which underlies boolean_element_block.
+ const auto* blk = reinterpret_cast<const boolean_element_block*>(node.data);
+ auto it = boolean_element_block::cbegin(*blk);
+ it = std::next(it, node.offset);
+ length = std::min(node.size - node.offset, length);
+ auto it_end = std::next(it, length);
+
+ return {it, it_end};
+ }
+};
+
+template<>
+struct make_element_range<column_block_t::numeric>
+{
+ const_element_block_range<double> operator()(const column_block_shape_t& node, std::size_t length) const
+ {
+ const auto* blk = reinterpret_cast<const numeric_element_block*>(node.data);
+ const double* p = &numeric_element_block::at(*blk, node.offset);
+ length = std::min(node.size - node.offset, length);
+ const double* p_end = p + length;
+
+ return {p, p_end};
+ }
+};
+
+template<>
+struct make_element_range<column_block_t::formula>
+{
+ const_element_block_range<const formula_cell*> operator()(const column_block_shape_t& node, std::size_t length) const
+ {
+ const auto* blk = reinterpret_cast<const formula_element_block*>(node.data);
+ auto p = &formula_element_block::at(*blk, node.offset);
+ length = std::min(node.size - node.offset, length);
+ auto p_end = p + length;
+
+ return {p, p_end};
+ }
+};
+
+// TODO : add specialization for the other block types as needed.
+
+}}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/vulkan_obj.cpp b/src/libixion/vulkan_obj.cpp
new file mode 100644
index 0000000..e11181b
--- /dev/null
+++ b/src/libixion/vulkan_obj.cpp
@@ -0,0 +1,870 @@
+/* -*- 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 "vulkan_obj.hpp"
+#include "debug.hpp"
+
+#include "ixion/macros.hpp"
+
+#include <vector>
+#include <cstring>
+#include <iostream>
+#include <sstream>
+#include <iomanip>
+#include <bitset>
+
+namespace ixion { namespace draft {
+
+namespace {
+
+#include "vulkan_spirv_blobs.inl"
+
+VKAPI_ATTR VkBool32 VKAPI_CALL vulkan_debug_callback(
+ VkDebugUtilsMessageSeverityFlagBitsEXT severity,
+ VkDebugUtilsMessageTypeFlagsEXT type,
+ const VkDebugUtilsMessengerCallbackDataEXT* cb_data,
+ void* user)
+{
+ IXION_DEBUG(cb_data->pMessage);
+ return VK_FALSE;
+}
+
+/**
+ * When the original size is not a multiple of the device's atom size, pad
+ * it to the next multiple of the atom size. Certain memory operations
+ * require the size to be a multiple of the atom size.
+ */
+VkDeviceSize pad_to_atom_size(const vk_device& device, VkDeviceSize src_size)
+{
+ auto padded_size = src_size;
+
+ auto atom_size = device.get_physical_device_limits().nonCoherentAtomSize;
+ auto rem = padded_size % atom_size;
+ if (rem)
+ padded_size += atom_size - rem;
+
+ return padded_size;
+}
+
+} // anonymous namespace
+
+vk_instance::vk_instance()
+{
+ const char* validation_layer = "VK_LAYER_KHRONOS_validation";
+ const char* validation_ext = "VK_EXT_debug_utils";
+
+ VkApplicationInfo app_info = {};
+ app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
+ app_info.pApplicationName = "ixion-compute-engine-vulkan";
+ app_info.pEngineName = "none";
+ app_info.apiVersion = VK_API_VERSION_1_0;
+
+ VkInstanceCreateInfo instance_ci = {};
+ instance_ci.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
+ instance_ci.pApplicationInfo = &app_info;
+
+ uint32_t n_layers;
+ vkEnumerateInstanceLayerProperties(&n_layers, nullptr);
+ std::vector<VkLayerProperties> layers(n_layers);
+ vkEnumerateInstanceLayerProperties(&n_layers, layers.data());
+
+ IXION_TRACE("scanning for validation layer...");
+ bool has_validation_layer = false;
+ for (const VkLayerProperties& props : layers)
+ {
+ if (!strcmp(props.layerName, validation_layer))
+ {
+ IXION_TRACE("- " << props.layerName << " (found)");
+ has_validation_layer = true;
+ break;
+ }
+
+ IXION_TRACE("- " << props.layerName);
+ }
+
+ if (has_validation_layer)
+ {
+ instance_ci.ppEnabledLayerNames = &validation_layer;
+ instance_ci.enabledLayerCount = 1;
+ instance_ci.enabledExtensionCount = 1;
+ instance_ci.ppEnabledExtensionNames = &validation_ext;
+ }
+
+ VkResult res = vkCreateInstance(&instance_ci, nullptr, &m_instance);
+ if (res != VK_SUCCESS)
+ throw std::runtime_error("failed to create a vulkan instance.");
+
+ if (has_validation_layer)
+ {
+ VkDebugUtilsMessengerCreateInfoEXT debug_ci{};
+ debug_ci.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
+ debug_ci.messageSeverity =
+ VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |
+ VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
+ VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
+ debug_ci.messageType =
+ VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
+ VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
+ VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
+ debug_ci.pfnUserCallback = vulkan_debug_callback;
+ debug_ci.pUserData = nullptr;
+
+ auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(
+ m_instance, "vkCreateDebugUtilsMessengerEXT");
+
+ if (func)
+ {
+ res = func(m_instance, &debug_ci, nullptr, &m_debug_messenger);
+ if (res != VK_SUCCESS)
+ throw std::runtime_error("failed to create debug utils messenger.");
+ }
+ }
+}
+
+vk_instance::~vk_instance()
+{
+ if (m_debug_messenger)
+ {
+ auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(
+ m_instance, "vkDestroyDebugUtilsMessengerEXT");
+
+ if (func)
+ func(m_instance, m_debug_messenger, nullptr);
+ }
+
+ vkDestroyInstance(m_instance, nullptr);
+}
+
+VkInstance& vk_instance::get() { return m_instance; }
+
+vk_queue::vk_queue(VkQueue queue) : m_queue(queue) {}
+
+vk_queue::~vk_queue() {}
+
+void vk_queue::submit(vk_command_buffer& cmd, vk_fence& fence, VkPipelineStageFlags dst_stages)
+{
+ VkSubmitInfo info{};
+ info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+ info.pWaitDstStageMask = dst_stages ? &dst_stages : nullptr;
+ info.commandBufferCount = 1;
+ info.pCommandBuffers = &cmd.get();
+
+ VkResult res = vkQueueSubmit(m_queue, 1, &info, fence.get());
+ if (res != VK_SUCCESS)
+ throw std::runtime_error("failed to submit command to queue.");
+}
+
+void vk_queue::wait_idle()
+{
+ vkQueueWaitIdle(m_queue);
+}
+
+vk_device::vk_device(vk_instance& instance)
+{
+ uint32_t n_devices = 0;
+ VkResult res = vkEnumeratePhysicalDevices(instance.get(), &n_devices, nullptr);
+ if (res != VK_SUCCESS)
+ throw std::runtime_error("failed to query the number of physical devices.");
+
+ if (!n_devices)
+ throw std::runtime_error("no vulkan devices found!");
+
+ std::vector<VkPhysicalDevice> devices(n_devices);
+ res = vkEnumeratePhysicalDevices(instance.get(), &n_devices, devices.data());
+ if (res != VK_SUCCESS)
+ throw std::runtime_error("failed to obtain the physical device data.");
+
+#ifdef IXION_TRACE_ON
+ for (const VkPhysicalDevice pd : devices)
+ {
+ VkPhysicalDeviceProperties props;
+ vkGetPhysicalDeviceProperties(pd, &props);
+
+ IXION_TRACE("--");
+ IXION_TRACE("name: " << props.deviceName);
+ IXION_TRACE("vendor ID: " << props.vendorID);
+ IXION_TRACE("device ID: " << props.deviceID);
+ }
+#endif
+
+ m_physical_device = devices[0]; // pick the first physical device for now.
+ vkGetPhysicalDeviceProperties(m_physical_device, &m_physical_device_props);
+
+ {
+ uint32_t n_qfs;
+ vkGetPhysicalDeviceQueueFamilyProperties(m_physical_device, &n_qfs, nullptr);
+
+ std::vector<VkQueueFamilyProperties> qf_props(n_qfs);
+ vkGetPhysicalDeviceQueueFamilyProperties(m_physical_device, &n_qfs, qf_props.data());
+
+ IXION_TRACE("scanning for a queue family that supports compute...");
+
+ uint8_t current_n_flags = std::numeric_limits<uint8_t>::max();
+
+ for (size_t i = 0; i < qf_props.size(); ++i)
+ {
+ uint8_t n_flags = 0;
+ bool supports_compute = false;
+
+ const VkQueueFamilyProperties& props = qf_props[i];
+
+ std::ostringstream os;
+ os << "- queue family " << i << ": ";
+
+ if (props.queueFlags & VK_QUEUE_GRAPHICS_BIT)
+ {
+ os << "graphics ";
+ ++n_flags;
+ }
+
+ if (props.queueFlags & VK_QUEUE_COMPUTE_BIT)
+ {
+ os << "compute ";
+ ++n_flags;
+ supports_compute = true;
+ }
+
+ if (props.queueFlags & VK_QUEUE_TRANSFER_BIT)
+ {
+ os << "transfer ";
+ ++n_flags;
+ }
+
+ if (props.queueFlags & VK_QUEUE_SPARSE_BINDING_BIT)
+ {
+ os << "sparse-binding ";
+ ++n_flags;
+ }
+
+ if (props.queueFlags & VK_QUEUE_PROTECTED_BIT)
+ {
+ os << "protected ";
+ ++n_flags;
+ }
+
+ if (supports_compute && n_flags < current_n_flags)
+ {
+ current_n_flags = n_flags;
+ m_queue_family_index = i;
+ os << "(picked)";
+ }
+
+ IXION_TRACE(os.str());
+ }
+
+ IXION_TRACE("final queue family index: " << m_queue_family_index);
+
+ VkDeviceQueueCreateInfo queue_ci = {};
+
+ const float queue_prio = 0.0f;
+ queue_ci.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+ queue_ci.queueFamilyIndex = m_queue_family_index;
+ queue_ci.queueCount = 1;
+ queue_ci.pQueuePriorities = &queue_prio;
+
+ VkDeviceCreateInfo device_ci = {};
+ device_ci.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
+ device_ci.queueCreateInfoCount = 1;
+ device_ci.pQueueCreateInfos = &queue_ci;
+ res = vkCreateDevice(m_physical_device, &device_ci, nullptr, &m_device);
+ if (res != VK_SUCCESS)
+ throw std::runtime_error("failed to create a logical device.");
+
+ vkGetDeviceQueue(m_device, m_queue_family_index, 0, &m_queue);
+ }
+}
+
+vk_device::~vk_device()
+{
+ vkDestroyDevice(m_device, nullptr);
+}
+
+VkDevice& vk_device::get()
+{
+ return m_device;
+}
+
+const VkDevice& vk_device::get() const
+{
+ return m_device;
+}
+
+VkPhysicalDevice vk_device::get_physical_device()
+{
+ return m_physical_device;
+}
+
+const VkPhysicalDeviceLimits& vk_device::get_physical_device_limits() const
+{
+ return m_physical_device_props.limits;
+}
+
+vk_queue vk_device::get_queue()
+{
+ return vk_queue(m_queue);
+}
+
+VkDevice& vk_command_pool::get_device()
+{
+ return m_device;
+}
+
+VkCommandPool& vk_command_pool::get()
+{
+ return m_cmd_pool;
+}
+
+vk_command_pool::vk_command_pool(vk_device& device) :
+ m_device(device.get())
+{
+ VkCommandPoolCreateInfo ci = {};
+ ci.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+ ci.queueFamilyIndex = device.get_queue_family_index();
+ ci.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
+ VkResult res = vkCreateCommandPool(device.get(), &ci, nullptr, &m_cmd_pool);
+ if (res != VK_SUCCESS)
+ throw std::runtime_error("failed to create command pool.");
+}
+
+vk_command_pool::~vk_command_pool()
+{
+ vkDestroyCommandPool(m_device, m_cmd_pool, nullptr);
+}
+
+vk_command_buffer vk_command_pool::create_command_buffer()
+{
+ return vk_command_buffer(*this);
+}
+
+vk_command_buffer::vk_command_buffer(vk_command_pool& cmd_pool) :
+ m_cmd_pool(cmd_pool)
+{
+ VkCommandBufferAllocateInfo cb_ai {};
+ cb_ai.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+ cb_ai.commandPool = m_cmd_pool.get();
+ cb_ai.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+ cb_ai.commandBufferCount = 1;
+
+ VkResult res = vkAllocateCommandBuffers(m_cmd_pool.get_device(), &cb_ai, &m_cmd_buffer);
+ if (res != VK_SUCCESS)
+ throw std::runtime_error("failed to create a command buffer.");
+}
+
+vk_command_buffer::~vk_command_buffer()
+{
+ vkFreeCommandBuffers(m_cmd_pool.get_device(), m_cmd_pool.get(), 1, &m_cmd_buffer);
+}
+
+VkCommandBuffer& vk_command_buffer::get()
+{
+ return m_cmd_buffer;
+}
+
+void vk_command_buffer::begin()
+{
+ VkCommandBufferBeginInfo info{};
+ info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+ vkBeginCommandBuffer(m_cmd_buffer, &info);
+}
+
+void vk_command_buffer::end()
+{
+ vkEndCommandBuffer(m_cmd_buffer);
+}
+
+void vk_command_buffer::copy_buffer(vk_buffer& src, vk_buffer& dst, VkDeviceSize size)
+{
+ VkBufferCopy copy_region{};
+ copy_region.size = size;
+ vkCmdCopyBuffer(m_cmd_buffer, src.get(), dst.get(), 1, &copy_region);
+}
+
+void vk_command_buffer::buffer_memory_barrier(
+ const vk_buffer& buffer, VkAccessFlags src_access, VkAccessFlags dst_access,
+ VkPipelineStageFlagBits src_stage, VkPipelineStageFlagBits dst_stage)
+{
+ VkBufferMemoryBarrier info{};
+ info.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
+ info.buffer = buffer.get();
+ info.size = VK_WHOLE_SIZE;
+ info.srcAccessMask = src_access;
+ info.dstAccessMask = dst_access;
+ info.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+ info.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+
+ vkCmdPipelineBarrier(
+ m_cmd_buffer,
+ src_stage,
+ dst_stage,
+ 0,
+ 0, nullptr,
+ 1, &info,
+ 0, nullptr);
+}
+
+void vk_command_buffer::bind_pipeline(
+ const vk_pipeline& pipeline, VkPipelineBindPoint bind_point)
+{
+ vkCmdBindPipeline(m_cmd_buffer, bind_point, pipeline.get());
+}
+
+void vk_command_buffer::bind_descriptor_set(
+ VkPipelineBindPoint bind_point, const vk_pipeline_layout& pl_layout,
+ const vk_descriptor_set& desc_set)
+{
+ vkCmdBindDescriptorSets(
+ m_cmd_buffer, bind_point, pl_layout.get(), 0, 1, &desc_set.get(), 0, 0);
+}
+
+void vk_command_buffer::dispatch(uint32_t gc_x, uint32_t gc_y, uint32_t gc_z)
+{
+ vkCmdDispatch(m_cmd_buffer, gc_x, gc_y, gc_z);
+}
+
+vk_buffer::mem_type vk_buffer::find_memory_type(VkMemoryPropertyFlags mem_props) const
+{
+ mem_type ret;
+
+ VkPhysicalDeviceMemoryProperties pm_props;
+ vkGetPhysicalDeviceMemoryProperties(m_device.get_physical_device(), &pm_props);
+
+ VkMemoryRequirements mem_reqs;
+ vkGetBufferMemoryRequirements(m_device.get(), m_buffer, &mem_reqs);
+
+ ret.size = mem_reqs.size;
+
+ IXION_TRACE("buffer memory requirements:");
+ IXION_TRACE(" - size: " << mem_reqs.size);
+ IXION_TRACE(" - alignment: " << mem_reqs.alignment);
+ IXION_TRACE(" - memory type bits: "
+ << std::bitset<32>(mem_reqs.memoryTypeBits)
+ << " (" << mem_reqs.memoryTypeBits << ")");
+
+ IXION_TRACE("requested memory property flags: 0x"
+ << std::setw(2) << std::hex << std::setfill('0') << mem_props);
+
+ IXION_TRACE("memory types: n=" << std::dec << pm_props.memoryTypeCount);
+ for (uint32_t i = 0; i < pm_props.memoryTypeCount; ++i)
+ {
+ auto mt = pm_props.memoryTypes[i];
+ IXION_TRACE("- "
+ << std::setw(2)
+ << std::setfill('0')
+ << i
+ << ": property flags: " << std::bitset<32>(mt.propertyFlags) << " (0x"
+ << std::setw(2)
+ << std::hex
+ << std::setfill('0')
+ << mt.propertyFlags
+ << "); heap index: "
+ << std::dec
+ << mt.heapIndex);
+
+ if ((mem_reqs.memoryTypeBits & 1) == 1)
+ {
+ // The buffer can take this memory type. Now, check against
+ // the requested usage types.
+
+ if ((mt.propertyFlags & mem_props) == mem_props)
+ {
+ ret.index = i;
+ return ret;
+ }
+ }
+
+ mem_reqs.memoryTypeBits >>= 1;
+ }
+
+ throw std::runtime_error("no usable memory type found!");
+}
+
+vk_buffer::vk_buffer(vk_device& device, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags mem_props) :
+ m_device(device)
+{
+ VkBufferCreateInfo buf_ci {};
+ buf_ci.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+ buf_ci.usage = usage;
+ buf_ci.size = size;
+ buf_ci.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+
+ IXION_TRACE("buffer usage flags: " << std::bitset<32>(usage) << " (0x" << std::hex << usage << ")");
+ IXION_TRACE("memory property flags: " << std::bitset<32>(mem_props) << " (0x" << std::hex << mem_props << ")");
+ IXION_TRACE("buffer size: " << std::dec << size);
+
+ VkResult res = vkCreateBuffer(m_device.get(), &buf_ci, nullptr, &m_buffer);
+ if (res != VK_SUCCESS)
+ throw std::runtime_error("failed to create buffer.");
+
+ mem_type mt = find_memory_type(mem_props);
+
+ IXION_TRACE("memory type: (index=" << mt.index << "; size=" << mt.size << ")");
+
+ VkMemoryAllocateInfo mem_ai {};
+ mem_ai.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+ mem_ai.allocationSize = mt.size;
+ mem_ai.memoryTypeIndex = mt.index;
+
+ res = vkAllocateMemory(m_device.get(), &mem_ai, nullptr, &m_memory);
+ if (res != VK_SUCCESS)
+ throw std::runtime_error("failed to allocate memory.");
+
+ res = vkBindBufferMemory(m_device.get(), m_buffer, m_memory, 0);
+ if (res != VK_SUCCESS)
+ throw std::runtime_error("failed to bind buffer to memory.");
+}
+
+vk_buffer::~vk_buffer()
+{
+ vkFreeMemory(m_device.get(), m_memory, nullptr);
+ vkDestroyBuffer(m_device.get(), m_buffer, nullptr);
+}
+
+VkBuffer& vk_buffer::get()
+{
+ return m_buffer;
+}
+
+const VkBuffer& vk_buffer::get() const
+{
+ return m_buffer;
+}
+
+void vk_buffer::write_to_memory(void* data, VkDeviceSize size)
+{
+ IXION_TRACE("copying data of size " << size);
+
+ VkDeviceSize padded_size = pad_to_atom_size(m_device, size);
+
+ void* mapped = nullptr;
+ VkResult res = vkMapMemory(m_device.get(), m_memory, 0, padded_size, 0, &mapped);
+ if (res != VK_SUCCESS)
+ throw std::runtime_error("failed to map memory.");
+
+ memcpy(mapped, data, size);
+
+ // flush the modified memory range.
+ VkMappedMemoryRange flush_range{};
+ flush_range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
+ flush_range.memory = m_memory;
+ flush_range.offset = 0;
+ flush_range.size = padded_size;
+ vkFlushMappedMemoryRanges(m_device.get(), 1, &flush_range);
+
+ vkUnmapMemory(m_device.get(), m_memory);
+}
+
+void vk_buffer::read_from_memory(void* data, VkDeviceSize size)
+{
+ IXION_TRACE("reading data from memory for size " << size << "...");
+
+ VkDeviceSize padded_size = pad_to_atom_size(m_device, size);
+
+ void *mapped;
+ vkMapMemory(m_device.get(), m_memory, 0, padded_size, 0, &mapped);
+
+ VkMappedMemoryRange invalidate_range{};
+ invalidate_range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
+ invalidate_range.memory = m_memory;
+ invalidate_range.offset = 0;
+ invalidate_range.size = padded_size;
+ vkInvalidateMappedMemoryRanges(m_device.get(), 1, &invalidate_range);
+
+ // Copy to output
+ memcpy(data, mapped, size);
+ vkUnmapMemory(m_device.get(), m_memory);
+}
+
+vk_fence::vk_fence(vk_device& device, VkFenceCreateFlags flags) :
+ m_device(device)
+{
+ VkFenceCreateInfo info{};
+ info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
+ info.flags = flags;
+
+ vkCreateFence(m_device.get(), &info, nullptr, &m_fence);
+}
+
+vk_fence::~vk_fence()
+{
+ vkDestroyFence(m_device.get(), m_fence, nullptr);
+}
+
+VkFence& vk_fence::get()
+{
+ return m_fence;
+}
+
+void vk_fence::wait()
+{
+ VkResult res = vkWaitForFences(m_device.get(), 1, &m_fence, VK_TRUE, UINT64_MAX);
+ if (res != VK_SUCCESS)
+ throw std::runtime_error("failed to wait for a fence.");
+}
+
+void vk_fence::reset()
+{
+ vkResetFences(m_device.get(), 1, &m_fence);
+}
+
+vk_descriptor_pool::vk_descriptor_pool(
+ vk_device& device, uint32_t max_sets, std::initializer_list<VkDescriptorPoolSize> sizes) :
+ m_device(device)
+{
+ VkDescriptorPoolCreateInfo info{};
+ info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
+ info.poolSizeCount = sizes.size();
+ info.pPoolSizes = sizes.begin();
+ info.maxSets = max_sets;
+
+ VkResult res = vkCreateDescriptorPool(m_device.get(), &info, nullptr, &m_pool);
+ if (res != VK_SUCCESS)
+ throw std::runtime_error("failed to create a descriptor pool.");
+}
+
+vk_descriptor_pool::~vk_descriptor_pool()
+{
+ vkDestroyDescriptorPool(m_device.get(), m_pool, nullptr);
+}
+
+vk_descriptor_set vk_descriptor_pool::allocate(const vk_descriptor_set_layout& ds_layout)
+{
+ VkDescriptorSetAllocateInfo ai{};
+ ai.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+ ai.descriptorPool = m_pool;
+ ai.pSetLayouts = &ds_layout.get();
+ ai.descriptorSetCount = 1u;
+
+ VkDescriptorSet ds;
+ VkResult res = vkAllocateDescriptorSets(m_device.get(), &ai, &ds);
+ if (res != VK_SUCCESS)
+ throw std::runtime_error("failed to allocate a descriptor set.");
+
+ return vk_descriptor_set(ds);
+}
+
+vk_descriptor_set_layout::vk_descriptor_set_layout(
+ vk_device& device, std::initializer_list<VkDescriptorSetLayoutBinding> bindings) :
+ m_device(device)
+{
+ VkDescriptorSetLayoutCreateInfo info{};
+ info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+ info.pBindings = bindings.begin();
+ info.bindingCount = bindings.size();
+
+ VkResult res = vkCreateDescriptorSetLayout(m_device.get(), &info, nullptr, &m_ds_layout);
+ if (res != VK_SUCCESS)
+ throw std::runtime_error("failed to create a descriptor set layout.");
+}
+
+vk_descriptor_set_layout::~vk_descriptor_set_layout()
+{
+ vkDestroyDescriptorSetLayout(m_device.get(), m_ds_layout, nullptr);
+}
+
+VkDescriptorSetLayout& vk_descriptor_set_layout::get()
+{
+ return m_ds_layout;
+}
+
+const VkDescriptorSetLayout& vk_descriptor_set_layout::get() const
+{
+ return m_ds_layout;
+}
+
+vk_descriptor_set::vk_descriptor_set(VkDescriptorSet ds) :
+ m_set(ds) {}
+
+vk_descriptor_set::~vk_descriptor_set() {}
+
+VkDescriptorSet& vk_descriptor_set::get()
+{
+ return m_set;
+}
+
+const VkDescriptorSet& vk_descriptor_set::get() const
+{
+ return m_set;
+}
+
+void vk_descriptor_set::update(
+ const vk_device& device, uint32_t binding, VkDescriptorType type,
+ const vk_buffer& buffer)
+{
+ VkDescriptorBufferInfo buffer_desc = { buffer.get(), 0, VK_WHOLE_SIZE };
+
+ VkWriteDescriptorSet write_ds{};
+ write_ds.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ write_ds.dstSet = m_set;
+ write_ds.descriptorType = type;
+ write_ds.dstBinding = binding;
+ write_ds.pBufferInfo = &buffer_desc;
+ write_ds.descriptorCount = 1u;
+
+ vkUpdateDescriptorSets(device.get(), 1u, &write_ds, 0, nullptr);
+}
+
+vk_pipeline_layout::vk_pipeline_layout(
+ vk_device& device, vk_descriptor_set_layout& ds_layout) :
+ m_device(device)
+{
+ VkPipelineLayoutCreateInfo info{};
+ info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
+ info.setLayoutCount = 1;
+ info.pSetLayouts = &ds_layout.get();
+
+ VkResult res = vkCreatePipelineLayout(m_device.get(), &info, nullptr, &m_layout);
+ if (res != VK_SUCCESS)
+ throw std::runtime_error("failed to create a pipeline layout.");
+}
+
+vk_pipeline_layout::~vk_pipeline_layout()
+{
+ vkDestroyPipelineLayout(m_device.get(), m_layout, nullptr);
+}
+
+VkPipelineLayout& vk_pipeline_layout::get()
+{
+ return m_layout;
+}
+
+const VkPipelineLayout& vk_pipeline_layout::get() const
+{
+ return m_layout;
+}
+
+vk_pipeline_cache::vk_pipeline_cache(vk_device& device) :
+ m_device(device)
+{
+ VkPipelineCacheCreateInfo info{};
+ info.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
+ VkResult res = vkCreatePipelineCache(m_device.get(), &info, nullptr, &m_cache);
+ if (res != VK_SUCCESS)
+ throw std::runtime_error("failed to create a pipeline cache.");
+}
+
+vk_pipeline_cache::~vk_pipeline_cache()
+{
+ vkDestroyPipelineCache(m_device.get(), m_cache, nullptr);
+}
+
+VkPipelineCache& vk_pipeline_cache::get()
+{
+ return m_cache;
+}
+
+const VkPipelineCache& vk_pipeline_cache::get() const
+{
+ return m_cache;
+}
+
+vk_shader_module::vk_shader_module(vk_device& device, module_type mt) :
+ m_device(device)
+{
+ const uint32_t* array = nullptr;
+ std::size_t n_array = 0;
+
+ switch (mt)
+ {
+ case module_type::fibonacci:
+ {
+ array = reinterpret_cast<const uint32_t*>(fibonacci_spirv);
+ n_array = std::size(fibonacci_spirv);
+
+ IXION_TRACE("module type: fibonacci (size=" << n_array << ")");
+
+ break;
+ }
+ default:
+ throw std::runtime_error("invalid module type");
+ }
+
+ VkShaderModuleCreateInfo ci{};
+ ci.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+ ci.codeSize = n_array;
+ ci.pCode = array;
+
+ VkResult res = vkCreateShaderModule(m_device.get(), &ci, NULL, &m_module);
+ if (res != VK_SUCCESS)
+ throw std::runtime_error("failed to create a shader module.");
+}
+
+vk_shader_module::~vk_shader_module()
+{
+ vkDestroyShaderModule(m_device.get(), m_module, nullptr);
+}
+
+VkShaderModule& vk_shader_module::get()
+{
+ return m_module;
+}
+
+const VkShaderModule& vk_shader_module::get() const
+{
+ return m_module;
+}
+
+vk_pipeline::vk_pipeline(
+ const runtime_context& cxt, vk_device& device, vk_pipeline_layout& pl_layout,
+ vk_pipeline_cache& pl_cache, vk_shader_module& shader) :
+ m_device(device)
+{
+ struct sp_data_type
+ {
+ uint32_t BUFFER_ELEMENTS;
+ };
+
+ sp_data_type sp_data;
+ sp_data.BUFFER_ELEMENTS = cxt.input_buffer_size;
+
+ VkSpecializationMapEntry sp_map_entry{};
+ sp_map_entry.constantID = 0;
+ sp_map_entry.offset = 0;
+ sp_map_entry.size = sizeof(uint32_t);
+
+ VkSpecializationInfo sp_info{};
+ sp_info.mapEntryCount = 1;
+ sp_info.pMapEntries = &sp_map_entry;
+ sp_info.dataSize = sizeof(sp_data_type);
+ sp_info.pData = &sp_data;
+
+ // Data about the shader module, with special constant data via specialiation
+ // info member.
+ VkPipelineShaderStageCreateInfo shader_stage_ci{};
+ shader_stage_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ shader_stage_ci.stage = VK_SHADER_STAGE_COMPUTE_BIT;
+ shader_stage_ci.module = shader.get();
+ shader_stage_ci.pName = "main";
+ shader_stage_ci.pSpecializationInfo = &sp_info;
+
+ VkComputePipelineCreateInfo pipeline_ci{};
+ pipeline_ci.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
+ pipeline_ci.layout = pl_layout.get();
+ pipeline_ci.flags = 0;
+ pipeline_ci.stage = shader_stage_ci;
+
+ vkCreateComputePipelines(
+ m_device.get(), pl_cache.get(), 1, &pipeline_ci, nullptr, &m_pipeline);
+}
+
+vk_pipeline::~vk_pipeline()
+{
+ vkDestroyPipeline(m_device.get(), m_pipeline, nullptr);
+}
+
+VkPipeline& vk_pipeline::get()
+{
+ return m_pipeline;
+}
+
+const VkPipeline& vk_pipeline::get() const
+{
+ return m_pipeline;
+}
+
+}}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/vulkan_obj.hpp b/src/libixion/vulkan_obj.hpp
new file mode 100644
index 0000000..498efa8
--- /dev/null
+++ b/src/libixion/vulkan_obj.hpp
@@ -0,0 +1,319 @@
+/* -*- 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/.
+ */
+
+#ifndef INCLUDED_IXION_VULKAN_OBJ_HPP
+#define INCLUDED_IXION_VULKAN_OBJ_HPP
+
+#include <vulkan/vulkan.h>
+#include <memory>
+#include <limits>
+
+namespace ixion { namespace draft {
+
+template<typename T, typename U = void>
+struct null_value;
+
+template<typename T>
+struct null_value<T, typename std::enable_if<std::is_pointer<T>::value>::type>
+{
+ static constexpr std::nullptr_t value = nullptr;
+};
+
+template<typename T>
+struct null_value<T, typename std::enable_if<std::is_integral<T>::value>::type>
+{
+ static constexpr T value = 0;
+};
+
+struct runtime_context
+{
+ uint32_t input_buffer_size = 0;
+};
+
+class vk_buffer;
+class vk_command_buffer;
+class vk_command_pool;
+class vk_descriptor_set;
+class vk_descriptor_set_layout;
+class vk_fence;
+class vk_pipeline;
+class vk_pipeline_layout;
+
+class vk_instance
+{
+ VkInstance m_instance = null_value<VkInstance>::value;
+ VkDebugUtilsMessengerEXT m_debug_messenger = null_value<VkDebugUtilsMessengerEXT>::value;
+
+public:
+ vk_instance();
+ ~vk_instance();
+
+ VkInstance& get();
+};
+
+class vk_queue
+{
+ VkQueue m_queue;
+
+public:
+ vk_queue(VkQueue queue);
+ ~vk_queue();
+
+ void submit(vk_command_buffer& cmd, vk_fence& fence, VkPipelineStageFlags dst_stages = 0);
+
+ void wait_idle();
+};
+
+class vk_device
+{
+ friend class vk_command_pool;
+ friend class vk_buffer;
+
+ static constexpr uint32_t QUEUE_FAMILY_NOT_SET = std::numeric_limits<uint32_t>::max();
+
+ VkPhysicalDevice m_physical_device = null_value<VkPhysicalDevice>::value;
+ VkPhysicalDeviceProperties m_physical_device_props;
+ VkDevice m_device = null_value<VkDevice>::value;
+ uint32_t m_queue_family_index = QUEUE_FAMILY_NOT_SET;
+ VkQueue m_queue = null_value<VkQueue>::value;
+
+ uint32_t get_queue_family_index() const
+ {
+ return m_queue_family_index;
+ }
+
+public:
+ vk_device(vk_instance& instance);
+ ~vk_device();
+
+ VkDevice& get();
+ const VkDevice& get() const;
+
+ VkPhysicalDevice get_physical_device();
+
+ const VkPhysicalDeviceLimits& get_physical_device_limits() const;
+
+ vk_queue get_queue();
+};
+
+class vk_command_pool
+{
+ friend class vk_command_buffer;
+
+ VkDevice m_device = null_value<VkDevice>::value;
+ VkCommandPool m_cmd_pool = null_value<VkCommandPool>::value;
+
+ VkDevice& get_device();
+ VkCommandPool& get();
+
+public:
+ vk_command_pool(vk_device& device);
+ ~vk_command_pool();
+
+ vk_command_buffer create_command_buffer();
+};
+
+class vk_command_buffer
+{
+ friend class vk_command_pool;
+
+ vk_command_pool& m_cmd_pool;
+ VkCommandBuffer m_cmd_buffer = null_value<VkCommandBuffer>::value;
+
+ vk_command_buffer(vk_command_pool& cmd_pool);
+
+public:
+ ~vk_command_buffer();
+
+ VkCommandBuffer& get();
+
+ void begin();
+ void end();
+
+ void copy_buffer(vk_buffer& src, vk_buffer& dst, VkDeviceSize size);
+
+ void buffer_memory_barrier(
+ const vk_buffer& buffer, VkAccessFlags src_access, VkAccessFlags dst_access,
+ VkPipelineStageFlagBits src_stage, VkPipelineStageFlagBits dst_stage);
+
+ void bind_pipeline(const vk_pipeline& pipeline, VkPipelineBindPoint bind_point);
+
+ void bind_descriptor_set(
+ VkPipelineBindPoint bind_point, const vk_pipeline_layout& pl_layout,
+ const vk_descriptor_set& desc_set);
+
+ void dispatch(uint32_t gc_x, uint32_t gc_y, uint32_t gc_z);
+};
+
+class vk_buffer
+{
+ vk_device& m_device;
+ VkBuffer m_buffer = null_value<VkBuffer>::value;
+ VkDeviceMemory m_memory = null_value<VkDeviceMemory>::value;
+
+ struct mem_type
+ {
+ uint32_t index;
+ VkDeviceSize size;
+ };
+
+ /**
+ * Find a suitable device memory type that can be used to store data for
+ * the buffer.
+ *
+ * @param mem_props desired memory properties.
+ *
+ * @return mem_type memory type as an index into the list of device memory
+ * types, and the memory size as required by the buffer.
+ */
+ mem_type find_memory_type(VkMemoryPropertyFlags mem_props) const;
+
+public:
+ vk_buffer(vk_device& device, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags mem_props);
+ ~vk_buffer();
+
+ VkBuffer& get();
+ const VkBuffer& get() const;
+
+ void write_to_memory(void* data, VkDeviceSize size);
+
+ void read_from_memory(void* data, VkDeviceSize size);
+};
+
+class vk_fence
+{
+ vk_device& m_device;
+ VkFence m_fence = null_value<VkFence>::value;
+
+public:
+ vk_fence(vk_device& device, VkFenceCreateFlags flags);
+ ~vk_fence();
+
+ VkFence& get();
+
+ void wait();
+ void reset();
+};
+
+class vk_descriptor_pool
+{
+ vk_device& m_device;
+ VkDescriptorPool m_pool = null_value<VkDescriptorPool>::value;
+
+public:
+ vk_descriptor_pool(vk_device& device, uint32_t max_sets, std::initializer_list<VkDescriptorPoolSize> sizes);
+ ~vk_descriptor_pool();
+
+ vk_descriptor_set allocate(const vk_descriptor_set_layout& ds_layout);
+};
+
+class vk_descriptor_set_layout
+{
+ vk_device& m_device;
+ VkDescriptorSetLayout m_ds_layout = null_value<VkDescriptorSetLayout>::value;
+
+public:
+ vk_descriptor_set_layout(vk_device& device, std::initializer_list<VkDescriptorSetLayoutBinding> bindings);
+ ~vk_descriptor_set_layout();
+
+ VkDescriptorSetLayout& get();
+ const VkDescriptorSetLayout& get() const;
+};
+
+/**
+ * Descriptor set contains a collection of descriptors. Think of a
+ * descriptor as a handle into a resource, which can be a buffer or an
+ * image.
+ *
+ * @see https://vkguide.dev/docs/chapter-4/descriptors/
+ *
+ */
+class vk_descriptor_set
+{
+ friend class vk_descriptor_pool;
+ VkDescriptorSet m_set = null_value<VkDescriptorSet>::value;
+
+ vk_descriptor_set(VkDescriptorSet ds);
+public:
+ ~vk_descriptor_set();
+
+ VkDescriptorSet& get();
+ const VkDescriptorSet& get() const;
+
+ /**
+ * Update the descriptor set with the content of the device local buffer.
+ *
+ * @param device logical device
+ * @param binding binding position
+ * @param type descriptor type.
+ * @param buffer device local buffer to get the content from.
+ */
+ void update(const vk_device& device, uint32_t binding, VkDescriptorType type, const vk_buffer& buffer);
+};
+
+class vk_pipeline_layout
+{
+ vk_device& m_device;
+ VkPipelineLayout m_layout = null_value<VkPipelineLayout>::value;
+
+public:
+ vk_pipeline_layout(vk_device& device, vk_descriptor_set_layout& ds_layout);
+ ~vk_pipeline_layout();
+
+ VkPipelineLayout& get();
+ const VkPipelineLayout& get() const;
+};
+
+class vk_pipeline_cache
+{
+ vk_device& m_device;
+ VkPipelineCache m_cache = null_value<VkPipelineCache>::value;
+
+public:
+ vk_pipeline_cache(vk_device& device);
+ ~vk_pipeline_cache();
+
+ VkPipelineCache& get();
+ const VkPipelineCache& get() const;
+};
+
+class vk_shader_module
+{
+ vk_device& m_device;
+ VkShaderModule m_module = null_value<VkShaderModule>::value;
+
+public:
+ enum class module_type { fibonacci };
+
+ vk_shader_module(vk_device& device, module_type mt);
+ ~vk_shader_module();
+
+ VkShaderModule& get();
+ const VkShaderModule& get() const;
+};
+
+class vk_pipeline
+{
+ vk_device& m_device;
+ VkPipeline m_pipeline = null_value<VkPipeline>::value;
+
+public:
+ vk_pipeline(
+ const runtime_context& cxt, vk_device& device, vk_pipeline_layout& pl_layout,
+ vk_pipeline_cache& pl_cache, vk_shader_module& shader);
+
+ ~vk_pipeline();
+
+ VkPipeline& get();
+ const VkPipeline& get() const;
+};
+
+}}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/workbook.cpp b/src/libixion/workbook.cpp
new file mode 100644
index 0000000..7fd7328
--- /dev/null
+++ b/src/libixion/workbook.cpp
@@ -0,0 +1,55 @@
+/* -*- 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 "ixion/global.hpp"
+
+#include "workbook.hpp"
+
+namespace ixion {
+
+worksheet::worksheet() {}
+
+worksheet::worksheet(size_t row_size, size_t col_size)
+{
+ m_pos_hints.reserve(col_size);
+ for (size_t i = 0; i < col_size; ++i)
+ {
+ m_columns.emplace_back(row_size);
+ m_pos_hints.push_back(m_columns.back().begin());
+ }
+}
+
+worksheet::~worksheet() {}
+
+workbook::workbook() {}
+
+workbook::workbook(size_t sheet_size, size_t row_size, size_t col_size)
+{
+ for (size_t i = 0; i < sheet_size; ++i)
+ m_sheets.emplace_back(row_size, col_size);
+}
+
+workbook::~workbook() {}
+
+void workbook::push_back(size_t row_size, size_t col_size)
+{
+ m_sheets.emplace_back(row_size, col_size);
+}
+
+size_t workbook::size() const
+{
+ return m_sheets.size();
+}
+
+bool workbook::empty() const
+{
+ return m_sheets.empty();
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/libixion/workbook.hpp b/src/libixion/workbook.hpp
new file mode 100644
index 0000000..453e989
--- /dev/null
+++ b/src/libixion/workbook.hpp
@@ -0,0 +1,78 @@
+/* -*- 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/.
+ */
+
+#ifndef INCLUDED_IXION_WORKBOOK_HPP
+#define INCLUDED_IXION_WORKBOOK_HPP
+
+#include "column_store_type.hpp"
+#include "model_types.hpp"
+
+#include <vector>
+
+namespace ixion {
+
+class worksheet
+{
+public:
+ typedef column_store_t::size_type size_type;
+
+ worksheet();
+ worksheet(size_type row_size, size_type col_size);
+ ~worksheet();
+
+ column_store_t& operator[](size_type n) { return m_columns[n]; }
+ const column_store_t& operator[](size_type n) const { return m_columns[n]; }
+
+ column_store_t& at(size_type n) { return m_columns.at(n); }
+ const column_store_t& at(size_type n) const { return m_columns.at(n); }
+
+ column_store_t::iterator& get_pos_hint(size_type n) { return m_pos_hints.at(n); }
+
+ /**
+ * Return the number of columns.
+ *
+ * @return number of columns.
+ */
+ size_type size() const { return m_columns.size(); }
+
+ const column_stores_t& get_columns() const { return m_columns; }
+
+ detail::named_expressions_t& get_named_expressions() { return m_named_expressions; }
+ const detail::named_expressions_t& get_named_expressions() const { return m_named_expressions; }
+
+private:
+ column_stores_t m_columns;
+ std::vector<column_store_t::iterator> m_pos_hints;
+ detail::named_expressions_t m_named_expressions;
+};
+
+class workbook
+{
+public:
+ workbook();
+ workbook(size_t sheet_size, size_t row_size, size_t col_size);
+ ~workbook();
+
+ worksheet& operator[](size_t n) { return m_sheets[n]; }
+ const worksheet& operator[](size_t n) const { return m_sheets[n]; }
+
+ worksheet& at(size_t n) { return m_sheets.at(n); }
+ const worksheet& at(size_t n) const { return m_sheets.at(n); }
+
+ void push_back(size_t row_size, size_t col_size);
+
+ size_t size() const;
+ bool empty() const;
+
+private:
+ std::deque<worksheet> m_sheets;
+};
+
+}
+
+#endif
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
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: */
diff --git a/src/model_parser.hpp b/src/model_parser.hpp
new file mode 100644
index 0000000..c15a50e
--- /dev/null
+++ b/src/model_parser.hpp
@@ -0,0 +1,157 @@
+/* -*- 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/.
+ */
+
+#ifndef INCLUDED_IXION_MODEL_PARSER_HPP
+#define INCLUDED_IXION_MODEL_PARSER_HPP
+
+#include "ixion/exceptions.hpp"
+#include "ixion/model_context.hpp"
+#include "ixion/formula_result.hpp"
+
+#include "session_handler.hpp"
+#include "table_handler.hpp"
+
+#include <string>
+#include <exception>
+#include <vector>
+#include <unordered_map>
+
+namespace ixion {
+
+class model_parser
+{
+ enum parse_mode_type
+ {
+ parse_mode_unknown = 0,
+ parse_mode_init,
+ parse_mode_result,
+ parse_mode_result_cache,
+ parse_mode_edit,
+ parse_mode_table,
+ parse_mode_session,
+ parse_mode_named_expression,
+ parse_mode_exit
+ };
+
+ using parsed_assignment_type = std::pair<std::string_view, std::string_view>;
+
+ struct named_expression_type
+ {
+ std::string name;
+ std::string expression;
+ abs_address_t origin;
+ sheet_t scope = global_scope;
+ };
+
+ /**
+ * Right-hand-side cell content type.
+ */
+ enum cell_type
+ {
+ ct_unknown = 0,
+ ct_formula,
+ ct_value,
+ ct_string,
+ ct_boolean
+ };
+
+ struct cell_def_type
+ {
+ std::string_view name;
+ std::string_view value;
+ cell_type type;
+ abs_range_t pos;
+
+ bool matrix_value = false;
+ };
+
+public:
+ typedef std::unordered_map< ::std::string, formula_result> results_type;
+
+ class parse_error : public general_error
+ {
+ public:
+ explicit parse_error(const std::string& msg);
+ };
+
+ class check_error : public general_error
+ {
+ public:
+ check_error(const ::std::string& msg);
+ };
+
+ model_parser() = delete;
+ model_parser(const model_parser&) = delete;
+ model_parser& operator= (model_parser) = delete;
+
+ model_parser(const ::std::string& filepath, size_t thread_count);
+ ~model_parser();
+
+ void parse();
+
+private:
+ void init_model();
+
+ void parse_command();
+
+ void parse_session();
+ void parse_init();
+ void parse_edit();
+ void parse_result();
+ void parse_result_cache();
+
+ void parse_table();
+ void parse_table_columns(std::string_view str);
+ void push_table();
+
+ void parse_named_expression();
+ void push_named_expression();
+
+ void print_dependency();
+
+ /**
+ * Parse a simple left=right assignment line.
+ */
+ parsed_assignment_type parse_assignment();
+
+ cell_def_type parse_cell_definition();
+
+ void check();
+
+ std::string get_display_cell_string(const abs_address_t& pos) const;
+ std::string get_display_range_string(const abs_range_t& pos) const;
+
+private:
+
+ model_context m_context;
+ table_handler m_table_handler;
+ session_handler::factory m_session_handler_factory;
+ std::unique_ptr<table_handler::entry> mp_table_entry;
+ std::unique_ptr<formula_name_resolver> mp_name_resolver;
+ std::unique_ptr<named_expression_type> mp_named_expression;
+ std::string m_filepath;
+ std::string m_strm;
+ size_t m_thread_count;
+ abs_range_set_t m_dirty_formula_cells;
+ abs_range_set_t m_modified_cells;
+ results_type m_formula_results;
+
+ const char* mp_head;
+ const char* mp_end;
+ const char* mp_char;
+
+ sheet_t m_current_sheet;
+
+ parse_mode_type m_parse_mode;
+ bool m_print_separator:1;
+ bool m_print_sheet_name:1;
+};
+
+}
+
+#endif
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/python/Makefile.am b/src/python/Makefile.am
new file mode 100644
index 0000000..1dc98c4
--- /dev/null
+++ b/src/python/Makefile.am
@@ -0,0 +1,35 @@
+if BUILD_PYTHON
+
+EXTRA_DIST = test-env.sh
+
+pyexec_LTLIBRARIES = ixion.la
+ixion_la_SOURCES = \
+ document.hpp \
+ document.cpp \
+ global.hpp \
+ global.cpp \
+ python.cpp \
+ sheet.hpp \
+ sheet.cpp
+
+ixion_la_LDFLAGS = -module -avoid-version -export-symbols-regex PyInit_ixion
+ixion_la_CPPFLAGS = -I$(top_srcdir)/include $(PYTHON_CFLAGS) $(MDDS_CFLAGS)
+ixion_la_LIBADD = \
+ ../libixion/libixion-@IXION_API_VERSION@.la \
+ $(PYTHON_LIBS)
+
+if OSX
+
+TESTS = ../../bin/run-python-test-osx.sh
+
+else
+
+AM_TESTS_ENVIRONMENT = . $(srcdir)/test-env.sh;
+
+TESTS = \
+ ../../test/python/document.py \
+ ../../test/python/module.py
+
+endif
+
+endif
diff --git a/src/python/Makefile.in b/src/python/Makefile.in
new file mode 100644
index 0000000..11b660a
--- /dev/null
+++ b/src/python/Makefile.in
@@ -0,0 +1,1145 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/python
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \
+ $(top_srcdir)/m4/ax_cxx_compile_stdcxx_17.m4 \
+ $(top_srcdir)/m4/boost.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \
+ $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(pyexecdir)"
+LTLIBRARIES = $(pyexec_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+@BUILD_PYTHON_TRUE@ixion_la_DEPENDENCIES = ../libixion/libixion-@IXION_API_VERSION@.la \
+@BUILD_PYTHON_TRUE@ $(am__DEPENDENCIES_1)
+am__ixion_la_SOURCES_DIST = document.hpp document.cpp global.hpp \
+ global.cpp python.cpp sheet.hpp sheet.cpp
+@BUILD_PYTHON_TRUE@am_ixion_la_OBJECTS = ixion_la-document.lo \
+@BUILD_PYTHON_TRUE@ ixion_la-global.lo ixion_la-python.lo \
+@BUILD_PYTHON_TRUE@ ixion_la-sheet.lo
+ixion_la_OBJECTS = $(am_ixion_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+ixion_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
+ $(CXXFLAGS) $(ixion_la_LDFLAGS) $(LDFLAGS) -o $@
+@BUILD_PYTHON_TRUE@am_ixion_la_rpath = -rpath $(pyexecdir)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/ixion_la-document.Plo \
+ ./$(DEPDIR)/ixion_la-global.Plo \
+ ./$(DEPDIR)/ixion_la-python.Plo ./$(DEPDIR)/ixion_la-sheet.Plo
+am__mv = mv -f
+CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS)
+LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CXXFLAGS) $(CXXFLAGS)
+AM_V_CXX = $(am__v_CXX_@AM_V@)
+am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@)
+am__v_CXX_0 = @echo " CXX " $@;
+am__v_CXX_1 =
+CXXLD = $(CXX)
+CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
+ $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CXXLD = $(am__v_CXXLD_@AM_V@)
+am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@)
+am__v_CXXLD_0 = @echo " CXXLD " $@;
+am__v_CXXLD_1 =
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(ixion_la_SOURCES)
+DIST_SOURCES = $(am__ixion_la_SOURCES_DIST)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__tty_colors_dummy = \
+ mgn= red= grn= lgn= blu= brg= std=; \
+ am__color_tests=no
+am__tty_colors = { \
+ $(am__tty_colors_dummy); \
+ if test "X$(AM_COLOR_TESTS)" = Xno; then \
+ am__color_tests=no; \
+ elif test "X$(AM_COLOR_TESTS)" = Xalways; then \
+ am__color_tests=yes; \
+ elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \
+ am__color_tests=yes; \
+ fi; \
+ if test $$am__color_tests = yes; then \
+ red=''; \
+ grn=''; \
+ lgn=''; \
+ blu=''; \
+ mgn=''; \
+ brg=''; \
+ std=''; \
+ fi; \
+}
+am__recheck_rx = ^[ ]*:recheck:[ ]*
+am__global_test_result_rx = ^[ ]*:global-test-result:[ ]*
+am__copy_in_global_log_rx = ^[ ]*:copy-in-global-log:[ ]*
+# A command that, given a newline-separated list of test names on the
+# standard input, print the name of the tests that are to be re-run
+# upon "make recheck".
+am__list_recheck_tests = $(AWK) '{ \
+ recheck = 1; \
+ while ((rc = (getline line < ($$0 ".trs"))) != 0) \
+ { \
+ if (rc < 0) \
+ { \
+ if ((getline line2 < ($$0 ".log")) < 0) \
+ recheck = 0; \
+ break; \
+ } \
+ else if (line ~ /$(am__recheck_rx)[nN][Oo]/) \
+ { \
+ recheck = 0; \
+ break; \
+ } \
+ else if (line ~ /$(am__recheck_rx)[yY][eE][sS]/) \
+ { \
+ break; \
+ } \
+ }; \
+ if (recheck) \
+ print $$0; \
+ close ($$0 ".trs"); \
+ close ($$0 ".log"); \
+}'
+# A command that, given a newline-separated list of test names on the
+# standard input, create the global log from their .trs and .log files.
+am__create_global_log = $(AWK) ' \
+function fatal(msg) \
+{ \
+ print "fatal: making $@: " msg | "cat >&2"; \
+ exit 1; \
+} \
+function rst_section(header) \
+{ \
+ print header; \
+ len = length(header); \
+ for (i = 1; i <= len; i = i + 1) \
+ printf "="; \
+ printf "\n\n"; \
+} \
+{ \
+ copy_in_global_log = 1; \
+ global_test_result = "RUN"; \
+ while ((rc = (getline line < ($$0 ".trs"))) != 0) \
+ { \
+ if (rc < 0) \
+ fatal("failed to read from " $$0 ".trs"); \
+ if (line ~ /$(am__global_test_result_rx)/) \
+ { \
+ sub("$(am__global_test_result_rx)", "", line); \
+ sub("[ ]*$$", "", line); \
+ global_test_result = line; \
+ } \
+ else if (line ~ /$(am__copy_in_global_log_rx)[nN][oO]/) \
+ copy_in_global_log = 0; \
+ }; \
+ if (copy_in_global_log) \
+ { \
+ rst_section(global_test_result ": " $$0); \
+ while ((rc = (getline line < ($$0 ".log"))) != 0) \
+ { \
+ if (rc < 0) \
+ fatal("failed to read from " $$0 ".log"); \
+ print line; \
+ }; \
+ printf "\n"; \
+ }; \
+ close ($$0 ".trs"); \
+ close ($$0 ".log"); \
+}'
+# Restructured Text title.
+am__rst_title = { sed 's/.*/ & /;h;s/./=/g;p;x;s/ *$$//;p;g' && echo; }
+# Solaris 10 'make', and several other traditional 'make' implementations,
+# pass "-e" to $(SHELL), and POSIX 2008 even requires this. Work around it
+# by disabling -e (using the XSI extension "set +e") if it's set.
+am__sh_e_setup = case $$- in *e*) set +e;; esac
+# Default flags passed to test drivers.
+am__common_driver_flags = \
+ --color-tests "$$am__color_tests" \
+ --enable-hard-errors "$$am__enable_hard_errors" \
+ --expect-failure "$$am__expect_failure"
+# To be inserted before the command running the test. Creates the
+# directory for the log if needed. Stores in $dir the directory
+# containing $f, in $tst the test, in $log the log. Executes the
+# developer- defined test setup AM_TESTS_ENVIRONMENT (if any), and
+# passes TESTS_ENVIRONMENT. Set up options for the wrapper that
+# will run the test scripts (or their associated LOG_COMPILER, if
+# thy have one).
+am__check_pre = \
+$(am__sh_e_setup); \
+$(am__vpath_adj_setup) $(am__vpath_adj) \
+$(am__tty_colors); \
+srcdir=$(srcdir); export srcdir; \
+case "$@" in \
+ */*) am__odir=`echo "./$@" | sed 's|/[^/]*$$||'`;; \
+ *) am__odir=.;; \
+esac; \
+test "x$$am__odir" = x"." || test -d "$$am__odir" \
+ || $(MKDIR_P) "$$am__odir" || exit $$?; \
+if test -f "./$$f"; then dir=./; \
+elif test -f "$$f"; then dir=; \
+else dir="$(srcdir)/"; fi; \
+tst=$$dir$$f; log='$@'; \
+if test -n '$(DISABLE_HARD_ERRORS)'; then \
+ am__enable_hard_errors=no; \
+else \
+ am__enable_hard_errors=yes; \
+fi; \
+case " $(XFAIL_TESTS) " in \
+ *[\ \ ]$$f[\ \ ]* | *[\ \ ]$$dir$$f[\ \ ]*) \
+ am__expect_failure=yes;; \
+ *) \
+ am__expect_failure=no;; \
+esac; \
+$(AM_TESTS_ENVIRONMENT) $(TESTS_ENVIRONMENT)
+# A shell command to get the names of the tests scripts with any registered
+# extension removed (i.e., equivalently, the names of the test logs, with
+# the '.log' extension removed). The result is saved in the shell variable
+# '$bases'. This honors runtime overriding of TESTS and TEST_LOGS. Sadly,
+# we cannot use something simpler, involving e.g., "$(TEST_LOGS:.log=)",
+# since that might cause problem with VPATH rewrites for suffix-less tests.
+# See also 'test-harness-vpath-rewrite.sh' and 'test-trs-basic.sh'.
+am__set_TESTS_bases = \
+ bases='$(TEST_LOGS)'; \
+ bases=`for i in $$bases; do echo $$i; done | sed 's/\.log$$//'`; \
+ bases=`echo $$bases`
+AM_TESTSUITE_SUMMARY_HEADER = ' for $(PACKAGE_STRING)'
+RECHECK_LOGS = $(TEST_LOGS)
+AM_RECURSIVE_TARGETS = check recheck
+TEST_SUITE_LOG = test-suite.log
+TEST_EXTENSIONS = @EXEEXT@ .test
+LOG_DRIVER = $(SHELL) $(top_srcdir)/test-driver
+LOG_COMPILE = $(LOG_COMPILER) $(AM_LOG_FLAGS) $(LOG_FLAGS)
+am__set_b = \
+ case '$@' in \
+ */*) \
+ case '$*' in \
+ */*) b='$*';; \
+ *) b=`echo '$@' | sed 's/\.log$$//'`; \
+ esac;; \
+ *) \
+ b='$*';; \
+ esac
+am__test_logs1 = $(TESTS:=.log)
+am__test_logs2 = $(am__test_logs1:@EXEEXT@.log=.log)
+TEST_LOGS = $(am__test_logs2:.test.log=.log)
+TEST_LOG_DRIVER = $(SHELL) $(top_srcdir)/test-driver
+TEST_LOG_COMPILE = $(TEST_LOG_COMPILER) $(AM_TEST_LOG_FLAGS) \
+ $(TEST_LOG_FLAGS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp \
+ $(top_srcdir)/test-driver
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AS = @AS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_CPPFLAGS = @BOOST_CPPFLAGS@
+BOOST_FILESYSTEM_LDFLAGS = @BOOST_FILESYSTEM_LDFLAGS@
+BOOST_FILESYSTEM_LDPATH = @BOOST_FILESYSTEM_LDPATH@
+BOOST_FILESYSTEM_LIBS = @BOOST_FILESYSTEM_LIBS@
+BOOST_LDPATH = @BOOST_LDPATH@
+BOOST_PROGRAM_OPTIONS_LDFLAGS = @BOOST_PROGRAM_OPTIONS_LDFLAGS@
+BOOST_PROGRAM_OPTIONS_LDPATH = @BOOST_PROGRAM_OPTIONS_LDPATH@
+BOOST_PROGRAM_OPTIONS_LIBS = @BOOST_PROGRAM_OPTIONS_LIBS@
+BOOST_ROOT = @BOOST_ROOT@
+BOOST_SYSTEM_LDFLAGS = @BOOST_SYSTEM_LDFLAGS@
+BOOST_SYSTEM_LDPATH = @BOOST_SYSTEM_LDPATH@
+BOOST_SYSTEM_LIBS = @BOOST_SYSTEM_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+HAVE_CXX17 = @HAVE_CXX17@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+IXION_API_VERSION = @IXION_API_VERSION@
+IXION_MAJOR_API_VERSION = @IXION_MAJOR_API_VERSION@
+IXION_MAJOR_VERSION = @IXION_MAJOR_VERSION@
+IXION_MICRO_VERSION = @IXION_MICRO_VERSION@
+IXION_MINOR_API_VERSION = @IXION_MINOR_API_VERSION@
+IXION_MINOR_VERSION = @IXION_MINOR_VERSION@
+IXION_VERSION = @IXION_VERSION@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MDDS_CFLAGS = @MDDS_CFLAGS@
+MDDS_LIBS = @MDDS_LIBS@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POW_LIB = @POW_LIB@
+PYTHON = @PYTHON@
+PYTHON_CFLAGS = @PYTHON_CFLAGS@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_LIBS = @PYTHON_LIBS@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VERSION = @VERSION@
+VULKAN_CFLAGS = @VULKAN_CFLAGS@
+VULKAN_LIBS = @VULKAN_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+@BUILD_PYTHON_TRUE@EXTRA_DIST = test-env.sh
+@BUILD_PYTHON_TRUE@pyexec_LTLIBRARIES = ixion.la
+@BUILD_PYTHON_TRUE@ixion_la_SOURCES = \
+@BUILD_PYTHON_TRUE@ document.hpp \
+@BUILD_PYTHON_TRUE@ document.cpp \
+@BUILD_PYTHON_TRUE@ global.hpp \
+@BUILD_PYTHON_TRUE@ global.cpp \
+@BUILD_PYTHON_TRUE@ python.cpp \
+@BUILD_PYTHON_TRUE@ sheet.hpp \
+@BUILD_PYTHON_TRUE@ sheet.cpp
+
+@BUILD_PYTHON_TRUE@ixion_la_LDFLAGS = -module -avoid-version -export-symbols-regex PyInit_ixion
+@BUILD_PYTHON_TRUE@ixion_la_CPPFLAGS = -I$(top_srcdir)/include $(PYTHON_CFLAGS) $(MDDS_CFLAGS)
+@BUILD_PYTHON_TRUE@ixion_la_LIBADD = \
+@BUILD_PYTHON_TRUE@ ../libixion/libixion-@IXION_API_VERSION@.la \
+@BUILD_PYTHON_TRUE@ $(PYTHON_LIBS)
+
+@BUILD_PYTHON_TRUE@@OSX_FALSE@TESTS = \
+@BUILD_PYTHON_TRUE@@OSX_FALSE@ ../../test/python/document.py \
+@BUILD_PYTHON_TRUE@@OSX_FALSE@ ../../test/python/module.py
+
+@BUILD_PYTHON_TRUE@@OSX_TRUE@TESTS = ../../bin/run-python-test-osx.sh
+@BUILD_PYTHON_TRUE@@OSX_FALSE@AM_TESTS_ENVIRONMENT = . $(srcdir)/test-env.sh;
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .cpp .lo .log .o .obj .test .test$(EXEEXT) .trs
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/python/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/python/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-pyexecLTLIBRARIES: $(pyexec_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(pyexec_LTLIBRARIES)'; test -n "$(pyexecdir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pyexecdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pyexecdir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(pyexecdir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(pyexecdir)"; \
+ }
+
+uninstall-pyexecLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pyexec_LTLIBRARIES)'; test -n "$(pyexecdir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(pyexecdir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(pyexecdir)/$$f"; \
+ done
+
+clean-pyexecLTLIBRARIES:
+ -test -z "$(pyexec_LTLIBRARIES)" || rm -f $(pyexec_LTLIBRARIES)
+ @list='$(pyexec_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+ixion.la: $(ixion_la_OBJECTS) $(ixion_la_DEPENDENCIES) $(EXTRA_ixion_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(ixion_la_LINK) $(am_ixion_la_rpath) $(ixion_la_OBJECTS) $(ixion_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ixion_la-document.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ixion_la-global.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ixion_la-python.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ixion_la-sheet.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.cpp.o:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $<
+
+.cpp.obj:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.cpp.lo:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $<
+
+ixion_la-document.lo: document.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ixion_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ixion_la-document.lo -MD -MP -MF $(DEPDIR)/ixion_la-document.Tpo -c -o ixion_la-document.lo `test -f 'document.cpp' || echo '$(srcdir)/'`document.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/ixion_la-document.Tpo $(DEPDIR)/ixion_la-document.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='document.cpp' object='ixion_la-document.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ixion_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ixion_la-document.lo `test -f 'document.cpp' || echo '$(srcdir)/'`document.cpp
+
+ixion_la-global.lo: global.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ixion_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ixion_la-global.lo -MD -MP -MF $(DEPDIR)/ixion_la-global.Tpo -c -o ixion_la-global.lo `test -f 'global.cpp' || echo '$(srcdir)/'`global.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/ixion_la-global.Tpo $(DEPDIR)/ixion_la-global.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='global.cpp' object='ixion_la-global.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ixion_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ixion_la-global.lo `test -f 'global.cpp' || echo '$(srcdir)/'`global.cpp
+
+ixion_la-python.lo: python.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ixion_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ixion_la-python.lo -MD -MP -MF $(DEPDIR)/ixion_la-python.Tpo -c -o ixion_la-python.lo `test -f 'python.cpp' || echo '$(srcdir)/'`python.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/ixion_la-python.Tpo $(DEPDIR)/ixion_la-python.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='python.cpp' object='ixion_la-python.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ixion_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ixion_la-python.lo `test -f 'python.cpp' || echo '$(srcdir)/'`python.cpp
+
+ixion_la-sheet.lo: sheet.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ixion_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ixion_la-sheet.lo -MD -MP -MF $(DEPDIR)/ixion_la-sheet.Tpo -c -o ixion_la-sheet.lo `test -f 'sheet.cpp' || echo '$(srcdir)/'`sheet.cpp
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/ixion_la-sheet.Tpo $(DEPDIR)/ixion_la-sheet.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='sheet.cpp' object='ixion_la-sheet.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ixion_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ixion_la-sheet.lo `test -f 'sheet.cpp' || echo '$(srcdir)/'`sheet.cpp
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+# Recover from deleted '.trs' file; this should ensure that
+# "rm -f foo.log; make foo.trs" re-run 'foo.test', and re-create
+# both 'foo.log' and 'foo.trs'. Break the recipe in two subshells
+# to avoid problems with "make -n".
+.log.trs:
+ rm -f $< $@
+ $(MAKE) $(AM_MAKEFLAGS) $<
+
+# Leading 'am--fnord' is there to ensure the list of targets does not
+# expand to empty, as could happen e.g. with make check TESTS=''.
+am--fnord $(TEST_LOGS) $(TEST_LOGS:.log=.trs): $(am__force_recheck)
+am--force-recheck:
+ @:
+
+$(TEST_SUITE_LOG): $(TEST_LOGS)
+ @$(am__set_TESTS_bases); \
+ am__f_ok () { test -f "$$1" && test -r "$$1"; }; \
+ redo_bases=`for i in $$bases; do \
+ am__f_ok $$i.trs && am__f_ok $$i.log || echo $$i; \
+ done`; \
+ if test -n "$$redo_bases"; then \
+ redo_logs=`for i in $$redo_bases; do echo $$i.log; done`; \
+ redo_results=`for i in $$redo_bases; do echo $$i.trs; done`; \
+ if $(am__make_dryrun); then :; else \
+ rm -f $$redo_logs && rm -f $$redo_results || exit 1; \
+ fi; \
+ fi; \
+ if test -n "$$am__remaking_logs"; then \
+ echo "fatal: making $(TEST_SUITE_LOG): possible infinite" \
+ "recursion detected" >&2; \
+ elif test -n "$$redo_logs"; then \
+ am__remaking_logs=yes $(MAKE) $(AM_MAKEFLAGS) $$redo_logs; \
+ fi; \
+ if $(am__make_dryrun); then :; else \
+ st=0; \
+ errmsg="fatal: making $(TEST_SUITE_LOG): failed to create"; \
+ for i in $$redo_bases; do \
+ test -f $$i.trs && test -r $$i.trs \
+ || { echo "$$errmsg $$i.trs" >&2; st=1; }; \
+ test -f $$i.log && test -r $$i.log \
+ || { echo "$$errmsg $$i.log" >&2; st=1; }; \
+ done; \
+ test $$st -eq 0 || exit 1; \
+ fi
+ @$(am__sh_e_setup); $(am__tty_colors); $(am__set_TESTS_bases); \
+ ws='[ ]'; \
+ results=`for b in $$bases; do echo $$b.trs; done`; \
+ test -n "$$results" || results=/dev/null; \
+ all=` grep "^$$ws*:test-result:" $$results | wc -l`; \
+ pass=` grep "^$$ws*:test-result:$$ws*PASS" $$results | wc -l`; \
+ fail=` grep "^$$ws*:test-result:$$ws*FAIL" $$results | wc -l`; \
+ skip=` grep "^$$ws*:test-result:$$ws*SKIP" $$results | wc -l`; \
+ xfail=`grep "^$$ws*:test-result:$$ws*XFAIL" $$results | wc -l`; \
+ xpass=`grep "^$$ws*:test-result:$$ws*XPASS" $$results | wc -l`; \
+ error=`grep "^$$ws*:test-result:$$ws*ERROR" $$results | wc -l`; \
+ if test `expr $$fail + $$xpass + $$error` -eq 0; then \
+ success=true; \
+ else \
+ success=false; \
+ fi; \
+ br='==================='; br=$$br$$br$$br$$br; \
+ result_count () \
+ { \
+ if test x"$$1" = x"--maybe-color"; then \
+ maybe_colorize=yes; \
+ elif test x"$$1" = x"--no-color"; then \
+ maybe_colorize=no; \
+ else \
+ echo "$@: invalid 'result_count' usage" >&2; exit 4; \
+ fi; \
+ shift; \
+ desc=$$1 count=$$2; \
+ if test $$maybe_colorize = yes && test $$count -gt 0; then \
+ color_start=$$3 color_end=$$std; \
+ else \
+ color_start= color_end=; \
+ fi; \
+ echo "$${color_start}# $$desc $$count$${color_end}"; \
+ }; \
+ create_testsuite_report () \
+ { \
+ result_count $$1 "TOTAL:" $$all "$$brg"; \
+ result_count $$1 "PASS: " $$pass "$$grn"; \
+ result_count $$1 "SKIP: " $$skip "$$blu"; \
+ result_count $$1 "XFAIL:" $$xfail "$$lgn"; \
+ result_count $$1 "FAIL: " $$fail "$$red"; \
+ result_count $$1 "XPASS:" $$xpass "$$red"; \
+ result_count $$1 "ERROR:" $$error "$$mgn"; \
+ }; \
+ { \
+ echo "$(PACKAGE_STRING): $(subdir)/$(TEST_SUITE_LOG)" | \
+ $(am__rst_title); \
+ create_testsuite_report --no-color; \
+ echo; \
+ echo ".. contents:: :depth: 2"; \
+ echo; \
+ for b in $$bases; do echo $$b; done \
+ | $(am__create_global_log); \
+ } >$(TEST_SUITE_LOG).tmp || exit 1; \
+ mv $(TEST_SUITE_LOG).tmp $(TEST_SUITE_LOG); \
+ if $$success; then \
+ col="$$grn"; \
+ else \
+ col="$$red"; \
+ test x"$$VERBOSE" = x || cat $(TEST_SUITE_LOG); \
+ fi; \
+ echo "$${col}$$br$${std}"; \
+ echo "$${col}Testsuite summary"$(AM_TESTSUITE_SUMMARY_HEADER)"$${std}"; \
+ echo "$${col}$$br$${std}"; \
+ create_testsuite_report --maybe-color; \
+ echo "$$col$$br$$std"; \
+ if $$success; then :; else \
+ echo "$${col}See $(subdir)/$(TEST_SUITE_LOG)$${std}"; \
+ if test -n "$(PACKAGE_BUGREPORT)"; then \
+ echo "$${col}Please report to $(PACKAGE_BUGREPORT)$${std}"; \
+ fi; \
+ echo "$$col$$br$$std"; \
+ fi; \
+ $$success || exit 1
+
+check-TESTS:
+ @list='$(RECHECK_LOGS)'; test -z "$$list" || rm -f $$list
+ @list='$(RECHECK_LOGS:.log=.trs)'; test -z "$$list" || rm -f $$list
+ @test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+ @set +e; $(am__set_TESTS_bases); \
+ log_list=`for i in $$bases; do echo $$i.log; done`; \
+ trs_list=`for i in $$bases; do echo $$i.trs; done`; \
+ log_list=`echo $$log_list`; trs_list=`echo $$trs_list`; \
+ $(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) TEST_LOGS="$$log_list"; \
+ exit $$?;
+recheck: all
+ @test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+ @set +e; $(am__set_TESTS_bases); \
+ bases=`for i in $$bases; do echo $$i; done \
+ | $(am__list_recheck_tests)` || exit 1; \
+ log_list=`for i in $$bases; do echo $$i.log; done`; \
+ log_list=`echo $$log_list`; \
+ $(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) \
+ am__force_recheck=am--force-recheck \
+ TEST_LOGS="$$log_list"; \
+ exit $$?
+../../test/python/document.py.log: ../../test/python/document.py
+ @p='../../test/python/document.py'; \
+ b='../../test/python/document.py'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+../../test/python/module.py.log: ../../test/python/module.py
+ @p='../../test/python/module.py'; \
+ b='../../test/python/module.py'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+../../bin/run-python-test-osx.sh.log: ../../bin/run-python-test-osx.sh
+ @p='../../bin/run-python-test-osx.sh'; \
+ b='../../bin/run-python-test-osx.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+.test.log:
+ @p='$<'; \
+ $(am__set_b); \
+ $(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+@am__EXEEXT_TRUE@.test$(EXEEXT).log:
+@am__EXEEXT_TRUE@ @p='$<'; \
+@am__EXEEXT_TRUE@ $(am__set_b); \
+@am__EXEEXT_TRUE@ $(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \
+@am__EXEEXT_TRUE@ --log-file $$b.log --trs-file $$b.trs \
+@am__EXEEXT_TRUE@ $(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \
+@am__EXEEXT_TRUE@ "$$tst" $(AM_TESTS_FD_REDIRECT)
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-TESTS
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+installdirs:
+ for dir in "$(DESTDIR)$(pyexecdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+ -test -z "$(TEST_LOGS)" || rm -f $(TEST_LOGS)
+ -test -z "$(TEST_LOGS:.log=.trs)" || rm -f $(TEST_LOGS:.log=.trs)
+ -test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-pyexecLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/ixion_la-document.Plo
+ -rm -f ./$(DEPDIR)/ixion_la-global.Plo
+ -rm -f ./$(DEPDIR)/ixion_la-python.Plo
+ -rm -f ./$(DEPDIR)/ixion_la-sheet.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-pyexecLTLIBRARIES
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/ixion_la-document.Plo
+ -rm -f ./$(DEPDIR)/ixion_la-global.Plo
+ -rm -f ./$(DEPDIR)/ixion_la-python.Plo
+ -rm -f ./$(DEPDIR)/ixion_la-sheet.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-pyexecLTLIBRARIES
+
+.MAKE: check-am install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-TESTS \
+ check-am clean clean-generic clean-libtool \
+ clean-pyexecLTLIBRARIES cscopelist-am ctags ctags-am distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-pyexecLTLIBRARIES install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ recheck tags tags-am uninstall uninstall-am \
+ uninstall-pyexecLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/python/document.cpp b/src/python/document.cpp
new file mode 100644
index 0000000..48d1073
--- /dev/null
+++ b/src/python/document.cpp
@@ -0,0 +1,313 @@
+/* -*- 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 "document.hpp"
+#include "sheet.hpp"
+#include "global.hpp"
+
+#include "ixion/formula.hpp"
+#include "ixion/exceptions.hpp"
+#include "ixion/config.hpp"
+
+#include <iostream>
+#include <vector>
+#include <algorithm>
+#include <sstream>
+
+using namespace std;
+
+namespace ixion { namespace python {
+
+namespace {
+
+/** non-python part of the document data */
+struct document_data
+{
+ document_global m_global;
+ vector<PyObject*> m_sheets;
+
+ ~document_data();
+};
+
+struct free_pyobj
+{
+ void operator() (PyObject* p)
+ {
+ Py_XDECREF(p);
+ }
+};
+
+document_data::~document_data()
+{
+ for_each(m_sheets.begin(), m_sheets.end(), free_pyobj());
+}
+
+/**
+ * Python Document object.
+ */
+struct pyobj_document
+{
+ PyObject_HEAD
+
+ document_data* m_data;
+};
+
+void document_dealloc(pyobj_document* self)
+{
+ delete self->m_data;
+ Py_TYPE(self)->tp_free(reinterpret_cast<PyObject*>(self));
+}
+
+PyObject* document_new(PyTypeObject* type, PyObject* /*args*/, PyObject* /*kwargs*/)
+{
+ pyobj_document* self = (pyobj_document*)type->tp_alloc(type, 0);
+ self->m_data = new document_data;
+ return reinterpret_cast<PyObject*>(self);
+}
+
+int document_init(pyobj_document* self, PyObject* /*args*/, PyObject* /*kwargs*/)
+{
+ return 0;
+}
+
+PyObject* document_append_sheet(pyobj_document* self, PyObject* args)
+{
+ char* sheet_name = nullptr;
+ if (!PyArg_ParseTuple(args, "s", &sheet_name))
+ {
+ PyErr_SetString(PyExc_TypeError, "The method must be given a sheet name string");
+ return nullptr;
+ }
+
+ assert(sheet_name);
+
+ PyTypeObject* sheet_type = get_sheet_type();
+ if (!sheet_type)
+ return nullptr;
+
+ PyObject* obj_sheet = sheet_type->tp_new(sheet_type, args, 0);
+ if (!obj_sheet)
+ {
+ PyErr_SetString(PyExc_RuntimeError,
+ "Failed to allocate memory for the new sheet object.");
+ return nullptr;
+ }
+
+ sheet_type->tp_init(obj_sheet, args, 0);
+
+ // Pass model_context to the sheet object.
+ sheet_data* sd = get_sheet_data(obj_sheet);
+ sd->m_global = &self->m_data->m_global;
+ ixion::model_context& cxt = sd->m_global->m_cxt;
+ try
+ {
+ sd->m_sheet_index = cxt.append_sheet(sheet_name);
+ }
+ catch (const model_context_error& e)
+ {
+ // Most likely the sheet name already exists in this document.
+ Py_XDECREF(obj_sheet);
+ switch (e.get_error_type())
+ {
+ case model_context_error::sheet_name_conflict:
+ PyErr_SetString(get_python_document_error(), e.what());
+ break;
+ default:
+ PyErr_SetString(get_python_document_error(),
+ "Sheet insertion failed for unknown reason.");
+ }
+ return nullptr;
+ }
+ catch (const general_error& e)
+ {
+ Py_XDECREF(obj_sheet);
+ ostringstream os;
+ os << "Sheet insertion failed and the reason is '" << e.what() << "'";
+ PyErr_SetString(get_python_document_error(), os.str().c_str());
+ return nullptr;
+ }
+
+ // Append this sheet instance to the document.
+ Py_INCREF(obj_sheet);
+ self->m_data->m_sheets.push_back(obj_sheet);
+
+ return obj_sheet;
+}
+
+const char* doc_document_calculate =
+"Document.calculate([threads])\n"
+"\n"
+"(Re-)calculate all modified formula cells in the document.\n"
+"\n"
+"Keyword arguments:\n"
+"\n"
+"threads -- number of threads to use besides the main thread, or 0 if all\n"
+" calculations are to be performed on the main thread. (default 0)\n"
+;
+
+PyObject* document_calculate(pyobj_document* self, PyObject* args, PyObject* kwargs)
+{
+ static const char* kwlist[] = { "threads", nullptr };
+
+ long threads = 0;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|i", const_cast<char**>(kwlist), &threads))
+ {
+ PyErr_SetString(PyExc_TypeError, "Failed to parse the arguments for Document.calculate()");
+ return nullptr;
+ }
+
+ document_global& dg = self->m_data->m_global;
+
+ // Query additional dirty formula cells and add them to the current set.
+ std::vector<abs_range_t> sorted = ixion::query_and_sort_dirty_cells(
+ dg.m_cxt, dg.m_modified_cells, &dg.m_dirty_formula_cells);
+ ixion::calculate_sorted_cells(dg.m_cxt, sorted, threads);
+
+ dg.m_modified_cells.clear();
+ dg.m_dirty_formula_cells.clear();
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+PyObject* document_get_sheet(pyobj_document* self, PyObject* arg)
+{
+ const vector<PyObject*>& sheets = self->m_data->m_sheets;
+ if (PyLong_Check(arg))
+ {
+ long index = PyLong_AsLong(arg);
+ if (index == -1 && PyErr_Occurred())
+ return nullptr;
+
+ if (index < 0 || static_cast<size_t>(index) >= sheets.size())
+ {
+ PyErr_SetString(PyExc_IndexError, "Out-of-bound sheet index");
+ return nullptr;
+ }
+
+ PyObject* sheet_obj = sheets[index];
+ Py_INCREF(sheet_obj);
+ return sheet_obj;
+ }
+
+ // Not a python int object. See if it's a string object.
+ const char* name = PyUnicode_AsUTF8(arg);
+ if (!name)
+ {
+ PyErr_SetString(PyExc_TypeError,
+ "The 'arg' value must be either of type int or type str.");
+ return nullptr;
+ }
+
+ // Iterate through all sheets to find a match.
+ // TODO : Use string hash to speed up the lookup.
+ for (PyObject* sh : sheets)
+ {
+ PyObject* obj = get_sheet_name(sh);
+ if (!obj)
+ continue;
+
+ const char* this_name = PyUnicode_AsUTF8(obj);
+ if (!this_name)
+ continue;
+
+ if (!strcmp(name, this_name))
+ {
+ Py_INCREF(sh);
+ return sh;
+ }
+ }
+
+ ostringstream os;
+ os << "No sheet named '" << name << "' found";
+ PyErr_SetString(PyExc_IndexError, os.str().c_str());
+ return nullptr;
+}
+
+PyObject* document_getter_sheet_names(pyobj_document* self, void* closure)
+{
+ model_context& cxt = self->m_data->m_global.m_cxt;
+ const vector<PyObject*>& sheets = self->m_data->m_sheets;
+ size_t n = sheets.size();
+ PyObject* t = PyTuple_New(n);
+ for (size_t i = 0; i < n; ++i)
+ {
+ std::string name = cxt.get_sheet_name(i);
+ PyObject* o = PyUnicode_FromString(name.c_str());
+ PyTuple_SetItem(t, i, o);
+ }
+
+ return t;
+}
+
+PyMethodDef document_methods[] =
+{
+ { "append_sheet", (PyCFunction)document_append_sheet, METH_VARARGS, "append new sheet to the document" },
+ { "calculate", (PyCFunction)document_calculate, METH_VARARGS | METH_KEYWORDS, doc_document_calculate },
+ { "get_sheet", (PyCFunction)document_get_sheet, METH_O, "get a sheet object either by index or name" },
+ { nullptr }
+};
+
+PyGetSetDef document_getset[] =
+{
+ { "sheet_names", (getter)document_getter_sheet_names, (setter)nullptr, "A tuple of sheet names", nullptr },
+ { nullptr }
+};
+
+PyTypeObject document_type =
+{
+ PyVarObject_HEAD_INIT(nullptr, 0)
+ "ixion.Document", // tp_name
+ sizeof(pyobj_document), // tp_basicsize
+ 0, // tp_itemsize
+ (destructor)document_dealloc, // tp_dealloc
+ 0, // tp_print
+ 0, // tp_getattr
+ 0, // tp_setattr
+ 0, // tp_compare
+ 0, // tp_repr
+ 0, // tp_as_number
+ 0, // tp_as_sequence
+ 0, // tp_as_mapping
+ 0, // tp_hash
+ 0, // tp_call
+ 0, // tp_str
+ 0, // tp_getattro
+ 0, // tp_setattro
+ 0, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, // tp_flags
+ "ixion document object", // tp_doc
+ 0, // tp_traverse
+ 0, // tp_clear
+ 0, // tp_richcompare
+ 0, // tp_weaklistoffset
+ 0, // tp_iter
+ 0, // tp_iternext
+ document_methods, // tp_methods
+ 0, // tp_members
+ document_getset, // tp_getset
+ 0, // tp_base
+ 0, // tp_dict
+ 0, // tp_descr_get
+ 0, // tp_descr_set
+ 0, // tp_dictoffset
+ (initproc)document_init, // tp_init
+ 0, // tp_alloc
+ document_new, // tp_new
+};
+
+}
+
+PyTypeObject* get_document_type()
+{
+ return &document_type;
+}
+
+}}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/python/document.hpp b/src/python/document.hpp
new file mode 100644
index 0000000..b024000
--- /dev/null
+++ b/src/python/document.hpp
@@ -0,0 +1,21 @@
+/* -*- 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/.
+ */
+
+#ifndef INCLUDED_IXION_PYTHON_DOCUMENT_HPP
+#define INCLUDED_IXION_PYTHON_DOCUMENT_HPP
+
+#include <Python.h>
+
+namespace ixion { namespace python {
+
+PyTypeObject* get_document_type();
+
+}}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/python/global.cpp b/src/python/global.cpp
new file mode 100644
index 0000000..4d9a76b
--- /dev/null
+++ b/src/python/global.cpp
@@ -0,0 +1,39 @@
+/* -*- 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 "ixion/config.hpp"
+#include "global.hpp"
+
+namespace ixion { namespace python {
+
+document_global::document_global() :
+ m_cxt(),
+ m_resolver(ixion::formula_name_resolver::get(formula_name_resolver_t::excel_a1, &m_cxt))
+{
+}
+
+PyObject* get_python_document_error()
+{
+ static PyObject* p = PyErr_NewException(const_cast<char*>("ixion.DocumentError"), NULL, NULL);
+ return p;
+}
+
+PyObject* get_python_sheet_error()
+{
+ static PyObject* p = PyErr_NewException(const_cast<char*>("ixion.SheetError"), NULL, NULL);
+ return p;
+}
+
+PyObject* get_python_formula_error()
+{
+ static PyObject* p = PyErr_NewException(const_cast<char*>("ixion.FormulaError"), NULL, NULL);
+ return p;
+}
+
+}}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/python/global.hpp b/src/python/global.hpp
new file mode 100644
index 0000000..d0a917f
--- /dev/null
+++ b/src/python/global.hpp
@@ -0,0 +1,45 @@
+/* -*- 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/.
+ */
+
+#ifndef INCLUDED_IXION_PYTHON_GLOBAL_HPP
+#define INCLUDED_IXION_PYTHON_GLOBAL_HPP
+
+#include <Python.h>
+
+#include "ixion/model_context.hpp"
+#include "ixion/formula_name_resolver.hpp"
+#include "ixion/address.hpp"
+
+namespace ixion { namespace python {
+
+struct document_global
+{
+ model_context m_cxt;
+
+ /**
+ * positions of all modified cells (formula and non-formula cells) since
+ * last calculation.
+ */
+ abs_range_set_t m_modified_cells;
+
+ /** positions of all dirty formula cells since last calculation. */
+ abs_range_set_t m_dirty_formula_cells;
+
+ std::unique_ptr<formula_name_resolver> m_resolver;
+
+ document_global();
+};
+
+PyObject* get_python_document_error();
+PyObject* get_python_sheet_error();
+PyObject* get_python_formula_error();
+
+}}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/python/python.cpp b/src/python/python.cpp
new file mode 100644
index 0000000..1c8b5bf
--- /dev/null
+++ b/src/python/python.cpp
@@ -0,0 +1,174 @@
+/* -*- 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 "document.hpp"
+#include "sheet.hpp"
+#include "global.hpp"
+
+#include "ixion/env.hpp"
+#include "ixion/info.hpp"
+
+#include <iostream>
+#include <string>
+
+#define IXION_DEBUG_PYTHON 0
+#define GETSTATE(m) ((struct module_state*)PyModule_GetState(m))
+
+using namespace std;
+
+namespace ixion { namespace python {
+
+namespace {
+
+#if IXION_DEBUG_PYTHON
+void print_args(PyObject* args)
+{
+ string args_str;
+ PyObject* repr = PyObject_Repr(args);
+ if (repr)
+ {
+ Py_INCREF(repr);
+ args_str = PyBytes_AsString(repr);
+ Py_DECREF(repr);
+ }
+ cout << args_str << "\n";
+}
+#endif
+
+PyObject* info(PyObject*, PyObject*)
+{
+ cout << "ixion version: "
+ << ixion::get_version_major() << '.'
+ << ixion::get_version_minor() << '.'
+ << ixion::get_version_micro() << endl;
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+PyObject* column_label(PyObject* /*module*/, PyObject* args, PyObject* kwargs)
+{
+ int start;
+ int stop;
+ int resolver_index = 1; // Excel A1 by default
+ static const char* kwlist[] = { "start", "stop", "resolver", nullptr };
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ii|i", const_cast<char**>(kwlist), &start, &stop, &resolver_index))
+ return nullptr;
+
+ if (start >= stop)
+ {
+ PyErr_SetString(
+ PyExc_IndexError,
+ "Start position is larger or equal to the stop position.");
+ return nullptr;
+ }
+
+ if (start < 0)
+ {
+ PyErr_SetString(
+ PyExc_IndexError,
+ "Start position should be larger than or equal to 0.");
+ return nullptr;
+ }
+
+ auto resolver = formula_name_resolver::get(
+ static_cast<formula_name_resolver_t>(resolver_index), nullptr);
+ if (!resolver)
+ {
+ PyErr_SetString(
+ get_python_formula_error(), "Specified resolver type is invalid.");
+ return nullptr;
+ }
+
+ int size = stop - start;
+ PyObject* t = PyTuple_New(size);
+
+ for (int i = start; i < stop; ++i)
+ {
+ string s = resolver->get_column_name(i);
+ PyObject* o = PyUnicode_FromString(s.c_str());
+ PyTuple_SetItem(t, i-start, o);
+ }
+
+ return t;
+}
+
+PyMethodDef ixion_methods[] =
+{
+ { "info", (PyCFunction)info, METH_NOARGS, "Print ixion module information." },
+ { "column_label", (PyCFunction)column_label, METH_VARARGS | METH_KEYWORDS,
+ "Return a list of column label strings based on specified column range values." },
+ { nullptr, nullptr, 0, nullptr }
+};
+
+struct module_state
+{
+ PyObject* error;
+};
+
+int ixion_traverse(PyObject* m, visitproc visit, void* arg)
+{
+ Py_VISIT(GETSTATE(m)->error);
+ return 0;
+}
+
+int ixion_clear(PyObject* m)
+{
+ Py_CLEAR(GETSTATE(m)->error);
+ return 0;
+}
+
+}
+
+struct PyModuleDef moduledef =
+{
+ PyModuleDef_HEAD_INIT,
+ "ixion",
+ nullptr,
+ sizeof(struct module_state),
+ ixion_methods,
+ nullptr,
+ ixion_traverse,
+ ixion_clear,
+ nullptr
+};
+
+}}
+
+extern "C" {
+
+IXION_DLLPUBLIC PyObject* PyInit_ixion()
+{
+ PyTypeObject* doc_type = ixion::python::get_document_type();
+ if (PyType_Ready(doc_type) < 0)
+ return nullptr;
+
+ PyTypeObject* sheet_type = ixion::python::get_sheet_type();
+ if (PyType_Ready(sheet_type) < 0)
+ return nullptr;
+
+ PyObject* m = PyModule_Create(&ixion::python::moduledef);
+
+ Py_INCREF(doc_type);
+ PyModule_AddObject(m, "Document", reinterpret_cast<PyObject*>(doc_type));
+
+ Py_INCREF(sheet_type);
+ PyModule_AddObject(m, "Sheet", reinterpret_cast<PyObject*>(sheet_type));
+
+ PyModule_AddObject(
+ m, "DocumentError", ixion::python::get_python_document_error());
+ PyModule_AddObject(
+ m, "SheetError", ixion::python::get_python_sheet_error());
+ PyModule_AddObject(
+ m, "FormulaError", ixion::python::get_python_formula_error());
+
+ return m;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/python/sheet.cpp b/src/python/sheet.cpp
new file mode 100644
index 0000000..873004a
--- /dev/null
+++ b/src/python/sheet.cpp
@@ -0,0 +1,376 @@
+/* -*- 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 "sheet.hpp"
+#include "global.hpp"
+
+#include <ixion/model_context.hpp>
+#include <ixion/formula_name_resolver.hpp>
+#include <ixion/formula.hpp>
+#include <ixion/cell.hpp>
+#include <ixion/exceptions.hpp>
+
+#include <structmember.h>
+
+#include <iostream>
+
+using namespace std;
+
+namespace ixion { namespace python {
+
+sheet_data::sheet_data() : m_global(nullptr), m_sheet_index(-1) {}
+
+namespace {
+
+struct sheet
+{
+ PyObject_HEAD
+ PyObject* name; // sheet name
+
+ sheet_data* m_data;
+};
+
+void sheet_dealloc(sheet* self)
+{
+ delete self->m_data;
+
+ Py_XDECREF(self->name);
+ Py_TYPE(self)->tp_free(reinterpret_cast<PyObject*>(self));
+}
+
+PyObject* sheet_new(PyTypeObject* type, PyObject* /*args*/, PyObject* /*kwargs*/)
+{
+ sheet* self = (sheet*)type->tp_alloc(type, 0);
+ if (!self)
+ return nullptr;
+
+ self->m_data = new sheet_data;
+
+ self->name = PyUnicode_FromString("");
+ if (!self->name)
+ {
+ Py_DECREF(self);
+ return nullptr;
+ }
+ return reinterpret_cast<PyObject*>(self);
+}
+
+int sheet_init(sheet* self, PyObject* args, PyObject* kwargs)
+{
+ PyObject* name = nullptr;
+ static const char* kwlist[] = { "name", nullptr };
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|O", (char**)kwlist, &name))
+ return -1;
+
+ if (name)
+ {
+ PyObject* tmp = self->name;
+ Py_INCREF(name);
+ self->name = name;
+ Py_XDECREF(tmp);
+ }
+
+ return 0;
+}
+
+PyObject* sheet_set_numeric_cell(sheet* self, PyObject* args, PyObject* kwargs)
+{
+ int col = -1;
+ int row = -1;
+ double val = 0.0;
+
+ static const char* kwlist[] = { "row", "column", "value", nullptr };
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "iid", const_cast<char**>(kwlist), &row, &col, &val))
+ return nullptr;
+
+ sheet_data* sd = get_sheet_data(reinterpret_cast<PyObject*>(self));
+ if (!sd->m_global)
+ {
+ PyErr_SetString(get_python_sheet_error(),
+ "This Sheet object does not belong to a Document object.");
+ return nullptr;
+ }
+
+ ixion::model_context& cxt = sd->m_global->m_cxt;
+ ixion::abs_address_t pos(sd->m_sheet_index, row, col);
+ sd->m_global->m_modified_cells.insert(pos);
+ cxt.set_numeric_cell(pos, val);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+PyObject* sheet_set_string_cell(sheet* self, PyObject* args, PyObject* kwargs)
+{
+ int col = -1;
+ int row = -1;
+ char* val = nullptr;
+
+ static const char* kwlist[] = { "row", "column", "value", nullptr };
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "iis", const_cast<char**>(kwlist), &row, &col, &val))
+ return nullptr;
+
+ sheet_data* sd = get_sheet_data(reinterpret_cast<PyObject*>(self));
+ if (!sd->m_global)
+ {
+ PyErr_SetString(get_python_sheet_error(),
+ "This Sheet object does not belong to a Document object.");
+ return nullptr;
+ }
+
+ ixion::model_context& cxt = sd->m_global->m_cxt;
+ ixion::abs_address_t pos(sd->m_sheet_index, row, col);
+ sd->m_global->m_modified_cells.insert(pos);
+ cxt.set_string_cell(pos, val);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+PyObject* sheet_set_formula_cell(sheet* self, PyObject* args, PyObject* kwargs)
+{
+ int col = -1;
+ int row = -1;
+ char* formula = nullptr;
+
+ static const char* kwlist[] = { "row", "column", "value", nullptr };
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "iis", const_cast<char**>(kwlist), &row, &col, &formula))
+ return nullptr;
+
+ sheet_data* sd = get_sheet_data(reinterpret_cast<PyObject*>(self));
+ if (!sd->m_global)
+ {
+ PyErr_SetString(get_python_sheet_error(),
+ "This Sheet object does not belong to a Document object.");
+ return nullptr;
+ }
+
+ ixion::model_context& cxt = sd->m_global->m_cxt;
+
+ ixion::abs_address_t pos(sd->m_sheet_index, row, col);
+ sd->m_global->m_dirty_formula_cells.insert(pos);
+
+ ixion::formula_tokens_t tokens =
+ ixion::parse_formula_string(cxt, pos, *sd->m_global->m_resolver, formula);
+
+ auto ts = formula_tokens_store::create();
+ ts->get() = std::move(tokens);
+ cxt.set_formula_cell(pos, ts);
+
+ // Put this formula cell in a dependency chain.
+ ixion::register_formula_cell(cxt, pos);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+PyObject* sheet_get_numeric_value(sheet* self, PyObject* args, PyObject* kwargs)
+{
+ int col = -1;
+ int row = -1;
+
+ static const char* kwlist[] = { "row", "column", nullptr };
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ii", const_cast<char**>(kwlist), &row, &col))
+ return nullptr;
+
+ sheet_data* sd = get_sheet_data(reinterpret_cast<PyObject*>(self));
+ if (!sd->m_global)
+ {
+ PyErr_SetString(get_python_sheet_error(),
+ "This Sheet object does not belong to a Document object.");
+ return nullptr;
+ }
+
+ ixion::model_context& cxt = sd->m_global->m_cxt;
+ double val = 0.0;
+ try
+ {
+ val = cxt.get_numeric_value(ixion::abs_address_t(sd->m_sheet_index, row, col));
+ }
+ catch (const formula_error&)
+ {
+ PyErr_SetString(PyExc_TypeError, "The formula cell has yet to be calculated");
+ return nullptr;
+ }
+
+ return PyFloat_FromDouble(val);
+}
+
+PyObject* sheet_get_string_value(sheet* self, PyObject* args, PyObject* kwargs)
+{
+ int col = -1;
+ int row = -1;
+
+ static const char* kwlist[] = { "row", "column", nullptr };
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ii", const_cast<char**>(kwlist), &row, &col))
+ return nullptr;
+
+ sheet_data* sd = get_sheet_data(reinterpret_cast<PyObject*>(self));
+ if (!sd->m_global)
+ {
+ PyErr_SetString(get_python_sheet_error(),
+ "This Sheet object does not belong to a Document object.");
+ return nullptr;
+ }
+
+ ixion::model_context& cxt = sd->m_global->m_cxt;
+ std::string_view s = cxt.get_string_value(ixion::abs_address_t(sd->m_sheet_index, row, col));
+ if (s.empty())
+ return PyUnicode_FromStringAndSize(nullptr, 0);
+
+ return PyUnicode_FromStringAndSize(s.data(), s.size());
+}
+
+PyObject* sheet_get_formula_expression(sheet* self, PyObject* args, PyObject* kwargs)
+{
+ int col = -1;
+ int row = -1;
+
+ static const char* kwlist[] = { "row", "column", nullptr };
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ii", const_cast<char**>(kwlist), &row, &col))
+ return nullptr;
+
+ sheet_data* sd = get_sheet_data(reinterpret_cast<PyObject*>(self));
+ if (!sd->m_global)
+ {
+ PyErr_SetString(get_python_sheet_error(),
+ "This Sheet object does not belong to a Document object.");
+ return nullptr;
+ }
+
+ ixion::model_context& cxt = sd->m_global->m_cxt;
+ ixion::abs_address_t pos(sd->m_sheet_index, row, col);
+ const ixion::formula_cell* fc = cxt.get_formula_cell(pos);
+
+ if (!fc)
+ {
+ PyErr_SetString(get_python_sheet_error(), "No formula cell at specified position.");
+ return nullptr;
+ }
+
+ const ixion::formula_tokens_t& ft = fc->get_tokens()->get();
+
+ string str = ixion::print_formula_tokens(cxt, pos, *sd->m_global->m_resolver, ft);
+ if (str.empty())
+ return PyUnicode_FromString("");
+
+ return PyUnicode_FromStringAndSize(str.data(), str.size());
+}
+
+PyObject* sheet_erase_cell(sheet* self, PyObject* args, PyObject* kwargs)
+{
+ PyErr_SetString(PyExc_RuntimeError, "erase_cell() method has been deprecated. Please use empty_cell() instead.");
+ return nullptr;
+}
+
+PyObject* sheet_empty_cell(sheet* self, PyObject* args, PyObject* kwargs)
+{
+ int col = -1;
+ int row = -1;
+
+ static const char* kwlist[] = { "row", "column", nullptr };
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ii", const_cast<char**>(kwlist), &row, &col))
+ return nullptr;
+
+ sheet_data* sd = get_sheet_data(reinterpret_cast<PyObject*>(self));
+ if (!sd->m_global)
+ {
+ PyErr_SetString(get_python_sheet_error(),
+ "This Sheet object does not belong to a Document object.");
+ return nullptr;
+ }
+
+ ixion::model_context& cxt = sd->m_global->m_cxt;
+ abs_address_t pos(sd->m_sheet_index, row, col);
+ sd->m_global->m_modified_cells.insert(pos);
+ cxt.empty_cell(pos);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+PyMethodDef sheet_methods[] =
+{
+ { "set_numeric_cell", (PyCFunction)sheet_set_numeric_cell, METH_VARARGS | METH_KEYWORDS, "set numeric value to specified cell" },
+ { "set_formula_cell", (PyCFunction)sheet_set_formula_cell, METH_VARARGS | METH_KEYWORDS, "set formula to specified cell" },
+ { "set_string_cell", (PyCFunction)sheet_set_string_cell, METH_VARARGS | METH_KEYWORDS, "set string to specified cell" },
+ { "get_numeric_value", (PyCFunction)sheet_get_numeric_value, METH_VARARGS | METH_KEYWORDS, "get numeric value from specified cell" },
+ { "get_string_value", (PyCFunction)sheet_get_string_value, METH_VARARGS | METH_KEYWORDS, "get string value from specified cell" },
+ { "get_formula_expression", (PyCFunction)sheet_get_formula_expression, METH_VARARGS | METH_KEYWORDS, "get formula expression string from specified cell position" },
+ { "empty_cell", (PyCFunction)sheet_empty_cell, METH_VARARGS | METH_KEYWORDS, "empty cell at specified position" },
+ { "erase_cell", (PyCFunction)sheet_erase_cell, METH_VARARGS | METH_KEYWORDS, "erase cell at specified position" },
+ { nullptr }
+};
+
+PyMemberDef sheet_members[] =
+{
+ { (char*)"name", T_OBJECT_EX, offsetof(sheet, name), READONLY, (char*)"sheet name" },
+ { nullptr }
+};
+
+PyTypeObject sheet_type =
+{
+ PyVarObject_HEAD_INIT(nullptr, 0)
+ "ixion.Sheet", // tp_name
+ sizeof(sheet), // tp_basicsize
+ 0, // tp_itemsize
+ (destructor)sheet_dealloc, // tp_dealloc
+ 0, // tp_print
+ 0, // tp_getattr
+ 0, // tp_setattr
+ 0, // tp_compare
+ 0, // tp_repr
+ 0, // tp_as_number
+ 0, // tp_as_sequence
+ 0, // tp_as_mapping
+ 0, // tp_hash
+ 0, // tp_call
+ 0, // tp_str
+ 0, // tp_getattro
+ 0, // tp_setattro
+ 0, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, // tp_flags
+ "ixion sheet object", // tp_doc
+ 0, // tp_traverse
+ 0, // tp_clear
+ 0, // tp_richcompare
+ 0, // tp_weaklistoffset
+ 0, // tp_iter
+ 0, // tp_iternext
+ sheet_methods, // tp_methods
+ sheet_members, // tp_members
+ 0, // tp_getset
+ 0, // tp_base
+ 0, // tp_dict
+ 0, // tp_descr_get
+ 0, // tp_descr_set
+ 0, // tp_dictoffset
+ (initproc)sheet_init, // tp_init
+ 0, // tp_alloc
+ sheet_new, // tp_new
+};
+
+}
+
+PyTypeObject* get_sheet_type()
+{
+ return &sheet_type;
+}
+
+sheet_data* get_sheet_data(PyObject* obj)
+{
+ return reinterpret_cast<sheet*>(obj)->m_data;
+}
+
+PyObject* get_sheet_name(PyObject* obj)
+{
+ return reinterpret_cast<sheet*>(obj)->name;
+}
+
+}}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/python/sheet.hpp b/src/python/sheet.hpp
new file mode 100644
index 0000000..e85596e
--- /dev/null
+++ b/src/python/sheet.hpp
@@ -0,0 +1,44 @@
+/* -*- 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/.
+ */
+
+#ifndef INCLUDED_IXION_PYTHON_SHEET_HPP
+#define INCLUDED_IXION_PYTHON_SHEET_HPP
+
+#include <Python.h>
+
+#include "ixion/types.hpp"
+
+namespace ixion { namespace python {
+
+struct document_global;
+
+struct sheet_data
+{
+ document_global* m_global;
+ ixion::sheet_t m_sheet_index;
+
+ sheet_data();
+};
+
+PyTypeObject* get_sheet_type();
+
+sheet_data* get_sheet_data(PyObject* obj);
+
+/**
+ * Get the sheet name of a python sheet object.
+ *
+ * @param obj python sheet object.
+ *
+ * @return python string object storing the sheet name.
+ */
+PyObject* get_sheet_name(PyObject* obj);
+
+}}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/python/test-env.sh b/src/python/test-env.sh
new file mode 100644
index 0000000..fc3f6cb
--- /dev/null
+++ b/src/python/test-env.sh
@@ -0,0 +1,9 @@
+
+PYTHONPATH=.libs:$PYTHONPATH
+
+# Ensure that the libixion shared library built as part of the current build
+# is discovered first.
+LD_LIBRARY_PATH=../libixion/.libs:$LD_LIBRARY_PATH
+
+export PYTHONPATH LD_LIBRARY_PATH
+
diff --git a/src/session_handler.cpp b/src/session_handler.cpp
new file mode 100644
index 0000000..b5e49a9
--- /dev/null
+++ b/src/session_handler.cpp
@@ -0,0 +1,169 @@
+/* -*- 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 "session_handler.hpp"
+#include "app_common.hpp"
+
+#include "ixion/formula_name_resolver.hpp"
+#include "ixion/formula_result.hpp"
+#include "ixion/formula_tokens.hpp"
+#include "ixion/config.hpp"
+
+#include <string>
+#include <iostream>
+#include <sstream>
+#include <mutex>
+
+using namespace std;
+
+namespace ixion {
+
+session_handler::factory::factory(const model_context& cxt) :
+ m_context(cxt), m_show_sheet_name(false) {}
+
+session_handler::factory::~factory() {}
+
+std::unique_ptr<iface::session_handler> session_handler::factory::create()
+{
+ return std::make_unique<session_handler>(m_context, m_show_sheet_name);
+}
+
+void session_handler::factory::show_sheet_name(bool b)
+{
+ m_show_sheet_name = b;
+}
+
+struct session_handler::impl
+{
+ const model_context& m_context;
+ std::unique_ptr<formula_name_resolver> mp_resolver;
+ std::string m_cell_name;
+ std::ostringstream m_buf;
+ bool m_show_sheet_name;
+
+ impl(const model_context& cxt, bool show_sheet_name) :
+ m_context(cxt),
+ mp_resolver(formula_name_resolver::get(formula_name_resolver_t::excel_a1, &cxt)),
+ m_show_sheet_name(show_sheet_name) {}
+};
+
+session_handler::session_handler(const model_context& cxt, bool show_sheet_name) :
+ mp_impl(std::make_unique<impl>(cxt, show_sheet_name)) {}
+
+session_handler::~session_handler() {}
+
+void session_handler::begin_cell_interpret(const abs_address_t& pos)
+{
+ // Convert absolute to relative address, which looks better when printed.
+ address_t pos_display(pos);
+ pos_display.set_absolute(false);
+ mp_impl->m_cell_name = mp_impl->mp_resolver->get_name(pos_display, abs_address_t(), mp_impl->m_show_sheet_name);
+
+ mp_impl->m_buf << detail::get_formula_result_output_separator() << endl;
+ mp_impl->m_buf << mp_impl->m_cell_name << ": ";
+}
+
+void session_handler::end_cell_interpret()
+{
+ print(mp_impl->m_buf.str());
+}
+
+void session_handler::set_result(const formula_result& result)
+{
+ mp_impl->m_buf << std::endl << mp_impl->m_cell_name << ": result = ";
+
+ switch (result.get_type())
+ {
+ case formula_result::result_type::string:
+ {
+ constexpr char quote = '\'';
+ mp_impl->m_buf << quote << result.str(mp_impl->m_context) << quote;
+ break;
+ }
+ default:
+ mp_impl->m_buf << result.str(mp_impl->m_context);
+ }
+
+ mp_impl->m_buf << " [" << result.get_type() << ']' << std::endl;
+}
+
+void session_handler::set_invalid_expression(std::string_view msg)
+{
+ mp_impl->m_buf << endl << mp_impl->m_cell_name << ": invalid expression: " << msg << endl;
+}
+
+void session_handler::set_formula_error(std::string_view msg)
+{
+ mp_impl->m_buf << endl << mp_impl->m_cell_name << ": result = " << msg << endl;
+}
+
+void session_handler::push_token(fopcode_t fop)
+{
+ switch (fop)
+ {
+ case fop_sep:
+ {
+ mp_impl->m_buf << mp_impl->m_context.get_config().sep_function_arg;
+ break;
+ }
+ case fop_array_row_sep:
+ {
+ mp_impl->m_buf << mp_impl->m_context.get_config().sep_matrix_row;
+ break;
+ }
+ default:
+ mp_impl->m_buf << get_formula_opcode_string(fop);
+ }
+}
+
+void session_handler::push_value(double val)
+{
+ mp_impl->m_buf << val;
+}
+
+void session_handler::push_string(size_t sid)
+{
+ const string* p = mp_impl->m_context.get_string(sid);
+ mp_impl->m_buf << '"';
+ if (p)
+ mp_impl->m_buf << *p;
+ else
+ mp_impl->m_buf << "(null string)";
+ mp_impl->m_buf << '"';
+}
+
+void session_handler::push_single_ref(const address_t& addr, const abs_address_t& pos)
+{
+ mp_impl->m_buf << mp_impl->mp_resolver->get_name(addr, pos, false);
+}
+
+void session_handler::push_range_ref(const range_t& range, const abs_address_t& pos)
+{
+ mp_impl->m_buf << mp_impl->mp_resolver->get_name(range, pos, false);
+}
+
+void session_handler::push_table_ref(const table_t& table)
+{
+ mp_impl->m_buf << mp_impl->mp_resolver->get_name(table);
+}
+
+void session_handler::push_function(formula_function_t foc)
+{
+ mp_impl->m_buf << get_formula_function_name(foc);
+}
+
+void session_handler::print(const std::string& msg)
+{
+ static std::mutex mtx;
+ std::lock_guard<std::mutex> lock(mtx);
+ cout << msg;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
+
diff --git a/src/session_handler.hpp b/src/session_handler.hpp
new file mode 100644
index 0000000..cdedf41
--- /dev/null
+++ b/src/session_handler.hpp
@@ -0,0 +1,66 @@
+/* -*- 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/.
+ */
+
+#ifndef INCLUDED_IXION_SESSION_HANDLER_HPP
+#define INCLUDED_IXION_SESSION_HANDLER_HPP
+
+#include "ixion/interface/session_handler.hpp"
+#include "ixion/model_context.hpp"
+
+#include <memory>
+
+namespace ixion {
+
+class session_handler : public iface::session_handler
+{
+ struct impl;
+ std::unique_ptr<impl> mp_impl;
+
+public:
+ session_handler(const model_context& cxt, bool show_sheet_name);
+ virtual ~session_handler();
+
+ virtual void begin_cell_interpret(const abs_address_t& pos) override;
+ virtual void end_cell_interpret() override;
+ virtual void set_result(const formula_result& result) override;
+ virtual void set_invalid_expression(std::string_view msg) override;
+ virtual void set_formula_error(std::string_view msg) override;
+
+ virtual void push_token(fopcode_t fop) override;
+ virtual void push_value(double val) override;
+ virtual void push_string(size_t sid) override;
+ virtual void push_single_ref(const address_t& addr, const abs_address_t& pos) override;
+ virtual void push_range_ref(const range_t& range, const abs_address_t& pos) override;
+ virtual void push_table_ref(const table_t& table) override;
+ virtual void push_function(formula_function_t foc) override;
+
+ class factory : public model_context::session_handler_factory
+ {
+ const model_context& m_context;
+ bool m_show_sheet_name;
+ public:
+ factory(const model_context& cxt);
+ virtual ~factory() override;
+
+ virtual std::unique_ptr<iface::session_handler> create() override;
+
+ void show_sheet_name(bool b);
+ };
+
+ /**
+ * Print string to stdout in a thread-safe way.
+ *
+ * @param msg string to print to stdout.
+ */
+ static void print(const std::string& msg);
+};
+
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/sort_input_parser.cpp b/src/sort_input_parser.cpp
new file mode 100644
index 0000000..2069455
--- /dev/null
+++ b/src/sort_input_parser.cpp
@@ -0,0 +1,151 @@
+/* -*- 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 "sort_input_parser.hpp"
+#include "app_common.hpp"
+
+#include <ixion/global.hpp>
+
+#include <fstream>
+#include <iostream>
+#include <algorithm>
+
+using namespace std;
+
+namespace ixion {
+
+size_t hash_value(const std::string_view& s)
+{
+ size_t n = s.size();
+ size_t hash_val = 0;
+ for (size_t i = 0; i < n; ++i)
+ hash_val += (s[i] << i);
+ return hash_val;
+}
+
+
+namespace {
+
+struct mem_str_buf_printer
+{
+ void operator() (const std::string_view& r) const
+ {
+ std::cout << r << std::endl;
+ }
+};
+
+}
+
+sort_input_parser::parse_error::parse_error(const string& msg) :
+ general_error(msg) {}
+
+sort_input_parser::parse_error::~parse_error() throw() {}
+
+// ----------------------------------------------------------------------------
+
+sort_input_parser::sort_input_parser(const string& filepath) :
+ m_content(detail::load_file_content(filepath)),
+ mp(nullptr), mp_last(nullptr)
+{
+}
+
+sort_input_parser::~sort_input_parser()
+{
+}
+
+void sort_input_parser::parse()
+{
+ if (m_content.empty())
+ return;
+
+ mp = &m_content[0];
+ mp_last = &m_content[m_content.size()-1];
+ std::string_view cell, dep;
+ bool in_name = true;
+ for (;mp != mp_last; ++mp)
+ {
+ switch (*mp)
+ {
+ case ' ':
+ // Let's skip blanks for now.
+ break;
+ case '\n':
+ // end of line.
+ if (cell.empty())
+ {
+ if (!dep.empty())
+ throw parse_error("cell name is emtpy but dependency name isn't.");
+ }
+ else
+ {
+ if (dep.empty())
+ throw parse_error("dependency name is empty.");
+ else
+ insert_depend(cell, dep);
+ }
+
+ cell = std::string_view{};
+ dep = std::string_view{};
+ in_name = true;
+ break;
+ case ':':
+ if (!dep.empty())
+ throw parse_error("more than one separator in a single line!");
+ if (cell.empty())
+ throw parse_error("cell name is empty");
+ in_name = false;
+ break;
+ default:
+ if (in_name)
+ {
+ if (cell.empty())
+ cell = std::string_view{mp, 1u};
+ else
+ cell = std::string_view{cell.data(), cell.size() + 1u};
+ }
+ else
+ {
+ if (dep.empty())
+ dep = std::string_view{mp, 1u};
+ else
+ dep = std::string_view{dep.data(), dep.size() + 1u};
+ }
+ }
+ }
+}
+
+void sort_input_parser::print()
+{
+ remove_duplicate_cells();
+
+ // Run the depth first search.
+ vector<std::string_view> sorted;
+ sorted.reserve(m_all_cells.size());
+ dfs_type::back_inserter handler(sorted);
+ dfs_type dfs(m_all_cells.begin(), m_all_cells.end(), m_set, handler);
+ dfs.run();
+
+ // Print the result.
+ for_each(sorted.begin(), sorted.end(), mem_str_buf_printer());
+}
+
+void sort_input_parser::remove_duplicate_cells()
+{
+ sort(m_all_cells.begin(), m_all_cells.end());
+ vector<std::string_view>::iterator itr = unique(m_all_cells.begin(), m_all_cells.end());
+ m_all_cells.erase(itr, m_all_cells.end());
+}
+
+void sort_input_parser::insert_depend(const std::string_view& cell, const std::string_view& dep)
+{
+ m_set.insert(cell, dep);
+ m_all_cells.push_back(cell);
+ m_all_cells.push_back(dep);
+}
+
+}
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/sort_input_parser.hpp b/src/sort_input_parser.hpp
new file mode 100644
index 0000000..92970e1
--- /dev/null
+++ b/src/sort_input_parser.hpp
@@ -0,0 +1,54 @@
+/* -*- 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/.
+ */
+
+#ifndef __IXION_SORT_INPUT_PARSER_HXX__
+#define __IXION_SORT_INPUT_PARSER_HXX__
+
+#include <ixion/exceptions.hpp>
+
+#include "depth_first_search.hpp"
+
+#include <vector>
+#include <string>
+
+namespace ixion {
+
+class sort_input_parser
+{
+ class parse_error : public general_error
+ {
+ public:
+ parse_error(const ::std::string& msg);
+ virtual ~parse_error() throw();
+ };
+
+ using dfs_type = depth_first_search<std::string_view, std::hash<std::string_view>>;
+
+public:
+ sort_input_parser(const ::std::string& filepath);
+ ~sort_input_parser();
+
+ void parse();
+ void print();
+
+private:
+ void remove_duplicate_cells();
+ void insert_depend(const std::string_view& cell, const std::string_view& dep);
+
+private:
+ dfs_type::relations m_set;
+ std::string m_content;
+ std::vector<std::string_view> m_all_cells;
+
+ const char* mp;
+ const char* mp_last;
+};
+
+}
+
+#endif
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/table_handler.cpp b/src/table_handler.cpp
new file mode 100644
index 0000000..7d3c185
--- /dev/null
+++ b/src/table_handler.cpp
@@ -0,0 +1,141 @@
+/* -*- 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 "table_handler.hpp"
+
+namespace ixion {
+
+table_handler::entry::entry() :
+ name(empty_string_id), range(abs_range_t::invalid), totals_row_count(0) {}
+
+table_handler::~table_handler() {}
+
+abs_range_t table_handler::get_range(
+ const abs_address_t& pos, string_id_t column_first, string_id_t column_last,
+ table_areas_t areas) const
+{
+ entries_type::const_iterator it = m_entries.begin(), it_end = m_entries.end();
+ for (; it != it_end; ++it)
+ {
+ const entry& e = *it->second;
+ if (!e.range.contains(pos))
+ continue;
+
+ return get_column_range(e, column_first, column_last, areas);
+ }
+
+ return abs_range_t(abs_range_t::invalid);
+}
+
+abs_range_t table_handler::get_range(
+ string_id_t table, string_id_t column_first, string_id_t column_last,
+ table_areas_t areas) const
+{
+ entries_type::const_iterator it = m_entries.find(table);
+ if (it == m_entries.end())
+ // Table name not found.
+ return abs_range_t(abs_range_t::invalid);
+
+ const entry& e = *it->second;
+ return get_column_range(e, column_first, column_last, areas);
+}
+
+void table_handler::insert(std::unique_ptr<entry>& p)
+{
+ if (!p)
+ return;
+
+ string_id_t name = p->name;
+ m_entries.insert(
+ entries_type::value_type(
+ name, std::unique_ptr<entry>(p.release())));
+}
+
+void adjust_table_area(abs_range_t& range, const table_handler::entry& e, table_areas_t areas)
+{
+ bool headers = (areas & table_area_headers);
+ bool data = (areas & table_area_data);
+ bool totals = (areas & table_area_totals);
+
+ if (headers)
+ {
+ if (data)
+ {
+ if (totals)
+ {
+ // All areas.
+ return;
+ }
+
+ // Headers + data areas
+ range.last.row -= e.totals_row_count;
+ return;
+ }
+
+ // Headers only.
+ range.last.row = range.first.row;
+ return;
+ }
+
+ // No header row.
+ --range.first.row;
+
+ if (data)
+ {
+ if (totals)
+ {
+ // Data + totals areas
+ return;
+ }
+
+ // Data area only.
+ range.last.row -= e.totals_row_count;
+ return;
+ }
+
+ // Totals area only.
+ if (e.totals_row_count <= 0)
+ {
+ range = abs_range_t(abs_range_t::invalid);
+ return;
+ }
+
+ range.first.row = range.last.row;
+ range.first.row -= e.totals_row_count - 1;
+}
+
+abs_range_t table_handler::get_column_range(
+ const entry& e, string_id_t column_first, string_id_t column_last, table_areas_t areas) const
+{
+ if (column_first == empty_string_id)
+ {
+ // Area specifiers only.
+ abs_range_t ret = e.range;
+ adjust_table_area(ret, e, areas);
+ return ret;
+ }
+
+ for (size_t i = 0, n = e.columns.size(); i < n; ++i)
+ {
+ if (e.columns[i] == column_first)
+ {
+ // Matching column name found.
+ abs_range_t ret = e.range;
+ col_t col = e.range.first.column + i;
+ ret.first.column = col;
+ ret.last.column = col;
+ adjust_table_area(ret, e, areas);
+ return ret;
+ }
+ }
+
+ return abs_range_t(abs_range_t::invalid);
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/table_handler.hpp b/src/table_handler.hpp
new file mode 100644
index 0000000..68f5859
--- /dev/null
+++ b/src/table_handler.hpp
@@ -0,0 +1,59 @@
+/* -*- 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/.
+ */
+
+#ifndef IXION_TABLE_HANDLER_HPP
+#define IXION_TABLE_HANDLER_HPP
+
+#include "ixion/interface/table_handler.hpp"
+#include "ixion/types.hpp"
+#include "ixion/address.hpp"
+
+#include <vector>
+#include <map>
+#include <memory>
+
+namespace ixion {
+
+class table_handler : public iface::table_handler
+{
+public:
+
+ /** single table entry */
+ struct entry
+ {
+ string_id_t name;
+ abs_range_t range;
+ std::vector<string_id_t> columns;
+ row_t totals_row_count;
+
+ entry();
+ };
+
+ typedef std::map<string_id_t, std::unique_ptr<entry>> entries_type;
+
+ virtual ~table_handler();
+
+ virtual abs_range_t get_range(
+ const abs_address_t& pos, string_id_t column_first, string_id_t column_last, table_areas_t areas) const;
+ virtual abs_range_t get_range(
+ string_id_t table, string_id_t column_first, string_id_t column_last, table_areas_t areas) const;
+
+ void insert(std::unique_ptr<entry>& p);
+
+private:
+ abs_range_t get_column_range(
+ const entry& e, string_id_t column_first, string_id_t column_last,
+ table_areas_t areas) const;
+
+ entries_type m_entries;
+};
+
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/src/test/Makefile.am b/src/test/Makefile.am
new file mode 100644
index 0000000..c6e7c23
--- /dev/null
+++ b/src/test/Makefile.am
@@ -0,0 +1,9 @@
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/include \
+ -I$(top_srcdir)/src/include
+
+noinst_LIBRARIES = libixion-test.a
+
+libixion_test_a_SOURCES = \
+ test_global.cpp
diff --git a/src/test/Makefile.in b/src/test/Makefile.in
new file mode 100644
index 0000000..7e1f61f
--- /dev/null
+++ b/src/test/Makefile.in
@@ -0,0 +1,646 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/test
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \
+ $(top_srcdir)/m4/ax_cxx_compile_stdcxx_17.m4 \
+ $(top_srcdir)/m4/boost.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \
+ $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LIBRARIES = $(noinst_LIBRARIES)
+ARFLAGS = cru
+AM_V_AR = $(am__v_AR_@AM_V@)
+am__v_AR_ = $(am__v_AR_@AM_DEFAULT_V@)
+am__v_AR_0 = @echo " AR " $@;
+am__v_AR_1 =
+libixion_test_a_AR = $(AR) $(ARFLAGS)
+libixion_test_a_LIBADD =
+am_libixion_test_a_OBJECTS = test_global.$(OBJEXT)
+libixion_test_a_OBJECTS = $(am_libixion_test_a_OBJECTS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/test_global.Po
+am__mv = mv -f
+CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CXXFLAGS) $(CXXFLAGS)
+AM_V_CXX = $(am__v_CXX_@AM_V@)
+am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@)
+am__v_CXX_0 = @echo " CXX " $@;
+am__v_CXX_1 =
+CXXLD = $(CXX)
+CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
+ $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CXXLD = $(am__v_CXXLD_@AM_V@)
+am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@)
+am__v_CXXLD_0 = @echo " CXXLD " $@;
+am__v_CXXLD_1 =
+SOURCES = $(libixion_test_a_SOURCES)
+DIST_SOURCES = $(libixion_test_a_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AS = @AS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_CPPFLAGS = @BOOST_CPPFLAGS@
+BOOST_FILESYSTEM_LDFLAGS = @BOOST_FILESYSTEM_LDFLAGS@
+BOOST_FILESYSTEM_LDPATH = @BOOST_FILESYSTEM_LDPATH@
+BOOST_FILESYSTEM_LIBS = @BOOST_FILESYSTEM_LIBS@
+BOOST_LDPATH = @BOOST_LDPATH@
+BOOST_PROGRAM_OPTIONS_LDFLAGS = @BOOST_PROGRAM_OPTIONS_LDFLAGS@
+BOOST_PROGRAM_OPTIONS_LDPATH = @BOOST_PROGRAM_OPTIONS_LDPATH@
+BOOST_PROGRAM_OPTIONS_LIBS = @BOOST_PROGRAM_OPTIONS_LIBS@
+BOOST_ROOT = @BOOST_ROOT@
+BOOST_SYSTEM_LDFLAGS = @BOOST_SYSTEM_LDFLAGS@
+BOOST_SYSTEM_LDPATH = @BOOST_SYSTEM_LDPATH@
+BOOST_SYSTEM_LIBS = @BOOST_SYSTEM_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+HAVE_CXX17 = @HAVE_CXX17@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+IXION_API_VERSION = @IXION_API_VERSION@
+IXION_MAJOR_API_VERSION = @IXION_MAJOR_API_VERSION@
+IXION_MAJOR_VERSION = @IXION_MAJOR_VERSION@
+IXION_MICRO_VERSION = @IXION_MICRO_VERSION@
+IXION_MINOR_API_VERSION = @IXION_MINOR_API_VERSION@
+IXION_MINOR_VERSION = @IXION_MINOR_VERSION@
+IXION_VERSION = @IXION_VERSION@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MDDS_CFLAGS = @MDDS_CFLAGS@
+MDDS_LIBS = @MDDS_LIBS@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POW_LIB = @POW_LIB@
+PYTHON = @PYTHON@
+PYTHON_CFLAGS = @PYTHON_CFLAGS@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_LIBS = @PYTHON_LIBS@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VERSION = @VERSION@
+VULKAN_CFLAGS = @VULKAN_CFLAGS@
+VULKAN_LIBS = @VULKAN_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/include \
+ -I$(top_srcdir)/src/include
+
+noinst_LIBRARIES = libixion-test.a
+libixion_test_a_SOURCES = \
+ test_global.cpp
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .cpp .lo .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/test/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/test/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLIBRARIES:
+ -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES)
+
+libixion-test.a: $(libixion_test_a_OBJECTS) $(libixion_test_a_DEPENDENCIES) $(EXTRA_libixion_test_a_DEPENDENCIES)
+ $(AM_V_at)-rm -f libixion-test.a
+ $(AM_V_AR)$(libixion_test_a_AR) libixion-test.a $(libixion_test_a_OBJECTS) $(libixion_test_a_LIBADD)
+ $(AM_V_at)$(RANLIB) libixion-test.a
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_global.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.cpp.o:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $<
+
+.cpp.obj:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.cpp.lo:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/test_global.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/test_global.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/test/test_global.cpp b/src/test/test_global.cpp
new file mode 100644
index 0000000..0325b9c
--- /dev/null
+++ b/src/test/test_global.cpp
@@ -0,0 +1,24 @@
+#include "test_global.hpp"
+
+namespace ixion { namespace test {
+
+stack_printer::stack_printer(std::string msg) :
+ m_msg(std::move(msg))
+{
+ std::cerr << m_msg << ": --begin" << std::endl;
+ m_start_time = get_time();
+}
+
+stack_printer::~stack_printer()
+{
+ double end_time = get_time();
+ std::cerr << m_msg << ": --end (duration: " << (end_time-m_start_time) << " sec)" << std::endl;
+}
+
+double stack_printer::get_time() const
+{
+ double v = std::chrono::system_clock::now().time_since_epoch() / std::chrono::milliseconds(1);
+ return v / 1000.0;
+}
+
+}}