diff options
Diffstat (limited to 'src')
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='[0;31m'; \ + grn='[0;32m'; \ + lgn='[1;32m'; \ + blu='[1;34m'; \ + mgn='[0;35m'; \ + brg='[1m'; \ + std='[m'; \ + 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='[0;31m'; \ + grn='[0;32m'; \ + lgn='[1;32m'; \ + blu='[1;34m'; \ + mgn='[0;35m'; \ + brg='[1m'; \ + std='[m'; \ + 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, ©_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='[0;31m'; \ + grn='[0;32m'; \ + lgn='[1;32m'; \ + blu='[1;34m'; \ + mgn='[0;35m'; \ + brg='[1m'; \ + std='[m'; \ + 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; +} + +}} |