Repository: biojppm/rapidyaml (language: python)

Issue:
Even with the exceptions disabled there is still a throw happening
On line 16796 in single header file version; rapidyaml-0.5.0.hpp, there is a throw which causes the header to not compile when exceptions are disabled in the project.
```cpp
explicit constexpr vtable() noexcept :
        invoke_ptr{ [](storage_ptr_t, Args&&...) -> R
            { throw std::bad_function_call(); }
        },
        copy_ptr{ [](storage_ptr_t, storage_ptr_t) noexcept -> void {} },
        move_ptr{ [](storage_ptr_t, storage_ptr_t) noexcept -> void {} },
        destructor_ptr{ [](storage_ptr_t) noexcept -> void {} }
    {}
```
[Feature Request] Add an option to support customize installation
Thx for the wonderful ryml!
Recently, I integrated ryml into my company's projects.It works well, however when I use CPack packaging our project, ryml is always packaged into the final product. We want the packaging to be as lean as possible. Could you help add a custom option that allows users to choose not to install ryml？(If you think this option is necessary but don't have the time, I I would also be happy to pull a merge request if you don't mind.)

e.g. yaml-cpp has [YAML_CPP_INSTALL](https://github.com/jbeder/yaml-cpp/blob/master/CMakeLists.txt#L27) option to help users ignore it's installation. 
Even with the exceptions disabled there is still a throw happening
On line 16796 in single header file version; rapidyaml-0.5.0.hpp, there is a throw which causes the header to not compile when exceptions are disabled in the project.
```cpp
explicit constexpr vtable() noexcept :
        invoke_ptr{ [](storage_ptr_t, Args&&...) -> R
            { throw std::bad_function_call(); }
        },
        copy_ptr{ [](storage_ptr_t, storage_ptr_t) noexcept -> void {} },
        move_ptr{ [](storage_ptr_t, storage_ptr_t) noexcept -> void {} },
        destructor_ptr{ [](storage_ptr_t) noexcept -> void {} }
    {}
```


Discussion / hints:
Thanks for reporting. This is coming from an external header in c4core that is not used, and can be safely removed. I will do that soon.
The issue was in c4core, and was fixed in biojppm/c4core#105. If you look above, you'll see that most of the work was in getting the tests to compile and run cleanly on all the different platform/compiler combinations. Now the same must be done in this repo, so this is not ready yet.
Thanks!

Sure, that's definitely useful and a pull request would be very welcome.
Thanks for reporting. This is coming from an external header in c4core that is not used, and can be safely removed. I will do that soon.
The issue was in c4core, and was fixed in biojppm/c4core#105. If you look above, you'll see that most of the work was in getting the tests to compile and run cleanly on all the different platform/compiler combinations. Now the same must be done in this repo, so this is not ready yet.

Fix patch:
diff --git a/.github/release.sh b/.github/release.sh
index 43eb6e71b..68d24d3d0 100644
--- a/.github/release.sh
+++ b/.github/release.sh
@@ -108,6 +108,8 @@ function _c4_validate_ver()
     if [ ! -f changelog/$ver.md ] ; then \
         if [ -f changelog/current.md ] ; then
             git mv changelog/current.md changelog/$ver.md
+            touch changelog/current.md
+            git add changelog/current.md
         else
             echo "ERROR: could not find changelog/$ver.md or changelog/current.md"
             exit 1
diff --git a/.github/reqs.sh b/.github/reqs.sh
index b1747747c..14a3afae9 100644
--- a/.github/reqs.sh
+++ b/.github/reqs.sh
@@ -19,6 +19,11 @@ set -x
 function c4_install_test_requirements()
 {
     os=$1
+    if [ "$os" == "" ] ; then
+        if [ "$(which lsb_release)" != "" ] ; then
+            os=ubuntu
+        fi
+    fi
     case "$os" in
         ubuntu*)
             c4_install_test_requirements_ubuntu
@@ -80,11 +85,12 @@ function c4_install_test_requirements_ubuntu()
 
 function c4_install_all_possible_requirements_ubuntu()
 {
+    UBUNTU_RELEASE=$(lsb_release -rs)
+    UBUNTU_RELEASE_NAME=$(lsb_release -cs)
     export CXX_=all
     export BT=Coverage
     APT_PKG=""  # all
     PIP_PKG=""
-    sudo dpkg --add-architecture i386
     c4_gather_test_requirements_ubuntu
     _c4_add_arm_compilers
     echo "apt packages: $APT_PKG"
@@ -96,17 +102,15 @@ function c4_install_all_possible_requirements_ubuntu()
 
 function c4_gather_test_requirements_ubuntu()
 {
-    if [ "$GITHUB_WORKFLOW" != "" ] ; then
-        sudo dpkg --add-architecture i386
-    else
-        _add_apt build-essential
-        _add_apt cmake
-    fi
+    sudo dpkg --add-architecture i386
+    _add_apt build-essential
+    _add_apt cmake
 
     _add_apt linux-libc-dev:i386
     _add_apt libc6:i386
     _add_apt libc6-dev:i386
     _add_apt libc6-dbg:i386
+    _c4_addlibcxx
 
     _c4_gather_compilers "$CXX_"
 
@@ -130,6 +134,7 @@ function c4_gather_test_requirements_ubuntu()
         _add_pip ndg-httpsclient
         _add_pip pyasn1
         _add_pip cpp-coveralls
+        _add_pip setuptools_rust
     fi
 
     if [ "$CMANY" != "" ] ; then
@@ -148,8 +153,8 @@ function c4_install_test_requirements_ubuntu_impl()
 {
     wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key 2>/dev/null | sudo apt-key add -
     wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | sudo apt-key add -
-    sudo -E apt-add-repository --yes "deb https://apt.kitware.com/ubuntu/ $UBUNTU_RELEASE_NAME main"
-    sudo -E add-apt-repository --yes ppa:ubuntu-toolchain-r/test
+    sudo -E apt-add-repository --yes --no-update "deb https://apt.kitware.com/ubuntu/ $UBUNTU_RELEASE_NAME main"
+    sudo -E apt-add-repository --yes --no-update ppa:ubuntu-toolchain-r/test
 
     if [ "$APT_PKG" != "" ] ; then
         #sudo -E apt-get clean
@@ -158,6 +163,8 @@ function c4_install_test_requirements_ubuntu_impl()
     fi
 
     if [ "$PIP_PKG" != "" ]; then
+        sudo pip3 install setuptools-rust
+        sudo pip3 install --upgrade pip
         sudo pip3 install $PIP_PKG
     fi
 }
@@ -167,21 +174,68 @@ function c4_install_test_requirements_ubuntu_impl()
 
 function _c4_add_arm_compilers()
 {
+    _add_apt qemu
+
     # this is going to be deprecated:
     # https://askubuntu.com/questions/1243252/how-to-install-arm-none-eabi-gdb-on-ubuntu-20-04-lts-focal-fossa
-    sudo -E add-apt-repository --yes ppa:team-gcc-arm-embedded/ppa
+    #sudo -E add-apt-repository --yes --no-update ppa:team-gcc-arm-embedded/ppa
+    #_add_apt gcc-arm-embedded
+    #_add_apt g++-arm-linux-gnueabihf
+    #_add_apt g++-multilib-arm-linux-gnueabihf
 
-    _add_apt gcc-arm-embedded
-    _add_apt g++-arm-linux-gnueabihf
-    _add_apt g++-multilib-arm-linux-gnueabihf
-    _add_apt qemu
+    # do this instead:
+    _c4_install_arm_none_eabi
 }
 
+function _c4_install_arm_none_eabi()
+{
+    # taken from:
+    # https://askubuntu.com/questions/1243252/how-to-install-arm-none-eabi-gdb-on-ubuntu-20-04-lts-focal-fossa/1371525#1371525
+    VER=${1:-15:10.3-2021.10-9}
+    SHORTVER=${2:-10.3-2021.10}
+    URL=https://developer.arm.com/-/media/Files/downloads/gnu-rm/$SHORTVER/gcc-arm-none-eabi-$SHORTVER-x86_64-linux.tar.bz2
+    echo "Creating gcc-arm-none-eabi debian package version $VER"
+
+    echo "Entering temporary directory..."
+    cd /tmp
+
+    echo "Downloading..."
+    name=gcc-arm-none-eabi-$SHORTVER
+    curl -fSL -A "Mozilla/4.0" -o $name.tar "$URL"
+
+    echo "Extracting..."
+    tar -xf $name.tar
+    rm -f $name.tar
+
+    echo "Generating debian package..."
+    [ -d $name-dpkg ] && rm -rf $name-dpkg
+    mkdir $name-dpkg
+    mkdir $name-dpkg/DEBIAN
+    mkdir $name-dpkg/usr
+    cat >  $name-dpkg/DEBIAN/control <<EOF
+Package: gcc-arm-none-eabi
+Version: $VER
+Architecture: amd64
+Maintainer: maintainer
+Description: Arm Embedded toolchain
+EOF
+    mv -f $name/* $name-dpkg/usr/
+    dpkg-deb --build --root-owner-group $name-dpkg
+    mv -v $name-dpkg.deb $name.deb
+    dpkg -i $name.deb
+
+    echo "Testing install..."
+    arm-none-eabi-gcc --version
+    arm-none-eabi-g++ --version
+
+    cd -
+}
 
 function _c4_gather_compilers()
 {
     cxx=$1
     case $cxx in
+        g++-13     ) _c4_addgcc 13 ;;
         g++-12     ) _c4_addgcc 12 ;;
         g++-11     ) _c4_addgcc 11 ;;
         g++-10     ) _c4_addgcc 10 ;;
@@ -190,8 +244,9 @@ function _c4_gather_compilers()
         g++-7      ) _c4_addgcc 7  ;;
         g++-6      ) _c4_addgcc 6  ;;
         g++-5      ) _c4_addgcc 5  ;;
-        #g++-4.9    ) _c4_addgcc 4.9 ;;  # https://askubuntu.com/questions/1036108/install-gcc-4-9-at-ubuntu-18-04
+        g++-4.9    ) _c4_addgcc 4.9 ;;  # https://askubuntu.com/questions/1036108/install-gcc-4-9-at-ubuntu-18-04
         g++-4.8    ) _c4_addgcc 4.8 ;;
+        clang++-15 ) _c4_addclang 15  ;;
         clang++-14 ) _c4_addclang 14  ;;
         clang++-13 ) _c4_addclang 13  ;;
         clang++-12 ) _c4_addclang 12  ;;
@@ -205,7 +260,15 @@ function _c4_gather_compilers()
         clang++-4.0) _c4_addclang 4.0 ;;
         clang++-3.9) _c4_addclang 3.9 ;;
         all)
-            all="g++-12 g++-11 g++-10 g++-9 g++-8 g++-7 g++-6 g++-5 clang++-14 clang++-13 clang++-12 clang++-11 clang++-10 clang++-9 clang++-8 clang++-7 clang++-6.0 clang++-5.0 clang++-4.0 clang++-3.9"
+            for ver in 11 10 9 8 7 6 5 4.9 4.8 ; do
+                all="$all g++-$ver"
+            done
+            if [ "$UBUNTU_RELEASE_NAME" == "jammy" ] ; then  # jammy is 22.04
+                all="g++-13 g++-12 $all"
+            fi
+            for ver in 15 14 13 12 11 10 9 8 7 6.0 5.0 4.0 3.9 ; do
+                all="$all clang++-$ver"
+            done
             echo "installing all compilers: $all"
             for cxx in $all ; do
                 _c4_gather_compilers $cxx
@@ -228,17 +291,50 @@ function _c4_addgcc()
 {
     gccversion=$1
     case $gccversion in
+        6 )
+            if [ "$UBUNTU_RELEASE_NAME" == "focal" ] ; then  # focal is 20.04
+                _add_src gcc-6 "deb http://dk.archive.ubuntu.com/ubuntu/ bionic main"
+                _add_src gcc-6 "deb http://dk.archive.ubuntu.com/ubuntu/ bionic universe"
+            fi
+            ;;
         5 )
-            _add_apt gcc-5 "deb http://dk.archive.ubuntu.com/ubuntu/ xenial main"
-            _add_apt gcc-5 "deb http://dk.archive.ubuntu.com/ubuntu/ xenial universe"
+            _add_src gcc-5 "deb http://dk.archive.ubuntu.com/ubuntu/ xenial main"
+            _add_src gcc-5 "deb http://dk.archive.ubuntu.com/ubuntu/ xenial universe"
+            ;;
+        4.9 )
+            ( cd /tmp ; \
+              wget http://security.ubuntu.com/ubuntu/pool/main/m/mpfr4/libmpfr4_3.1.4-1_amd64.deb ; \
+              dpkg -i libmpfr4_3.1.4-1_amd64.deb \
+            )
+            _add_src gcc-4.9 "deb http://dk.archive.ubuntu.com/ubuntu/ xenial main"
+            _add_src gcc-4.9 "deb http://dk.archive.ubuntu.com/ubuntu/ xenial universe"
             ;;
         *)
             ;;
     esac
-    _add_apt g++-$gccversion
-    _add_apt g++-$gccversion-multilib
-    _add_apt libstdc++-$gccversion-dev
-    _add_apt lib32stdc++-$gccversion-dev
+    case $gccversion in
+        4.9 )
+            _add_apt libmpfr4
+            _add_apt cpp-$gccversion
+            _add_apt gcc-$gccversion
+            _add_apt gcc-$gccversion-multilib
+            _add_apt g++-$gccversion
+            ;;
+        12 | 13 )
+            _add_apt cpp-$gccversion
+            _add_apt gcc-$gccversion-multilib
+            _add_apt gcc-$gccversion
+            _add_apt g++-$gccversion
+            ;;
+        *)
+            _add_apt cpp-$gccversion
+            _add_apt gcc-$gccversion
+            _add_apt g++-$gccversion
+            _add_apt g++-$gccversion-multilib
+            _add_apt libstdc++-$gccversion-dev
+            _add_apt lib32stdc++-$gccversion-dev
+            ;;
+    esac
 }
 
 # add a clang compiler
@@ -247,10 +343,9 @@ function _c4_addclang()
     clversion=$1
     case $clversion in
         # in 18.04, clang9 and later require PPAs
-        9 | 10 | 11 | 12 | 13 | 14)
+        9 | 10 | 11 | 12 | 13 | 14 | 15)
             _add_apt clang-$clversion "deb http://apt.llvm.org/$UBUNTU_RELEASE_NAME/ llvm-toolchain-$UBUNTU_RELEASE_NAME-$clversion main"
             # libstdc++ is required
-            #_c4_addgcc 12
             _c4_addgcc 11
             _c4_addgcc 10
             _c4_addgcc 9
@@ -259,6 +354,9 @@ function _c4_addclang()
             _add_apt clang
             ;;
         *)
+            if [ "$UBUNTU_RELEASE_NAME" == "focal" ] ; then  # focal is 20.04
+                _add_apt clang-$clversion "deb http://apt.llvm.org/$UBUNTU_RELEASE_NAME/ llvm-toolchain-$UBUNTU_RELEASE_NAME-$clversion main"
+            fi
             _add_apt clang-$clversion
             ;;
     esac
@@ -296,19 +394,19 @@ function _add_apt()
     sourceslist=$2
     APT_PKG="$APT_PKG $pkgs"
     echo "adding to apt packages: $pkgs"
-    _add_src "$sourceslist" "# for packages: $pkgs"
+    _add_src "$pkgs" "$sourceslist"
 }
 
 # add an apt source
 function _add_src()
 {
-    sourceslist=$1
-    comment=$2
+    comment=$1
+    sourceslist=$2
     if [ ! -z "$sourceslist" ] ; then
         echo "adding apt source: $sourceslist"
+        sudo sed "/# c4proj $comment/d" -i /etc/apt/sources.list
         sudo bash -c "cat >> /etc/apt/sources.list <<EOF
-$comment
-$sourceslist
+$sourceslist   # c4proj $comment
 EOF"
         #cat /etc/apt/sources.list
     fi
diff --git a/.github/setenv.sh b/.github/setenv.sh
index b3159ddbd..3eb65a108 100644
--- a/.github/setenv.sh
+++ b/.github/setenv.sh
@@ -18,7 +18,7 @@ function c4_show_info()
     pwd
     ls -lFhp
     echo "BITLINKS=$BITLINKS"
-    for bl in shared64 static64 shared32 static32 ; do
+    for bl in shared64 static64 shared32 static32 arm64 arm64shared arm64static shared64arm static64arm arm32 arm32shared arm32static shared32arm static32arm arm ; do
         if _c4skipbitlink $bl ; then
             echo "skip $bl"
         else
@@ -62,8 +62,8 @@ function c4_show_info()
 function _c4bits()
 {
     case "$1" in
-        shared64|static64|arm64) echo 64 ;;
-        shared32|static32|arm32|arm) echo 32 ;;
+        shared64|static64|static64arm|shared64arm|arm64static|arm64shared|arm64) echo 64 ;;
+        shared32|static32|static32arm|shared32arm|arm32static|arm32shared|arm32|arm) echo 32 ;;
         *) exit 1 ;;
     esac
 }
@@ -71,8 +71,20 @@ function _c4bits()
 function _c4linktype()
 {
     case "$1" in
-        shared64|shared32) echo shared ;;
-        static64|static32) echo static ;;
+        shared64|shared32|arm64static|arm32static|static64arm|static32arm) echo shared ;;
+        static64|static32|arm64shared|arm32shared|shared64arm|shared32arm|arm64|arm32|arm) echo static ;;
+        *) exit 1 ;;
+    esac
+}
+
+function _c4vsarchtype()
+{
+    # https://cmake.org/cmake/help/git-stage/generator/Visual%20Studio%2016%202019.html
+    case "$1" in
+        shared64|static64) echo x64 ;;
+        shared32|static32) echo Win32 ;;
+        arm64|arm64shared|arm64static|shared64arm|static64arm) echo ARM64 ;;
+        arm32|arm32shared|arm32static|shared32arm|static32arm|arm) echo ARM ;;
         *) exit 1 ;;
     esac
 }
@@ -101,8 +113,7 @@ function c4_run_test()
     c4_run_target $* test
 }
 
-# runs in parallel
-function c4_build_target()
+function c4_build_target()  # runs in parallel
 {
     if _c4skipbitlink "$1" ; then return 0 ; fi
     id=$1
@@ -118,8 +129,7 @@ function c4_build_target()
     cmake --build $build_dir --config $BT $target --verbose -- $(_c4_generator_build_flags) $(_c4_parallel_build_flags)
 }
 
-# does not run in parallel
-function c4_run_target()
+function c4_run_target()  # does not run in parallel
 {
     if _c4skipbitlink "$1" ; then return 0 ; fi
     id=$1
@@ -237,7 +247,7 @@ function c4_cfg_test()
         # the coverage repo tokens can be set in the travis environment:
         # export CODECOV_TOKEN=.......
         # export COVERALLS_REPO_TOKEN=.......
-        _addprojflags COVERAGE_CODECOV=ON COVERAGE_CODECOV_SILENT=ON
+        _addprojflags COVERAGE_CODECOV=ON COVERAGE_CODECOV_SILENT=OFF
         _addprojflags COVERAGE_COVERALLS=ON COVERAGE_COVERALLS_SILENT=OFF
     fi
     if [ ! -z "$VERBOSE_MAKEFILES" ] ; then
@@ -247,6 +257,34 @@ function c4_cfg_test()
     if [ ! -z "$CMAKE_FLAGS" ] ; then
         _addcmkflags $CMAKE_FLAGS
     fi
+    # do this before setting CMAKE_C_FLAGS
+    case "$CXX_" in
+        vs*)
+            # WATCHOUT: leave a leading space in the _FLAGS options!
+            # This is needed because bash will expand a leading
+            # /DWIN32 to the fs root, ie to something like
+            # C:/Git/DWIN32. The leading space prevents this.
+            #
+            # see https://github.com/bmatzelle/gow/issues/196
+            CFLAGS=" /DWIN32 /D_WINDOWS $CFLAGS"
+            CXXFLAGS=" /DWIN32 /D_WINDOWS /EHsc /GR $CXXFLAGS"
+            ;;
+        xcode) ;;
+        arm*|"") # make sure arm* comes before *g++ or *gcc*
+            ;;
+        *g++*|*gcc*|*clang*)
+            CFLAGS="-m$bits $CFLAGS"
+            CXXFLAGS="-m$bits $CXXFLAGS"
+            ;;
+        em++)
+            CFLAGS="-s DISABLE_EXCEPTION_CATCHING=0 $CFLAGS"
+            CXXFLAGS="-s DISABLE_EXCEPTION_CATCHING=0 $CXXFLAGS"
+            ;;
+        *)
+            echo "unknown compiler"
+            exit 1
+            ;;
+    esac
 
     echo "building with additional cmake flags: $CMFLAGS"
 
@@ -262,78 +300,68 @@ function c4_cfg_test()
     # so we have to do this precious jewell of chicanery:
     case "$CXX_" in
         vs2022)
-            g='Visual Studio 17 2022'
-            case "$bits" in
-                64) a=x64 ;;
-                32) a=Win32 ;;
-            esac
             cmake -S $PROJ_DIR -B $build_dir -DCMAKE_INSTALL_PREFIX="$install_dir" \
-                  -DCMAKE_BUILD_TYPE=$BT \
-                  -G "$g" -A \
-                  $a $CMFLAGS
+                  -G 'Visual Studio 17 2022' -A $(_c4vsarchtype $id) \
+                  $(_c4_add_ehsc_to_vs_arm32 $id) \
+                  -DCMAKE_BUILD_TYPE=$BT $CMFLAGS \
+                  -DCMAKE_C_FLAGS=" $CFLAGS" -DCMAKE_CXX_FLAGS=" $CXXFLAGS"
             ;;
         vs2019)
-            g='Visual Studio 16 2019'
-            case "$bits" in
-                64) a=x64 ;;
-                32) a=Win32 ;;
-            esac
             cmake -S $PROJ_DIR -B $build_dir -DCMAKE_INSTALL_PREFIX="$install_dir" \
-                  -DCMAKE_BUILD_TYPE=$BT \
-                  -G "$g" -A $a \
-                  $CMFLAGS
+                  -G 'Visual Studio 16 2019' -A $(_c4vsarchtype $id) \
+                  $(_c4_add_ehsc_to_vs_arm32 $id) \
+                  -DCMAKE_BUILD_TYPE=$BT $CMFLAGS \
+                  -DCMAKE_C_FLAGS=" $CFLAGS" -DCMAKE_CXX_FLAGS=" $CXXFLAGS"
             ;;
         vs2017)
             case "$bits" in
                 64) g="Visual Studio 15 2017 Win64" ;;
                 32) g="Visual Studio 15 2017" ;;
+                *) exit 1 ;;
             esac
             cmake -S $PROJ_DIR -B $build_dir -DCMAKE_INSTALL_PREFIX="$install_dir" \
-                  -DCMAKE_BUILD_TYPE=$BT \
-                  -G "$g" \
-                  $CMFLAGS
+                  $(_c4_add_ehsc_to_vs_arm32 $id) \
+                  -DCMAKE_BUILD_TYPE=$BT -G "$g" $CMFLAGS \
+                  -DCMAKE_C_FLAGS=" $CFLAGS" -DCMAKE_CXX_FLAGS=" $CXXFLAGS"
             ;;
         xcode)
             g=Xcode
             case "$bits" in
                 64) a="x86_64" ;;
                 32) a="i386"
+                    echo "xcode does not support i386"
                     exit 1 # i386 is deprecated in xcode
                     ;;
             esac
             cmake -S $PROJ_DIR -B $build_dir -DCMAKE_INSTALL_PREFIX="$install_dir" \
-                  -DCMAKE_BUILD_TYPE=$BT \
-                  -G "$g" -DCMAKE_OSX_ARCHITECTURES=$a \
-                  $CMFLAGS
-            ;;
-        arm*|"") # make sure arm* comes before *g++ or *gcc*
-            cmake -S $PROJ_DIR -B $build_dir -DCMAKE_INSTALL_PREFIX="$install_dir" \
-                  -DCMAKE_BUILD_TYPE=$BT \
-                  $CMFLAGS
+                  -DCMAKE_BUILD_TYPE=$BT -G "$g" \
+                  -DCMAKE_OSX_ARCHITECTURES=$a $CMFLAGS \
+                  -DCMAKE_C_FLAGS="$CFLAGS" -DCMAKE_CXX_FLAGS="$CXXFLAGS"
             ;;
         *mingw*)
             export CC_=$(echo "$CXX_" | sed 's:clang++:clang:g' | sed 's:g++:gcc:g')
             cmake -S $PROJ_DIR -B $build_dir -DCMAKE_INSTALL_PREFIX="$install_dir" \
-                  -G "MinGW Makefiles" \
+                  -G "MinGW Makefiles" $CMFLAGS \
+                  -DCMAKE_C_FLAGS="$CFLAGS" -DCMAKE_CXX_FLAGS="$CXXFLAGS"
+            ;;
+        arm*|"") # make sure arm* comes before *g++ or *gcc*
+            cmake -S $PROJ_DIR -B $build_dir -DCMAKE_INSTALL_PREFIX="$install_dir" \
                   -DCMAKE_BUILD_TYPE=$BT $CMFLAGS \
-                  -DCMAKE_C_COMPILER=$CC_ -DCMAKE_CXX_COMPILER=$CXX_ \
-                  -DCMAKE_C_FLAGS="-m$bits" -DCMAKE_CXX_FLAGS="-m$bits"
-            cmake --build $build_dir --target help | sed 1d | sort
+                  -DCMAKE_C_FLAGS="$CFLAGS" -DCMAKE_CXX_FLAGS="$CXXFLAGS"
             ;;
         *g++*|*gcc*|*clang*)
             export CC_=$(echo "$CXX_" | sed 's:clang++:clang:g' | sed 's:g++:gcc:g')
             _c4_choose_clang_tidy $CXX_
             cmake -S $PROJ_DIR -B $build_dir -DCMAKE_INSTALL_PREFIX="$install_dir" \
-                  -DCMAKE_BUILD_TYPE=$BT $CMFLAGS \
                   -DCMAKE_C_COMPILER=$CC_ -DCMAKE_CXX_COMPILER=$CXX_ \
-                  -DCMAKE_C_FLAGS="-m$bits" -DCMAKE_CXX_FLAGS="-m$bits"
+                  -DCMAKE_BUILD_TYPE=$BT $CMFLAGS \
+                  -DCMAKE_C_FLAGS="$CFLAGS" -DCMAKE_CXX_FLAGS="$CXXFLAGS"
             cmake --build $build_dir --target help | sed 1d | sort
             ;;
         em++)
             emcmake cmake -S $PROJ_DIR -B $build_dir -DCMAKE_INSTALL_PREFIX="$install_dir" \
                   -DCMAKE_BUILD_TYPE=$BT $CMFLAGS \
-                  -DCMAKE_CXX_FLAGS="-s DISABLE_EXCEPTION_CATCHING=0" \
-                  -DRYML_TEST_TOOLS=OFF
+                  -DCMAKE_C_FLAGS="$CFLAGS" -DCMAKE_CXX_FLAGS="$CXXFLAGS"
             ;;
         *)
             echo "unknown compiler"
@@ -383,6 +411,21 @@ function _addprojflags()
     done
 }
 
+function _c4_add_ehsc_to_vs_arm32()
+{
+    id=$1
+    case "$CXX_" in
+        vs*)
+            case "$id" in
+                arm32|arm32shared|arm32static|shared32arm|static32arm|arm)
+                    echo '-DCMAKE_CXX_FLAGS="/EHsc"'
+                    ;;
+                *)
+            esac
+            ;;
+    esac
+}
+
 function _c4_parallel_build_flags()
 {
     case "$CXX_" in
diff --git a/.github/workflows/clang.yml b/.github/workflows/clang.yml
index b93b808cd..911163d65 100644
--- a/.github/workflows/clang.yml
+++ b/.github/workflows/clang.yml
@@ -24,30 +24,27 @@ env:
 
 
 jobs:
+
+  #----------------------------------------------------------------------------
   clang_canary:
-    name: ${{matrix.cxx}}/canary/c++${{matrix.std}}/${{matrix.bt}}
-    if: |
-      (!contains(github.event.head_commit.message, 'skip all')) ||
-      (!contains(github.event.head_commit.message, 'skip clang')) ||
-      contains(github.event.head_commit.message, 'only clang')
+    name: clang_canary/${{matrix.cxx}}/c++${{matrix.std}}/${{matrix.bt}}
     continue-on-error: true
-    runs-on: ${{matrix.os}}
+    if: always()  # https://stackoverflow.com/questions/62045967/github-actions-is-there-a-way-to-continue-on-error-while-still-getting-correct
+    runs-on: ubuntu-latest
+    container: ghcr.io/biojppm/c4core/ubuntu18.04:latest  # use the docker image
     strategy:
       fail-fast: false
       matrix:
         include:
-          - {std: 17, cxx: clang++-10 , bt: Debug  , os: ubuntu-20.04, bitlinks: shared64 static32}
-          - {std: 17, cxx: clang++-10 , bt: Release, os: ubuntu-20.04, bitlinks: shared64 static32}
-          - {std: 20, cxx: clang++-10 , bt: Debug  , os: ubuntu-20.04, bitlinks: shared64 static32}
-          - {std: 20, cxx: clang++-10 , bt: Release, os: ubuntu-20.04, bitlinks: shared64 static32}
-          - {std: 11, cxx: clang++-6.0, bt: Debug  , os: ubuntu-20.04, bitlinks: shared64 static32}
-          - {std: 11, cxx: clang++-6.0, bt: Release, os: ubuntu-20.04, bitlinks: shared64 static32}
-    env: {STD: "${{matrix.std}}", CXX_: "${{matrix.cxx}}", BT: "${{matrix.bt}}", BITLINKS: "${{matrix.bitlinks}}",
-          CMAKE_FLAGS: "${{matrix.cmkflags}}",
-          VG: "${{matrix.vg}}", SAN: "${{matrix.san}}", LINT: "${{matrix.lint}}", OS: "${{matrix.os}}"}
+          - {std: 20, cxx: "10" , bt: Debug  , bitlinks: shared64 static32}
+          - {std: 20, cxx: "10" , bt: Release, bitlinks: shared64 static32}
+          - {std: 11, cxx: "6.0", bt: Debug  , bitlinks: shared64 static32}
+          - {std: 11, cxx: "6.0", bt: Release, bitlinks: shared64 static32}
+    env: {STD: "${{matrix.std}}", CXX_: "clang++-${{matrix.cxx}}", BT: "${{matrix.bt}}", BITLINKS: "${{matrix.bitlinks}}", VG: "${{matrix.vg}}", SAN: "${{matrix.san}}", LINT: "${{matrix.lint}}", OS: "${{matrix.os}}"}
     steps:
-      - {name: checkout, uses: actions/checkout@v4, with: {submodules: recursive}}
-      - {name: install requirements, run: source .github/reqs.sh && c4_install_test_requirements $OS}
+      - {name: checkout, uses: actions/checkout@v3, with: {submodules: recursive}}
+      - run: git config --system --add safe.directory '*'  # needed for running in the docker image. see https://github.com/actions/checkout/issues/1169
+      - run: c4core-install $CXX_
       - {name: show info, run: source .github/setenv.sh && c4_show_info}
       - name: shared64-configure---------------------------------------------------
         run: source .github/setenv.sh && c4_cfg_test shared64
@@ -70,30 +67,70 @@ jobs:
       - {name: shared32-run, run: source .github/setenv.sh && c4_run_test shared32}
       - {name: shared32-pack, run: source .github/setenv.sh && c4_package shared32}
 
-  clang_canary_tabtokens:
-    name: tabtokens/${{matrix.cxx}}/canary/c++${{matrix.std}}/${{matrix.bt}}
-    if: |
-      (!contains(github.event.head_commit.message, 'skip all')) ||
-      (!contains(github.event.head_commit.message, 'skip clang')) ||
-      contains(github.event.head_commit.message, 'only clang')
+  clang_tabtokens:
+    name: clang_tabtokens/${{matrix.cxx}}/c++${{matrix.std}}/${{matrix.bt}}
     continue-on-error: true
-    runs-on: ${{matrix.os}}
+    if: always()  # https://stackoverflow.com/questions/62045967/github-actions-is-there-a-way-to-continue-on-error-while-still-getting-correct
+    runs-on: ubuntu-latest
+    container: ghcr.io/biojppm/c4core/ubuntu18.04:latest  # use the docker image
     strategy:
       fail-fast: false
       matrix:
         include:
-          - {std: 17, cxx: clang++-10 , bt: Debug  , os: ubuntu-20.04, bitlinks: static64, cmkflags: "-DRYML_WITH_TAB_TOKENS=ON"}
-          - {std: 17, cxx: clang++-10 , bt: Release, os: ubuntu-20.04, bitlinks: static64, cmkflags: "-DRYML_WITH_TAB_TOKENS=ON"}
-          - {std: 20, cxx: clang++-10 , bt: Debug  , os: ubuntu-20.04, bitlinks: static64, cmkflags: "-DRYML_WITH_TAB_TOKENS=ON"}
-          - {std: 20, cxx: clang++-10 , bt: Release, os: ubuntu-20.04, bitlinks: static64, cmkflags: "-DRYML_WITH_TAB_TOKENS=ON"}
-          - {std: 11, cxx: clang++-6.0, bt: Debug  , os: ubuntu-20.04, bitlinks: static64, cmkflags: "-DRYML_WITH_TAB_TOKENS=ON"}
-          - {std: 11, cxx: clang++-6.0, bt: Release, os: ubuntu-20.04, bitlinks: static64, cmkflags: "-DRYML_WITH_TAB_TOKENS=ON"}
-    env: {STD: "${{matrix.std}}", CXX_: "${{matrix.cxx}}", BT: "${{matrix.bt}}", BITLINKS: "${{matrix.bitlinks}}",
-          CMAKE_FLAGS: "${{matrix.cmkflags}}",
+          - {std: 20, cxx: "10" , bt: Debug  , bitlinks: shared64 static32}
+          - {std: 20, cxx: "10" , bt: Release, bitlinks: shared64 static32}
+          - {std: 11, cxx: "6.0", bt: Debug  , bitlinks: shared64 static32}
+          - {std: 11, cxx: "6.0", bt: Release, bitlinks: shared64 static32}
+    env: {STD: "${{matrix.std}}", CXX_: "clang++-${{matrix.cxx}}", BT: "${{matrix.bt}}", BITLINKS: "${{matrix.bitlinks}}",
+          CMAKE_FLAGS: "-DRYML_WITH_TAB_TOKENS=ON",
           VG: "${{matrix.vg}}", SAN: "${{matrix.san}}", LINT: "${{matrix.lint}}", OS: "${{matrix.os}}"}
     steps:
-      - {name: checkout, uses: actions/checkout@v4, with: {submodules: recursive}}
-      - {name: install requirements, run: source .github/reqs.sh && c4_install_test_requirements $OS}
+      - {name: checkout, uses: actions/checkout@v3, with: {submodules: recursive}}
+      - run: git config --system --add safe.directory '*'  # needed for running in the docker image. see https://github.com/actions/checkout/issues/1169
+      - run: c4core-install $CXX_
+      - {name: show info, run: source .github/setenv.sh && c4_show_info}
+      - name: shared64-configure---------------------------------------------------
+        run: source .github/setenv.sh && c4_cfg_test shared64
+      - {name: shared64-build, run: source .github/setenv.sh && c4_build_test shared64}
+      - {name: shared64-run, run: source .github/setenv.sh && c4_run_test shared64}
+      - {name: shared64-pack, run: source .github/setenv.sh && c4_package shared64}
+      - name: static64-configure---------------------------------------------------
+        run: source .github/setenv.sh && c4_cfg_test static64
+      - {name: static64-build, run: source .github/setenv.sh && c4_build_test static64}
+      - {name: static64-run, run: source .github/setenv.sh && c4_run_test static64}
+      - {name: static64-pack, run: source .github/setenv.sh && c4_package static64}
+      - name: static32-configure---------------------------------------------------
+        run: source .github/setenv.sh && c4_cfg_test static32
+      - {name: static32-build, run: source .github/setenv.sh && c4_build_test static32}
+      - {name: static32-run, run: source .github/setenv.sh && c4_run_test static32}
+      - {name: static32-pack, run: source .github/setenv.sh && c4_package static32}
+      - name: shared32-configure---------------------------------------------------
+        run: source .github/setenv.sh && c4_cfg_test shared32
+      - {name: shared32-build, run: source .github/setenv.sh && c4_build_test shared32}
+      - {name: shared32-run, run: source .github/setenv.sh && c4_run_test shared32}
+      - {name: shared32-pack, run: source .github/setenv.sh && c4_package shared32}
+
+  #----------------------------------------------------------------------------
+  clang_noexceptions:
+    name: clang_noexceptions/${{matrix.cxx}}/c++${{matrix.std}}/${{matrix.bt}}
+    continue-on-error: true
+    if: always()  # https://stackoverflow.com/questions/62045967/github-actions-is-there-a-way-to-continue-on-error-while-still-getting-correct
+    runs-on: ubuntu-latest
+    container: ghcr.io/biojppm/c4core/ubuntu18.04:latest  # use the docker image
+    strategy:
+      fail-fast: false
+      matrix:
+        include:
+          - {std: 20, cxx: "10" , bt: Debug  , bitlinks: shared64 static32}
+          - {std: 20, cxx: "10" , bt: Release, bitlinks: shared64 static32}
+          - {std: 11, cxx: "6.0", bt: Debug  , bitlinks: shared64 static32}
+          - {std: 11, cxx: "6.0", bt: Release, bitlinks: shared64 static32}
+    env: {CXXFLAGS: "-fno-exceptions -fno-rtti", STD: "${{matrix.std}}", CXX_: "clang++-${{matrix.cxx}}", BT: "${{matrix.bt}}", BITLINKS: "${{matrix.bitlinks}}", VG: "${{matrix.vg}}", SAN: "${{matrix.san}}", LINT: "${{matrix.lint}}", OS: "${{matrix.os}}"}
+    steps:
+      - {name: checkout, uses: actions/checkout@v3, with: {submodules: recursive}}
+      - run: git config --system --add safe.directory '*'  # needed for running in the docker image. see https://github.com/actions/checkout/issues/1169
+      - run: c4core-install $CXX_
+      - run: git config --system --add safe.directory '*'  # needed for running in the docker image. see https://github.com/actions/checkout/issues/1169
       - {name: show info, run: source .github/setenv.sh && c4_show_info}
       - name: shared64-configure---------------------------------------------------
         run: source .github/setenv.sh && c4_cfg_test shared64
@@ -118,26 +155,48 @@ jobs:
 
   #----------------------------------------------------------------------------
   clang_extended:
-    name: ${{matrix.cxx}}/extended/c++${{matrix.std}}/${{matrix.bt}}
-    needs: clang_canary
-    if: |
-      (!contains(github.event.head_commit.message, 'skip all')) ||
-      (!contains(github.event.head_commit.message, 'skip clang')) ||
-      contains(github.event.head_commit.message, 'only clang')
+    name: clang_extended/${{matrix.cxx}}/c++${{matrix.std}}/${{matrix.bt}}/vg${{matrix.vg}}
     continue-on-error: true
-    runs-on: ${{matrix.os}}
+    if: always()  # https://stackoverflow.com/questions/62045967/github-actions-is-there-a-way-to-continue-on-error-while-still-getting-correct
+    runs-on: ubuntu-latest
+    container: ghcr.io/biojppm/c4core/${{matrix.img}}:latest  # use the docker image
     strategy:
       fail-fast: false
       matrix:
         include:
-          - {std: 11, cxx: clang++-10 , bt: Debug  , vg: on, os: ubuntu-20.04}
-          - {std: 11, cxx: clang++-10 , bt: Release, vg: on, os: ubuntu-20.04}
-    env: {STD: "${{matrix.std}}", CXX_: "${{matrix.cxx}}", BT: "${{matrix.bt}}", BITLINKS: "${{matrix.bitlinks}}",
-          CMAKE_FLAGS: "${{matrix.cmkflags}}",
-          VG: "${{matrix.vg}}", SAN: "${{matrix.san}}", LINT: "${{matrix.lint}}", OS: "${{matrix.os}}"}
+          - {std: 11, cxx: "16" , bt: Debug  , vg: on, img: ubuntu18.04}
+          - {std: 11, cxx: "16" , bt: Release, vg: on, img: ubuntu18.04}
+          - {std: 11, cxx: "15" , bt: Debug  , vg: on, img: ubuntu22.04} # install of clang15 fails on ubuntu18.04
+          - {std: 11, cxx: "15" , bt: Release, vg: on, img: ubuntu22.04} # install of clang15 fails on ubuntu18.04
+          - {std: 11, cxx: "14" , bt: Debug  , vg: on, img: ubuntu22.04} # install of clang14 fails on ubuntu18.04
+          - {std: 11, cxx: "14" , bt: Release, vg: on, img: ubuntu22.04} # install of clang14 fails on ubuntu18.04
+          - {std: 11, cxx: "13" , bt: Debug  , vg: on, img: ubuntu18.04}
+          - {std: 11, cxx: "13" , bt: Release, vg: on, img: ubuntu18.04}
+          - {std: 11, cxx: "12" , bt: Debug  , vg: on, img: ubuntu18.04}
+          - {std: 11, cxx: "12" , bt: Release, vg: on, img: ubuntu18.04}
+          - {std: 11, cxx: "11" , bt: Debug  , vg: on, img: ubuntu18.04}
+          - {std: 11, cxx: "11" , bt: Release, vg: on, img: ubuntu18.04}
+          - {std: 11, cxx: "10" , bt: Debug  , vg: on, img: ubuntu18.04}
+          - {std: 11, cxx: "10" , bt: Release, vg: on, img: ubuntu18.04}
+          - {std: 11, cxx: "9"  , bt: Debug  , vg: on, img: ubuntu18.04}
+          - {std: 11, cxx: "9"  , bt: Release, vg: on, img: ubuntu18.04}
+          - {std: 11, cxx: "8"  , bt: Debug  , vg: on, img: ubuntu18.04}
+          - {std: 11, cxx: "8"  , bt: Release, vg: on, img: ubuntu18.04}
+          - {std: 11, cxx: "7"  , bt: Debug  , vg: on, img: ubuntu18.04}
+          - {std: 11, cxx: "7"  , bt: Release, vg: on, img: ubuntu18.04}
+          - {std: 11, cxx: "6.0", bt: Debug  , vg: on, img: ubuntu18.04}
+          - {std: 11, cxx: "6.0", bt: Release, vg: on, img: ubuntu18.04}
+          - {std: 11, cxx: "5.0", bt: Debug  , vg: on, img: ubuntu18.04}
+          - {std: 11, cxx: "5.0", bt: Release, vg: on, img: ubuntu18.04}
+          - {std: 11, cxx: "4.0", bt: Debug  , vg: on, img: ubuntu18.04}
+          - {std: 11, cxx: "4.0", bt: Release, vg: on, img: ubuntu18.04}
+          - {std: 11, cxx: "3.9", bt: Debug  , vg: on, img: ubuntu18.04}
+          - {std: 11, cxx: "3.9", bt: Release, vg: on, img: ubuntu18.04}
+    env: {STD: "${{matrix.std}}", CXX_: "clang++-${{matrix.cxx}}", BT: "${{matrix.bt}}", BITLINKS: "${{matrix.bitlinks}}", VG: "${{matrix.vg}}", SAN: "${{matrix.san}}", LINT: "${{matrix.lint}}", OS: "${{matrix.os}}"}
     steps:
-      - {name: checkout, uses: actions/checkout@v4, with: {submodules: recursive}}
-      - {name: install requirements, run: source .github/reqs.sh && c4_install_test_requirements $OS}
+      - {name: checkout, uses: actions/checkout@v3, with: {submodules: recursive}}
+      - run: git config --system --add safe.directory '*'  # needed for running in the docker image. see https://github.com/actions/checkout/issues/1169
+      - run: c4core-install $CXX_
       - {name: show info, run: source .github/setenv.sh && c4_show_info}
       - name: shared64-configure---------------------------------------------------
         run: source .github/setenv.sh && c4_cfg_test shared64
@@ -161,48 +220,37 @@ jobs:
       - {name: shared32-pack, run: source .github/setenv.sh && c4_package shared32}
 
   #----------------------------------------------------------------------------
-  clang_sanitize:
-    name: ${{matrix.cxx}}/san/c++${{matrix.std}}/${{matrix.bt}}
-    needs: clang_canary
-    if: |
-      (!contains(github.event.head_commit.message, 'skip all')) ||
-      (!contains(github.event.head_commit.message, 'skip clang')) ||
-      contains(github.event.head_commit.message, 'only clang')
+  clangsan:
+    name: clang/${{matrix.cxx}}/${{matrix.bt}}/c++${{matrix.std}}
     continue-on-error: true
-    runs-on: ${{matrix.os}}
+    if: always()  # https://stackoverflow.com/questions/62045967/github-actions-is-there-a-way-to-continue-on-error-while-still-getting-correct
+    runs-on: ubuntu-latest
+    container: ghcr.io/biojppm/c4core/ubuntu18.04:latest  # use the docker image
     strategy:
       fail-fast: false
       matrix:
         include:
-          # these jobs take much longer, so run only one bitlink pair per job to profit from parallelism
-          - {std: 11, cxx: clang++-10, bt: Debug  , vg: ON, san: ALL, bitlinks: shared64, os: ubuntu-20.04}
-          - {std: 11, cxx: clang++-10, bt: Debug  , vg: ON, san: ALL, bitlinks: static64, os: ubuntu-20.04}
-          - {std: 11, cxx: clang++-10, bt: Debug  , vg: ON, san: ALL, bitlinks: shared32, os: ubuntu-20.04}
-          - {std: 11, cxx: clang++-10, bt: Debug  , vg: ON, san: ALL, bitlinks: static32, os: ubuntu-20.04}
-          - {std: 11, cxx: clang++-10, bt: Release, vg: ON, san: ALL, bitlinks: shared64, os: ubuntu-20.04}
-          - {std: 11, cxx: clang++-10, bt: Release, vg: ON, san: ALL, bitlinks: static64, os: ubuntu-20.04}
-          - {std: 11, cxx: clang++-10, bt: Release, vg: ON, san: ALL, bitlinks: shared32, os: ubuntu-20.04}
-          - {std: 11, cxx: clang++-10, bt: Release, vg: ON, san: ALL, bitlinks: static32, os: ubuntu-20.04}
-          - {std: 17, cxx: clang++-10, bt: Debug  , vg: ON, san: ALL, bitlinks: shared64, os: ubuntu-20.04}
-          - {std: 17, cxx: clang++-10, bt: Debug  , vg: ON, san: ALL, bitlinks: static64, os: ubuntu-20.04}
-          - {std: 17, cxx: clang++-10, bt: Debug  , vg: ON, san: ALL, bitlinks: shared32, os: ubuntu-20.04}
-          - {std: 17, cxx: clang++-10, bt: Debug  , vg: ON, san: ALL, bitlinks: static32, os: ubuntu-20.04}
-          - {std: 17, cxx: clang++-10, bt: Release, vg: ON, san: ALL, bitlinks: shared64, os: ubuntu-20.04}
-          - {std: 17, cxx: clang++-10, bt: Release, vg: ON, san: ALL, bitlinks: static64, os: ubuntu-20.04}
-          - {std: 17, cxx: clang++-10, bt: Release, vg: ON, san: ALL, bitlinks: shared32, os: ubuntu-20.04}
-          - {std: 17, cxx: clang++-10, bt: Release, vg: ON, san: ALL, bitlinks: static32, os: ubuntu-20.04}
-          - {std: 20, cxx: clang++-10, bt: Debug  , vg: ON, san: ALL, bitlinks: shared64, os: ubuntu-20.04}
-          - {std: 20, cxx: clang++-10, bt: Debug  , vg: ON, san: ALL, bitlinks: static64, os: ubuntu-20.04}
-          - {std: 20, cxx: clang++-10, bt: Debug  , vg: ON, san: ALL, bitlinks: shared32, os: ubuntu-20.04}
-          - {std: 20, cxx: clang++-10, bt: Debug  , vg: ON, san: ALL, bitlinks: static32, os: ubuntu-20.04}
-          - {std: 20, cxx: clang++-10, bt: Release, vg: ON, san: ALL, bitlinks: shared64, os: ubuntu-20.04}
-          - {std: 20, cxx: clang++-10, bt: Release, vg: ON, san: ALL, bitlinks: static64, os: ubuntu-20.04}
-          - {std: 20, cxx: clang++-10, bt: Release, vg: ON, san: ALL, bitlinks: shared32, os: ubuntu-20.04}
-          - {std: 20, cxx: clang++-10, bt: Release, vg: ON, san: ALL, bitlinks: static32, os: ubuntu-20.04}
-    env: {STD: "${{matrix.std}}", CXX_: "${{matrix.cxx}}", BT: "${{matrix.bt}}", BITLINKS: "${{matrix.bitlinks}}", VG: "${{matrix.vg}}", SAN: "${{matrix.san}}", LINT: "${{matrix.lint}}", OS: "${{matrix.os}}"}
+          - {std: 11, cxx: "16" , bt: asan, bitlinks: static64}
+          - {std: 14, cxx: "16" , bt: asan, bitlinks: static64}
+          - {std: 17, cxx: "16" , bt: asan, bitlinks: static64}
+          - {std: 20, cxx: "16" , bt: asan, bitlinks: static64}
+          - {std: 11, cxx: "16" , bt: lsan, bitlinks: static64}
+          - {std: 14, cxx: "16" , bt: lsan, bitlinks: static64}
+          - {std: 17, cxx: "16" , bt: lsan, bitlinks: static64}
+          - {std: 20, cxx: "16" , bt: lsan, bitlinks: static64}
+          - {std: 11, cxx: "16" , bt: tsan, bitlinks: static64}
+          - {std: 14, cxx: "16" , bt: tsan, bitlinks: static64}
+          - {std: 17, cxx: "16" , bt: tsan, bitlinks: static64}
+          - {std: 20, cxx: "16" , bt: tsan, bitlinks: static64}
+          - {std: 11, cxx: "16" , bt: ubsan, bitlinks: static64}
+          - {std: 14, cxx: "16" , bt: ubsan, bitlinks: static64}
+          - {std: 17, cxx: "16" , bt: ubsan, bitlinks: static64}
+          - {std: 20, cxx: "16" , bt: ubsan, bitlinks: static64}
+    env: {STD: "${{matrix.std}}", CXX_: "clang++-${{matrix.cxx}}", BT: "${{matrix.bt}}", BITLINKS: "${{matrix.bitlinks}}", VG: "${{matrix.vg}}", SAN: "${{matrix.san}}", LINT: "${{matrix.lint}}", OS: "${{matrix.os}}"}
     steps:
-      - {name: checkout, uses: actions/checkout@v4, with: {submodules: recursive}}
-      - {name: install requirements, run: source .github/reqs.sh && c4_install_test_requirements $OS}
+      - {name: checkout, uses: actions/checkout@v3, with: {submodules: recursive}}
+      - run: git config --system --add safe.directory '*'  # needed for running in the docker image. see https://github.com/actions/checkout/issues/1169
+      - run: c4core-install $CXX_
       - {name: show info, run: source .github/setenv.sh && c4_show_info}
       - name: shared64-configure---------------------------------------------------
         run: source .github/setenv.sh && c4_cfg_test shared64
@@ -225,14 +273,11 @@ jobs:
       - {name: shared32-run, run: source .github/setenv.sh && c4_run_test shared32}
       - {name: shared32-pack, run: source .github/setenv.sh && c4_package shared32}
 
-#  #----------------------------------------------------------------------------
+  #----------------------------------------------------------------------------
 #  # https://blog.kitware.com/static-checks-with-cmake-cdash-iwyu-clang-tidy-lwyu-cpplint-and-cppcheck/
 #  static_analysis:
-#    if: |
-#      (!contains(github.event.head_commit.message, 'skip all')) ||
-#      (!contains(github.event.head_commit.message, 'skip clang')) ||
-#      contains(github.event.head_commit.message, 'only clang')
 #    continue-on-error: true
+#    if: always()  # https://stackoverflow.com/questions/62045967/github-actions-is-there-a-way-to-continue-on-error-while-still-getting-correct
 #    runs-on: ${{matrix.os}}
 #    strategy:
 #      fail-fast: false
@@ -249,7 +294,8 @@ jobs:
 #          - {std: 20, cxx: clang++-10, bt: Release, bitlinks: shared64, os: ubuntu-20.04}
 #    env: {STD: "${{matrix.std}}", CXX_: "${{matrix.cxx}}", BT: "${{matrix.bt}}", BITLINKS: "${{matrix.bitlinks}}", VG: "${{matrix.vg}}", SAN: "${{matrix.san}}", LINT: "${{matrix.lint}}", OS: "${{matrix.os}}"}
 #    steps:
-#      - {name: checkout, uses: actions/checkout@v4, with: {submodules: recursive}}
+#      - {name: checkout, uses: actions/checkout@v3, with: {submodules: recursive}}
+#      - run: git config --system --add safe.directory '*'  # needed for running in the docker image. see https://github.com/actions/checkout/issues/1169
 #      - {name: install requirements, run: source .github/reqs.sh && c4_install_test_requirements $OS}
 #      - {name: show info, run: source .github/setenv.sh && c4_show_info}
 #      - name: shared64-configure---------------------------------------------------
diff --git a/.github/workflows/clang_tidy.yml b/.github/workflows/clang_tidy.yml
index bbfbd1fb9..cd79d7e8c 100644
--- a/.github/workflows/clang_tidy.yml
+++ b/.github/workflows/clang_tidy.yml
@@ -24,31 +24,32 @@ env:
 
 
 jobs:
+
+  #----------------------------------------------------------------------------
   clang_tidy:
-    name: ${{matrix.cxx}}/tidy/c++${{matrix.std}}/${{matrix.bt}}
-    if: |
-      (!contains(github.event.head_commit.message, 'skip all')) ||
-      (!contains(github.event.head_commit.message, 'skip clang')) ||
-      contains(github.event.head_commit.message, 'only clang')
-    continue-on-error: false
-    runs-on: ${{matrix.os}}
+    name: clang_tidy/c++${{matrix.std}}/${{matrix.bt}}
+    continue-on-error: true
+    if: always()  # https://stackoverflow.com/questions/62045967/github-actions-is-there-a-way-to-continue-on-error-while-still-getting-correct
+    runs-on: ubuntu-latest
+    container: ghcr.io/biojppm/c4core/ubuntu18.04:latest  # use the docker image
     strategy:
       fail-fast: false
       matrix:
         include:
           # clang tidy takes a long time, so don't do multiple bits/linktypes
-          - {std: 11, cxx: clang++-10, bt: Debug             , lint: clang-tidy, bitlinks: shared64, os: ubuntu-20.04}
-          - {std: 11, cxx: clang++-10, bt: Debug             , lint: clang-tidy, bitlinks: static64, os: ubuntu-20.04}
-          - {std: 11, cxx: clang++-10, bt: Debug             , lint: clang-tidy, bitlinks: shared32, os: ubuntu-20.04}
-          - {std: 11, cxx: clang++-10, bt: Debug             , lint: clang-tidy, bitlinks: static32, os: ubuntu-20.04}
-          - {std: 11, cxx: clang++-10, bt: ReleaseWithDebInfo, lint: clang-tidy, bitlinks: shared64, os: ubuntu-20.04}
-          - {std: 11, cxx: clang++-10, bt: ReleaseWithDebInfo, lint: clang-tidy, bitlinks: static64, os: ubuntu-20.04}
-          - {std: 11, cxx: clang++-10, bt: ReleaseWithDebInfo, lint: clang-tidy, bitlinks: shared32, os: ubuntu-20.04}
-          - {std: 11, cxx: clang++-10, bt: ReleaseWithDebInfo, lint: clang-tidy, bitlinks: static32, os: ubuntu-20.04}
+          - {std: 11, cxx: clang++-16, bt: Debug             , lint: clang-tidy, bitlinks: shared64}
+          - {std: 11, cxx: clang++-16, bt: Debug             , lint: clang-tidy, bitlinks: shared32}
+          - {std: 11, cxx: clang++-16, bt: Debug             , lint: clang-tidy, bitlinks: static64}
+          - {std: 11, cxx: clang++-16, bt: Debug             , lint: clang-tidy, bitlinks: static32}
+          - {std: 11, cxx: clang++-16, bt: ReleaseWithDebInfo, lint: clang-tidy, bitlinks: shared64}
+          - {std: 11, cxx: clang++-16, bt: ReleaseWithDebInfo, lint: clang-tidy, bitlinks: shared32}
+          - {std: 11, cxx: clang++-16, bt: ReleaseWithDebInfo, lint: clang-tidy, bitlinks: static64}
+          - {std: 11, cxx: clang++-16, bt: ReleaseWithDebInfo, lint: clang-tidy, bitlinks: static32}
     env: {STD: "${{matrix.std}}", CXX_: "${{matrix.cxx}}", BT: "${{matrix.bt}}", BITLINKS: "${{matrix.bitlinks}}", VG: "${{matrix.vg}}", SAN: "${{matrix.san}}", LINT: "${{matrix.lint}}", OS: "${{matrix.os}}"}
     steps:
-      - {name: checkout, uses: actions/checkout@v4, with: {submodules: recursive}}
-      - {name: install requirements, run: source .github/reqs.sh && c4_install_test_requirements $OS}
+      - {name: checkout, uses: actions/checkout@v3, with: {submodules: recursive}}
+      - run: git config --system --add safe.directory '*'  # needed for running in the docker image. see https://github.com/actions/checkout/issues/1169
+      - run: c4core-install ${{matrix.cxx}}
       - {name: show info, run: source .github/setenv.sh && c4_show_info}
       - name: shared64-configure---------------------------------------------------
         run: source .github/setenv.sh && c4_cfg_test shared64
diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
index aa4501f8a..92e0dd9a8 100644
--- a/.github/workflows/coverage.yml
+++ b/.github/workflows/coverage.yml
@@ -24,28 +24,27 @@ env:
 
 
 jobs:
+
   #----------------------------------------------------------------------------
   coverage:
-    name: coverage/c++${{matrix.std}}
-    if: |
-      (!contains(github.event.head_commit.message, 'skip all')) ||
-      (!contains(github.event.head_commit.message, 'skip coverage')) ||
-      contains(github.event.head_commit.message, 'only coverage')
+    name: coverage/${{matrix.name}}
+    # if: github.ref == 'refs/heads/master'
     continue-on-error: true
+    if: always()  # https://stackoverflow.com/questions/62045967/github-actions-is-there-a-way-to-continue-on-error-while-still-getting-correct
     runs-on: ${{matrix.os}}
     strategy:
       fail-fast: false
       matrix:
         include:
-          - {std: 11, cxx: g++-9, bt: Coverage, os: ubuntu-20.04}
-          - {std: 17, cxx: g++-9, bt: Coverage, os: ubuntu-20.04}
-    env: {STD: "${{matrix.std}}", CXX_: "${{matrix.cxx}}", BT: "${{matrix.bt}}", BITLINKS: "${{matrix.bitlinks}}", VG: "${{matrix.vg}}", SAN: "${{matrix.san}}", LINT: "${{matrix.lint}}", OS: "${{matrix.os}}",
-          CODECOV_TOKEN: "${{secrets.CODECOV_TOKEN}}",
-          COVERALLS_REPO_TOKEN: "${{secrets.COVERALLS_REPO_TOKEN}}",
-          # coveralls disabled: https://github.com/lemurheavy/coveralls-public/issues/1665
-          # https://docs.coveralls.io/parallel-build-webhook
-          #COVERALLS_PARALLEL: true
-          }
+          - {name: c++11, std: 11, cxx: g++-9, cc: gcc-9, bt: Coverage, os: ubuntu-20.04}
+          - {name: c++17, std: 17, cxx: g++-9, cc: gcc-9, bt: Coverage, os: ubuntu-20.04}
+          #- {name: c++20, std: 20, cxx: g++-9, cc: gcc-9, bt: Coverage, os: ubuntu-20.04}
+    env: {
+      STD: "${{matrix.std}}", CXX_: "${{matrix.cxx}}", BT: "${{matrix.bt}}", BITLINKS: "${{matrix.bitlinks}}", VG: "${{matrix.vg}}", SAN: "${{matrix.san}}", LINT: "${{matrix.lint}}", OS: "${{matrix.os}}",
+      CODECOV_TOKEN: "${{secrets.CODECOV_TOKEN}}",
+      COVERALLS_REPO_TOKEN: "${{secrets.COVERALLS_REPO_TOKEN}}",
+      COVERALLS_PARALLEL: true,
+      }
     steps:
       - {name: checkout, uses: actions/checkout@v4, with: {submodules: recursive}}
       - {name: install requirements, run: source .github/reqs.sh && c4_install_test_requirements $OS}
@@ -53,13 +52,13 @@ jobs:
       - name: static64-configure---------------------------------------------------
         run: source .github/setenv.sh && c4_cfg_test static64
       - {name: static64-build, run: source .github/setenv.sh && c4_build_test static64}
-      - {name: static64-coverage, run: source .github/setenv.sh && c4_run_target static64 ryml-coverage}
+      - {name: static64-run, run: source .github/setenv.sh && c4_build_target static64 ryml-coverage}
       - name: static64-coverage-artifacts
         uses: actions/upload-artifact@v3
         with:
-          name: coverage-static64-cxx${{matrix.std}}
+          name: coverage-static64
           path: |
-            build/static64/lcov
+            build/static64/lcov/
             build/static64/coverage3-final_filtered.lcov
       - {name: static64-submit-codecov, run: source .github/setenv.sh && c4_submit_coverage static64 codecov}
       # coveralls disabled: https://github.com/lemurheavy/coveralls-public/issues/1665
@@ -67,7 +66,7 @@ jobs:
       - name: static32-configure---------------------------------------------------
         run: source .github/setenv.sh && c4_cfg_test static32
       - {name: static32-build, run: source .github/setenv.sh && c4_build_test static32}
-      - {name: static32-coverage, run: source .github/setenv.sh && c4_run_target static32 ryml-coverage}
+      - {name: static32-run, run: source .github/setenv.sh && c4_build_target static32 ryml-coverage}
       - name: static32-coverage-artifacts
         uses: actions/upload-artifact@v3
         with:
diff --git a/.github/workflows/gcc.yml b/.github/workflows/gcc.yml
index ae9f8f2fa..2a16edb27 100644
--- a/.github/workflows/gcc.yml
+++ b/.github/workflows/gcc.yml
@@ -24,30 +24,31 @@ env:
 
 
 jobs:
+
+  #----------------------------------------------------------------------------
   gcc_canary:
-    name: ${{matrix.cxx}}/canary/${{matrix.bt}}
-    if: |
-      (!contains(github.event.head_commit.message, 'skip all')) ||
-      (!contains(github.event.head_commit.message, 'skip gcc')) ||
-      contains(github.event.head_commit.message, 'only gcc')
+    name: gcc_canary/${{matrix.cxx}}/c++${{matrix.std}}/${{matrix.bt}}
     continue-on-error: true
-    runs-on: ${{matrix.os}}
+    if: always()  # https://stackoverflow.com/questions/62045967/github-actions-is-there-a-way-to-continue-on-error-while-still-getting-correct
+    runs-on: ubuntu-latest
+    container: ghcr.io/biojppm/c4core/ubuntu18.04:latest  # use the docker image
     strategy:
       fail-fast: false
       matrix:
         include:
-          - {std: 11, cxx: g++-7      , bt: Debug  , os: ubuntu-20.04, bitlinks: shared64 static32}
-          - {std: 11, cxx: g++-7      , bt: Release, os: ubuntu-20.04, bitlinks: shared64 static32}
-          - {std: 20, cxx: g++-10     , bt: Debug  , os: ubuntu-20.04, bitlinks: shared64 static32}
-          - {std: 20, cxx: g++-10     , bt: Release, os: ubuntu-20.04, bitlinks: shared64 static32}
-          - {std: 11, cxx: g++-5      , bt: Debug  , os: ubuntu-20.04, bitlinks: shared64 static32}
-          - {std: 11, cxx: g++-5      , bt: Release, os: ubuntu-20.04, bitlinks: shared64 static32}
-          - {std: 11, cxx: g++-4.8    , bt: Debug,   os: ubuntu-18.04, bitlinks: shared64 static32}
-          - {std: 11, cxx: g++-4.8    , bt: Release, os: ubuntu-18.04, bitlinks: shared64 static32}
+          - {std: 11, cxx: g++-7  , bt: Debug  , bitlinks: shared64 static32}
+          - {std: 11, cxx: g++-7  , bt: Release, bitlinks: shared64 static32}
+          - {std: 20, cxx: g++-10 , bt: Debug  , bitlinks: shared64 static32}
+          - {std: 20, cxx: g++-10 , bt: Release, bitlinks: shared64 static32}
+          - {std: 11, cxx: g++-5  , bt: Debug  , bitlinks: shared64 static32}
+          - {std: 11, cxx: g++-5  , bt: Release, bitlinks: shared64 static32}
+          - {std: 11, cxx: g++-4.8, bt: Debug  , bitlinks: shared64 static32}
+          - {std: 11, cxx: g++-4.8, bt: Release, bitlinks: shared64 static32}
     env: {STD: "${{matrix.std}}", CXX_: "${{matrix.cxx}}", BT: "${{matrix.bt}}", BITLINKS: "${{matrix.bitlinks}}", VG: "${{matrix.vg}}", SAN: "${{matrix.san}}", LINT: "${{matrix.lint}}", OS: "${{matrix.os}}"}
     steps:
-      - {name: checkout, uses: actions/checkout@v4, with: {submodules: recursive}}
-      - {name: install requirements, run: source .github/reqs.sh && c4_install_test_requirements $OS}
+      - {name: checkout, uses: actions/checkout@v3, with: {submodules: recursive}}
+      - run: git config --system --add safe.directory '*'  # needed for running in the docker image. see https://github.com/actions/checkout/issues/1169
+      - run: c4core-install ${{matrix.cxx}}
       - {name: show info, run: source .github/setenv.sh && c4_show_info}
       - name: shared64-configure---------------------------------------------------
         run: source .github/setenv.sh && c4_cfg_test shared64
@@ -70,30 +71,74 @@ jobs:
       - {name: shared32-run, run: source .github/setenv.sh && c4_run_test shared32}
       - {name: shared32-pack, run: source .github/setenv.sh && c4_package shared32}
 
+  #----------------------------------------------------------------------------
   gcc_tabtokens:
-    name: tabtokens/${{matrix.cxx}}/canary/${{matrix.bt}}
-    if: |
-      (!contains(github.event.head_commit.message, 'skip all')) ||
-      (!contains(github.event.head_commit.message, 'skip gcc')) ||
-      contains(github.event.head_commit.message, 'only gcc')
+    name: gcc_canary/${{matrix.cxx}}/c++${{matrix.std}}/${{matrix.bt}}
     continue-on-error: true
-    runs-on: ${{matrix.os}}
+    if: always()  # https://stackoverflow.com/questions/62045967/github-actions-is-there-a-way-to-continue-on-error-while-still-getting-correct
+    runs-on: ubuntu-latest
+    container: ghcr.io/biojppm/c4core/ubuntu18.04:latest  # use the docker image
     strategy:
       fail-fast: false
       matrix:
         include:
-          - {std: 11, cxx: g++-7      , bt: Debug  , os: ubuntu-20.04, bitlinks: shared64 static32, cmkflags: "-DRYML_WITH_TAB_TOKENS=ON"}
-          - {std: 11, cxx: g++-7      , bt: Release, os: ubuntu-20.04, bitlinks: shared64 static32, cmkflags: "-DRYML_WITH_TAB_TOKENS=ON"}
-          - {std: 20, cxx: g++-10     , bt: Debug  , os: ubuntu-20.04, bitlinks: shared64 static32, cmkflags: "-DRYML_WITH_TAB_TOKENS=ON"}
-          - {std: 20, cxx: g++-10     , bt: Release, os: ubuntu-20.04, bitlinks: shared64 static32, cmkflags: "-DRYML_WITH_TAB_TOKENS=ON"}
-          - {std: 11, cxx: g++-5      , bt: Debug  , os: ubuntu-20.04, bitlinks: shared64 static32, cmkflags: "-DRYML_WITH_TAB_TOKENS=ON"}
-          - {std: 11, cxx: g++-5      , bt: Release, os: ubuntu-20.04, bitlinks: shared64 static32, cmkflags: "-DRYML_WITH_TAB_TOKENS=ON"}
+          - {std: 11, cxx: g++-7  , bt: Debug  , bitlinks: shared64 static32}
+          - {std: 11, cxx: g++-7  , bt: Release, bitlinks: shared64 static32}
+          - {std: 20, cxx: g++-10 , bt: Debug  , bitlinks: shared64 static32}
+          - {std: 20, cxx: g++-10 , bt: Release, bitlinks: shared64 static32}
+          - {std: 11, cxx: g++-5  , bt: Debug  , bitlinks: shared64 static32}
+          - {std: 11, cxx: g++-5  , bt: Release, bitlinks: shared64 static32}
+          - {std: 11, cxx: g++-4.8, bt: Debug  , bitlinks: shared64 static32}
+          - {std: 11, cxx: g++-4.8, bt: Release, bitlinks: shared64 static32}
     env: {STD: "${{matrix.std}}", CXX_: "${{matrix.cxx}}", BT: "${{matrix.bt}}", BITLINKS: "${{matrix.bitlinks}}",
-          CMAKE_FLAGS: "${{matrix.cmkflags}}",
+          CMAKE_FLAGS: "-DRYML_WITH_TAB_TOKENS=ON",
           VG: "${{matrix.vg}}", SAN: "${{matrix.san}}", LINT: "${{matrix.lint}}", OS: "${{matrix.os}}"}
     steps:
-      - {name: checkout, uses: actions/checkout@v4, with: {submodules: recursive}}
-      - {name: install requirements, run: source .github/reqs.sh && c4_install_test_requirements $OS}
+      - {name: checkout, uses: actions/checkout@v3, with: {submodules: recursive}}
+      - run: git config --system --add safe.directory '*'  # needed for running in the docker image. see https://github.com/actions/checkout/issues/1169
+      - run: c4core-install ${{matrix.cxx}}
+      - {name: show info, run: source .github/setenv.sh && c4_show_info}
+      - name: shared64-configure---------------------------------------------------
+        run: source .github/setenv.sh && c4_cfg_test shared64
+      - {name: shared64-build, run: source .github/setenv.sh && c4_build_test shared64}
+      - {name: shared64-run, run: source .github/setenv.sh && c4_run_test shared64}
+      - {name: shared64-pack, run: source .github/setenv.sh && c4_package shared64}
+      - name: static64-configure---------------------------------------------------
+        run: source .github/setenv.sh && c4_cfg_test static64
+      - {name: static64-build, run: source .github/setenv.sh && c4_build_test static64}
+      - {name: static64-run, run: source .github/setenv.sh && c4_run_test static64}
+      - {name: static64-pack, run: source .github/setenv.sh && c4_package static64}
+      - name: static32-configure---------------------------------------------------
+        run: source .github/setenv.sh && c4_cfg_test static32
+      - {name: static32-build, run: source .github/setenv.sh && c4_build_test static32}
+      - {name: static32-run, run: source .github/setenv.sh && c4_run_test static32}
+      - {name: static32-pack, run: source .github/setenv.sh && c4_package static32}
+      - name: shared32-configure---------------------------------------------------
+        run: source .github/setenv.sh && c4_cfg_test shared32
+      - {name: shared32-build, run: source .github/setenv.sh && c4_build_test shared32}
+      - {name: shared32-run, run: source .github/setenv.sh && c4_run_test shared32}
+      - {name: shared32-pack, run: source .github/setenv.sh && c4_package shared32}
+
+  #----------------------------------------------------------------------------
+  gcc_noexceptions:
+    name: gcc_noexceptions/${{matrix.cxx}}/c++${{matrix.std}}/${{matrix.bt}}
+    continue-on-error: true
+    if: always()  # https://stackoverflow.com/questions/62045967/github-actions-is-there-a-way-to-continue-on-error-while-still-getting-correct
+    runs-on: ubuntu-latest
+    container: ghcr.io/biojppm/c4core/ubuntu18.04:latest  # use the docker image
+    strategy:
+      fail-fast: false
+      matrix:
+        include:
+          - {std: 11, cxx: g++-7      , bt: Debug  , bitlinks: shared64 static32}
+          - {std: 11, cxx: g++-7      , bt: Release, bitlinks: shared64 static32}
+          - {std: 20, cxx: g++-10     , bt: Debug  , bitlinks: shared64 static32}
+          - {std: 20, cxx: g++-10     , bt: Release, bitlinks: shared64 static32}
+    env: {CXXFLAGS: "-fno-exceptions -fno-rtti", STD: "${{matrix.std}}", CXX_: "${{matrix.cxx}}", BT: "${{matrix.bt}}", BITLINKS: "${{matrix.bitlinks}}", VG: "${{matrix.vg}}", SAN: "${{matrix.san}}", LINT: "${{matrix.lint}}", OS: "${{matrix.os}}"}
+    steps:
+      - {name: checkout, uses: actions/checkout@v3, with: {submodules: recursive}}
+      - run: git config --system --add safe.directory '*'  # needed for running in the docker image. see https://github.com/actions/checkout/issues/1169
+      - run: c4core-install ${{matrix.cxx}}
       - {name: show info, run: source .github/setenv.sh && c4_show_info}
       - name: shared64-configure---------------------------------------------------
         run: source .github/setenv.sh && c4_cfg_test shared64
@@ -118,33 +163,48 @@ jobs:
 
   #----------------------------------------------------------------------------
   gcc_extended:
-    name: ${{matrix.cxx}}/extended/${{matrix.bt}}
-    needs: gcc_canary
-    if: |
-      (!contains(github.event.head_commit.message, 'skip all')) ||
-      (!contains(github.event.head_commit.message, 'skip gcc')) ||
-      contains(github.event.head_commit.message, 'only gcc')
+    name: gcc_extended/${{matrix.cxx}}/c++${{matrix.std}}/${{matrix.bt}}/vg${{matrix.vg}}
     continue-on-error: true
-    runs-on: ${{matrix.os}}
+    if: always()  # https://stackoverflow.com/questions/62045967/github-actions-is-there-a-way-to-continue-on-error-while-still-getting-correct
+    runs-on: ubuntu-latest
+    container: ghcr.io/biojppm/c4core/${{matrix.img}}:latest  # use the docker image
     strategy:
       fail-fast: false
       matrix:
         include:
           #                                     VALGRIND
-          - {std: 11, cxx: g++-10, bt: Debug  , vg: ON, os: ubuntu-20.04}
-          - {std: 11, cxx: g++-10, bt: Release, vg: ON, os: ubuntu-20.04}
-          - {std: 14, cxx: g++-10, bt: Debug  , vg: ON, os: ubuntu-20.04}
-          - {std: 14, cxx: g++-10, bt: Release, vg: ON, os: ubuntu-20.04}
-          - {std: 17, cxx: g++-10, bt: Debug  , vg: ON, os: ubuntu-20.04}
-          - {std: 17, cxx: g++-10, bt: Release, vg: ON, os: ubuntu-20.04}
-          - {std: 20, cxx: g++-10, bt: Debug  , vg: ON, os: ubuntu-20.04}
-          - {std: 20, cxx: g++-10, bt: Release, vg: ON, os: ubuntu-20.04}
-          - {std: 11, cxx: g++-4.8, bt: Debug,   vg: ON, os: ubuntu-18.04}
-          - {std: 11, cxx: g++-4.8, bt: Release, vg: ON, os: ubuntu-18.04}
+          - {std: 11, cxx: g++-12, bt: Debug  , vg: ON, img: ubuntu22.04}
+          - {std: 11, cxx: g++-12, bt: Release, vg: ON, img: ubuntu22.04}
+          - {std: 14, cxx: g++-12, bt: Debug  , vg: ON, img: ubuntu22.04}
+          - {std: 14, cxx: g++-12, bt: Release, vg: ON, img: ubuntu22.04}
+          - {std: 17, cxx: g++-12, bt: Debug  , vg: ON, img: ubuntu22.04}
+          - {std: 17, cxx: g++-12, bt: Release, vg: ON, img: ubuntu22.04}
+          - {std: 20, cxx: g++-12, bt: Debug  , vg: ON, img: ubuntu22.04}
+          - {std: 20, cxx: g++-12, bt: Release, vg: ON, img: ubuntu22.04}
+          #
+          - {std: 11, cxx: g++-11 , bt: Debug  , img: ubuntu18.04}
+          - {std: 11, cxx: g++-11 , bt: Release, img: ubuntu18.04}
+          - {std: 11, cxx: g++-10 , bt: Debug  , img: ubuntu18.04}
+          - {std: 11, cxx: g++-10 , bt: Release, img: ubuntu18.04}
+          - {std: 11, cxx: g++-9  , bt: Debug  , img: ubuntu18.04}
+          - {std: 11, cxx: g++-9  , bt: Release, img: ubuntu18.04}
+          - {std: 11, cxx: g++-8  , bt: Debug  , img: ubuntu18.04}
+          - {std: 11, cxx: g++-8  , bt: Release, img: ubuntu18.04}
+          - {std: 11, cxx: g++-7  , bt: Debug  , img: ubuntu18.04}
+          - {std: 11, cxx: g++-7  , bt: Release, img: ubuntu18.04}
+          - {std: 11, cxx: g++-6  , bt: Debug  , img: ubuntu18.04}
+          - {std: 11, cxx: g++-6  , bt: Release, img: ubuntu18.04}
+          - {std: 11, cxx: g++-5  , bt: Debug  , img: ubuntu18.04}
+          - {std: 11, cxx: g++-5  , bt: Release, img: ubuntu18.04}
+          - {std: 11, cxx: g++-4.9, bt: Debug  , img: ubuntu18.04}
+          - {std: 11, cxx: g++-4.9, bt: Release, img: ubuntu18.04}
+          - {std: 11, cxx: g++-4.8, bt: Debug  , img: ubuntu18.04}
+          - {std: 11, cxx: g++-4.8, bt: Release, img: ubuntu18.04}
     env: {STD: "${{matrix.std}}", CXX_: "${{matrix.cxx}}", BT: "${{matrix.bt}}", BITLINKS: "${{matrix.bitlinks}}", VG: "${{matrix.vg}}", SAN: "${{matrix.san}}", LINT: "${{matrix.lint}}", OS: "${{matrix.os}}"}
     steps:
-      - {name: checkout, uses: actions/checkout@v4, with: {submodules: recursive}}
-      - {name: install requirements, run: source .github/reqs.sh && c4_install_test_requirements $OS}
+      - {name: checkout, uses: actions/checkout@v3, with: {submodules: recursive}}
+      - run: git config --system --add safe.directory '*'  # needed for running in the docker image. see https://github.com/actions/checkout/issues/1169
+      - run: c4core-install ${{matrix.cxx}}
       - {name: show info, run: source .github/setenv.sh && c4_show_info}
       - name: shared64-configure---------------------------------------------------
         run: source .github/setenv.sh && c4_cfg_test shared64
@@ -166,3 +226,89 @@ jobs:
       - {name: shared32-build, run: source .github/setenv.sh && c4_build_test shared32}
       - {name: shared32-run, run: source .github/setenv.sh && c4_run_test shared32}
       - {name: shared32-pack, run: source .github/setenv.sh && c4_package shared32}
+
+  #----------------------------------------------------------------------------
+  gccsan:
+    name: gccsan/${{matrix.cxx}}/c++${{matrix.std}}/${{matrix.bt}}/vg${{matrix.vg}}
+    continue-on-error: true
+    if: always()  # https://stackoverflow.com/questions/62045967/github-actions-is-there-a-way-to-continue-on-error-while-still-getting-correct
+    runs-on: ubuntu-latest
+    container: ghcr.io/biojppm/c4core/ubuntu22.04:latest  # use the docker image
+    strategy:
+      fail-fast: false
+      matrix:
+        include:
+          - {std: 11, cxx: g++-12, bt: asan, bitlinks: static64}
+          - {std: 14, cxx: g++-12, bt: asan, bitlinks: static64}
+          - {std: 17, cxx: g++-12, bt: asan, bitlinks: static64}
+          - {std: 20, cxx: g++-12, bt: asan, bitlinks: static64}
+          - {std: 11, cxx: g++-12, bt: lsan, bitlinks: static64}
+          - {std: 14, cxx: g++-12, bt: lsan, bitlinks: static64}
+          - {std: 17, cxx: g++-12, bt: lsan, bitlinks: static64}
+          - {std: 20, cxx: g++-12, bt: lsan, bitlinks: static64}
+          - {std: 11, cxx: g++-12, bt: tsan, bitlinks: static64}
+          - {std: 14, cxx: g++-12, bt: tsan, bitlinks: static64}
+          - {std: 17, cxx: g++-12, bt: tsan, bitlinks: static64}
+          - {std: 20, cxx: g++-12, bt: tsan, bitlinks: static64}
+          - {std: 11, cxx: g++-12, bt: ubsan, bitlinks: static64}
+          - {std: 14, cxx: g++-12, bt: ubsan, bitlinks: static64}
+          - {std: 17, cxx: g++-12, bt: ubsan, bitlinks: static64}
+          - {std: 20, cxx: g++-12, bt: ubsan, bitlinks: static64}
+    env: {STD: "${{matrix.std}}", CXX_: "${{matrix.cxx}}", BT: "${{matrix.bt}}", BITLINKS: "${{matrix.bitlinks}}", VG: "${{matrix.vg}}", SAN: "${{matrix.san}}", LINT: "${{matrix.lint}}", OS: "${{matrix.os}}"}
+    steps:
+      - {name: checkout, uses: actions/checkout@v3, with: {submodules: recursive}}
+      - run: git config --system --add safe.directory '*'  # needed for running in the docker image. see https://github.com/actions/checkout/issues/1169
+      - run: c4core-install ${{matrix.cxx}}
+      - {name: show info, run: source .github/setenv.sh && c4_show_info}
+      - name: shared64-configure---------------------------------------------------
+        run: source .github/setenv.sh && c4_cfg_test shared64
+      - {name: shared64-build, run: source .github/setenv.sh && c4_build_test shared64}
+      - {name: shared64-run, run: source .github/setenv.sh && c4_run_test shared64}
+      - {name: shared64-pack, run: source .github/setenv.sh && c4_package shared64}
+      - name: static64-configure---------------------------------------------------
+        run: source .github/setenv.sh && c4_cfg_test static64
+      - {name: static64-build, run: source .github/setenv.sh && c4_build_test static64}
+      - {name: static64-run, run: source .github/setenv.sh && c4_run_test static64}
+      - {name: static64-pack, run: source .github/setenv.sh && c4_package static64}
+      - name: static32-configure---------------------------------------------------
+        run: source .github/setenv.sh && c4_cfg_test static32
+      - {name: static32-build, run: source .github/setenv.sh && c4_build_test static32}
+      - {name: static32-run, run: source .github/setenv.sh && c4_run_test static32}
+      - {name: static32-pack, run: source .github/setenv.sh && c4_package static32}
+      - name: shared32-configure---------------------------------------------------
+        run: source .github/setenv.sh && c4_cfg_test shared32
+      - {name: shared32-build, run: source .github/setenv.sh && c4_build_test shared32}
+      - {name: shared32-run, run: source .github/setenv.sh && c4_run_test shared32}
+      - {name: shared32-pack, run: source .github/setenv.sh && c4_package shared32}
+
+  #----------------------------------------------------------------------------
+  arm:
+    continue-on-error: true
+    if: always()  # https://stackoverflow.com/questions/62045967/github-actions-is-there-a-way-to-continue-on-error-while-still-getting-correct 
+    runs-on: ubuntu-latest
+    container: ghcr.io/biojppm/c4core/ubuntu18.04:latest  # use the docker image
+    strategy:
+      fail-fast: false
+      matrix:
+        include:
+          # these jobs take much longer, so run only one bitlink pair per job to profit from parallelism
+          - {std: 11, bt: Debug  , toolchain: ext/c4core/cmake/Toolchain-Arm-ubuntu.cmake, cxx: arm-linux-gnueabihf-gcc, os: ubuntu-20.04}
+          - {std: 11, bt: Release, toolchain: ext/c4core/cmake/Toolchain-Arm-ubuntu.cmake, cxx: arm-linux-gnueabihf-gcc, os: ubuntu-20.04}
+          - {std: 14, bt: Debug  , toolchain: ext/c4core/cmake/Toolchain-Arm-ubuntu.cmake, cxx: arm-linux-gnueabihf-gcc, os: ubuntu-20.04}
+          - {std: 14, bt: Release, toolchain: ext/c4core/cmake/Toolchain-Arm-ubuntu.cmake, cxx: arm-linux-gnueabihf-gcc, os: ubuntu-20.04}
+          - {std: 17, bt: Debug  , toolchain: ext/c4core/cmake/Toolchain-Arm-ubuntu.cmake, cxx: arm-linux-gnueabihf-gcc, os: ubuntu-20.04}
+          - {std: 17, bt: Release, toolchain: ext/c4core/cmake/Toolchain-Arm-ubuntu.cmake, cxx: arm-linux-gnueabihf-gcc, os: ubuntu-20.04}
+    env: {TOOLCHAIN: "${{matrix.toolchain}}", STD: "${{matrix.std}}", CXX_: "${{matrix.cxx}}", BT: "${{matrix.bt}}", BITLINKS: "${{matrix.bitlinks}}", VG: "${{matrix.vg}}", SAN: "${{matrix.san}}", LINT: "${{matrix.lint}}", OS: "${{matrix.os}}"}
+    steps:
+      - {name: checkout, uses: actions/checkout@v3, with: {submodules: recursive}}
+      - run: git config --system --add safe.directory '*'  # needed for running in the docker image. see https://github.com/actions/checkout/issues/1169
+      - name: install gcc-arm-linux-gnueabihf
+        run: |
+          c4core-install arm-linux-gnueabihf
+          c4core-install qemu
+      - {name: show info, run: source .github/setenv.sh && c4_show_info}
+      - name: configure---------------------------------------------------
+        run: source .github/setenv.sh && c4_cfg_test arm
+      - {name: build, run: source .github/setenv.sh && c4_build_test arm}
+      - {name: run, run: source .github/setenv.sh && c4_run_test arm}
+      - {name: pack, run: source .github/setenv.sh && c4_package arm}
diff --git a/.github/workflows/install.yml b/.github/workflows/install.yml
index 58ad9c1b8..ea9e19f5f 100644
--- a/.github/workflows/install.yml
+++ b/.github/workflows/install.yml
@@ -71,7 +71,7 @@ jobs:
       IDIR: "install/${{matrix.name}}-${{matrix.bt}}"
       PDIR:  "prefix/${{matrix.name}}-${{matrix.bt}}"
     steps:
-      - {name: checkout, uses: actions/checkout@v4, with: {submodules: recursive}}
+      - {name: checkout, uses: actions/checkout@v3, with: {submodules: recursive}}
       - {name: install requirements, run: source .github/reqs.sh && c4_install_test_requirements $OS}
       - {name: show info, run: source .github/setenv.sh && c4_show_info}
       - name: Install python 3.9
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 888a45d15..9543b9f7f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -22,11 +22,15 @@ option(RYML_BUILD_TOOLS "Build tools" OFF)
 option(RYML_BUILD_API "Enable API generation (python, etc)" OFF)
 option(RYML_DBG "Enable (very verbose) ryml debug prints." OFF)
 
+option(RYML_INSTALL "Enable install target" ON)
+
 
 #-------------------------------------------------------
 
 c4_require_subproject(c4core INCORPORATE
-    SUBDIRECTORY ${RYML_EXT_DIR}/c4core)
+    SUBDIRECTORY ${RYML_EXT_DIR}/c4core
+    OVERRIDE C4CORE_INSTALL ${RYML_INSTALL}
+)
 
 c4_add_library(ryml
     SOURCES
@@ -87,9 +91,11 @@ endif()
 
 #-------------------------------------------------------
 
-c4_install_target(ryml)
-c4_install_exports(DEPENDENCIES c4core)
-c4_pack_project()
+if(RYML_INSTALL)
+    c4_install_target(ryml)
+    c4_install_exports(DEPENDENCIES c4core)
+    c4_pack_project()
+endif()
 
 
 #-------------------------------------------------------
diff --git a/bm/CMakeLists.txt b/bm/CMakeLists.txt
index d5151107b..ff1854d67 100644
--- a/bm/CMakeLists.txt
+++ b/bm/CMakeLists.txt
@@ -19,7 +19,7 @@ c4_require_subproject(libyaml REMOTE
 if(NOT WIN32)
     c4_require_subproject(libfyaml REMOTE
         GIT_REPOSITORY https://github.com/pantoniou/libfyaml
-        GIT_TAG fd35c3a5fa58ff2effd8207f2185852c3a837d76 #v0.7.11
+        GIT_TAG v0.9 #fd35c3a5fa58ff2effd8207f2185852c3a837d76 #v0.7.11
         SET_FOLDER_TARGETS ext fyaml
         )
 endif()
diff --git a/doc/sphinx_other_languages.rst b/doc/sphinx_other_languages.rst
index f1768849b..3c0cefdf9 100644
--- a/doc/sphinx_other_languages.rst
+++ b/doc/sphinx_other_languages.rst
@@ -14,6 +14,22 @@ JavaScript
 A JavaScript+WebAssembly port is available, compiled through
 `emscripten <https://emscripten.org/>`__.
 
+Here's a quick example on how to configure, compile and run the tests
+using `emscripten`:
+
+.. code:: bash
+
+   cd rapidyaml
+   emcmake cmake -S . -B build/emscripten \
+      -D RYML_DEV=ON \
+      -D RYML_BUILD_TESTS=ON \
+      -D RYML_BUILD_BENCHMARKS=OFF \
+      -D RYML_TEST_SUITE=OFF \
+      -D CMAKE_BUILD_TYPE=Release \
+      -D CMAKE_CXX_FLAGS='-s DISABLE_EXCEPTION_CATCHING=0'
+   cmake --build build/emscripten --target ryml-test-run -j
+
+
 
 Python
 ------
diff --git a/ext/c4core b/ext/c4core
index 1cd92b7f3..9d57fbf3d 160000
--- a/ext/c4core
+++ b/ext/c4core
@@ -1,1 +1,1 @@
-Subproject commit 1cd92b7f3372e020eca5a1bbb57f5e7059eb8cdd
+Subproject commit 9d57fbf3d5dd0fae69e186ad5c5b1e0bf6386b76
diff --git a/samples/quickstart.cpp b/samples/quickstart.cpp
index f20e7077e..b0ed03987 100644
--- a/samples/quickstart.cpp
+++ b/samples/quickstart.cpp
@@ -24,6 +24,11 @@
 #include <vector>
 #include <array>
 #include <map>
+#ifdef C4_EXCEPTIONS
+#include <stdexcept>
+#else
+#include <csetjmp>
+#endif
 
 
 //-----------------------------------------------------------------------------
@@ -192,6 +197,9 @@ namespace sample {
  * @{ */
 
 
+bool report_check(int line, const char *predicate, bool result);
+
+
 // GCC 4.8 has a problem with the CHECK() macro
 #ifndef _DOXYGEN_
 #if (defined(__GNUC__) && (__GNUC__ == 4 && __GNUC_MINOR__ >= 8))
@@ -220,8 +228,6 @@ struct CheckPredicate
 #endif
 
 
-bool report_check(int line, const char *predicate, bool result);
-
 // helper functions for sample_parse_file()
 template<class CharContainer> CharContainer file_get_contents(const char *filename);
 template<class CharContainer> size_t        file_get_contents(const char *filename, CharContainer *v);
@@ -3049,28 +3055,12 @@ void sample_formatting()
         // FTOA_HEXA: AKA %a (hexadecimal formatting of floats)
         CHECK("0x1.e8480p+19" == cat_sub(buf, fmt::real(1000000.000000000, 5, FTOA_HEXA)));   // AKA %a
         CHECK("0x1.2d53ap+20" == cat_sub(buf, fmt::real(1234234.234234234, 5, FTOA_HEXA)));   // AKA %a
-        // Earlier versions of emscripten's sprintf() (from MUSL) do not
-        // respect some precision values when printing in hexadecimal
-        // format.
-        //
-        // @see https://github.com/biojppm/c4core/pull/52
-        #if defined(__EMSCRIPTEN__) && __EMSCRIPTEN_major__ < 3
-        #define _c4emscripten_alt(alt1, alt2) alt2
-        #else
-        #define _c4emscripten_alt(alt1, alt2) alt1
-        #endif
-        CHECK(_c4emscripten_alt("0x1.2d54p+20", "0x1.2d538p+20")
-                              == cat_sub(buf, fmt::real(1234234.234234234, 4, FTOA_HEXA)));   // AKA %a
-        CHECK("0x1.2d5p+20"   == cat_sub(buf, fmt::real(1234234.234234234, 3, FTOA_HEXA)));   // AKA %a
-        CHECK(_c4emscripten_alt("0x1.2dp+20", "0x1.2d8p+20")
-                              == cat_sub(buf, fmt::real(1234234.234234234, 2, FTOA_HEXA)));   // AKA %a
-        #undef _c4emscripten_alt
 
         // --------------------------------------------------------------
         // fmt::raw(): dump data in machine format (respecting alignment)
         // --------------------------------------------------------------
         {
-            C4_SUPPRESS_WARNING_CLANG_WITH_PUSH("-Wcast-align")  // we're casting the values directly, so alignment is strictly respected.
+            C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wcast-align")  // we're casting the values directly, so alignment is strictly respected.
             const uint32_t payload[] = {10, 20, 30, 40, UINT32_C(0xdeadbeef)};
             // (package payload as a substr, for comparison only)
             csubstr expected = csubstr((const char *)payload, sizeof(payload));
@@ -3103,7 +3093,7 @@ void sample_formatting()
                 result = *(uint32_t*)reader.buf;
                 CHECK(result == value); // roundtrip completed successfully
             }
-            C4_SUPPRESS_WARNING_CLANG_POP
+            C4_SUPPRESS_WARNING_GCC_CLANG_POP
         }
 
         // -------------------------
@@ -4580,18 +4570,22 @@ d: 3
 
 //-----------------------------------------------------------------------------
 
-// To avoid imposing a particular type of error handling, ryml accepts
-// custom error handlers. This enables users to use exceptions, or
-// plain calls to abort(), as they see fit.
+// To avoid imposing a particular type of error handling, ryml uses an
+// error handler callback. This enables users to use exceptions, or
+// setjmp()/longjmp(), or plain calls to abort(), as they see fit.
 //
 // However, it is important to note that the error callback must never
 // return to the caller! Otherwise, an infinite loop or program crash
-// may occur.
+// will likely occur.
 //
-// The default error handler calls std::abort(). You can use the cmake
-// option and the macro RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS to have
-// the default error handler throw a std::runtime_error instead.
-
+// For this reason, to recover from an error when exceptions are disabled,
+// then a non-local jump must be performed using setjmp()/longjmp().
+// The code below demonstrates both flows.
+//
+// ryml provides a default error handler, which calls
+// std::abort(). You can use the cmake option and the macro
+// RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS to have the default error
+// handler throw a std::runtime_error instead.
 
 /** demonstrates how to set a custom error handler for ryml */
 void sample_error_handler()
@@ -4636,7 +4630,7 @@ void sample_error_handler()
 struct GlobalAllocatorExample
 {
     std::vector<char> memory_pool = std::vector<char>(10u * 1024u); // 10KB
-    size_t num_allocs = 0, alloc_size = 0;
+    size_t num_allocs = 0, alloc_size = 0, corr_size = 0;
     size_t num_deallocs = 0, dealloc_size = 0;
 
     void *allocate(size_t len)
@@ -4644,11 +4638,20 @@ struct GlobalAllocatorExample
         void *ptr = &memory_pool[alloc_size];
         alloc_size += len;
         ++num_allocs;
-        if(C4_UNLIKELY(alloc_size > memory_pool.size()))
+        // ensure the ptr is aligned
+        uintptr_t uptr = (uintptr_t)ptr;
+        const uintptr_t align = alignof(max_align_t);
+        if (uptr % align)
         {
-            std::cerr << "out of memory! requested=" << alloc_size << " vs " << memory_pool.size() << " available" << std::endl;
-            std::abort();
+            uintptr_t prev = uptr - (uptr % align);
+            uintptr_t next = prev + align;
+            uintptr_t corr = next - uptr;
+            ptr = (void*)(((char*)ptr) + corr);
+            corr_size += corr;
         }
+        C4_CHECK_MSG(alloc_size + corr_size <= memory_pool.size(),
+                     "out of memory! requested=%zu+%zu available=%zu\n",
+                     alloc_size, corr_size, memory_pool.size());
         return ptr;
     }
 
@@ -5089,13 +5092,26 @@ int report_checks()
 
 // methods for the example error handler
 
+// this macro selects code for when exceptions are enabled/disabled
+C4_IF_EXCEPTIONS_( /*nothing for exceptions*/ ,
+                   /*environment for setjmp*/
+                   static std::jmp_buf s_jmp_env;
+                   static std::string s_jmp_msg;
+                   )
+
 // checking
 template<class Fn>
 C4_NODISCARD bool ErrorHandlerExample::check_error_occurs(Fn &&fn) const
 {
     bool expected_error_occurred = false;
-    try { fn(); }
-    catch(std::exception const&) { expected_error_occurred = true; }
+    C4_IF_EXCEPTIONS_(try, if(setjmp(s_jmp_env) == 0)) // selectively picks based on availability of exceptions
+    {
+        fn();
+    }
+    C4_IF_EXCEPTIONS_(catch(...), else)
+    {
+        expected_error_occurred = true;
+    }
     return expected_error_occurred;
 }
 template<class Fn>
@@ -5121,7 +5137,15 @@ C4_NORETURN void ErrorHandlerExample::on_error(const char* msg, size_t len, ryml
     std::string full_msg = ryml::formatrs<std::string>(
         "{}:{}:{} ({}B): ERROR: {}",
         loc.name, loc.line, loc.col, loc.offset, ryml::csubstr(msg, len));
-    throw std::runtime_error(full_msg);
+    C4_IF_EXCEPTIONS(
+        // this will execute if exceptions are enabled.
+        throw std::runtime_error(full_msg);
+        ,
+        // this will execute if exceptions are disabled. It will
+        // jump to the function calling the corresponding setjmp().
+        s_jmp_msg = full_msg;
+        std::longjmp(s_jmp_env, 1);
+    );
 }
 
 /** a helper to create the Callbacks object with the custom error handler */
diff --git a/src/c4/yml/common.hpp b/src/c4/yml/common.hpp
index d08281ea4..f9c7b9abb 100644
--- a/src/c4/yml/common.hpp
+++ b/src/c4/yml/common.hpp
@@ -415,8 +415,11 @@ struct _SubstrWriter
     void append(csubstr s)
     {
         C4_ASSERT(!s.overlaps(buf));
-        if(pos + s.len <= buf.len)
+        if(s.len && pos + s.len <= buf.len)
+        {
+            C4_ASSERT(s.str);
             memcpy(buf.str + pos, s.str, s.len);
+        }
         pos += s.len;
     }
     void append(char c)
@@ -427,7 +430,7 @@ struct _SubstrWriter
     }
     void append_n(char c, size_t numtimes)
     {
-        if(pos + numtimes < buf.len)
+        if(numtimes && pos + numtimes < buf.len)
             memset(buf.str + pos, c, numtimes);
         pos += numtimes;
     }
diff --git a/src/c4/yml/detail/parser_dbg.hpp b/src/c4/yml/detail/parser_dbg.hpp
index 457f1700d..0f5e69361 100644
--- a/src/c4/yml/detail/parser_dbg.hpp
+++ b/src/c4/yml/detail/parser_dbg.hpp
@@ -86,25 +86,25 @@ inline void __c4presc(const char *s, size_t len)
     {
         switch(s[i])
         {
-        case '\n'  : fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('n'); putchar('\n'); prev = i+1; break;
-        case '\t'  : fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('t'); prev = i+1; break;
-        case '\0'  : fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('0'); prev = i+1; break;
-        case '\r'  : fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('r'); prev = i+1; break;
-        case '\f'  : fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('f'); prev = i+1; break;
-        case '\b'  : fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('b'); prev = i+1; break;
-        case '\v'  : fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('v'); prev = i+1; break;
-        case '\a'  : fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('a'); prev = i+1; break;
-        case '\x1b': fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('e'); prev = i+1; break;
+        case '\n'  : if(i > prev) { fwrite(s+prev, 1, i-prev, stdout); } putchar('\\'); putchar('n'); putchar('\n'); prev = i+1; break;
+        case '\t'  : if(i > prev) { fwrite(s+prev, 1, i-prev, stdout); } putchar('\\'); putchar('t'); prev = i+1; break;
+        case '\0'  : if(i > prev) { fwrite(s+prev, 1, i-prev, stdout); } putchar('\\'); putchar('0'); prev = i+1; break;
+        case '\r'  : if(i > prev) { fwrite(s+prev, 1, i-prev, stdout); } putchar('\\'); putchar('r'); prev = i+1; break;
+        case '\f'  : if(i > prev) { fwrite(s+prev, 1, i-prev, stdout); } putchar('\\'); putchar('f'); prev = i+1; break;
+        case '\b'  : if(i > prev) { fwrite(s+prev, 1, i-prev, stdout); } putchar('\\'); putchar('b'); prev = i+1; break;
+        case '\v'  : if(i > prev) { fwrite(s+prev, 1, i-prev, stdout); } putchar('\\'); putchar('v'); prev = i+1; break;
+        case '\a'  : if(i > prev) { fwrite(s+prev, 1, i-prev, stdout); } putchar('\\'); putchar('a'); prev = i+1; break;
+        case '\x1b': if(i > prev) { fwrite(s+prev, 1, i-prev, stdout); } putchar('\\'); putchar('e'); prev = i+1; break;
         case -0x3e/*0xc2u*/:
             if(i+1 < len)
             {
                 if(s[i+1] == -0x60/*0xa0u*/)
                 {
-                    fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('_'); prev = i+2; ++i;
+                    if(i > prev) { fwrite(s+prev, 1, i-prev, stdout); } putchar('\\'); putchar('_'); prev = i+2; ++i;
                 }
                 else if(s[i+1] == -0x7b/*0x85u*/)
                 {
-                    fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('N'); prev = i+2; ++i;
+                    if(i > prev) { fwrite(s+prev, 1, i-prev, stdout); } putchar('\\'); putchar('N'); prev = i+2; ++i;
                 }
                 break;
             }
@@ -113,17 +113,18 @@ inline void __c4presc(const char *s, size_t len)
             {
                 if(s[i+2] == -0x58/*0xa8u*/)
                 {
-                    fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('L'); prev = i+3; i += 2;
+                    if(i > prev) { fwrite(s+prev, 1, i-prev, stdout); } putchar('\\'); putchar('L'); prev = i+3; i += 2;
                 }
                 else if(s[i+2] == -0x57/*0xa9u*/)
                 {
-                    fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('P'); prev = i+3; i += 2;
+                    if(i > prev) { fwrite(s+prev, 1, i-prev, stdout); } putchar('\\'); putchar('P'); prev = i+3; i += 2;
                 }
                 break;
             }
         }
     }
-    fwrite(s + prev, 1, len - prev, stdout);
+    if(len > prev)
+        fwrite(s + prev, 1, len - prev, stdout);
 }
 
 #pragma clang diagnostic pop
diff --git a/tools/yaml_events.cpp b/tools/yaml_events.cpp
index 75b3cfac8..5da368a18 100644
--- a/tools/yaml_events.cpp
+++ b/tools/yaml_events.cpp
@@ -7,7 +7,13 @@
 #include <test_suite/test_suite_events.hpp>
 #include <c4/fs/fs.hpp>
 #include <cstdio>
+#ifdef C4_EXCEPTIONS
 #include <stdexcept>
+#else
+#include <csetjmp>
+std::jmp_buf jmp_env = {};
+c4::csubstr jmp_msg = {};
+#endif
 
 C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wold-style-cast")
 
@@ -30,11 +36,18 @@ int main(int argc, const char *argv[])
     pfn_error error = [](const char *msg, size_t msg_len, Location location, void *)
     {
         report_error(msg, msg_len, location, stderr);
-        throw std::runtime_error({msg, msg_len});
-        C4_UNREACHABLE_AFTER_ERR();
+        C4_IF_EXCEPTIONS(
+            throw std::runtime_error({msg, msg_len});
+            C4_UNREACHABLE_AFTER_ERR();
+            ,
+            jmp_msg.assign(msg, msg_len);
+            std::longjmp(jmp_env, 1);
+        );
     };
     callbacks.m_error = error;
-    try {
+    set_callbacks(callbacks);
+    C4_IF_EXCEPTIONS_(try, if(setjmp(jmp_env) == 0))
+    {
         Tree tree(callbacks);
         csubstr filename = to_csubstr(argv[1]);
         std::string evt, src = load_file(filename);
@@ -43,7 +56,7 @@ int main(int argc, const char *argv[])
         emit_events(&evt, tree);
         std::fwrite(evt.data(), 1, evt.size(), stdout);
     }
-    catch(std::exception const&)
+    C4_IF_EXCEPTIONS_(catch(std::exception const&), else)
     {
         return 1;
     }


Test patch:
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index a7811a7dd..d662cb93a 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -109,21 +109,23 @@ ryml_add_test_case_group(github_issues)
 #-------------------------------------------------------------------------
 # test the tools as well
 
-option(RYML_TEST_TOOLS "Enable tests for the tools. Requires file system access." ON)
+if(NOT EMSCRIPTEN)
+    option(RYML_TEST_TOOLS "Enable tests for the tools. Requires file system access." ON)
+endif()
 if(RYML_TEST_TOOLS)
     if(NOT RYML_BUILD_TOOLS)
         add_subdirectory(../tools tools)
     endif()
     add_dependencies(ryml-test-build ryml-parse-emit)
     add_dependencies(ryml-test-build ryml-yaml-events)
-
+    #
     # parse & emit
     if(NOT EXISTS ${CMAKE_CURRENT_LIST_DIR}/../bm/cases/appveyor.yml)
         c4_err("could not find test file")
     endif()
     ryml_get_target_exe(ryml-parse-emit RYML_TGT_PARSE_EMIT)
     add_test(NAME ryml-test-tool-parse_emit COMMAND ${RYML_TGT_PARSE_EMIT} ${CMAKE_CURRENT_LIST_DIR}/../bm/cases/appveyor.yml)
-
+    #
     # events emitter
     function(ryml_create_file name contents fileout)
         set(filename ${CMAKE_CURRENT_BINARY_DIR}/${name})
diff --git a/test/test_callbacks.cpp b/test/test_callbacks.cpp
index 714f3d1ad..3c522eb5b 100644
--- a/test/test_callbacks.cpp
+++ b/test/test_callbacks.cpp
@@ -3,6 +3,7 @@
 #include "c4/yml/common.hpp"
 #endif
 #include <stdexcept>
+#include <csetjmp>
 
 
 namespace c4 {
@@ -18,11 +19,21 @@ Location stored_location;
 void * stored_mem;
 size_t stored_length;
 
+C4_IF_EXCEPTIONS_(
+    ,
+    std::jmp_buf s_jmp_env_expect_error = {};
+    )
+
 C4_NORETURN void test_error_impl(const char* msg, size_t length, Location loc, void * /*user_data*/)
 {
     stored_msg = std::string(msg, length);
     stored_location = loc;
-    throw std::runtime_error(stored_msg);
+    C4_IF_EXCEPTIONS(
+        throw std::runtime_error(stored_msg);
+        ,
+        std::longjmp(s_jmp_env_expect_error, 1);
+    );
+    C4_UNREACHABLE_AFTER_ERR();
 }
 
 void* test_allocate_impl(size_t length, void * /*hint*/, void * /*user_data*/)
@@ -222,11 +233,13 @@ TEST(Callbacks, ne)
 
 TEST(Callbacks, cmp_user_data)
 {
+    #ifndef C4_UBSAN
     Callbacks before = get_callbacks();
     Callbacks cp = before;
     EXPECT_EQ(cp, before);
-    cp.m_user_data = (void*)(((char*)before.m_user_data) + 100u);
+    cp.m_user_data = (void*)(((char*)before.m_user_data) + 100u); // ubsan: runtime error: applying non-zero offset 100 to null pointer
     EXPECT_NE(cp, before);
+    #endif
 }
 
 TEST(Callbacks, cmp_allocate)
@@ -287,10 +300,12 @@ TEST(error, basic)
     set_callbacks(cb);
     // message
     EXPECT_EQ(get_callbacks().m_error, &test_error_impl);
-    try {
+    C4_IF_EXCEPTIONS_(try, if(setjmp(s_jmp_env_expect_error) == 0))
+    {
         c4::yml::error("some message 123"); // calls test_error_impl, which sets stored_msg and stored_location
     }
-    catch (std::exception const&) {
+    C4_IF_EXCEPTIONS_(catch(std::exception const&), else)
+    {
         ;
     }
     EXPECT_EQ(stored_msg, "some message 123");
@@ -299,10 +314,12 @@ TEST(error, basic)
     EXPECT_EQ(stored_location.line, 0u);
     EXPECT_EQ(stored_location.col, 0u);
     // location
-    try {
+    C4_IF_EXCEPTIONS_(try, if(setjmp(s_jmp_env_expect_error) == 0))
+    {
         c4::yml::error("some message 456", Location("file.yml", 433u, 123u, 4u));
     }
-    catch (std::exception const&) {
+    C4_IF_EXCEPTIONS_(catch(std::exception const&), else)
+    {
         ;
     }
     EXPECT_EQ(stored_msg, "some message 456");
@@ -320,11 +337,13 @@ TEST(RYML_CHECK, basic)
     Callbacks cb(nullptr, nullptr, nullptr, &test_error_impl);
     set_callbacks(cb);
     ASSERT_EQ(get_callbacks(), cb);
-    size_t the_line;
-    try {
-        the_line = __LINE__; RYML_CHECK(false);  // keep both statements in the same line
+    const size_t the_line = (size_t)(__LINE__ + 3); // careful
+    C4_IF_EXCEPTIONS_(try, if(setjmp(s_jmp_env_expect_error) == 0))
+    {
+        RYML_CHECK(false);  // keep both statements in the same line
     }
-    catch (std::exception const&) {
+    C4_IF_EXCEPTIONS_(catch(std::exception const&), else)
+    {
         ;
     }
     EXPECT_EQ(stored_msg, "check failed: false");
@@ -344,11 +363,13 @@ TEST(RYML_ASSERT, basic)
     set_callbacks(cb);
     stored_msg = "";
     stored_location = {};
-    size_t the_line;
-    try {
-        the_line = __LINE__; RYML_ASSERT(false);  // keep both statements in the same line
+    const size_t the_line = (size_t)(__LINE__ + 3); // careful
+    C4_IF_EXCEPTIONS_(try, if(setjmp(s_jmp_env_expect_error) == 0))
+    {
+        RYML_ASSERT(false);
     }
-    catch (std::exception const&) {
+    C4_IF_EXCEPTIONS_(catch(std::exception const&), else)
+    {
         ;
     }
     #if RYML_USE_ASSERT
@@ -358,7 +379,7 @@ TEST(RYML_ASSERT, basic)
     EXPECT_EQ(stored_location.line, the_line);
     EXPECT_EQ(stored_location.col, 0u);
     #else
-    C4_UNUSED(the_line);
+    (void)the_line;
     EXPECT_EQ(stored_msg, "");
     EXPECT_EQ(stored_location.name, nullptr);
     EXPECT_EQ(stored_location.offset, 0u);
diff --git a/test/test_case.cpp b/test/test_case.cpp
index b0b5557fb..55c697866 100644
--- a/test/test_case.cpp
+++ b/test/test_case.cpp
@@ -9,6 +9,9 @@
 #endif
 
 #include <gtest/gtest.h>
+#ifndef C4_EXCEPTIONS
+#include <csetjmp>
+#endif
 
 #if defined(_MSC_VER)
 #   pragma warning(push)
@@ -172,11 +175,17 @@ std::string format_error(const char* msg, size_t len, Location loc)
     return out;
 }
 
-struct ExpectedError : public std::runtime_error
+struct ExpectedError C4_IF_EXCEPTIONS_( : public std::runtime_error, )
 {
+    C4_IF_EXCEPTIONS_( ,
+        std::string smsg;
+        const char *what() const { return smsg.c_str(); }
+        ExpectedError() = default;
+    )
     Location error_location;
     ExpectedError(const char* msg, size_t len, Location loc)
-        : std::runtime_error(format_error(msg, len, loc))
+        : C4_IF_EXCEPTIONS_(std::runtime_error(format_error(msg, len, loc)),
+                            smsg(format_error(msg, len, loc)))
         , error_location(loc)
     {
     }
@@ -184,6 +193,11 @@ struct ExpectedError : public std::runtime_error
 
 
 //-----------------------------------------------------------------------------
+C4_IF_EXCEPTIONS_(
+    ,
+    std::jmp_buf s_jmp_env_expect_error = {};
+    ExpectedError s_jmp_err = {};
+    )
 
 ExpectError::ExpectError(Tree *tree, Location loc)
     : m_got_an_error(false)
@@ -194,8 +208,13 @@ ExpectError::ExpectError(Tree *tree, Location loc)
 {
     auto err = [](const char* msg, size_t len, Location errloc, void *this_)  {
         ((ExpectError*)this_)->m_got_an_error = true;
-        throw ExpectedError(msg, len, errloc);
-        C4_UNREACHABLE_AFTER_ERR();
+        C4_IF_EXCEPTIONS(
+            throw ExpectedError(msg, len, errloc);
+            C4_UNREACHABLE_AFTER_ERR();
+            ,
+            s_jmp_err = ExpectedError(msg, len, errloc);
+            std::longjmp(s_jmp_env_expect_error, 1);
+        );
     };
     #ifdef RYML_NO_DEFAULT_CALLBACKS
     c4::yml::Callbacks tcb((void*)this, nullptr, nullptr, err);
@@ -219,11 +238,11 @@ ExpectError::~ExpectError()
 void ExpectError::check_success(Tree *tree, std::function<void()> fn)
 {
     auto context = ExpectError(tree, {});
-    try
+    C4_IF_EXCEPTIONS_(try, if(setjmp(s_jmp_env_expect_error) == 0))
     {
         fn();
     }
-    catch(ExpectedError const&)
+    C4_IF_EXCEPTIONS_(catch(ExpectedError const&), else)
     {
         ;
     }
@@ -233,12 +252,13 @@ void ExpectError::check_success(Tree *tree, std::function<void()> fn)
 void ExpectError::do_check(Tree *tree, std::function<void()> fn, Location expected_location)
 {
     auto context = ExpectError(tree, expected_location);
-    try
+    C4_IF_EXCEPTIONS_(try, if(setjmp(s_jmp_env_expect_error) == 0))
     {
         fn();
     }
-    catch(ExpectedError const& e)
+    C4_IF_EXCEPTIONS_(catch(ExpectedError const& e), else)
     {
+        C4_IF_EXCEPTIONS_( , ExpectedError const& e = s_jmp_err);
         #if defined(RYML_DBG)
         std::cout << "---------------\n";
         std::cout << "got an expected error:\n" << e.what() << "\n";
diff --git a/test/test_number.cpp b/test/test_number.cpp
index 41c388ae6..85afdc2ce 100644
--- a/test/test_number.cpp
+++ b/test/test_number.cpp
@@ -20,6 +20,7 @@ void test_ints()
     std::vector<I> values = mkvals<I>();
     Tree t = parse_in_arena("{dec: [], hex: [], bin: [], oct: [], versions: ['0.1', 0.1.2, 0.1.2.3, 0.1.2.3.4]}");
     NodeRef r = t.rootref();
+    auto notval = [](I val) { return val <= 0 ? (I)(val + I(1)) : (I)(val - I(1)); }; // try not to overflow
     for(I val : values)
     {
         I out;
@@ -27,16 +28,16 @@ void test_ints()
         r["hex"].append_child() << fmt::hex(val);
         r["bin"].append_child() << fmt::bin(val);
         r["oct"].append_child() << fmt::oct(val);
-        out = (I)(val + I(1));
+        out = notval(val);
         r["dec"].last_child() >> out;
         EXPECT_EQ(out, val);
-        out = (I)(val + I(1));
+        out = notval(val);
         r["hex"].last_child() >> out;
         EXPECT_EQ(out, val);
-        out = (I)(val + I(1));
+        out = notval(val);
         r["bin"].last_child() >> out;
         EXPECT_EQ(out, val);
-        out = (I)(val + I(1));
+        out = notval(val);
         r["oct"].last_child() >> out;
         EXPECT_EQ(out, val);
     }
@@ -51,16 +52,16 @@ void test_ints()
         size_t pos = 0;
         for(I val : values)
         {
-            I out = (I)(val + I(1));
+            I out = notval(val);
             parsed["dec"][pos] >> out;
             EXPECT_EQ(out, val);
-            out = (I)(val + I(1));
+            out = notval(val);
             parsed["hex"][pos] >> out;
             EXPECT_EQ(out, val);
-            out = (I)(val + I(1));
+            out = notval(val);
             parsed["bin"][pos]>> out;
             EXPECT_EQ(out, val);
-            out = (I)(val + I(1));
+            out = notval(val);
             parsed["oct"][pos] >> out;
             EXPECT_EQ(out, val);
             ++pos;
@@ -81,16 +82,16 @@ void test_ints()
         size_t pos = 0;
         for(I val : values)
         {
-            I out = (I)(val + I(1));
+            I out = notval(val);
             parsed["dec"][pos] >> out;
             EXPECT_EQ(out, val);
-            out = (I)(val + I(1));
+            out = notval(val);
             parsed["hex"][pos] >> out;
             EXPECT_EQ(out, val);
-            out = (I)(val + I(1));
+            out = notval(val);
             parsed["bin"][pos]>> out;
             EXPECT_EQ(out, val);
-            out = (I)(val + I(1));
+            out = notval(val);
             parsed["oct"][pos] >> out;
             EXPECT_EQ(out, val);
             ++pos;
diff --git a/test/test_suite/test_suite_events.cpp b/test/test_suite/test_suite_events.cpp
index 3e844a1da..d0fe20247 100644
--- a/test/test_suite/test_suite_events.cpp
+++ b/test/test_suite/test_suite_events.cpp
@@ -227,7 +227,7 @@ void EventsParser::parse(csubstr src, Tree *C4_RESTRICT tree_)
     struct ParseLevel { size_t tree_node; };
     detail::stack<ParseLevel> m_stack = {};
     Tree &C4_RESTRICT tree = *tree_;
-    size_t linenum = 0;(void)linenum;
+    size_t linenum = 0; (void)linenum;
     Scalar key = {};
     _nfo_logf("parsing events! src:\n{}", src);
     for(csubstr line : src.split('\n'))
diff --git a/test/test_suite/test_suite_events_emitter.cpp b/test/test_suite/test_suite_events_emitter.cpp
index 776bb9e27..51dac4f34 100644
--- a/test/test_suite/test_suite_events_emitter.cpp
+++ b/test/test_suite/test_suite_events_emitter.cpp
@@ -31,8 +31,11 @@ struct EventsEmitter
     }
     C4_ALWAYS_INLINE void pr(csubstr s)
     {
-        if(pos + s.len <= buf.len)
+        if(s.len && pos + s.len <= buf.len)
+        {
+            C4_ASSERT(s.str);
             memcpy(buf.str + pos, s.str, s.len);
+        }
         pos += s.len;
     }
     C4_ALWAYS_INLINE void pr(char c)