Browse Source

YAML loading optimization (#5997)

* Use rapidyaml library to parse YAML databases instead of yaml-cpp.
* Drastically reduces the parse time for yaml databases.
* Removes yaml-cpp content from main servers, except for tool emitter.
Co-authored-by: Vincent Stumpf <vincents.995@gmail.com>
Co-authored-by: Atemo <capucrath@gmail.com>
Co-authored-by: Lemongrass3110 <lemongrass@kstp.at>
Co-authored-by: Aleos <aleos89@users.noreply.github.com>
Jittapan Pluemsumran 3 years ago
parent
commit
d1b7061f5a
100 changed files with 23700 additions and 1 deletions
  1. 3 0
      .gitignore
  2. 2 1
      3rdparty/CMakeLists.txt
  3. 291 0
      3rdparty/rapidyaml/.gitchangelog.rc
  4. 50 0
      3rdparty/rapidyaml/.gitignore
  5. 3 0
      3rdparty/rapidyaml/.gitmodules
  6. 2 0
      3rdparty/rapidyaml/.lgtm.yml
  7. 100 0
      3rdparty/rapidyaml/CMakeLists.txt
  8. 18 0
      3rdparty/rapidyaml/CONTRIBUTING.md
  9. 20 0
      3rdparty/rapidyaml/LICENSE.txt
  10. 3 0
      3rdparty/rapidyaml/MANIFEST.in
  11. 44 0
      3rdparty/rapidyaml/Makefile.in
  12. 1049 0
      3rdparty/rapidyaml/README.md
  13. 18 0
      3rdparty/rapidyaml/ROADMAP.md
  14. 7 0
      3rdparty/rapidyaml/c4core.dir/Debug/c4core.lib.recipe
  15. 0 0
      3rdparty/rapidyaml/c4core.dir/Debug/c4core.vcxproj.FileListAbsolute.txt
  16. 294 0
      3rdparty/rapidyaml/c4core.vcxproj
  17. 239 0
      3rdparty/rapidyaml/c4core.vcxproj.filters
  18. 44 0
      3rdparty/rapidyaml/changelog/0.1.0.md
  19. 29 0
      3rdparty/rapidyaml/changelog/0.2.0.md
  20. 235 0
      3rdparty/rapidyaml/changelog/0.2.1.md
  21. 1 0
      3rdparty/rapidyaml/changelog/0.2.2.md
  22. 285 0
      3rdparty/rapidyaml/changelog/0.2.3.md
  23. 104 0
      3rdparty/rapidyaml/changelog/0.3.0.md
  24. 229 0
      3rdparty/rapidyaml/changelog/0.4.0.md
  25. 0 0
      3rdparty/rapidyaml/changelog/current.md
  26. 11 0
      3rdparty/rapidyaml/compat.cmake
  27. 9 0
      3rdparty/rapidyaml/ext/c4core/.github/.old/log.hpp
  28. 4091 0
      3rdparty/rapidyaml/ext/c4core/.github/.old/util.hpp
  29. 108 0
      3rdparty/rapidyaml/ext/c4core/.github/.travis.yml.old
  30. 82 0
      3rdparty/rapidyaml/ext/c4core/.github/appveyor.yml.old
  31. 129 0
      3rdparty/rapidyaml/ext/c4core/.github/release.sh
  32. 306 0
      3rdparty/rapidyaml/ext/c4core/.github/reqs.sh
  33. 410 0
      3rdparty/rapidyaml/ext/c4core/.github/setenv.sh
  34. 80 0
      3rdparty/rapidyaml/ext/c4core/.github/vagrant/Vagrantfile
  35. 71 0
      3rdparty/rapidyaml/ext/c4core/.github/vagrant/macos/Vagrantfile
  36. 71 0
      3rdparty/rapidyaml/ext/c4core/.github/vagrant/vagrant-provision.sh
  37. 116 0
      3rdparty/rapidyaml/ext/c4core/.github/workflows/arch.yml
  38. 87 0
      3rdparty/rapidyaml/ext/c4core/.github/workflows/benchmarks.yml
  39. 692 0
      3rdparty/rapidyaml/ext/c4core/.github/workflows/ci.yml
  40. 95 0
      3rdparty/rapidyaml/ext/c4core/.github/workflows/emscripten.yml
  41. 111 0
      3rdparty/rapidyaml/ext/c4core/.github/workflows/libcxx.yml
  42. 103 0
      3rdparty/rapidyaml/ext/c4core/.github/workflows/macosx.yml
  43. 199 0
      3rdparty/rapidyaml/ext/c4core/.github/workflows/release.yml
  44. 576 0
      3rdparty/rapidyaml/ext/c4core/.github/workflows/test.yml
  45. 104 0
      3rdparty/rapidyaml/ext/c4core/.github/workflows/test_install.yml
  46. 34 0
      3rdparty/rapidyaml/ext/c4core/.gitignore
  47. 9 0
      3rdparty/rapidyaml/ext/c4core/.gitmodules
  48. 107 0
      3rdparty/rapidyaml/ext/c4core/CMakeLists.txt
  49. 26 0
      3rdparty/rapidyaml/ext/c4core/LICENSE-BOOST.txt
  50. 20 0
      3rdparty/rapidyaml/ext/c4core/LICENSE.txt
  51. 232 0
      3rdparty/rapidyaml/ext/c4core/README.md
  52. 23 0
      3rdparty/rapidyaml/ext/c4core/ROADMAP.md
  53. 3 0
      3rdparty/rapidyaml/ext/c4core/changelog/0.1.0.md
  54. 5 0
      3rdparty/rapidyaml/ext/c4core/changelog/0.1.1.md
  55. 4 0
      3rdparty/rapidyaml/ext/c4core/changelog/0.1.2.md
  56. 1 0
      3rdparty/rapidyaml/ext/c4core/changelog/0.1.3.md
  57. 6 0
      3rdparty/rapidyaml/ext/c4core/changelog/0.1.4.md
  58. 2 0
      3rdparty/rapidyaml/ext/c4core/changelog/0.1.5.md
  59. 2 0
      3rdparty/rapidyaml/ext/c4core/changelog/0.1.6.md
  60. 5 0
      3rdparty/rapidyaml/ext/c4core/changelog/0.1.7.md
  61. 45 0
      3rdparty/rapidyaml/ext/c4core/changelog/0.1.8.md
  62. 31 0
      3rdparty/rapidyaml/ext/c4core/changelog/current.md
  63. 1 0
      3rdparty/rapidyaml/ext/c4core/cmake/.gitignore
  64. 120 0
      3rdparty/rapidyaml/ext/c4core/cmake/ConfigurationTypes.cmake
  65. 30 0
      3rdparty/rapidyaml/ext/c4core/cmake/CreateSourceGroup.cmake
  66. 2566 0
      3rdparty/rapidyaml/ext/c4core/cmake/Doxyfile.full.in
  67. 2566 0
      3rdparty/rapidyaml/ext/c4core/cmake/Doxyfile.in
  68. 215 0
      3rdparty/rapidyaml/ext/c4core/cmake/ExternalProjectUtils.cmake
  69. 75 0
      3rdparty/rapidyaml/ext/c4core/cmake/FindD3D12.cmake
  70. 76 0
      3rdparty/rapidyaml/ext/c4core/cmake/FindDX12.cmake
  71. 53 0
      3rdparty/rapidyaml/ext/c4core/cmake/GetFlags.cmake
  72. 51 0
      3rdparty/rapidyaml/ext/c4core/cmake/GetNames.cmake
  73. 20 0
      3rdparty/rapidyaml/ext/c4core/cmake/LICENSE.txt
  74. 275 0
      3rdparty/rapidyaml/ext/c4core/cmake/PVS-Studio.cmake
  75. 25 0
      3rdparty/rapidyaml/ext/c4core/cmake/PatchUtils.cmake
  76. 27 0
      3rdparty/rapidyaml/ext/c4core/cmake/PrintVar.cmake
  77. 25 0
      3rdparty/rapidyaml/ext/c4core/cmake/README.md
  78. 176 0
      3rdparty/rapidyaml/ext/c4core/cmake/TargetArchitecture.cmake
  79. 29 0
      3rdparty/rapidyaml/ext/c4core/cmake/Toolchain-Arm-ubuntu.cmake
  80. 84 0
      3rdparty/rapidyaml/ext/c4core/cmake/Toolchain-Armv7.cmake
  81. 73 0
      3rdparty/rapidyaml/ext/c4core/cmake/Toolchain-PS4.cmake
  82. 93 0
      3rdparty/rapidyaml/ext/c4core/cmake/Toolchain-XBoxOne.cmake
  83. 216 0
      3rdparty/rapidyaml/ext/c4core/cmake/amalgamate_utils.py
  84. 1 0
      3rdparty/rapidyaml/ext/c4core/cmake/bm-xp/.gitignore
  85. 7 0
      3rdparty/rapidyaml/ext/c4core/cmake/bm-xp/README.md
  86. 475 0
      3rdparty/rapidyaml/ext/c4core/cmake/bm-xp/bm.js
  87. 568 0
      3rdparty/rapidyaml/ext/c4core/cmake/bm-xp/bm.py
  88. 10 0
      3rdparty/rapidyaml/ext/c4core/cmake/bm-xp/requirements.txt
  89. 45 0
      3rdparty/rapidyaml/ext/c4core/cmake/bm-xp/template/index.html
  90. 105 0
      3rdparty/rapidyaml/ext/c4core/cmake/c4CatSources.cmake
  91. 121 0
      3rdparty/rapidyaml/ext/c4core/cmake/c4Doxygen.cmake
  92. 24 0
      3rdparty/rapidyaml/ext/c4core/cmake/c4DoxygenConfig.cmake
  93. 186 0
      3rdparty/rapidyaml/ext/c4core/cmake/c4GetTargetPropertyRecursive.cmake
  94. 3666 0
      3rdparty/rapidyaml/ext/c4core/cmake/c4Project.cmake
  95. 292 0
      3rdparty/rapidyaml/ext/c4core/cmake/c4SanitizeTarget.cmake
  96. 154 0
      3rdparty/rapidyaml/ext/c4core/cmake/c4StaticAnalysis.cmake
  97. 5 0
      3rdparty/rapidyaml/ext/c4core/cmake/c4stlAddTarget.cmake
  98. 69 0
      3rdparty/rapidyaml/ext/c4core/cmake/compat/c4/gcc-4.8.hpp
  99. 97 0
      3rdparty/rapidyaml/ext/c4core/cmake/compat/gtest_gcc-4.8.patch
  100. 3 0
      3rdparty/rapidyaml/ext/c4core/cmake/requirements_doc.txt

+ 3 - 0
.gitignore

@@ -50,6 +50,9 @@ Thumbs.db
 /3rdparty/libconfig/*.o
 /3rdparty/libconfig/obj
 
+# /3rdparty/rapidyaml/
+/3rdparty/rapidyaml/Makefile
+
 # /3rdparty/yaml-cpp/
 /3rdparty/yaml-cpp/Makefile
 

+ 2 - 1
3rdparty/CMakeLists.txt

@@ -50,5 +50,6 @@ endmacro( CONFIGURE_WITH_LOCAL_OR_SYSTEM )
 add_subdirectory( libconfig )
 add_subdirectory( mysql )
 add_subdirectory( pcre )
-add_subdirectory( zlib )
+add_subdirectory( rapidyaml )
 add_subdirectory( yaml-cpp )
+add_subdirectory( zlib )

+ 291 - 0
3rdparty/rapidyaml/.gitchangelog.rc

@@ -0,0 +1,291 @@
+# -*- coding: utf-8; mode: python -*-
+##
+## https://pypi.org/project/gitchangelog/
+##
+## Format
+##
+##   ACTION: [AUDIENCE:] COMMIT_MSG [!TAG ...]
+##
+## Description
+##
+##   ACTION is one of 'chg', 'fix', 'new'
+##
+##       Is WHAT the change is about.
+##
+##       'chg' is for refactor, small improvement, cosmetic changes...
+##       'fix' is for bug fixes
+##       'new' is for new features, big improvement
+##
+##   AUDIENCE is optional and one of 'dev', 'usr', 'pkg', 'test', 'doc'
+##
+##       Is WHO is concerned by the change.
+##
+##       'dev'  is for developpers (API changes, refactors...)
+##       'usr'  is for final users (UI changes)
+##       'pkg'  is for packagers   (packaging changes)
+##       'test' is for testers     (test only related changes)
+##       'doc'  is for doc guys    (doc only changes)
+##
+##   COMMIT_MSG is ... well ... the commit message itself.
+##
+##   TAGs are additionnal adjective as 'refactor' 'minor' 'cosmetic'
+##
+##       They are preceded with a '!' or a '@' (prefer the former, as the
+##       latter is wrongly interpreted in github.) Commonly used tags are:
+##
+##       'refactor' is obviously for refactoring code only
+##       'minor' is for a very meaningless change (a typo, adding a comment)
+##       'cosmetic' is for cosmetic driven change (re-indentation, 80-col...)
+##       'wip' is for partial functionality but complete subfunctionality.
+##
+## Example:
+##
+##   new: usr: support of bazaar implemented
+##   chg: re-indentend some lines !cosmetic
+##   new: dev: updated code to be compatible with last version of killer lib.
+##   fix: pkg: updated year of licence coverage.
+##   new: test: added a bunch of test around user usability of feature X.
+##   fix: typo in spelling my name in comment. !minor
+##
+##   Please note that multi-line commit message are supported, and only the
+##   first line will be considered as the "summary" of the commit message. So
+##   tags, and other rules only applies to the summary.  The body of the commit
+##   message will be displayed in the changelog without reformatting.
+
+
+##
+## ``ignore_regexps`` is a line of regexps
+##
+## Any commit having its full commit message matching any regexp listed here
+## will be ignored and won't be reported in the changelog.
+##
+ignore_regexps = [
+    r'@minor', r'!minor',
+    r'@cosmetic', r'!cosmetic',
+    r'@refactor', r'!refactor',
+    r'@wip', r'!wip',
+    r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*[p|P]kg:',
+    r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*[d|D]ev:',
+    r'^(.{3,3}\s*:)?\s*[fF]irst commit.?\s*$',
+    r'^$',  ## ignore commits with empty messages
+]
+
+
+## ``section_regexps`` is a list of 2-tuples associating a string label and a
+## list of regexp
+##
+## Commit messages will be classified in sections thanks to this. Section
+## titles are the label, and a commit is classified under this section if any
+## of the regexps associated is matching.
+##
+## Please note that ``section_regexps`` will only classify commits and won't
+## make any changes to the contents. So you'll probably want to go check
+## ``subject_process`` (or ``body_process``) to do some changes to the subject,
+## whenever you are tweaking this variable.
+##
+section_regexps = [
+    ('New', [
+        r'^[nN]ew\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$',
+     ]),
+    ('Changes', [
+        r'^[cC]hg\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$',
+     ]),
+    ('Fix', [
+        r'^[fF]ix\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$',
+     ]),
+
+    ('Other', None ## Match all lines
+     ),
+
+]
+
+
+## ``body_process`` is a callable
+##
+## This callable will be given the original body and result will
+## be used in the changelog.
+##
+## Available constructs are:
+##
+##   - any python callable that take one txt argument and return txt argument.
+##
+##   - ReSub(pattern, replacement): will apply regexp substitution.
+##
+##   - Indent(chars="  "): will indent the text with the prefix
+##     Please remember that template engines gets also to modify the text and
+##     will usually indent themselves the text if needed.
+##
+##   - Wrap(regexp=r"\n\n"): re-wrap text in separate paragraph to fill 80-Columns
+##
+##   - noop: do nothing
+##
+##   - ucfirst: ensure the first letter is uppercase.
+##     (usually used in the ``subject_process`` pipeline)
+##
+##   - final_dot: ensure text finishes with a dot
+##     (usually used in the ``subject_process`` pipeline)
+##
+##   - strip: remove any spaces before or after the content of the string
+##
+##   - SetIfEmpty(msg="No commit message."): will set the text to
+##     whatever given ``msg`` if the current text is empty.
+##
+## Additionally, you can `pipe` the provided filters, for instance:
+#body_process = Wrap(regexp=r'\n(?=\w+\s*:)') | Indent(chars="  ")
+#body_process = Wrap(regexp=r'\n(?=\w+\s*:)')
+#body_process = noop
+body_process = ReSub(r'((^|\n)[A-Z]\w+(-\w+)*: .*(\n\s+.*)*)+$', r'') | strip
+
+
+## ``subject_process`` is a callable
+##
+## This callable will be given the original subject and result will
+## be used in the changelog.
+##
+## Available constructs are those listed in ``body_process`` doc.
+subject_process = (strip |
+    ReSub(r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n@]*)(@[a-z]+\s+)*$', r'\4') |
+    SetIfEmpty("No commit message.") | ucfirst | final_dot)
+
+
+## ``tag_filter_regexp`` is a regexp
+##
+## Tags that will be used for the changelog must match this regexp.
+##
+tag_filter_regexp = r'^[0-9]+\.[0-9]+(\.[0-9]+)?$'
+
+
+## ``unreleased_version_label`` is a string or a callable that outputs a string
+##
+## This label will be used as the changelog Title of the last set of changes
+## between last valid tag and HEAD if any.
+unreleased_version_label = "(unreleased)"
+
+
+## ``output_engine`` is a callable
+##
+## This will change the output format of the generated changelog file
+##
+## Available choices are:
+##
+##   - rest_py
+##
+##        Legacy pure python engine, outputs ReSTructured text.
+##        This is the default.
+##
+##   - mustache(<template_name>)
+##
+##        Template name could be any of the available templates in
+##        ``templates/mustache/*.tpl``.
+##        Requires python package ``pystache``.
+##        Examples:
+##           - mustache("markdown")
+##           - mustache("restructuredtext")
+##
+##   - makotemplate(<template_name>)
+##
+##        Template name could be any of the available templates in
+##        ``templates/mako/*.tpl``.
+##        Requires python package ``mako``.
+##        Examples:
+##           - makotemplate("restructuredtext")
+##
+#output_engine = rest_py
+#output_engine = mustache("restructuredtext")
+output_engine = mustache("markdown")
+#output_engine = makotemplate("restructuredtext")
+
+
+## ``include_merge`` is a boolean
+##
+## This option tells git-log whether to include merge commits in the log.
+## The default is to include them.
+include_merge = True
+
+
+## ``log_encoding`` is a string identifier
+##
+## This option tells gitchangelog what encoding is outputed by ``git log``.
+## The default is to be clever about it: it checks ``git config`` for
+## ``i18n.logOutputEncoding``, and if not found will default to git's own
+## default: ``utf-8``.
+#log_encoding = 'utf-8'
+
+
+## ``publish`` is a callable
+##
+## Sets what ``gitchangelog`` should do with the output generated by
+## the output engine. ``publish`` is a callable taking one argument
+## that is an interator on lines from the output engine.
+##
+## Some helper callable are provided:
+##
+## Available choices are:
+##
+##   - stdout
+##
+##        Outputs directly to standard output
+##        (This is the default)
+##
+##   - FileInsertAtFirstRegexMatch(file, pattern, idx=lamda m: m.start())
+##
+##        Creates a callable that will parse given file for the given
+##        regex pattern and will insert the output in the file.
+##        ``idx`` is a callable that receive the matching object and
+##        must return a integer index point where to insert the
+##        the output in the file. Default is to return the position of
+##        the start of the matched string.
+##
+##   - FileRegexSubst(file, pattern, replace, flags)
+##
+##        Apply a replace inplace in the given file. Your regex pattern must
+##        take care of everything and might be more complex. Check the README
+##        for a complete copy-pastable example.
+##
+# publish = FileInsertIntoFirstRegexMatch(
+#     "CHANGELOG.rst",
+#     r'/(?P<rev>[0-9]+\.[0-9]+(\.[0-9]+)?)\s+\([0-9]+-[0-9]{2}-[0-9]{2}\)\n--+\n/',
+#     idx=lambda m: m.start(1)
+# )
+#publish = stdout
+
+
+## ``revs`` is a list of callable or a list of string
+##
+## callable will be called to resolve as strings and allow dynamical
+## computation of these. The result will be used as revisions for
+## gitchangelog (as if directly stated on the command line). This allows
+## to filter exaclty which commits will be read by gitchangelog.
+##
+## To get a full documentation on the format of these strings, please
+## refer to the ``git rev-list`` arguments. There are many examples.
+##
+## Using callables is especially useful, for instance, if you
+## are using gitchangelog to generate incrementally your changelog.
+##
+## Some helpers are provided, you can use them::
+##
+##   - FileFirstRegexMatch(file, pattern): will return a callable that will
+##     return the first string match for the given pattern in the given file.
+##     If you use named sub-patterns in your regex pattern, it'll output only
+##     the string matching the regex pattern named "rev".
+##
+##   - Caret(rev): will return the rev prefixed by a "^", which is a
+##     way to remove the given revision and all its ancestor.
+##
+## Please note that if you provide a rev-list on the command line, it'll
+## replace this value (which will then be ignored).
+##
+## If empty, then ``gitchangelog`` will act as it had to generate a full
+## changelog.
+##
+## The default is to use all commits to make the changelog.
+#revs = ["^1.0.3", ]
+#revs = [
+#    Caret(
+#        FileFirstRegexMatch(
+#            "CHANGELOG.rst",
+#            r"(?P<rev>[0-9]+\.[0-9]+(\.[0-9]+)?)\s+\([0-9]+-[0-9]{2}-[0-9]{2}\)\n--+\n")),
+#    "HEAD"
+#]
+revs = []

+ 50 - 0
3rdparty/rapidyaml/.gitignore

@@ -0,0 +1,50 @@
+# text editor files
+*.bck
+\#*
+*~
+.cquery_cached_index/
+.clangd/
+.ccls-cache/
+.cache/
+__pycache__/
+
+# gdb files
+.gdbinit
+setup.gdb
+
+# valgrind files
+callgrind*
+vgcore*
+
+# Visual Studio files
+.vs/
+.vscode/
+# QtCreator files
+CMakeLists.txt.user*
+# Eclipse
+.project
+.cproject
+/.settings/
+# KDevelop files
+*.kdev4
+
+# build files
+build/
+install/
+.python-version
+compile_commands.json
+
+# test files
+/Testing/
+
+# python packaging
+.eggs/
+dist/
+rapidyaml.egg-info/
+
+# continuous integration files
+.ci/.vagrant
+
+# amalgamation files
+src/c4/c4core_all.hpp
+src_singleheader/

+ 3 - 0
3rdparty/rapidyaml/.gitmodules

@@ -0,0 +1,3 @@
+[submodule "extern/c4core"]
+	path = ext/c4core
+	url = https://github.com/biojppm/c4core

+ 2 - 0
3rdparty/rapidyaml/.lgtm.yml

@@ -0,0 +1,2 @@
+queries:
+- exclude: cpp/unsigned-comparison-zero

+ 100 - 0
3rdparty/rapidyaml/CMakeLists.txt

@@ -0,0 +1,100 @@
+cmake_minimum_required(VERSION 3.12)
+include(./ext/c4core/cmake/c4Project.cmake)
+project(ryml
+    DESCRIPTION "Rapid YAML parsing and emitting"
+    HOMEPAGE_URL "https://github.com/biojppm/rapidyaml"
+    LANGUAGES CXX)
+include(./compat.cmake)
+
+c4_project(VERSION 0.4.0 STANDALONE
+    AUTHOR "Joao Paulo Magalhaes <dev@jpmag.me>")
+
+
+#-------------------------------------------------------
+
+option(RYML_WITH_TAB_TOKENS "Enable parsing of tabs after ':' and '-'. This is costly and disabled by default." OFF)
+option(RYML_DEFAULT_CALLBACKS "Enable ryml's default implementation of callbacks: allocate(), free(), error()" ON)
+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)
+
+
+#-------------------------------------------------------
+
+c4_require_subproject(c4core INCORPORATE
+    SUBDIRECTORY ${RYML_EXT_DIR}/c4core)
+
+c4_add_library(ryml
+    SOURCES
+        ryml.hpp
+        ryml_std.hpp
+        c4/yml/detail/checks.hpp
+        c4/yml/detail/parser_dbg.hpp
+        c4/yml/detail/print.hpp
+        c4/yml/detail/stack.hpp
+        c4/yml/common.hpp
+        c4/yml/common.cpp
+        c4/yml/emit.def.hpp
+        c4/yml/emit.hpp
+        c4/yml/export.hpp
+        c4/yml/node.hpp
+        c4/yml/node.cpp
+        c4/yml/parse.hpp
+        c4/yml/parse.cpp
+        c4/yml/preprocess.hpp
+        c4/yml/preprocess.cpp
+        c4/yml/std/map.hpp
+        c4/yml/std/std.hpp
+        c4/yml/std/string.hpp
+        c4/yml/std/vector.hpp
+        c4/yml/tree.hpp
+        c4/yml/tree.cpp
+        c4/yml/writer.hpp
+        c4/yml/yml.hpp
+        ryml.natvis
+    SOURCE_ROOT ${RYML_SRC_DIR}
+    INC_DIRS
+        $<BUILD_INTERFACE:${RYML_SRC_DIR}>
+        $<INSTALL_INTERFACE:include>
+    LIBS c4core
+    INCORPORATE c4core
+    )
+
+if(RYML_WITH_TAB_TOKENS)
+    target_compile_definitions(ryml PUBLIC RYML_WITH_TAB_TOKENS)
+endif()
+
+if(NOT RYML_DEFAULT_CALLBACKS)
+    target_compile_definitions(ryml PRIVATE RYML_NO_DEFAULT_CALLBACKS)
+endif()
+
+if(RYML_DBG)
+    target_compile_definitions(ryml PRIVATE RYML_DBG)
+endif()
+
+
+#-------------------------------------------------------
+
+c4_install_target(ryml)
+c4_install_exports(DEPENDENCIES c4core)
+c4_pack_project()
+
+
+#-------------------------------------------------------
+# developer targets
+
+
+# extern libraries, used only for testing/benchmarking
+if(RYML_BUILD_TESTS OR RYML_BUILD_BENCHMARKS OR RYML_BUILD_TOOLS)
+    include(ext/testbm.cmake)
+endif()
+
+if(RYML_BUILD_TOOLS)
+    add_subdirectory(tools)
+endif()
+
+c4_add_dev_targets()
+
+add_custom_target(ryml-uninstall
+    "${CMAKE_COMMAND}" -P "${PROJECT_SOURCE_DIR}/cmake/uninstall.cmake"
+)

+ 18 - 0
3rdparty/rapidyaml/CONTRIBUTING.md

@@ -0,0 +1,18 @@
+# Contributing
+
+Thanks for your contribution!
+
+* Make sure to clone the project with `git clone --recursive` so that
+  the submodules are initialized correctly.
+* To enable both tests and benchmarks, configure ryml with `-DRYML_DEV=ON`
+  when calling cmake. To enable only tests, use `-DRYML_BUILD_TESTS=ON`; to
+  enable only benchmarks use `-DRYML_BUILD_BENCHMARKS=ON`. All these flags
+  are disabled by default.
+* Code style for pull requests should respect the existing code style:
+    ```c++
+    if(foo)  // no space before parens
+    {   // curly brackets on next line
+        // no tabs; indent with 4 spaces
+        bar();
+    }
+    ```

+ 20 - 0
3rdparty/rapidyaml/LICENSE.txt

@@ -0,0 +1,20 @@
+Copyright (c) 2018, Joao Paulo Magalhaes <dev@jpmag.me>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+

+ 3 - 0
3rdparty/rapidyaml/MANIFEST.in

@@ -0,0 +1,3 @@
+# MANIFEST.in must be in root directory.
+# See https://github.com/pypa/setuptools/issues/2615
+graft ext

+ 44 - 0
3rdparty/rapidyaml/Makefile.in

@@ -0,0 +1,44 @@
+
+RAPIDYAML_OBJ = $(shell find * -type f -name "*.cpp" | sed -e "s/\.cpp/\.o/g")
+RAPIDYAML_DIR_OBJ = $(RAPIDYAML_OBJ:%=obj/%)
+RAPIDYAML_H =  $(shell find * -type f -name "*.h*")
+RAPIDYAML_AR = obj/ryml.a
+
+CXXFLAG =-std=c++11
+INC=-Isrc -Iext/c4core/src
+
+@SET_MAKE@
+
+#####################################################################
+.PHONY : all clean help rapidyaml
+
+all: rapidyaml
+
+clean:
+	@echo "	CLEAN	rapidyaml"
+	@rm -rf obj *.o
+
+help:
+	@echo "possible targets are 'all' 'clean' 'help'"
+	@echo "'rapidyaml' - build $(RAPIDYAML_AR)
+	@echo "'all'       - builds $(RAPIDYAML_DIR_OBJ)"
+	@echo "'clean'     - deletes $(RAPIDYAML_DIR_OBJ)"
+	@echo "'help'      - outputs this message"
+
+#####################################################################
+
+obj:
+	@echo "	MKDIR	obj/src/c4/yml"
+	@echo "	MKDIR	obj/ext/c4core/src/c4"
+	@mkdir -p obj/src/c4/yml
+	@mkdir -p obj/ext/c4core/src/c4
+
+obj/%.o: %.cpp $(RAPIDYAML_H)
+	@echo "	CXX	$<"
+	@@CXX@ $(CXXFLAG) @CFLAGS_AR@ @CPPFLAGS@ -g $(INC) -c $(OUTPUT_OPTION) $<
+
+rapidyaml: obj $(RAPIDYAML_DIR_OBJ) $(RAPIDYAML_AR)
+
+$(RAPIDYAML_AR): $(RAPIDYAML_DIR_OBJ)
+	@echo "	AR	$@"
+	@@AR@ rcs obj/ryml.a $(RAPIDYAML_DIR_OBJ)

+ 1049 - 0
3rdparty/rapidyaml/README.md

@@ -0,0 +1,1049 @@
+# Rapid YAML
+[![MIT Licensed](https://img.shields.io/badge/License-MIT-green.svg)](https://github.com/biojppm/rapidyaml/blob/master/LICENSE.txt)
+[![release](https://img.shields.io/github/v/release/biojppm/rapidyaml?color=g&include_prereleases&label=release%20&sort=semver)](https://github.com/biojppm/rapidyaml/releases)
+[![PyPI](https://img.shields.io/pypi/v/rapidyaml?color=g)](https://pypi.org/project/rapidyaml/)
+[![Docs](https://img.shields.io/badge/docs-docsforge-blue)](https://rapidyaml.docsforge.com/)
+[![Gitter](https://badges.gitter.im/rapidyaml/community.svg)](https://gitter.im/rapidyaml/community)
+
+[![test](https://github.com/biojppm/rapidyaml/workflows/test/badge.svg?branch=master)](https://github.com/biojppm/rapidyaml/actions)
+[![Coveralls](https://coveralls.io/repos/github/biojppm/rapidyaml/badge.svg?branch=master)](https://coveralls.io/github/biojppm/rapidyaml)
+[![Codecov](https://codecov.io/gh/biojppm/rapidyaml/branch/master/graph/badge.svg?branch=master)](https://codecov.io/gh/biojppm/rapidyaml)
+[![Total alerts](https://img.shields.io/lgtm/alerts/g/biojppm/rapidyaml.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/biojppm/rapidyaml/alerts/)
+[![Language grade: C/C++](https://img.shields.io/lgtm/grade/cpp/g/biojppm/rapidyaml.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/biojppm/rapidyaml/context:cpp)
+
+
+Or ryml, for short. ryml is a C++ library to parse and emit YAML,
+and do it fast, on everything from x64 to bare-metal chips without
+operating system. (If you are looking to use your programs with a YAML tree
+as a configuration tree with override facilities, take a look at
+[c4conf](https://github.com/biojppm/c4conf)).
+
+ryml parses both read-only and in-situ source buffers; the resulting
+data nodes hold only views to sub-ranges of the source buffer. No
+string copies or duplications are done, and no virtual functions are
+used. The data tree is a flat index-based structure stored in a single
+array. Serialization happens only at your direct request, after
+parsing / before emitting. Internally, the data tree representation
+stores only string views and has no knowledge of types, but of course,
+every node can have a YAML type tag. ryml makes it easy and fast to
+read and modify the data tree.
+
+ryml is available as a single header file, or it can be used as a
+simple library with cmake -- both separately (ie
+build->install->`find_package()`) or together with your project (ie with
+`add_subdirectory()`). (See below for examples).
+
+ryml can use custom global and per-tree memory allocators and error
+handler callbacks, and is exception-agnostic. ryml provides a default
+implementation for the allocator (using `std::malloc()`) and error
+handlers (using using `std::abort()` is provided, but you can opt out
+and provide your own memory allocation and eg, exception-throwing
+callbacks.
+
+ryml does not depend on the STL, ie, it does not use any std container
+as part of its data structures), but it can serialize and deserialize
+these containers into the data tree, with the use of optional
+headers. ryml ships with [c4core](https://github.com/biojppm/c4core) a
+small C++ utilities multiplatform library.
+
+ryml is written in C++11, and compiles cleanly with:
+* Visual Studio 2015 and later
+* clang++ 3.9 and later
+* g++ 4.8 and later
+* Intel Compiler
+
+ryml is [extensively unit-tested in Linux, Windows and
+MacOS](https://github.com/biojppm/rapidyaml/actions). The tests cover
+x64, x86, wasm (emscripten), arm, aarch64, ppc64le and s390x
+architectures, and include analysing ryml with:
+  * valgrind
+  * clang-tidy
+  * clang sanitizers:
+    * memory
+    * address
+    * undefined behavior
+    * thread
+  * [LGTM.com](https://lgtm.com/projects/g/biojppm/rapidyaml)
+
+ryml also [runs in
+bare-metal](https://github.com/biojppm/rapidyaml/issues/193), and
+[RISC-V
+architectures](https://github.com/biojppm/c4core/pull/69). Both of
+these are pending implementation of CI actions for continuous
+validation, but ryml has been proven to work there.
+
+ryml is [available in Python](https://pypi.org/project/rapidyaml/),
+and can very easily be compiled to JavaScript through emscripten (see
+below).
+
+See also [the changelog](https://github.com/biojppm/rapidyaml/tree/master/changelog)
+and [the roadmap](https://github.com/biojppm/rapidyaml/tree/master/ROADMAP.md).
+
+<!-- endpythonreadme -->
+
+
+------
+
+## Table of contents
+* [Is it rapid?](#is-it-rapid)
+  * [Comparison with yaml-cpp](#comparison-with-yaml-cpp)
+  * [Performance reading JSON](#performance-reading-json)
+  * [Performance emitting](#performance-emitting)
+* [Quick start](#quick-start)
+* [Using ryml in your project](#using-ryml-in-your-project)
+  * [Package managers](#package-managers)
+  * [Single header file](#single-header-file)
+  * [As a library](#as-a-library)
+  * [Quickstart samples](#quickstart-samples)
+  * [CMake build settings for ryml](#cmake-build-settings-for-ryml)
+     * [Forcing ryml to use a different c4core version](#forcing-ryml-to-use-a-different-c4core-version)
+* [Other languages](#other-languages)
+  * [JavaScript](#javascript)
+  * [Python](#python)
+* [YAML standard conformance](#yaml-standard-conformance)
+  * [Test suite status](#test-suite-status)
+* [Known limitations](#known-limitations)
+* [Alternative libraries](#alternative-libraries)
+* [License](#license)
+
+
+------
+
+## Is it rapid?
+
+You bet! On a i7-6800K CPU @3.40GHz:
+ * ryml parses YAML at about ~150MB/s on Linux and ~100MB/s on Windows (vs2017). 
+ * **ryml parses JSON at about ~450MB/s on Linux**, faster than sajson (didn't
+   try yet on Windows).
+ * compared against the other existing YAML libraries for C/C++:
+   * ryml is in general between 2 and 3 times faster than [libyaml](https://github.com/yaml/libyaml)
+   * ryml is in general between 10 and 70 times faster than
+     [yaml-cpp](https://github.com/jbeder/yaml-cpp), and in some cases as
+     much as 100x and [even
+     200x](https://github.com/biojppm/c4core/pull/16#issuecomment-700972614) faster.
+
+[Here's the benchmark](./bm/bm_parse.cpp). Using different
+approaches within ryml (in-situ/read-only vs. with/without reuse), a YAML /
+JSON buffer is repeatedly parsed, and compared against other libraries.
+
+### Comparison with yaml-cpp
+
+The first result set is for Windows, and is using a [appveyor.yml config
+file](./bm/cases/appveyor.yml). A comparison of these results is
+summarized on the table below:
+
+| Read rates (MB/s)            | ryml   | yamlcpp | compared     |
+|------------------------------|--------|---------|--------------|
+| appveyor / vs2017 / Release  | 101.5  | 5.3     |  20x / 5.2%  |
+| appveyor / vs2017 / Debug    |   6.4  | 0.0844  |  76x / 1.3%  |
+
+
+The next set of results is taken in Linux, comparing g++ 8.2 and clang++ 7.0.1 in
+parsing a YAML buffer from a [travis.yml config
+file](./bm/cases/travis.yml) or a JSON buffer from a [compile_commands.json
+file](./bm/cases/compile_commands.json). You
+can [see the full results here](./bm/results/parse.linux.i7_6800K.md).
+Summarizing:
+
+| Read rates (MB/s)           | ryml   | yamlcpp | compared   |
+|-----------------------------|--------|---------|------------|
+| json   / clang++ / Release  | 453.5  | 15.1    |  30x / 3%  |
+| json   /     g++ / Release  | 430.5  | 16.3    |  26x / 4%  |
+| json   / clang++ / Debug    |  61.9  | 1.63    |  38x / 3%  |
+| json   /     g++ / Debug    |  72.6  | 1.53    |  47x / 2%  |
+| travis / clang++ / Release  | 131.6  | 8.08    |  16x / 6%  |
+| travis /     g++ / Release  | 176.4  | 8.23    |  21x / 5%  |
+| travis / clang++ / Debug    |  10.2  | 1.08    |   9x / 1%  |
+| travis /     g++ / Debug    |  12.5  | 1.01    |  12x / 8%  |
+
+The 450MB/s read rate for JSON puts ryml squarely in the same ballpark
+as [RapidJSON](https://github.com/Tencent/rapidjson) and other fast json
+readers
+([data from here](https://lemire.me/blog/2018/05/03/how-fast-can-you-parse-json/)).
+Even parsing full YAML is at ~150MB/s, which is still in that performance
+ballpark, albeit at its lower end. This is something to be proud of, as the
+YAML specification is much more complex than JSON: [23449 vs 1969 words](https://www.arp242.net/yaml-config.html#its-pretty-complex).
+
+
+### Performance reading JSON
+
+So how does ryml compare against other JSON readers? Well, it's one of the
+fastest! 
+
+The benchmark is the [same as above](./bm/parse.cpp), and it is reading
+the [compile_commands.json](./bm/cases/compile_commands.json), The `_arena`
+suffix notes parsing a read-only buffer (so buffer copies are performed),
+while the `_inplace` suffix means that the source buffer can be parsed in
+place. The `_reuse` means the data tree and/or parser are reused on each
+benchmark repeat.
+
+Here's what we get with g++ 8.2:
+
+| Benchmark             | Release,MB/s | Debug,MB/s  |
+|:----------------------|-------------:|------------:|
+| rapidjson_arena       |       509.9  |       43.4  |
+| rapidjson_inplace     |      1329.4  |       68.2  |
+| sajson_inplace        |       434.2  |      176.5  |
+| sajson_arena          |       430.7  |      175.6  |
+| jsoncpp_arena         |       183.6  |    ? 187.9  |
+| nlohmann_json_arena   |       115.8  |       21.5  |
+| yamlcpp_arena         |        16.6  |        1.6  |
+| libyaml_arena         |       113.9  |       35.7  |
+| libyaml_arena_reuse   |       114.6  |       35.9  |
+| ryml_arena            |       388.6  |       36.9  |
+| ryml_inplace          |       393.7  |       36.9  |
+| ryml_arena_reuse      |       446.2  |       74.6  |
+| ryml_inplace_reuse    |       457.1  |       74.9  |
+
+You can verify that (at least for this test) ryml beats most json
+parsers at their own game, with the only exception of
+[rapidjson](https://github.com/Tencent/rapidjson). And actually, in
+Debug, [rapidjson](https://github.com/Tencent/rapidjson) is slower
+than ryml, and [sajson](https://github.com/chadaustin/sajson)
+manages to be faster (but not sure about jsoncpp; need to scrutinize there
+the suspicious fact that the Debug result is faster than the Release result).
+
+
+### Performance emitting
+
+[Emitting benchmarks](bm/bm_emit.cpp) also show similar speedups from
+the existing libraries, also anecdotally reported by some users [(eg,
+here's a user reporting 25x speedup from
+yaml-cpp)](https://github.com/biojppm/rapidyaml/issues/28#issue-553855608). Also, in
+some cases (eg, block folded multiline scalars), the speedup is as
+high as 200x (eg, 7.3MB/s -> 1.416MG/s).
+
+
+### CI results and request for files
+
+While a more effective way of showing the benchmark results is not
+available yet, you can browse through the [runs of the benchmark
+workflow in the
+CI](https://github.com/biojppm/rapidyaml/actions/workflows/benchmarks.yml)
+to scroll through the results for yourself.
+
+Also, if you have a case where ryml behaves very nicely or not as nicely as
+claimed above, we would definitely like to see it! Please submit a pull request
+adding the file to [bm/cases](bm/cases), or just send us the files.
+
+
+------
+
+## Quick start
+
+If you're wondering whether ryml's speed comes at a usage cost, you
+need not: with ryml, you can have your cake and eat it too. Being
+rapid is definitely NOT the same as being unpractical, so ryml was
+written with easy AND efficient usage in mind, and comes with a two
+level API for accessing and traversing the data tree.
+
+The following snippet is a quick overview taken from [the quickstart
+sample](samples/quickstart.cpp). After cloning ryml (don't forget the
+`--recursive` flag for git), you can very
+easily build and run this executable using any of the build samples,
+eg the [`add_subdirectory()` sample](samples/add_subdirectory/).
+
+```c++
+// Parse YAML code in place, potentially mutating the buffer.
+// It is also possible to:
+//   - parse a read-only buffer using parse_in_arena()
+//   - reuse an existing tree (advised)
+//   - reuse an existing parser (advised)
+char yml_buf[] = "{foo: 1, bar: [2, 3], john: doe}";
+ryml::Tree tree = ryml::parse_in_place(ryml::substr(yml_buf));
+
+// Note: it will always be significantly faster to use mutable
+// buffers and reuse tree+parser.
+//
+// Below you will find samples that show how to achieve reuse; but
+// please note that for brevity and clarity, many of the examples
+// here are parsing immutable buffers, and not reusing tree or
+// parser.
+
+
+//------------------------------------------------------------------
+// API overview
+
+// ryml has a two-level API:
+//
+// The lower level index API is based on the indices of nodes,
+// where the node's id is the node's position in the tree's data
+// array. This API is very efficient, but somewhat difficult to use:
+size_t root_id = tree.root_id();
+size_t bar_id = tree.find_child(root_id, "bar"); // need to get the index right
+CHECK(tree.is_map(root_id)); // all of the index methods are in the tree
+CHECK(tree.is_seq(bar_id));  // ... and receive the subject index
+
+// The node API is a lightweight abstraction sitting on top of the
+// index API, but offering a much more convenient interaction:
+ryml::NodeRef root = tree.rootref();
+ryml::NodeRef bar = tree["bar"];
+CHECK(root.is_map());
+CHECK(bar.is_seq());
+// NodeRef is a lightweight handle to the tree and associated id:
+CHECK(root.tree() == &tree); // NodeRef points at its tree, WITHOUT refcount
+CHECK(root.id() == root_id); // NodeRef's id is the index of the node
+CHECK(bar.id() == bar_id);   // NodeRef's id is the index of the node
+
+// The node API translates very cleanly to the index API, so most
+// of the code examples below are using the node API.
+
+// One significant point of the node API is that it holds a raw
+// pointer to the tree. Care must be taken to ensure the lifetimes
+// match, so that a node will never access the tree after the tree
+// went out of scope.
+
+
+//------------------------------------------------------------------
+// To read the parsed tree
+
+// Node::operator[] does a lookup, is O(num_children[node]).
+// maps use string keys, seqs use integral keys.
+CHECK(tree["foo"].is_keyval());
+CHECK(tree["foo"].key() == "foo");
+CHECK(tree["foo"].val() == "1");
+CHECK(tree["bar"].is_seq());
+CHECK(tree["bar"].has_key());
+CHECK(tree["bar"].key() == "bar");
+CHECK(tree["bar"][0].val() == "2");
+CHECK(tree["bar"][1].val() == "3");
+CHECK(tree["john"].val() == "doe");
+// An integral key is the position of the child within its parent,
+// so even maps can also use int keys, if the key position is
+// known.
+CHECK(tree[0].id() == tree["foo"].id());
+CHECK(tree[1].id() == tree["bar"].id());
+CHECK(tree[2].id() == tree["john"].id());
+// Tree::operator[](int) searches a root child by its position.
+CHECK(tree[0].id() == tree["foo"].id());  // 0: first child of root
+CHECK(tree[1].id() == tree["bar"].id());  // 1: first child of root
+CHECK(tree[2].id() == tree["john"].id()); // 2: first child of root
+// NodeRef::operator[](int) searches a node child by its position
+// on __the node__'s children list:
+CHECK(bar[0].val() == "2"); // 0 means first child of bar
+CHECK(bar[1].val() == "3"); // 1 means second child of bar
+// NodeRef::operator[](string):
+// A string key is the key of the node: lookup is by name. So it
+// is only available for maps, and it is NOT available for seqs,
+// since seq members do not have keys.
+CHECK(tree["foo"].key() == "foo");
+CHECK(tree["bar"].key() == "bar");
+CHECK(tree["john"].key() == "john");
+CHECK(bar.is_seq());
+// CHECK(bar["BOOM!"].is_seed()); // error, seqs do not have key lookup
+
+// Note that maps can also use index keys as well as string keys:
+CHECK(root["foo"].id() == root[0].id());
+CHECK(root["bar"].id() == root[1].id());
+CHECK(root["john"].id() == root[2].id());
+
+// Please note that since a ryml tree uses indexed linked lists for storing
+// children, the complexity of `Tree::operator[csubstr]` and
+// `Tree::operator[size_t]` is linear on the number of root children. If you use
+// it with a large tree where the root has many children, you may get a
+// performance hit. To avoid this hit, you can create your own accelerator
+// structure. For example, before doing a lookup, do a single traverse at the
+// root level to fill an `std::map<csubstr,size_t>` mapping key names to node
+// indices; with a node index, a lookup (via `Tree::get()`) is O(1), so this way
+// you can get O(log n) lookup from a key.
+//
+// As for `NodeRef`, the difference from `NodeRef::operator[]`
+// to `Tree::operator[]` is that the latter refers to the root node, whereas
+// the former can be invoked on any node. But the lookup process is the same for
+// both and their algorithmic complexity is the same: they are both linear in
+// the number of direct children; but depending on the data, that number may
+// be very different from one to another.
+
+//------------------------------------------------------------------
+// Hierarchy:
+
+{
+    ryml::NodeRef foo = root.first_child();
+    ryml::NodeRef john = root.last_child();
+    CHECK(tree.size() == 6); // O(1) number of nodes in the tree
+    CHECK(root.num_children() == 3); // O(num_children[root])
+    CHECK(foo.num_siblings() == 3); // O(num_children[parent(foo)])
+    CHECK(foo.parent().id() == root.id()); // parent() is O(1)
+    CHECK(root.first_child().id() == root["foo"].id()); // first_child() is O(1)
+    CHECK(root.last_child().id() == root["john"].id()); // last_child() is O(1)
+    CHECK(john.first_sibling().id() == foo.id());
+    CHECK(foo.last_sibling().id() == john.id());
+    // prev_sibling(), next_sibling(): (both are O(1))
+    CHECK(foo.num_siblings() == root.num_children());
+    CHECK(foo.prev_sibling().id() == ryml::NONE); // foo is the first_child()
+    CHECK(foo.next_sibling().key() == "bar");
+    CHECK(foo.next_sibling().next_sibling().key() == "john");
+    CHECK(foo.next_sibling().next_sibling().next_sibling().id() == ryml::NONE); // john is the last_child()
+}
+
+
+//------------------------------------------------------------------
+// Iterating:
+{
+    ryml::csubstr expected_keys[] = {"foo", "bar", "john"};
+    // iterate children using the high-level node API:
+    {
+        size_t count = 0;
+        for(ryml::NodeRef const& child : root.children())
+            CHECK(child.key() == expected_keys[count++]);
+    }
+    // iterate siblings using the high-level node API:
+    {
+        size_t count = 0;
+        for(ryml::NodeRef const& child : root["foo"].siblings())
+            CHECK(child.key() == expected_keys[count++]);
+    }
+    // iterate children using the lower-level tree index API:
+    {
+        size_t count = 0;
+        for(size_t child_id = tree.first_child(root_id); child_id != ryml::NONE; child_id = tree.next_sibling(child_id))
+            CHECK(tree.key(child_id) == expected_keys[count++]);
+    }
+    // iterate siblings using the lower-level tree index API:
+    // (notice the only difference from above is in the loop
+    // preamble, which calls tree.first_sibling(bar_id) instead of
+    // tree.first_child(root_id))
+    {
+        size_t count = 0;
+        for(size_t child_id = tree.first_sibling(bar_id); child_id != ryml::NONE; child_id = tree.next_sibling(child_id))
+            CHECK(tree.key(child_id) == expected_keys[count++]);
+    }
+}
+
+
+//------------------------------------------------------------------
+// Gotchas:
+CHECK(!tree["bar"].has_val());          // seq is a container, so no val
+CHECK(!tree["bar"][0].has_key());       // belongs to a seq, so no key
+CHECK(!tree["bar"][1].has_key());       // belongs to a seq, so no key
+//CHECK(tree["bar"].val() == BOOM!);    // ... so attempting to get a val is undefined behavior
+//CHECK(tree["bar"][0].key() == BOOM!); // ... so attempting to get a key is undefined behavior
+//CHECK(tree["bar"][1].key() == BOOM!); // ... so attempting to get a key is undefined behavior
+
+
+//------------------------------------------------------------------
+// Deserializing: use operator>>
+{
+    int foo = 0, bar0 = 0, bar1 = 0;
+    std::string john;
+    root["foo"] >> foo;
+    root["bar"][0] >> bar0;
+    root["bar"][1] >> bar1;
+    root["john"] >> john; // requires from_chars(std::string). see serialization samples below.
+    CHECK(foo == 1);
+    CHECK(bar0 == 2);
+    CHECK(bar1 == 3);
+    CHECK(john == "doe");
+}
+
+
+//------------------------------------------------------------------
+// Modifying existing nodes: operator<< vs operator=
+
+// operator= assigns an existing string to the receiving node.
+// This pointer will be in effect until the tree goes out of scope
+// so beware to only assign from strings outliving the tree.
+root["foo"] = "says you";
+root["bar"][0] = "-2";
+root["bar"][1] = "-3";
+root["john"] = "ron";
+// Now the tree is _pointing_ at the memory of the strings above.
+// That is OK because those are static strings and will outlive
+// the tree.
+CHECK(root["foo"].val() == "says you");
+CHECK(root["bar"][0].val() == "-2");
+CHECK(root["bar"][1].val() == "-3");
+CHECK(root["john"].val() == "ron");
+// WATCHOUT: do not assign from temporary objects:
+// {
+//     std::string crash("will dangle");
+//     root["john"] = ryml::to_csubstr(crash);
+// }
+// CHECK(root["john"] == "dangling"); // CRASH! the string was deallocated
+
+// operator<< first serializes the input to the tree's arena, then
+// assigns the serialized string to the receiving node. This avoids
+// constraints with the lifetime, since the arena lives with the tree.
+CHECK(tree.arena().empty());
+root["foo"] << "says who";  // requires to_chars(). see serialization samples below.
+root["bar"][0] << 20;
+root["bar"][1] << 30;
+root["john"] << "deere";
+CHECK(root["foo"].val() == "says who");
+CHECK(root["bar"][0].val() == "20");
+CHECK(root["bar"][1].val() == "30");
+CHECK(root["john"].val() == "deere");
+CHECK(tree.arena() == "says who2030deere"); // the result of serializations to the tree arena
+// using operator<< instead of operator=, the crash above is avoided:
+{
+    std::string ok("in_scope");
+    // root["john"] = ryml::to_csubstr(ok); // don't, will dangle
+    root["john"] << ryml::to_csubstr(ok); // OK, copy to the tree's arena
+}
+CHECK(root["john"] == "in_scope"); // OK!
+CHECK(tree.arena() == "says who2030deerein_scope"); // the result of serializations to the tree arena
+
+
+//------------------------------------------------------------------
+// Adding new nodes:
+
+// adding a keyval node to a map:
+CHECK(root.num_children() == 3);
+root["newkeyval"] = "shiny and new"; // using these strings
+root.append_child() << ryml::key("newkeyval (serialized)") << "shiny and new (serialized)"; // serializes and assigns the serialization
+CHECK(root.num_children() == 5);
+CHECK(root["newkeyval"].key() == "newkeyval");
+CHECK(root["newkeyval"].val() == "shiny and new");
+CHECK(root["newkeyval (serialized)"].key() == "newkeyval (serialized)");
+CHECK(root["newkeyval (serialized)"].val() == "shiny and new (serialized)");
+CHECK( ! root["newkeyval"].key().is_sub(tree.arena())); // it's using directly the static string above
+CHECK( ! root["newkeyval"].val().is_sub(tree.arena())); // it's using directly the static string above
+CHECK(   root["newkeyval (serialized)"].key().is_sub(tree.arena())); // it's using a serialization of the string above
+CHECK(   root["newkeyval (serialized)"].val().is_sub(tree.arena())); // it's using a serialization of the string above
+// adding a val node to a seq:
+CHECK(root["bar"].num_children() == 2);
+root["bar"][2] = "oh so nice";
+root["bar"][3] << "oh so nice (serialized)";
+CHECK(root["bar"].num_children() == 4);
+CHECK(root["bar"][2].val() == "oh so nice");
+CHECK(root["bar"][3].val() == "oh so nice (serialized)");
+// adding a seq node:
+CHECK(root.num_children() == 5);
+root["newseq"] |= ryml::SEQ;
+root.append_child() << ryml::key("newseq (serialized)") |= ryml::SEQ;
+CHECK(root.num_children() == 7);
+CHECK(root["newseq"].num_children() == 0);
+CHECK(root["newseq (serialized)"].num_children() == 0);
+// adding a map node:
+CHECK(root.num_children() == 7);
+root["newmap"] |= ryml::MAP;
+root.append_child() << ryml::key("newmap (serialized)") |= ryml::SEQ;
+CHECK(root.num_children() == 9);
+CHECK(root["newmap"].num_children() == 0);
+CHECK(root["newmap (serialized)"].num_children() == 0);
+// operator[] does not mutate the tree until the returned node is
+// written to.
+//
+// Until such time, the NodeRef object keeps in itself the required
+// information to write to the proper place in the tree. This is
+// called being in a "seed" state.
+//
+// This means that passing a key/index which does not exist will
+// not mutate the tree, but will instead store (in the node) the
+// proper place of the tree to do so if and when it is required.
+//
+// This is a significant difference from eg, the behavior of
+// std::map, which mutates the map immediately within the call to
+// operator[].
+CHECK(!root.has_child("I am nobody"));
+ryml::NodeRef nobody = root["I am nobody"];
+CHECK(nobody.valid());   // points at the tree, and a specific place in the tree
+CHECK(nobody.is_seed()); // ... but nothing is there yet.
+CHECK(!root.has_child("I am nobody")); // same as above
+ryml::NodeRef somebody = root["I am somebody"];
+CHECK(!root.has_child("I am somebody")); // same as above
+CHECK(somebody.valid());
+CHECK(somebody.is_seed()); // same as above
+somebody = "indeed";  // this will commit to the tree, mutating at the proper place
+CHECK(somebody.valid());
+CHECK(!somebody.is_seed()); // now the tree has this node, and it is no longer a seed
+CHECK(root.has_child("I am somebody"));
+CHECK(root["I am somebody"].val() == "indeed");
+
+
+//------------------------------------------------------------------
+// Emitting:
+
+// emit to a FILE*
+ryml::emit(tree, stdout);
+// emit to a stream
+std::stringstream ss;
+ss << tree;
+std::string stream_result = ss.str();
+// emit to a buffer:
+std::string str_result = ryml::emitrs<std::string>(tree);
+// can emit to any given buffer:
+char buf[1024];
+ryml::csubstr buf_result = ryml::emit(tree, buf);
+// now check
+ryml::csubstr expected_result = R"(foo: says who
+bar:
+- 20
+- 30
+- oh so nice
+- oh so nice (serialized)
+john: in_scope
+newkeyval: shiny and new
+newkeyval (serialized): shiny and new (serialized)
+newseq: []
+newseq (serialized): []
+newmap: {}
+newmap (serialized): []
+I am somebody: indeed
+)";
+CHECK(buf_result == expected_result);
+CHECK(str_result == expected_result);
+CHECK(stream_result == expected_result);
+// There are many possibilities to emit to buffer;
+// please look at the emit sample functions below.
+
+//------------------------------------------------------------------
+// Dealing with UTF8
+ryml::Tree langs = ryml::parse_in_arena(R"(
+en: Planet (Gas)
+fr: Planète (Gazeuse)
+ru: Планета (Газ)
+ja: 惑星(ガス)
+zh: 行星(气体)
+decode this: "\u263A \xE2\x98\xBA"
+and this as well: "\u2705 \U0001D11E"
+)");
+// in-place UTF8 just works:
+CHECK(langs["en"].val() == "Planet (Gas)");
+CHECK(langs["fr"].val() == "Planète (Gazeuse)");
+CHECK(langs["ru"].val() == "Планета (Газ)");
+CHECK(langs["ja"].val() == "惑星(ガス)");
+CHECK(langs["zh"].val() == "行星(气体)");
+// and \x \u \U codepoints are decoded (but only when
+// they appear inside double-quoted strings):
+CHECK(langs["decode this"].val() == "☺ ☺");
+CHECK(langs["and this as well"].val() == "✅ 𝄞");
+
+//------------------------------------------------------------------
+// Getting the location of nodes in the source:
+ryml::Parser parser;
+ryml::Tree tree2 = parser.parse_in_arena("expected.yml", expected_result);
+ryml::Location loc = parser.location(tree2["bar"][1]);
+CHECK(parser.location_contents(loc).begins_with("30"));
+CHECK(loc.line == 3u);
+CHECK(loc.col == 4u);
+// For further details in location tracking, refer to the sample function.
+```
+
+The [quickstart.cpp sample](./samples/quickstart.cpp) (from which the
+above overview was taken) has many more detailed examples, and should
+be your first port of call to find out any particular point about
+ryml's API. It is tested in the CI, and thus has the correct behavior.
+There you can find the following subjects being addressed:
+
+```c++
+sample_substr();               ///< about ryml's string views (from c4core)
+sample_parse_file();           ///< ready-to-go example of parsing a file from disk
+sample_parse_in_place();       ///< parse a mutable YAML source buffer
+sample_parse_in_arena();       ///< parse a read-only YAML source buffer
+sample_parse_reuse_tree();     ///< parse into an existing tree, maybe into a node
+sample_parse_reuse_parser();   ///< reuse an existing parser
+sample_parse_reuse_tree_and_parser(); ///< how to reuse existing trees and parsers
+sample_iterate_trees();        ///< visit individual nodes and iterate through trees
+sample_create_trees();         ///< programatically create trees
+sample_tree_arena();           ///< interact with the tree's serialization arena
+sample_fundamental_types();    ///< serialize/deserialize fundamental types
+sample_formatting();           ///< control formatting when serializing/deserializing
+sample_base64();               ///< encode/decode base64
+sample_user_scalar_types();    ///< serialize/deserialize scalar (leaf/string) types
+sample_user_container_types(); ///< serialize/deserialize container (map or seq) types
+sample_std_types();            ///< serialize/deserialize STL containers
+sample_emit_to_container();    ///< emit to memory, eg a string or vector-like container
+sample_emit_to_stream();       ///< emit to a stream, eg std::ostream
+sample_emit_to_file();         ///< emit to a FILE*
+sample_emit_nested_node();     ///< pick a nested node as the root when emitting
+sample_json();                 ///< JSON parsing and emitting: notes and constraints
+sample_anchors_and_aliases();  ///< deal with YAML anchors and aliases
+sample_tags();                 ///< deal with YAML type tags
+sample_docs();                 ///< deal with YAML docs
+sample_error_handler();        ///< set a custom error handler
+sample_global_allocator();     ///< set a global allocator for ryml
+sample_per_tree_allocator();   ///< set per-tree allocators
+sample_location_tracking();    ///< track node locations in the parsed source tree
+```
+
+
+------
+
+## Using ryml in your project
+
+### Package managers
+
+If you opt for package managers, here's where ryml is available so far
+(thanks to all the contributors!):
+  * [vcpkg](https://vcpkg.io/en/packages.html): `vcpkg install ryml`
+  * Arch Linux/Manjaro:
+    * [rapidyaml-git (AUR)](https://aur.archlinux.org/packages/rapidyaml-git/)
+    * [python-rapidyaml-git (AUR)](https://aur.archlinux.org/packages/python-rapidyaml-git/)
+  * [PyPI](https://pypi.org/project/rapidyaml/)
+
+Although package managers are very useful for quickly getting up to
+speed, the advised way is still to bring ryml as a submodule of your
+project, building both together. This makes it easy to track any
+upstream changes in ryml. Also, ryml is small and quick to build, so
+there's not much of a cost for building it with your project.
+
+### Single header file
+ryml is provided chiefly as a cmake library project, but it can also
+be used as a single header file, and there is a [tool to
+amalgamate](./tools/amalgamate.py) the code into a single header
+file. The amalgamated header file is provided with each release, but
+you can also generate a customized file suiting your particular needs
+(or commit):
+
+```console
+[user@host rapidyaml]$ python tools/amalgamate.py -h
+usage: amalgamate.py [-h] [--c4core | --no-c4core] [--fastfloat | --no-fastfloat] [--stl | --no-stl] [output]
+
+positional arguments:
+  output          output file. defaults to stdout
+
+optional arguments:
+  -h, --help      show this help message and exit
+  --c4core        amalgamate c4core together with ryml. this is the default.
+  --no-c4core     amalgamate c4core together with ryml. the default is --c4core.
+  --fastfloat     enable fastfloat library. this is the default.
+  --no-fastfloat  enable fastfloat library. the default is --fastfloat.
+  --stl           enable stl interop. this is the default.
+  --no-stl        enable stl interop. the default is --stl.
+```
+
+The amalgamated header file contains all the function declarations and
+definitions. To use it in the project, `#include` the header at will
+in any header or source file in the project, but in one source file,
+and only in that one source file, `#define` the macro
+`RYML_SINGLE_HDR_DEFINE_NOW` **before including the header**. This
+will enable the function definitions. For example:
+```c++
+// foo.h
+#include <ryml_all.hpp>
+
+// foo.cpp
+// ensure that foo.h is not included before this define!
+#define RYML_SINGLE_HDR_DEFINE_NOW
+#include <ryml_all.hpp>
+```
+
+If you wish to package the single header into a shared library, then
+you will need to define the preprocessor symbol `RYML_SHARED` during
+compilation.
+
+
+### As a library
+The single header file is a good approach to quickly try the library,
+but if you wish to make good use of CMake and its tooling ecosystem,
+(and get better compile times), then ryml has you covered.
+
+As with any other cmake library, you have the option to integrate ryml into
+your project's build setup, thereby building ryml together with your
+project, or -- prior to configuring your project -- you can have ryml
+installed either manually or through package managers.
+
+Currently [cmake](https://cmake.org/) is required to build ryml; we
+recommend a recent cmake version, at least 3.13.
+
+Note that ryml uses submodules. Take care to use the `--recursive` flag
+when cloning the repo, to ensure ryml's submodules are checked out as well:
+```bash
+git clone --recursive https://github.com/biojppm/rapidyaml
+```
+If you omit `--recursive`, after cloning you
+will have to do `git submodule init` and `git submodule update` 
+to ensure ryml's submodules are checked out.
+
+### Quickstart samples
+
+These samples show different ways of getting ryml into your application. All the
+samples use [the same quickstart executable
+source](./samples/quickstart.cpp), but are built in different ways,
+showing several alternatives to integrate ryml into your project. We
+also encourage you to refer to the [quickstart source](./samples/quickstart.cpp) itself, which
+extensively covers most of the functionality that you may want out of
+ryml.
+
+Each sample brings a `run.sh` script with the sequence of commands
+required to successfully build and run the application (this is a bash
+script and runs in Linux and MacOS, but it is also possible to run in
+Windows via Git Bash or the WSL). Click on the links below to find out
+more about each sample:
+
+| Sample name        | ryml is part of build?   | cmake file   | commands     |
+|:-------------------|--------------------------|:-------------|:-------------|
+| [`singleheader`](./samples/singleheader) | **yes**<br>ryml brought as a single header file,<br>not as a library | [`CMakeLists.txt`](./samples/singleheader/CMakeLists.txt) | [`run.sh`](./samples/singleheader/run.sh) |
+| [`singleheaderlib`](./samples/singleheaderlib) | **yes**<br>ryml brought as a library<br>but from the single header file | [`CMakeLists.txt`](./samples/singleheaderlib/CMakeLists.txt) | [`run_shared.sh` (shared library)](./samples/singleheaderlib/run_shared.sh)<br> [`run_static.sh` (static library)](./samples/singleheaderlib/run_static.sh) |
+| [`add_subdirectory`](./samples/add_subdirectory) | **yes**                      | [`CMakeLists.txt`](./samples/add_subdirectory/CMakeLists.txt) | [`run.sh`](./samples/add_subdirectory/run.sh) |
+| [`fetch_content`](./samples/fetch_content)      | **yes**                      | [`CMakeLists.txt`](./samples/fetch_content/CMakeLists.txt) | [`run.sh`](./samples/fetch_content/run.sh) |
+| [`find_package`](./samples/find_package)        | **no**<br>needs prior install or package  | [`CMakeLists.txt`](./samples/find_package/CMakeLists.txt) | [`run.sh`](./samples/find_package/run.sh) |
+
+### CMake build settings for ryml
+The following cmake variables can be used to control the build behavior of
+ryml:
+
+  * `RYML_WITH_TAB_TOKENS=ON/OFF`. Enable/disable support for tabs as
+    valid container tokens after `:` and `-`. Defaults to `OFF`,
+    because this may cost up to 10% in processing time.
+  * `RYML_DEFAULT_CALLBACKS=ON/OFF`. Enable/disable ryml's default
+    implementation of error and allocation callbacks. Defaults to `ON`.
+  * `RYML_STANDALONE=ON/OFF`. ryml uses
+    [c4core](https://github.com/biojppm/c4core), a C++ library with low-level
+    multi-platform utilities for C++. When `RYML_STANDALONE=ON`, c4core is
+    incorporated into ryml as if it is the same library. Defaults to `ON`.
+
+If you're developing ryml or just debugging problems with ryml itself, the
+following cmake variables can be helpful:
+  * `RYML_DEV=ON/OFF`: a bool variable which enables development targets such as
+    unit tests, benchmarks, etc. Defaults to `OFF`.
+  * `RYML_DBG=ON/OFF`: a bool variable which enables verbose prints from
+    parsing code; can be useful to figure out parsing problems. Defaults to
+    `OFF`.
+
+#### Forcing ryml to use a different c4core version
+
+ryml is strongly coupled to c4core, and this is reinforced by the fact
+that c4core is a submodule of the current repo. However, it is still
+possible to use a c4core version different from the one in the repo
+(of course, only if there are no incompatibilities between the
+versions). You can find out how to achieve this by looking at the
+[`custom_c4core` sample](./samples/custom_c4core/CMakeLists.txt).
+
+
+------
+
+## Other languages
+
+One of the aims of ryml is to provide an efficient YAML API for other
+languages. JavaScript is fully available, and there is already a
+cursory implementation for Python using only the low-level API. After
+ironing out the general approach, other languages are likely to
+follow (all of this is possible because we're using
+[SWIG](http://www.swig.org/), which makes it easy to do so).
+
+### JavaScript
+
+A JavaScript+WebAssembly port is available, compiled through [emscripten](https://emscripten.org/).
+
+
+### Python
+
+(Note that this is a work in progress. Additions will be made and things will
+be changed.) With that said, here's an example of the Python API:
+
+```python
+import ryml
+
+# ryml cannot accept strings because it does not take ownership of the
+# source buffer; only bytes or bytearrays are accepted.
+src = b"{HELLO: a, foo: b, bar: c, baz: d, seq: [0, 1, 2, 3]}"
+
+def check(tree):
+    # for now, only the index-based low-level API is implemented
+    assert tree.size() == 10
+    assert tree.root_id() == 0
+    assert tree.first_child(0) == 1
+    assert tree.next_sibling(1) == 2
+    assert tree.first_sibling(5) == 2
+    assert tree.last_sibling(1) == 5
+    # use bytes objects for queries
+    assert tree.find_child(0, b"foo") == 1
+    assert tree.key(1) == b"foo")
+    assert tree.val(1) == b"b")
+    assert tree.find_child(0, b"seq") == 5
+    assert tree.is_seq(5)
+    # to loop over children:
+    for i, ch in enumerate(ryml.children(tree, 5)):
+        assert tree.val(ch) == [b"0", b"1", b"2", b"3"][i]
+    # to loop over siblings:
+    for i, sib in enumerate(ryml.siblings(tree, 5)):
+        assert tree.key(sib) == [b"HELLO", b"foo", b"bar", b"baz", b"seq"][i]
+    # to walk over all elements
+    visited = [False] * tree.size()
+    for n, indentation_level in ryml.walk(tree):
+        # just a dumb emitter
+        left = "  " * indentation_level
+        if tree.is_keyval(n):
+           print("{}{}: {}".format(left, tree.key(n), tree.val(n))
+        elif tree.is_val(n):
+           print("- {}".format(left, tree.val(n))
+        elif tree.is_keyseq(n):
+           print("{}{}:".format(left, tree.key(n))
+        visited[inode] = True
+    assert False not in visited
+    # NOTE about encoding!
+    k = tree.get_key(5)
+    print(k)  # '<memory at 0x7f80d5b93f48>'
+    assert k == b"seq"               # ok, as expected
+    assert k != "seq"                # not ok - NOTE THIS! 
+    assert str(k) != "seq"           # not ok
+    assert str(k, "utf8") == "seq"   # ok again
+
+# parse immutable buffer
+tree = ryml.parse(src)
+check(tree) # OK
+
+# also works, but requires bytearrays or
+# objects offering writeable memory
+mutable = bytearray(src)
+tree = ryml.parse_in_place(mutable)
+check(tree) # OK
+```
+
+As expected, the performance results so far are encouraging. In
+a [timeit benchmark](api/python/parse_bm.py) compared
+against [PyYaml](https://pyyaml.org/)
+and [ruamel.yaml](https://yaml.readthedocs.io/en/latest/), ryml parses
+quicker by a factor of 30x-50x:
+
+```
++-----------------------+-------+----------+---------+----------------+
+| case                  | iters | time(ms) | avg(ms) | avg_read(MB/s) |
++-----------------------+-------+----------+---------+----------------+
+| parse:RuamelYaml      |    88 | 800.483  |  9.096  |      0.234     |
+| parse:PyYaml          |    88 | 541.370  |  6.152  |      0.346     |
+| parse:RymlRo          |  3888 | 776.020  |  0.200  |     10.667     |
+| parse:RymlRoReuse     |  1888 | 381.558  |  0.202  |     10.535     |
+| parse:RymlRw          |  3888 | 775.121  |  0.199  |     10.679     |
+| parse:RymlRwReuse     |  3888 | 774.534  |  0.199  |     10.687     |
++-----------------------+-------+----------+---------+----------------+
+```
+
+(Note that the results above are somewhat biased towards ryml, because it does
+not perform any type conversions: return types are merely `memoryviews` to
+the source buffer.)
+
+
+------
+
+## YAML standard conformance
+
+ryml is close to feature complete. Most of the YAML features are well
+covered in the unit tests, and expected to work, unless in the
+exceptions noted below.
+
+Of course, there are many dark corners in YAML, and there certainly
+can appear cases which ryml fails to parse. Your [bug reports or pull
+requests](https://github.com/biojppm/rapidyaml/issues) are very
+welcome.
+
+See also [the roadmap](./ROADMAP.md) for a list of future work.
+
+
+### Known limitations
+
+ryml deliberately makes no effort to follow the standard in the following situations:
+
+* Containers are not accepted as mapping keys: keys must be scalars.
+* Tab characters after `:` and `-` are not accepted tokens, unless
+  ryml is compiled with the macro `RYML_WITH_TAB_TOKENS`. This
+  requirement exists because checking for tabs introduces branching
+  into the parser's hot code and in some cases costs as much as 10%
+  in parsing time.
+* Anchor names must not end with a terminating colon: eg `&anchor: key: val`.
+* `%YAML` directives have no effect and are ignored.
+* `%TAG` directives are limited to a default maximum of 4 instances
+  per `Tree`. To increase this maximum, define the preprocessor symbol
+  `RYML_MAX_TAG_DIRECTIVES` to a suitable value. This arbitrary limit
+  reflects the usual practice of having at most 1 or 2 tag directives;
+  also, be aware that this feature is under consideration for removal
+  in YAML 1.3.
+
+Also, ryml tends to be on the permissive side where the YAML standard
+dictates there should be an error; in many of these cases, ryml will
+tolerate the input. This may be good or bad, but in any case is being
+improved on (meaning ryml will grow progressively less tolerant of
+YAML errors in the coming releases). So we strongly suggest to stay
+away from those dark corners of YAML which are generally a source of
+problems, which is a good practice anyway.
+
+If you do run into trouble and would like to investigate conformance
+of your YAML code, beware of existing online YAML linters, many of
+which are not fully conformant; instead, try using
+[https://play.yaml.io](https://play.yaml.io), an amazing tool which
+lets you dynamically input your YAML and continuously see the results
+from all the existing parsers (kudos to @ingydotnet and the people
+from the YAML test suite). And of course, if you detect anything wrong
+with ryml, please [open an
+issue](https://github.com/biojppm/rapidyaml/issues) so that we can
+improve.
+
+
+### Test suite status
+
+As part of its CI testing, ryml uses the [YAML test
+suite](https://github.com/yaml/yaml-test-suite). This is an extensive
+set of reference cases covering the full YAML spec. Each of these
+cases have several subparts:
+ * `in-yaml`: mildly, plainly or extremely difficult-to-parse YAML
+ * `in-json`: equivalent JSON (where possible/meaningful)
+ * `out-yaml`: equivalent standard YAML
+ * `emit-yaml`: equivalent standard YAML
+ * `events`: reference results (ie, expected tree)
+
+When testing, ryml parses each of the 4 yaml/json parts, then emits
+the parsed tree, then parses the emitted result and verifies that
+emission is idempotent, ie that the emitted result is semantically the
+same as its input without any loss of information. To ensure
+consistency, this happens over four levels of parse/emission
+pairs. And to ensure correctness, each of the stages is compared
+against the `events` spec from the test, which constitutes the
+reference. The tests also check for equality between the reference
+events in the test case and the events emitted by ryml from the data
+tree parsed from the test case input. All of this is then carried out
+combining several variations: both unix `\n` vs windows `\r\n` line
+endings, emitting to string, file or streams, which results in ~250
+tests per case part. With multiple parts per case and ~400 reference
+cases in the test suite, this makes over several hundred thousand
+individual tests to which ryml is subjected, which are added to the
+unit tests in ryml, which also employ the same extensive
+combinatorial approach.
+
+Also, note that in [their own words](http://matrix.yaml.io/), the
+tests from the YAML test suite *contain a lot of edge cases that don't
+play such an important role in real world examples*. And yet, despite
+the extreme focus of the test suite, currently ryml only fails a minor
+fraction of the test cases, mostly related with the deliberate
+limitations noted above. Other than those limitations, by far the main
+issue with ryml is that several standard-mandated parse errors fail to
+materialize. For the up-to-date list of ryml failures in the
+test-suite, refer to the [list of known
+exceptions](test/test_suite/test_suite_parts.cpp) from ryml's test
+suite runner, which is used as part of ryml's CI process.
+
+
+------
+
+## Alternative libraries
+
+Why this library? Because none of the existing libraries was quite
+what I wanted. When I started this project in 2018, I was aware of these two
+alternative C/C++ libraries:
+
+  * [libyaml](https://github.com/yaml/libyaml). This is a bare C
+    library. It does not create a representation of the data tree, so
+    I don't see it as practical. My initial idea was to wrap parsing
+    and emitting around libyaml's convenient event handling, but to my
+    surprise I found out it makes heavy use of allocations and string
+    duplications when parsing. I briefly pondered on sending PRs to
+    reduce these allocation needs, but not having a permanent tree to
+    store the parsed data was too much of a downside.
+  * [yaml-cpp](https://github.com/jbeder/yaml-cpp). This library may
+    be full of functionality, but is heavy on the use of
+    node-pointer-based structures like `std::map`, allocations, string
+    copies, polymorphism and slow C++ stream serializations. This is
+    generally a sure way of making your code slower, and strong
+    evidence of this can be seen in the benchmark results above.
+
+Recently [libfyaml](https://github.com/pantoniou/libfyaml)
+appeared. This is a newer C library, fully conformant to the YAML
+standard with an amazing 100% success in the test suite; it also offers
+the tree as a data structure. As a downside, it does not work in
+Windows, and it is also multiple times slower parsing and emitting.
+
+When performance and low latency are important, using contiguous
+structures for better cache behavior and to prevent the library from
+trampling caches, parsing in place and using non-owning strings is of
+central importance. Hence this Rapid YAML library which, with minimal
+compromise, bridges the gap from efficiency to usability. This library
+takes inspiration from
+[RapidJSON](https://github.com/Tencent/rapidjson) and
+[RapidXML](http://rapidxml.sourceforge.net/).
+
+------
+## License
+
+ryml is permissively licensed under the [MIT license](LICENSE.txt).
+ 

+ 18 - 0
3rdparty/rapidyaml/ROADMAP.md

@@ -0,0 +1,18 @@
+# Roadmap
+
+Roughly in order of priority:
+
+  * Cleanup:
+    * Review & cleanup API surface.
+    * Turn calls to `C4_ASSERT()` into calls to `RYML_ASSERT()`
+  * Add emit formatting controls:
+    * add single-line flow formatter
+    * add multi-line flow formatters
+      * indenting
+      * non indenting
+    * keep current block formatter
+    * add customizable linebreak limits (number of columns) to every formatter
+    * add per node format flags
+    * (lesser priority) add auto formatter using reasonable heuristics to
+      switch between other existing formatters
+  * Investigate possibility of comment-preserving roundtrips

+ 7 - 0
3rdparty/rapidyaml/c4core.dir/Debug/c4core.lib.recipe

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project>
+  <ProjectOutputs />
+  <ContentFiles />
+  <SatelliteDlls />
+  <NonRecipeFileRefs />
+</Project>

+ 0 - 0
3rdparty/rapidyaml/c4core.dir/Debug/c4core.vcxproj.FileListAbsolute.txt


+ 294 - 0
3rdparty/rapidyaml/c4core.vcxproj

@@ -0,0 +1,294 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="16.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|Win32">
+      <Configuration>Debug</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|Win32">
+      <Configuration>Release</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <PropertyGroup Label="Globals">
+    <ProjectGuid>{B795DCB1-2FEA-3BDC-A05C-33F5BF08CF31}</ProjectGuid>
+    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
+    <Keyword>Win32Proj</Keyword>
+    <ProjectName>c4core</ProjectName>
+    <VCProjectUpgraderObjectName>NoUpgrade</VCProjectUpgraderObjectName>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+    <ConfigurationType>StaticLibrary</ConfigurationType>
+    <CharacterSet>MultiByte</CharacterSet>
+    <PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+    <ConfigurationType>StaticLibrary</ConfigurationType>
+    <CharacterSet>MultiByte</CharacterSet>
+    <PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+    <ConfigurationType>StaticLibrary</ConfigurationType>
+    <CharacterSet>MultiByte</CharacterSet>
+    <PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+    <ConfigurationType>StaticLibrary</ConfigurationType>
+    <CharacterSet>MultiByte</CharacterSet>
+    <PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup>
+    <_ProjectFileVersion>10.0.20506.1</_ProjectFileVersion>
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(SolutionDir).vs\build\</OutDir>
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(SolutionDir).vs\build\$(ProjectName)\$(Platform)\$(Configuration)\</IntDir>
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(SolutionDir).vs\build\$(ProjectName)\$(Platform)\$(Configuration)\</IntDir>
+    <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(ProjectName)</TargetName>
+    <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(ProjectName)</TargetName>
+    <TargetExt Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.lib</TargetExt>
+    <TargetExt Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">.lib</TargetExt>
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(SolutionDir).vs\build\</OutDir>
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(SolutionDir).vs\build\$(ProjectName)\$(Platform)\$(Configuration)\</IntDir>
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(SolutionDir).vs\build\$(ProjectName)\$(Platform)\$(Configuration)\</IntDir>
+    <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(ProjectName)</TargetName>
+    <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(ProjectName)</TargetName>
+    <TargetExt Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.lib</TargetExt>
+    <TargetExt Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">.lib</TargetExt>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <OutDir>$(SolutionDir).vs\build\</OutDir>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <OutDir>$(SolutionDir).vs\build\</OutDir>
+  </PropertyGroup>
+  <PropertyGroup Label="Vcpkg">
+    <VcpkgEnabled>false</VcpkgEnabled>
+  </PropertyGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <ClCompile>
+      <AdditionalIncludeDirectories>ext\c4core\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
+      <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+      <CompileAs>CompileAsCpp</CompileAs>
+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+      <ExceptionHandling>Sync</ExceptionHandling>
+      <InlineFunctionExpansion>Disabled</InlineFunctionExpansion>
+      <Optimization>Disabled</Optimization>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+      <RuntimeTypeInfo>true</RuntimeTypeInfo>
+      <UseFullPaths>false</UseFullPaths>
+      <WarningLevel>Level3</WarningLevel>
+      <PreprocessorDefinitions>WIN32;_WINDOWS;CMAKE_INTDIR="Debug";%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ObjectFileName>$(IntDir)</ObjectFileName>
+      <LanguageStandard>stdcpp17</LanguageStandard>
+    </ClCompile>
+    <ResourceCompile>
+      <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;CMAKE_INTDIR=\"Debug\";%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <AdditionalIncludeDirectories>ext\c4core\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+    </ResourceCompile>
+    <Midl>
+      <AdditionalIncludeDirectories>ext\c4core\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <OutputDirectory>$(ProjectDir)/$(IntDir)</OutputDirectory>
+      <HeaderFileName>%(Filename).h</HeaderFileName>
+      <TypeLibraryName>%(Filename).tlb</TypeLibraryName>
+      <InterfaceIdentifierFileName>%(Filename)_i.c</InterfaceIdentifierFileName>
+      <ProxyFileName>%(Filename)_p.c</ProxyFileName>
+    </Midl>
+    <Lib>
+      <AdditionalOptions>%(AdditionalOptions) /machine:x64</AdditionalOptions>
+    </Lib>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <ClCompile>
+      <AdditionalIncludeDirectories>ext\c4core\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
+      <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+      <CompileAs>CompileAsCpp</CompileAs>
+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+      <ExceptionHandling>Sync</ExceptionHandling>
+      <InlineFunctionExpansion>Disabled</InlineFunctionExpansion>
+      <Optimization>Disabled</Optimization>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+      <RuntimeTypeInfo>true</RuntimeTypeInfo>
+      <UseFullPaths>false</UseFullPaths>
+      <WarningLevel>Level3</WarningLevel>
+      <PreprocessorDefinitions>WIN32;_WINDOWS;CMAKE_INTDIR="Debug";%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ObjectFileName>$(IntDir)</ObjectFileName>
+      <LanguageStandard>stdcpp17</LanguageStandard>
+    </ClCompile>
+    <ResourceCompile>
+      <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;CMAKE_INTDIR=\"Debug\";%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <AdditionalIncludeDirectories>ext\c4core\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+    </ResourceCompile>
+    <Midl>
+      <AdditionalIncludeDirectories>ext\c4core\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <OutputDirectory>$(ProjectDir)/$(IntDir)</OutputDirectory>
+      <HeaderFileName>%(Filename).h</HeaderFileName>
+      <TypeLibraryName>%(Filename).tlb</TypeLibraryName>
+      <InterfaceIdentifierFileName>%(Filename)_i.c</InterfaceIdentifierFileName>
+      <ProxyFileName>%(Filename)_p.c</ProxyFileName>
+    </Midl>
+    <Lib>
+      <AdditionalOptions>%(AdditionalOptions) /machine:x86</AdditionalOptions>
+    </Lib>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <ClCompile>
+      <AdditionalIncludeDirectories>ext\c4core\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
+      <CompileAs>CompileAsCpp</CompileAs>
+      <ExceptionHandling>Sync</ExceptionHandling>
+      <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>
+      <Optimization>MaxSpeed</Optimization>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+      <RuntimeTypeInfo>true</RuntimeTypeInfo>
+      <UseFullPaths>false</UseFullPaths>
+      <WarningLevel>Level3</WarningLevel>
+      <PreprocessorDefinitions>WIN32;_WINDOWS;NDEBUG;CMAKE_INTDIR="Release";%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ObjectFileName>$(IntDir)</ObjectFileName>
+      <DebugInformationFormat>
+      </DebugInformationFormat>
+      <LanguageStandard>stdcpp17</LanguageStandard>
+    </ClCompile>
+    <ResourceCompile>
+      <PreprocessorDefinitions>WIN32;_WINDOWS;NDEBUG;CMAKE_INTDIR=\"Release\";%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <AdditionalIncludeDirectories>ext\c4core\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+    </ResourceCompile>
+    <Midl>
+      <AdditionalIncludeDirectories>ext\c4core\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <OutputDirectory>$(ProjectDir)/$(IntDir)</OutputDirectory>
+      <HeaderFileName>%(Filename).h</HeaderFileName>
+      <TypeLibraryName>%(Filename).tlb</TypeLibraryName>
+      <InterfaceIdentifierFileName>%(Filename)_i.c</InterfaceIdentifierFileName>
+      <ProxyFileName>%(Filename)_p.c</ProxyFileName>
+    </Midl>
+    <Lib>
+      <AdditionalOptions>%(AdditionalOptions) /machine:x64</AdditionalOptions>
+    </Lib>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <ClCompile>
+      <AdditionalIncludeDirectories>ext\c4core\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
+      <CompileAs>CompileAsCpp</CompileAs>
+      <ExceptionHandling>Sync</ExceptionHandling>
+      <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>
+      <Optimization>MaxSpeed</Optimization>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+      <RuntimeTypeInfo>true</RuntimeTypeInfo>
+      <UseFullPaths>false</UseFullPaths>
+      <WarningLevel>Level3</WarningLevel>
+      <PreprocessorDefinitions>WIN32;_WINDOWS;NDEBUG;CMAKE_INTDIR="Release";%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ObjectFileName>$(IntDir)</ObjectFileName>
+      <DebugInformationFormat>
+      </DebugInformationFormat>
+      <LanguageStandard>stdcpp17</LanguageStandard>
+    </ClCompile>
+    <ResourceCompile>
+      <PreprocessorDefinitions>WIN32;_WINDOWS;NDEBUG;CMAKE_INTDIR=\"Release\";%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <AdditionalIncludeDirectories>ext\c4core\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+    </ResourceCompile>
+    <Midl>
+      <AdditionalIncludeDirectories>ext\c4core\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <OutputDirectory>$(ProjectDir)/$(IntDir)</OutputDirectory>
+      <HeaderFileName>%(Filename).h</HeaderFileName>
+      <TypeLibraryName>%(Filename).tlb</TypeLibraryName>
+      <InterfaceIdentifierFileName>%(Filename)_i.c</InterfaceIdentifierFileName>
+      <ProxyFileName>%(Filename)_p.c</ProxyFileName>
+    </Midl>
+    <Lib>
+      <AdditionalOptions>%(AdditionalOptions) /machine:x86</AdditionalOptions>
+    </Lib>
+  </ItemDefinitionGroup>
+  <ItemGroup>
+    <ClInclude Include="ext\c4core\src\c4\allocator.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\base64.hpp" />
+    <ClCompile Include="ext\c4core\src\c4\base64.cpp" />
+    <ClInclude Include="ext\c4core\src\c4\blob.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\bitmask.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\charconv.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\c4_pop.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\c4_push.hpp" />
+    <ClCompile Include="ext\c4core\src\c4\char_traits.cpp" />
+    <ClInclude Include="ext\c4core\src\c4\char_traits.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\common.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\compiler.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\config.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\cpu.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\ctor_dtor.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\dump.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\enum.hpp" />
+    <ClCompile Include="ext\c4core\src\c4\error.cpp" />
+    <ClInclude Include="ext\c4core\src\c4\error.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\export.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\format.hpp" />
+    <ClCompile Include="ext\c4core\src\c4\format.cpp" />
+    <ClInclude Include="ext\c4core\src\c4\hash.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\language.hpp" />
+    <ClCompile Include="ext\c4core\src\c4\language.cpp" />
+    <ClCompile Include="ext\c4core\src\c4\memory_resource.cpp" />
+    <ClInclude Include="ext\c4core\src\c4\memory_resource.hpp" />
+    <ClCompile Include="ext\c4core\src\c4\memory_util.cpp" />
+    <ClInclude Include="ext\c4core\src\c4\memory_util.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\platform.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\preprocessor.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\restrict.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\span.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\std\std.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\std\std_fwd.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\std\string.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\std\string_fwd.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\std\tuple.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\std\vector.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\std\vector_fwd.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\substr.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\substr_fwd.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\szconv.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\time.hpp" />
+    <ClCompile Include="ext\c4core\src\c4\time.cpp" />
+    <ClInclude Include="ext\c4core\src\c4\type_name.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\types.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\unrestrict.hpp" />
+    <ClCompile Include="ext\c4core\src\c4\utf.cpp" />
+    <ClInclude Include="ext\c4core\src\c4\utf.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\windows.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\windows_pop.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\windows_push.hpp" />
+    <Natvis Include="ext\c4core\src\c4\c4core.natvis" />
+    <ClInclude Include="ext\c4core\src\c4\ext\debugbreak\debugbreak.h" />
+    <ClInclude Include="ext\c4core\src\c4\ext\rng\rng.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\ext\sg14\inplace_function.h" />
+    <ClInclude Include="ext\c4core\src\c4\ext\fast_float.hpp" />
+    <ClInclude Include="ext\c4core\src\c4\ext\fast_float\include\fast_float\ascii_number.h" />
+    <ClInclude Include="ext\c4core\src\c4\ext\fast_float\include\fast_float\bigint.h" />
+    <ClInclude Include="ext\c4core\src\c4\ext\fast_float\include\fast_float\decimal_to_binary.h" />
+    <ClInclude Include="ext\c4core\src\c4\ext\fast_float\include\fast_float\digit_comparison.h" />
+    <ClInclude Include="ext\c4core\src\c4\ext\fast_float\include\fast_float\fast_float.h" />
+    <ClInclude Include="ext\c4core\src\c4\ext\fast_float\include\fast_float\fast_table.h" />
+    <ClInclude Include="ext\c4core\src\c4\ext\fast_float\include\fast_float\float_common.h" />
+    <ClInclude Include="ext\c4core\src\c4\ext\fast_float\include\fast_float\parse_number.h" />
+    <ClInclude Include="ext\c4core\src\c4\ext\fast_float\include\fast_float\simple_decimal_conversion.h" />
+  </ItemGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Label="ExtensionTargets">
+  </ImportGroup>
+</Project>

+ 239 - 0
3rdparty/rapidyaml/c4core.vcxproj.filters

@@ -0,0 +1,239 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="16.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+    <ClCompile Include="ext\c4core\src\c4\base64.cpp">
+      <Filter>c4</Filter>
+    </ClCompile>
+    <ClCompile Include="ext\c4core\src\c4\char_traits.cpp">
+      <Filter>c4</Filter>
+    </ClCompile>
+    <ClCompile Include="ext\c4core\src\c4\error.cpp">
+      <Filter>c4</Filter>
+    </ClCompile>
+    <ClCompile Include="ext\c4core\src\c4\format.cpp">
+      <Filter>c4</Filter>
+    </ClCompile>
+    <ClCompile Include="ext\c4core\src\c4\language.cpp">
+      <Filter>c4</Filter>
+    </ClCompile>
+    <ClCompile Include="ext\c4core\src\c4\memory_resource.cpp">
+      <Filter>c4</Filter>
+    </ClCompile>
+    <ClCompile Include="ext\c4core\src\c4\memory_util.cpp">
+      <Filter>c4</Filter>
+    </ClCompile>
+    <ClCompile Include="ext\c4core\src\c4\time.cpp">
+      <Filter>c4</Filter>
+    </ClCompile>
+    <ClCompile Include="ext\c4core\src\c4\utf.cpp">
+      <Filter>c4</Filter>
+    </ClCompile>
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="ext\c4core\src\c4\allocator.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\base64.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\blob.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\bitmask.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\charconv.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\c4_pop.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\c4_push.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\char_traits.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\common.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\compiler.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\config.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\cpu.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\ctor_dtor.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\dump.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\enum.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\error.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\export.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\format.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\hash.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\language.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\memory_resource.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\memory_util.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\platform.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\preprocessor.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\restrict.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\span.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\std\std.hpp">
+      <Filter>c4\std</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\std\std_fwd.hpp">
+      <Filter>c4\std</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\std\string.hpp">
+      <Filter>c4\std</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\std\string_fwd.hpp">
+      <Filter>c4\std</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\std\tuple.hpp">
+      <Filter>c4\std</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\std\vector.hpp">
+      <Filter>c4\std</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\std\vector_fwd.hpp">
+      <Filter>c4\std</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\substr.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\substr_fwd.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\szconv.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\time.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\type_name.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\types.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\unrestrict.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\utf.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\windows.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\windows_pop.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\windows_push.hpp">
+      <Filter>c4</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\ext\debugbreak\debugbreak.h">
+      <Filter>c4\ext\debugbreak</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\ext\rng\rng.hpp">
+      <Filter>c4\ext\rng</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\ext\sg14\inplace_function.h">
+      <Filter>c4\ext\sg14</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\ext\fast_float.hpp">
+      <Filter>c4\ext</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\ext\fast_float\include\fast_float\ascii_number.h">
+      <Filter>c4\ext\fast_float\include\fast_float</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\ext\fast_float\include\fast_float\bigint.h">
+      <Filter>c4\ext\fast_float\include\fast_float</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\ext\fast_float\include\fast_float\decimal_to_binary.h">
+      <Filter>c4\ext\fast_float\include\fast_float</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\ext\fast_float\include\fast_float\digit_comparison.h">
+      <Filter>c4\ext\fast_float\include\fast_float</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\ext\fast_float\include\fast_float\fast_float.h">
+      <Filter>c4\ext\fast_float\include\fast_float</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\ext\fast_float\include\fast_float\fast_table.h">
+      <Filter>c4\ext\fast_float\include\fast_float</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\ext\fast_float\include\fast_float\float_common.h">
+      <Filter>c4\ext\fast_float\include\fast_float</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\ext\fast_float\include\fast_float\parse_number.h">
+      <Filter>c4\ext\fast_float\include\fast_float</Filter>
+    </ClInclude>
+    <ClInclude Include="ext\c4core\src\c4\ext\fast_float\include\fast_float\simple_decimal_conversion.h">
+      <Filter>c4\ext\fast_float\include\fast_float</Filter>
+    </ClInclude>
+  </ItemGroup>
+  <ItemGroup>
+    <Natvis Include="ext\c4core\src\c4\c4core.natvis">
+      <Filter>c4</Filter>
+    </Natvis>
+  </ItemGroup>
+  <ItemGroup>
+    <Filter Include="c4">
+      <UniqueIdentifier>{D96EB7E9-E07F-31EF-8A8C-CDA60AEE417F}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="c4\ext">
+      <UniqueIdentifier>{0C8CED5D-8C03-3DC1-88A0-4532154293B9}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="c4\ext\debugbreak">
+      <UniqueIdentifier>{518E0C6D-105B-39F9-8193-356D9B7641CB}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="c4\ext\fast_float">
+      <UniqueIdentifier>{73394C47-97A1-34EE-A081-D6003F63E488}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="c4\ext\fast_float\include">
+      <UniqueIdentifier>{1B43ABC9-6254-3DF0-A9A5-86BCF6FEA126}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="c4\ext\fast_float\include\fast_float">
+      <UniqueIdentifier>{6328648E-A574-3A0D-8E0E-A2AA13716FDA}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="c4\ext\rng">
+      <UniqueIdentifier>{2A97DBBA-1057-3E33-AE1C-C4CF7DBD4D89}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="c4\ext\sg14">
+      <UniqueIdentifier>{4104B22C-27F9-38F8-9E20-00B05D09BD39}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="c4\std">
+      <UniqueIdentifier>{599BE36A-381B-3D35-9752-B177B3AA1E4D}</UniqueIdentifier>
+    </Filter>
+  </ItemGroup>
+</Project>

+ 44 - 0
3rdparty/rapidyaml/changelog/0.1.0.md

@@ -0,0 +1,44 @@
+This is the first ryml release. Future releases will have a more organized changelog; for now, only recent major changes are listed.
+
+Please be aware that there are still some anticipated breaking changes in the API before releasing the 1.0 major version. These are highlighted in [the repo ROADMAP](https://github.com/biojppm/rapidyaml/blob/v0.1.0/ROADMAP.md).
+
+* 2020/October
+  * [MR#89](https://github.com/biojppm/rapidyaml/pull/89):
+     * fix python API generation in windows
+     * use github actions for testing and releasing
+  * [MR#88](https://github.com/biojppm/rapidyaml/pull/88): [fix MacOS compilation and installs](https://github.com/biojppm/rapidyaml/issues/75). This is a fix from [c4core](https://github.com/biojppm/cmake/issues/1).
+  * [MR#88](https://github.com/biojppm/rapidyaml/pull/88): [fix boolean handling](https://github.com/biojppm/rapidyaml/issues/74). This is a fix from [c4core](https://github.com/biojppm/c4core/pull/18/).  `true` and  `false` are now parsed correctly into `bool` variables:
+    ```c++
+    auto tree = parse("{foo: true, bar: false}");
+    ```
+    Emitting `bool` variables still defaults to `0`/`1`, like the default behaviour in the STL. To explicitly request `true`/`false` use `c4::fmt::boolalpha()`:
+    ```c++
+    node << var;                     // "1"    or "0"
+    node << c4::fmt::boolalpha(var); // "true" or "false"
+    ```
+* 2020/September
+  * [***Breaking change***] [MR#85](https://github.com/biojppm/rapidyaml/pull/85) null values in YAML are now parsed to null strings instead of YAML null token "~":
+    ```c++
+    auto tree = parse("{foo: , bar: ''}");
+    // previous:
+    assert(tree["foo"].val() == "~");
+    assert(tree["bar"].val() == "");
+    // now:
+    assert(tree["foo"].val() == nullptr); // notice that this is now null
+    assert(tree["bar"].val() == "");
+    ```
+  * [MR#85](https://github.com/biojppm/rapidyaml/pull/85) Commas after tags are now allowed:
+    ```yaml
+    {foo: !!str, bar: ''}  # now the comma does not cause an error
+    ```
+  * [MR#81](https://github.com/biojppm/rapidyaml/pull/81): Always compile with extra pedantic warnings.
+* 2020/May
+  *  [***Breaking change***] the error callback now receives a source location object:
+    ```c++
+    // previous
+    using pfn_error = void (*)(const char* msg, size_t msg_len, void *user_data);
+    // now:
+    using pfn_error = void (*)(const char* msg, size_t msg_len, Location location, void *user_data);
+    ```
+  * Parser fixes to improve test suite success: [MR#73](https://github.com/biojppm/rapidyaml/pull/73), [MR#71](https://github.com/biojppm/rapidyaml/pull/71), [MR#68](https://github.com/biojppm/rapidyaml/pull/68), [MR#67](https://github.com/biojppm/rapidyaml/pull/67), [MR#66](https://github.com/biojppm/rapidyaml/pull/66)
+  * Fix compilation as DLL on windows [MR#69](https://github.com/biojppm/rapidyaml/pull/69)

+ 29 - 0
3rdparty/rapidyaml/changelog/0.2.0.md

@@ -0,0 +1,29 @@
+### New features & improvements
+- Enable parsing into nested nodes ([87f4184](https://github.com/biojppm/rapidyaml/commit/87f4184))
+- `as_json()` can now be called with tree and node id ([4c23041](https://github.com/biojppm/rapidyaml/commit/4c23041))
+- Add `Parser::reserve_stack()` ([f31fb9f](https://github.com/biojppm/rapidyaml/commit/f31fb9f))
+- Add uninstall target ([PR #122](https://github.com/biojppm/rapidyaml/pull/122))
+- Update [c4core](https://github.com/biojppm/c4core) to v0.1.1
+- Add a [quickstart sample](samples/quickstart.cpp) with build examples.
+- Update [README.md](README.md) to refer to the quickstart
+- Add [gdb visualizers](src/ryml-gdbtypes.py)
+- Add `SO_VERSION` to shared builds
+
+### Fixes
+- Fix [#139](https://github.com/biojppm/rapidyaml/issues/139): substr and csubstr not found in ryml namespace
+- Fix [#131](https://github.com/biojppm/rapidyaml/issues/131): resolve references to map keys
+- Fix [#129](https://github.com/biojppm/rapidyaml/issues/129): quoted strings starting with * parsed as references
+- Fix [#128](https://github.com/biojppm/rapidyaml/issues/128): segfault on nonexistent anchor
+- Fix [#124](https://github.com/biojppm/rapidyaml/issues/124): parse failure in comments with trailing colon
+- Fix [#121](https://github.com/biojppm/rapidyaml/issues/121): preserve quotes when emitting scalars
+- Fix [#103](https://github.com/biojppm/rapidyaml/issues/103): ambiguous parsing of null/empty scalars
+- Fix [#90](https://github.com/biojppm/rapidyaml/issues/90): CMAKE_CXX_STANDARD ignored
+- Fix [#40](https://github.com/biojppm/rapidyaml/issues/40): quadratic complexity from use of `sscanf(%f)`
+- Fix emitting json to streams ([dc6af83](https://github.com/biojppm/rapidyaml/commit/dc6af83))
+- Set the global memory resource when setting global callbacks ([511cba0](https://github.com/biojppm/rapidyaml/commit/511cba0))
+- Fix python packaging ([PR #102](https://github.com/biojppm/rapidyaml/pull/102))
+
+### Special thanks
+- @Gei0r
+- @litghost
+- @costashatz

+ 235 - 0
3rdparty/rapidyaml/changelog/0.2.1.md

@@ -0,0 +1,235 @@
+This release is focused on bug fixes and compliance with the [YAML test suite](https://github.com/yaml/yaml-test-suite).
+
+### Breaking changes
+
+- Fix parsing behavior of root-level scalars: now these are parsed into a DOCVAL, not SEQ->VAL ([5ba0d56](https://github.com/biojppm/rapidyaml/pull/144/commits/5ba0d56904daef1509f0073695145c4835ab1b30), from [PR #144](https://github.com/biojppm/rapidyaml/pull/144)). Eg,
+  ```yaml
+  ---
+  this is a scalar
+  --- # previously this was parsed as
+  - this is a scalar
+  ```
+- Cleanup type predicate API ([PR #155](https://github.com/biojppm/rapidyaml/pull/155))):
+  - ensure all type predicates from `Tree` and `NodeRef` forward to the corresponding predicate in `NodeType`
+  - remove all type predicates and methods from `NodeData`; use the equivalent call from `Tree` or `NodeRef`. For example, for `is_map()`:
+    ```c++
+    Tree t = parse("{foo: bar}");
+    size_t map_id = t.root_id();
+    NodeRef map = t.rootref();
+    t.get(map_id)->is_map(); // compile error: no longer exists
+    assert(t.is_map(map_id)); // OK
+    assert(map.is_map()); // OK
+    ```
+  - Further cleanup to the type predicate API will be done in the future, especially around the `.has_*()` vs corresponding `.is_*()` naming scheme.
+
+
+### New features & improvements
+
+- `Tree::lookup_path_or_modify()`: add overload to graft existing branches ([PR #141](https://github.com/biojppm/rapidyaml/pull/141))
+- Callbacks: improve test coverage ([PR #141](https://github.com/biojppm/rapidyaml/pull/141))
+- [YAML test suite](https://github.com/yaml/yaml-test-suite) ([PR #144](https://github.com/biojppm/rapidyaml/pull/144), [PR #145](https://github.com/biojppm/rapidyaml/pull/145)): big progress towards compliance with the suite. There are still a number of existing problems, which are the subject of ongoing work. See the [list of current known failures](../test/test_suite/test_suite_parts.cpp) in the test suite file.
+- Python wheels and source package are now [uploaded to PyPI](https://pypi.org/project/rapidyaml/) as part of the release process.
+
+
+### Fixes
+
+#### Anchors and references
+- Fix resolving of nodes with keyref+valref ([PR #144](https://github.com/biojppm/rapidyaml/pull/144)): `{&a a: &b b, *b: *a}`
+- Fix parsing of implicit scalars when tags are present ([PR #145](https://github.com/biojppm/rapidyaml/pull/145)):
+  ```yaml
+  - &a  # test case PW8X
+  - a
+  - &a : a
+    b: &b
+  - &c : &a
+  - ? &d
+  - ? &e
+    : &a
+  ```
+- Fix [#151](https://github.com/biojppm/rapidyaml/issues/151): scalars beginning with `*` or `&` or `<<` are now correctly quoted when emitting ([PR #156](https://github.com/biojppm/rapidyaml/pull/156)).
+- Also from [PR #156](https://github.com/biojppm/rapidyaml/pull/156), map inheritance nodes like `<<: *anchor` or `<<: [*anchor1, *anchor2]` now have a `KEYREF` flag in their type (until a call to `Tree::resolve()`):
+  ```c++
+  Tree tree = parse("{map: &anchor {foo: bar}, copy: {<<: *anchor}}");
+  assert(tree["copy"]["<<"].is_key_ref()); // previously this did not hold
+  assert(tree["copy"]["<<"].is_val_ref()); // ... but this did
+  ```
+
+#### Tags
+- Fix parsing of tag dense maps and seqs ([PR #144](https://github.com/biojppm/rapidyaml/pull/144)):
+  ```yaml
+  --- !!map {
+    k: !!seq [ a, !!str b],
+    j: !!seq
+       [ a, !!str b]
+  --- !!seq [
+    !!map { !!str k: v},
+    !!map { !!str ? k: v}
+  ]
+  --- !!map
+  !!str foo: !!map  # there was a parse error with the multiple tags
+    !!int 1: !!float 20.0
+    !!int 3: !!float 40.0
+  --- !!seq
+  - !!map
+    !!str k1: v1
+    !!str k2: v2
+    !!str k3: v3
+  ```
+
+#### Whitespace
+- Fix parsing of double-quoted scalars with tabs ([PR #145](https://github.com/biojppm/rapidyaml/pull/145)):
+  ```yaml
+  "This has a\ttab"
+  # is now correctly parsed as "This has a<TAB>tab"
+  ```
+- Fix filtering of leading and trailing whitespace within double-quoted scalars ([PR #145](https://github.com/biojppm/rapidyaml/pull/145)):
+  ```yaml
+  # test case 4ZYM, 7A4E, TL85
+  "
+  <SPC><SPC>foo<SPC>
+  <SPC> 
+  <SPC><TAB><SPC>bar
+  <SPC><SPC>baz
+  "
+  # is now correctly parsed as " foo\nbar\nbaz "
+  ```
+- Fix parsing of tabs within YAML tokens ([PR #145](https://github.com/biojppm/rapidyaml/pull/145)):
+  ```yaml
+  ---<TAB>scalar   # test case K54U
+  ---<TAB>{}       # test case Q5MG
+  ---              # test case DC7X
+  a: b<TAB>
+  seq:<TAB>
+   - a<TAB>
+  c: d<TAB>#X
+  ```
+- Fix parsing of flow-style maps with ommitted values without any space ([PR #145](https://github.com/biojppm/rapidyaml/pull/145)):
+  ```yaml
+  # test case 4ABK
+  - {foo: , bar: , baz: }  # this was parsed correctly as {foo: ~, bar: ~, baz: ~}
+  - {foo:, bar:, baz:}     # ... but this was parsed as {'foo:': , 'bar:': ~, 'baz:': ~}
+  ```
+
+#### Scalars
+- Unescape forward slashes in double quoted string ([PR #145](https://github.com/biojppm/rapidyaml/pull/145)):
+  ```yaml
+  --- escaped slash: "a\/b"   # test case 3UYS
+  # is now parsed as:
+  --- escaped slash: "a/b"
+  ```
+- Fix filtering of indented regions in folded scalars ([PR #145](https://github.com/biojppm/rapidyaml/pull/145)):
+  ```yaml
+  # test case 7T8X
+  - >
+    
+    folded
+    line
+    
+    next
+    line
+      * bullet
+    
+      * list
+      * lines
+    
+    last
+    line
+  ```
+  is now correctly parsed as `\nfolded line\nnext line\n  * bullet\n\n  * list\n  * lines\n\nlast line\n`.
+- Fix parsing of special characters within plain scalars ([PR #145](https://github.com/biojppm/rapidyaml/pull/145)):
+  ```yaml
+  # test case 3MYT
+  k:#foo
+    &a !t s
+    !t s
+  # now correctly parsed as "k:#foo &a !t s !t s"
+  ```
+- Fix parsing of comments after complex keys ([PR #145](https://github.com/biojppm/rapidyaml/pull/145)):
+  ```yaml
+  # test case X8DW
+  ? key
+  # comment 
+  : value
+  # now correctly parsed as {key: value}
+  ```
+- Fix parsing of consecutive complex keys within maps ([PR #145](https://github.com/biojppm/rapidyaml/pull/145))
+  ```yaml
+  # test case 7W2P, ZWK4
+  ? a
+  ? b
+  c:
+  ? d
+  e:
+  # now correctly parsed as {a: ~, b: ~, c: ~, d: ~, e: ~}
+  ```
+- Fix [#152](https://github.com/biojppm/rapidyaml/issues/152):  parse error with folded scalars that are the last in a container ([PR #157](https://github.com/biojppm/rapidyaml/pull/157)):
+  ```yaml
+  exec:
+    command:
+      # before the fix, this folded scalar failed to parse
+      - |
+        exec pg_isready -U "dog" -d "dbname=dog" -h 127.0.0.1 -p 5432
+    parses: no
+  ```
+- Fix: documents consisting of a quoted scalar now retain the VALQUO flag ([PR #156](https://github.com/biojppm/rapidyaml/pull/156))
+  ```c++
+  Tree tree = parse("'this is a quoted scalar'");
+  assert(tree.rootref().is_doc());
+  assert(tree.rootref().is_val());
+  assert(tree.rootref().is_val_quoted());
+  ```
+
+
+#### Document structure
+- Empty docs are now parsed as a docval with a null node:
+  ```yaml
+  ---   # test cases 6XDY, 6ZKB, 9BXL, PUW8
+  ---
+  ---
+  ```
+  is now parsed as
+  ```yaml
+  --- ~
+  --- ~
+  --- ~
+  ```
+- Prevent creation of DOC nodes from stream-level comments or tags ([PR #145](https://github.com/biojppm/rapidyaml/pull/145)):
+  ```yaml
+  !foo "bar"
+  ...
+  # Global
+  %TAG ! tag:example.com,2000:app/
+  ---
+  !foo "bar"
+  ```
+  was parsed as
+  ```yaml
+  ---
+  !foo "bar"
+  ---
+  # notice the empty doc in here
+  ---
+  !foo "bar"
+  ```
+  and it is now correctly parsed as
+  ```yaml
+  ---
+  !foo "bar"
+  ---
+  !foo "bar"
+  ```
+  (other than the known limitation that ryml does not do tag lookup).
+
+
+#### General
+
+- Fix [#147](https://github.com/biojppm/rapidyaml/issues/147): serialize/deserialize special float values `.nan`, `.inf`, `-.inf` ([PR #149](https://github.com/biojppm/rapidyaml/pull/149))
+- Fix [#142](https://github.com/biojppm/rapidyaml/issues/142): `preprocess_json()`: ensure quoted ranges are skipped when slurping containers
+- Ensure error macros expand to a single statement ([PR #141](https://github.com/biojppm/rapidyaml/pull/141))
+- Update c4core to [0.1.4](https://github.com/biojppm/c4core/releases/tag/v0.1.4)
+
+
+### Special thanks
+
+- @Gei0r
+

+ 1 - 0
3rdparty/rapidyaml/changelog/0.2.2.md

@@ -0,0 +1 @@
+Yank python package 0.2.1, was accidentally created while iterating the PyPI submission from the Github action. This release does not add any change, and is functionally the same as [0.2.1](https://github.com/biojppm/rapidyaml/releases/tag/v0.2.1).

+ 285 - 0
3rdparty/rapidyaml/changelog/0.2.3.md

@@ -0,0 +1,285 @@
+This release is focused on bug fixes and compliance with the [YAML test suite](https://github.com/yaml/yaml-test-suite).
+
+### New features
+- Add support for CPU architectures aarch64, ppc64le, s390x.
+- Update c4core to [0.1.7](https://github.com/biojppm/c4core/releases/tag/v0.1.7)
+- `Tree` and `NodeRef`: add document getter `doc()` and `docref()`
+  ```c++
+  Tree tree = parse(R"(---
+  doc0
+  ---
+  doc1
+  )");
+  NodeRef stream = t.rootref();
+  assert(stream.is_stream());
+  // tree.doc(i): get the index of the i-th doc node.
+  // Equivalent to tree.child(tree.root_id(), i)
+  assert(tree.doc(0) == 1u);
+  assert(tree.doc(1) == 2u);
+  // tree.docref(i), same as above, return NodeRef
+  assert(tree.docref(0).val() == "doc0");
+  assert(tree.docref(1).val() == "doc1");
+  // stream.doc(i), same as above, given NodeRef
+  assert(stream.doc(0).val() == "doc0");
+  assert(stream.doc(1).val() == "doc1");
+  ```
+
+### Fixes
+
+- Fix compilation with `C4CORE_NO_FAST_FLOAT` ([PR #163](https://github.com/biojppm/rapidyaml/pull/163))
+
+#### Flow maps
+
+- Fix parse of multiline plain scalars inside flow maps ([PR #161](https://github.com/biojppm/rapidyaml/pull/161)):
+  ```yaml
+  # test case UT92
+  # all parsed as "matches %": 20
+  - { matches
+  % : 20 }
+  - { matches
+  %: 20 }
+  - { matches
+  %:
+   20 }
+  ```
+
+
+#### Tags
+
+- Fix parsing of tags followed by comments in sequences ([PR #161](https://github.com/biojppm/rapidyaml/pull/161)):
+  ```yaml
+  # test case 735Y
+  - !!map # Block collection
+    foo : bar
+  ```
+
+#### Quoted scalars
+- Fix filtering of tab characters in quoted scalars ([PR #161](https://github.com/biojppm/rapidyaml/pull/161)):
+  ```yaml
+  ---
+  # test case 5GBF
+  "Empty line
+   <TAB>
+  as a line feed"
+  # now correctly parsed as "Empty line\nas a line feed"
+  ---
+  # test case PRH3
+  ' 1st non-empty
+  
+  <SPC>2nd non-empty<SPC>
+  <TAB>3rd non-empty '
+  # now correctly parsed as " 1st non-empty\n2nd non-empty 3rd non-empty "
+  ```
+- Fix filtering of backslash characters in double-quoted scalars ([PR #161](https://github.com/biojppm/rapidyaml/pull/161)):
+  ```yaml
+  # test cases NP9H, Q8AD
+  "folded<SPC>
+  to a space,<TAB>
+  <SPC>
+  to a line feed, or <TAB>\
+   \ <TAB>non-content"
+  # now correctly parsed as "folded to a space,\nto a line feed, or \t \tnon-content"
+  ```
+- Ensure filtering of multiline quoted scalars ([PR #161](https://github.com/biojppm/rapidyaml/pull/161)):
+  ```yaml
+  # all scalars now correctly parsed as "quoted string",
+  # both for double and single quotes
+  ---
+  "quoted
+  string"
+  --- "quoted
+  string"
+  ---
+  - "quoted
+    string"
+  ---
+  - "quoted
+  string"
+  ---
+  "quoted
+    string": "quoted
+    string"
+  ---
+  "quoted
+  string": "quoted
+  string"
+  ```
+
+
+#### Block scalars
+- Ensure no newlines are added when emitting block scalars ([PR #161](https://github.com/biojppm/rapidyaml/pull/161))
+- Fix parsing of block spec with both chomping and indentation: chomping may come before or after the indentation ([PR #161](https://github.com/biojppm/rapidyaml/pull/161)):
+  ```yaml
+  # the block scalar specs below now have the same effect.
+  # test cases: D83L, P2AD
+  - |2-
+    explicit indent and chomp
+  - |-2
+    chomp and explicit indent
+  ```
+- Fix [inference of block indentation](https://yaml.org/spec/1.2.2/#8111-block-indentation-indicator) with leading blank lines ([PR #161](https://github.com/biojppm/rapidyaml/pull/161)):
+  ```yaml
+  # test cases: 4QFQ, 7T8X
+  - >
+   
+    
+    # child1
+  # parsed as "\n\n child1"
+  --- # test case DWX9
+  |
+   
+    
+    literal
+     
+    
+    text
+  
+   # Comment
+  # parsed as "\n\nliteral\n \n\ntext\n"
+  ```
+- Fix parsing of same-indentation block scalars ([PR #161](https://github.com/biojppm/rapidyaml/pull/161)):
+  ```yaml
+  # test case W4TN
+  # all docs have the same value: "%!PS-Adobe-2.0"
+  --- |
+   %!PS-Adobe-2.0
+  ...
+  --- >
+   %!PS-Adobe-2.0
+  ...
+  --- |
+  %!PS-Adobe-2.0
+  ...
+  --- >
+  %!PS-Adobe-2.0
+  ...
+  --- |
+   %!PS-Adobe-2.0
+  --- >
+   %!PS-Adobe-2.0
+  --- |
+  %!PS-Adobe-2.0
+  --- >
+  %!PS-Adobe-2.0
+  ```
+- Folded block scalars: fix folding of newlines at the border of indented parts ([PR #161](https://github.com/biojppm/rapidyaml/pull/161)):
+  ```yaml
+  # test case 6VJK
+  # now correctly parsed as "Sammy Sosa completed another fine season with great stats.\n\n  63 Home Runs\n  0.288 Batting Average\n\nWhat a year!\n"
+  >
+    Sammy Sosa completed another
+    fine season with great stats.
+   
+      63 Home Runs
+      0.288 Batting Average
+   
+    What a year!
+  ---
+  # test case MJS9
+  # now correctly parsed as "foo \n\n \t bar\n\nbaz\n"
+  >
+    foo<SPC>
+  <SPC>
+    <SPC><TAB><SPC>bar
+  
+    baz
+  ```
+- Folded block scalars: fix folding of newlines when the indented part is at the begining of the scalar ([PR #161](https://github.com/biojppm/rapidyaml/pull/161)):
+  ```yaml
+  # test case F6MC
+  a: >2
+     more indented
+    regular
+  # parsed as a: " more indented\nregular\n"
+  b: >2
+  
+  
+     more indented
+    regular
+  # parsed as b: "\n\n more indented\nregular\n"
+  ```
+
+#### Plain scalars
+- Fix parsing of whitespace within plain scalars ([PR #161](https://github.com/biojppm/rapidyaml/pull/161)):
+  ```yaml
+  ---
+  # test case NB6Z
+  key:
+    value
+    with
+     	
+    tabs
+    tabs
+     	
+      foo
+     	
+        bar
+          baz
+     	
+  # is now correctly parsed as "value with\ntabs tabs\nfoo\nbar baz"
+  ---
+  # test case 9YRD, EX5H (trailing whitespace)
+  a
+  b  
+    c
+  d
+  
+  e
+  # is now correctly parsed as "a b c d\ne"
+  ```
+- Fix parsing of unindented plain scalars at the root level scope ([PR #161](https://github.com/biojppm/rapidyaml/pull/161))
+  ```yaml
+  --- # this parsed
+  Bare
+   scalar
+   is indented
+  # was correctly parsed as "Bare scalar is indented"
+  --- # but this failed to parse successfully:
+  Bare
+  scalar
+  is not indented
+  # is now correctly parsed as "Bare scalar is not indented"
+  --- # test case NB6Z
+  value
+  with
+   	
+  tabs
+  tabs
+   	
+    foo
+   	
+      bar
+        baz
+    	
+  # now correctly parsed as "value with\ntabs tabs\nfoo\nbar baz"
+  ---
+  --- # test cases EXG3, 82AN
+  ---word1
+  word2
+  # now correctly parsed as "---word1 word2"
+  ```
+- Fix parsing of comments within plain scalars
+  ```yaml
+  # test case 7TMG
+  --- # now correctly parsed as "word1"
+  word1
+  # comment
+  --- # now correctly parsed as [word1, word2]
+  [ word1
+  # comment
+  , word2]
+  ```
+
+#### Python API
+- Add missing node predicates in SWIG API definition ([PR #166](https://github.com/biojppm/rapidyaml/pull/166)):
+  - `is_anchor_or_ref()`
+  - `is_key_quoted()`
+  - `is_val_quoted()`
+  - `is_quoted()`
+
+
+### Thanks
+
+--- @mbs-c
+--- @simu
+--- @QuellaZhang

+ 104 - 0
3rdparty/rapidyaml/changelog/0.3.0.md

@@ -0,0 +1,104 @@
+### Breaking changes
+
+Despite ryml being still in a non-stable 0.x.y version, considerable effort goes into trying to avoid breaking changes. However, this release has to collect on the [semantic versioning](https://semver.org/) prerogative for breaking changes. This is a needed improvement, so sorry for any nuisance!
+
+**The allocation and error callback logic was revamped** on the [amalgamation PR](https://github.com/biojppm/rapidyaml/pull/172). Now trees and parsers receive (and store) a full `ryml::Callbacks` object instead of the (now removed) `ryml::Allocator` which had a pointer to a (now removed) `ryml::MemoryResourceCallbacks`, which was a (now removed) `ryml::MemoryResource`. To be clear, the `Callbacks` class is unchanged, other than removing some unneeded helper methods.
+
+These changes were motivated by unfortunate name clashes between `c4::Allocator/ryml::Allocator` and `c4::MemoryResource/ryml::MemoryResource`, occurring if `<c4/allocator.hpp>` or `<c4/memory_resource.hpp>` were included before `<c4/yml/common.hpp>`. They also significantly simplify this part of the API, making it really easier to understand.
+
+As a consequence of the above changes, the global memory resource getters and setters for ryml were also removed: `ryml::get_memory_resource()/ryml::set_memory_resource()`.
+
+Here's an example of the required changes in client code. First the old client code (from the quickstart):
+
+```c++
+struct PerTreeMemoryExample : public ryml::MemoryResource
+{
+    void *allocate(size_t len, void * hint) override;
+    void free(void *mem, size_t len) override;
+};
+
+PerTreeMemoryExample mrp;
+PerTreeMemoryExample mr1;
+PerTreeMemoryExample mr2;
+
+ryml::Parser parser = {ryml::Allocator(&mrp)};
+ryml::Tree   tree1  = {ryml::Allocator(&mr1)};
+ryml::Tree   tree2  = {ryml::Allocator(&mr2)};
+```
+
+Should now be rewritten to:
+
+```c++
+struct PerTreeMemoryExample
+{
+    ryml::Callbacks callbacks() const; // helper to create the callbacks
+};
+
+PerTreeMemoryExample mrp;
+PerTreeMemoryExample mr1;
+PerTreeMemoryExample mr2;
+
+ryml::Parser parser = {mrp.callbacks()};
+ryml::Tree   tree1  = {mr1.callbacks()};
+ryml::Tree   tree2  = {mr2.callbacks()};
+```
+
+
+### New features
+- Add amalgamation into a single header file ([PR #172](https://github.com/biojppm/rapidyaml/pull/172)):
+  - The amalgamated header will be available together with the deliverables from each release.
+  - To generate the amalgamated header:
+    ```console
+    $ python tools/amalgamate.py ryml_all.hpp
+    ```
+  - To use the amalgamated header:
+    - Include at will in any header of your project.
+    - In one - and only one - of your project source files, `#define RYML_SINGLE_HDR_DEFINE_NOW` and then `#include <ryml_all.hpp>`. This will enable the function and class definitions in the header file. For example, here's a sample program:
+      ```c++
+      #include <iostream>
+      #define RYML_SINGLE_HDR_DEFINE_NOW // do this before the include
+      #include <ryml_all.hpp>
+      int main()
+      {
+          auto tree = ryml::parse("{foo: bar}");
+          std::cout << tree["foo"].val() << "\n";
+      }
+      ```
+- Add `Tree::change_type()` and `NodeRef::change_type()` ([PR #171](https://github.com/biojppm/rapidyaml/pull/171)):
+  ```c++
+  // clears a node and sets its type to a different type (one of `VAL`, `SEQ`, `MAP`):
+  Tree t = parse("{keyval0: val0, keyval1: val1, keyval2: val2}");
+  t[0].change_type(VAL);
+  t[1].change_type(MAP);
+  t[2].change_type(SEQ);
+  Tree expected = parse("{keyval0: val0, keyval1: {}, keyval2: []}");
+  assert(emitrs<std::string>(t) == emitrs<std::string>(expected));
+  ```
+- Add support for compilation with emscripten (WebAssembly+javascript) ([PR #176](https://github.com/biojppm/rapidyaml/pull/176)).
+
+### Fixes
+
+- Take block literal indentation as relative to current indentation level, rather than as an absolute indentation level ([PR #178](https://github.com/biojppm/rapidyaml/pull/178)):
+  ```yaml
+  foo:
+    - |
+     child0
+    - |2
+      child2  # indentation is 4, not 2
+  ```
+- Fix parsing when seq member maps start without a key ([PR #178](https://github.com/biojppm/rapidyaml/pull/178)):
+  ```yaml
+  # previously this resulted in a parse error
+  - - : empty key
+  - - : another empty key
+  ```
+- Prefer passing `substr` and `csubstr` by value instead of const reference ([PR #171](https://github.com/biojppm/rapidyaml/pull/171))
+- Fix [#173](https://github.com/biojppm/rapidyaml/issues/173): add alias target `ryml::ryml` ([PR #174](https://github.com/biojppm/rapidyaml/pull/174))
+- Speedup compilation of tests by removing linking with yaml-cpp and libyaml. ([PR #177](https://github.com/biojppm/rapidyaml/pull/177))
+- Fix [c4core#53](https://github.com/biojppm/c4core/issues/53): cmake install targets were missing call to `export()` ([PR #179](https://github.com/biojppm/c4core/pull/179)).
+- Add missing export to `Tree` ([PR #181](https://github.com/biojppm/c4core/pull/181)).
+
+
+### Thanks
+
+- @aviktorov

+ 229 - 0
3rdparty/rapidyaml/changelog/0.4.0.md

@@ -0,0 +1,229 @@
+This release improves compliance with the [YAML test suite](https://github.com/yaml/yaml-test-suite/) (thanks @ingydotnet and @perlpunk for extensive and helpful cooperation), and adds node location tracking using the parser.
+
+
+### Breaking changes
+
+As part of the [new feature to track source locations](https://github.com/biojppm/rapidyaml/pull/168), opportunity was taken to address a number of pre-existing API issues. These changes consisted of:
+
+- Deprecate `c4::yml::parse()` and `c4::yml::Parser::parse()` overloads; all these functions will be removed in short order. Until removal, any call from client code will trigger a compiler warning.
+- Add `parse()` alternatives, either `parse_in_place()` or `parse_in_arena()`:
+  - `parse_in_place()` receives only `substr` buffers, ie mutable YAML source buffers. Trying to pass a `csubstr` buffer to `parse_in_place()` will cause a compile error:
+    ```c++
+    substr readwrite = /*...*/;
+    Tree tree = parse_in_place(readwrite); // OK
+    
+    csubstr readonly = /*...*/;
+    Tree tree = parse_in_place(readonly); // compile error
+    ```
+  - `parse_in_arena()` receives only `csubstr` buffers, ie immutable YAML source buffers. Prior to parsing, the buffer is copied to the tree's arena, then the copy is parsed in place. Because `parse_in_arena()` is meant for immutable buffers, overloads receiving a `substr` YAML buffer are now declared but marked deprecated, and intentionally left undefined, such that calling `parse_in_arena()` with a `substr` will cause a linker error as well as a compiler warning.
+    ```c++
+    substr readwrite = /*...*/;
+    Tree tree = parse_in_arena(readwrite); // compile warning+linker error
+    ```
+    This is to prevent an accidental extra copy of the mutable source buffer to the tree's arena: `substr` is implicitly convertible to `csubstr`. If you really intend to parse an originally mutable buffer in the tree's arena, convert it first explicitly to immutable by assigning the `substr` to a `csubstr` prior to calling `parse_in_arena()`:
+    ```c++
+    substr readwrite = /*...*/;
+    csubstr readonly = readwrite; // ok
+    Tree tree = parse_in_arena(readonly); // ok
+    ```
+    This problem does not occur with `parse_in_place()` because `csubstr` is not implicitly convertible to `substr`. 
+- In the python API, `ryml.parse()` was removed and not just deprecated; the `parse_in_arena()` and `parse_in_place()` now replace this.
+- `Callbacks`: changed behavior in `Parser` and `Tree`:
+  - When a tree is copy-constructed or move-constructed to another, the receiving tree will start with the callbacks of the original.
+  - When a tree is copy-assigned or move-assigned to another, the receiving tree will now change its callbacks to the original.
+  - When a parser creates a new tree, the tree will now use a copy of the parser's callbacks object.
+  - When an existing tree is given directly to the parser, both the tree and the parser now retain their own callback objects; any allocation or error during parsing will go through the respective callback object.
+
+
+### New features
+
+- Add tracking of source code locations. This is useful for reporting semantic errors after the parsing phase (ie where the YAML is syntatically valid and parsing is successful, but the tree contents are semantically invalid). The locations can be obtained lazily from the parser when the first location is queried:
+  ```c++
+  // To obtain locations, use of the parser is needed:
+  ryml::Parser parser;
+  ryml::Tree tree = parser.parse_in_arena("source.yml", R"({
+  aa: contents,
+  foo: [one, [two, three]]
+  })");
+  // After parsing, on the first call to obtain a location,
+  // the parser will cache a lookup structure to accelerate
+  // tracking the location of a node, with complexity
+  // O(numchars(srcbuffer)). Then it will do the lookup, with
+  // complexity O(log(numlines(srcbuffer))).
+  ryml::Location loc = parser.location(tree.rootref());
+  assert(parser.location_contents(loc).begins_with("{"));
+  // note the location members are zero-based:
+  assert(loc.offset == 0u);
+  assert(loc.line == 0u);
+  assert(loc.col == 0u);
+  // On the next call to location(), the accelerator is reused
+  // and only the lookup is done.
+  loc = parser.location(tree["aa"]);
+  assert(parser.location_contents(loc).begins_with("aa"));
+  assert(loc.offset == 2u);
+  assert(loc.line == 1u);
+  assert(loc.col == 0u);
+  // KEYSEQ in flow style: points at the key
+  loc = parser.location(tree["foo"]);
+  assert(parser.location_contents(loc).begins_with("foo"));
+  assert(loc.offset == 16u);
+  assert(loc.line == 2u);
+  assert(loc.col == 0u);
+  loc = parser.location(tree["foo"][0]);
+  assert(parser.location_contents(loc).begins_with("one"));
+  assert(loc.line == 2u);
+  assert(loc.col == 6u);
+  // SEQ in flow style: location points at the opening '[' (there's no key)
+  loc = parser.location(tree["foo"][1]);
+  assert(parser.location_contents(loc).begins_with("["));
+  assert(loc.line == 2u);
+  assert(loc.col == 11u);
+  loc = parser.location(tree["foo"][1][0]);
+  assert(parser.location_contents(loc).begins_with("two"));
+  assert(loc.line == 2u);
+  assert(loc.col == 12u);
+  loc = parser.location(tree["foo"][1][1]);
+  assert(parser.location_contents(loc).begins_with("three"));
+  assert(loc.line == 2u);
+  assert(loc.col == 17u);
+  // NOTE: reusing the parser with a new YAML source buffer
+  // will invalidate the accelerator.
+  ```
+  See more details in the [quickstart sample](https://github.com/biojppm/rapidyaml/blob/bfb073265abf8c58bbeeeed7fb43270e9205c71c/samples/quickstart.cpp#L3759). Thanks to @cschreib for submitting a working example proving how simple it could be to achieve this.
+- `Parser`:
+  - add `source()` and `filename()` to get the latest buffer and filename to be parsed
+  - add `callbacks()` to get the parser's callbacks
+- Add `from_tag_long()` and `normalize_tag_long()`:
+  ```c++
+  assert(from_tag_long(TAG_MAP) == "<tag:yaml.org,2002:map>");
+  assert(normalize_tag_long("!!map") == "<tag:yaml.org,2002:map>");
+  ```
+- Add an experimental API to resolve tags based on the tree's tag directives. This API is still imature and will likely be subject to changes, so we won't document it yet.
+- Regarding emit styles (see issue [#37](https://github.com/biojppm/rapidyaml/issues/37)): add an experimental API to force flow/block style on container nodes, as well as block-literal/block-folded/double-quoted/single-quoted/plain styles on scalar nodes. This API is also immature and will likely be subject to changes, so we won't document it yet. But if you are desperate for this functionality, the new facilities will let you go further.
+- Add preliminary support for bare-metal ARM architectures, with CI tests pending implementation of QEMU action. ([#193](https://github.com/biojppm/rapidyaml/issues/193), [c4core#63](https://github.com/biojppm/c4core/issues/63)).
+- Add preliminary support for RISC-V architectures, with CI tests pending availability of RISC-V based github actions. ([c4core#69](https://github.com/biojppm/c4core/pulls/69)).
+
+
+### Fixes
+
+- Fix edge cases of parsing of explicit keys (ie keys after `?`) ([PR#212](https://github.com/biojppm/rapidyaml/pulls/212)):
+  ```yaml
+  # all these were fixed:
+  ? : # empty
+  ? explicit key   # this comment was not parsed correctly
+  ?    # trailing empty key was not added to the map
+  ```
+- Fixed parsing of tabs used as whitespace tokens after `:` or `-`. This feature [is costly (see some benchmark results here)](https://github.com/biojppm/rapidyaml/pull/211#issuecomment-1030688035) and thus it is disabled by default, and requires defining a macro or cmake option `RYML_WITH_TAB_TOKENS` to enable ([PR#211](https://github.com/biojppm/rapidyaml/pulls/211)).
+- Allow tab indentation in flow seqs ([PR#215](https://github.com/biojppm/rapidyaml/pulls/215)) (6CA3).
+- ryml now parses successfully compact JSON code `{"like":"this"}` without any need for preprocessing. This code was not valid YAML 1.1, but was made valid in YAML 1.2. So the `preprocess_json()` functions, used to insert spaces after `:` are no longer necessary and have been removed. If you were using these functions, remove the calls and just pass the original source directly to ryml's parser ([PR#210](https://github.com/biojppm/rapidyaml/pulls/210)).
+- Fix handling of indentation when parsing block scalars ([PR#210](https://github.com/biojppm/rapidyaml/pulls/210)):
+  ```yaml
+  ---
+  |
+  hello
+  there
+  ---
+  |
+  ciao
+  qua
+  ---
+  - |
+   hello
+   there
+  - |
+   ciao
+   qua
+  ---
+  foo: |
+   hello
+   there
+  bar: |
+   ciao
+   qua
+  ```
+- Fix parsing of maps when opening a scope with whitespace before the colon ([PR#210](https://github.com/biojppm/rapidyaml/pulls/210)):
+  ```yaml
+  foo0 : bar
+  ---
+  foo1 : bar  # the " :" was causing an assert
+  ---
+  foo2 : bar
+  ---
+  foo3	: bar
+  ---
+  foo4   	  : bar
+  ```
+- Ensure container keys preserve quote flags when the key is quoted ([PR#210](https://github.com/biojppm/rapidyaml/pulls/210)).
+- Ensure scalars beginning with `%` are emitted with quotes (([PR#216](https://github.com/biojppm/rapidyaml/pulls/216)).
+- Fix [#203](https://github.com/biojppm/rapidyaml/issues/203): when parsing, do not convert `null` or `~` to null scalar strings. Now the scalar strings contain the verbatim contents of the original scalar; to query whether a scalar value is null, use `Tree::key_is_null()/val_is_null()` and `NodeRef::key_is_null()/val_is_null()` which return true if it is empty or any of the unquoted strings `~`, `null`, `Null`, or `NULL`. ([PR#207](https://github.com/biojppm/rapidyaml/pulls/207)):
+- Fix [#205](https://github.com/biojppm/rapidyaml/issues/205): fix parsing of escaped characters in double-quoted strings: `"\\\"\n\r\t\<TAB>\/\<SPC>\0\b\f\a\v\e\_\N\L\P"` ([PR#207](https://github.com/biojppm/rapidyaml/pulls/207)).
+- Fix [#204](https://github.com/biojppm/rapidyaml/issues/204): add decoding of unicode codepoints `\x` `\u` `\U` in double-quoted scalars:
+  ```c++
+  Tree tree = parse_in_arena(R"(["\u263A \xE2\x98\xBA \u2705 \U0001D11E"])");
+  assert(tree[0].val() == "☺ ☺ ✅ 𝄞");
+  ```
+  This is mandated by the YAML standard and was missing from ryml ([PR#207](https://github.com/biojppm/rapidyaml/pulls/207)).
+- Fix emission of nested nodes which are sequences: when these are given as the emit root, the `- ` from the parent node was added ([PR#210](https://github.com/biojppm/rapidyaml/pulls/210)):
+  ```c++
+  const ryml::Tree tree = ryml::parse_in_arena(R"(
+  - - Rochefort 10
+    - Busch
+    - Leffe Rituel
+    - - and so
+      - many other
+      - wonderful beers
+  )");
+  // before (error), YAML valid but not expected
+  //assert(ryml::emitrs<std::string>(tree[0][3]) == R"(- - and so
+  //  - many other
+  //  - wonderful beers
+  //)");
+  // now: YAML valid and expected
+  assert(ryml::emitrs<std::string>(tree[0][3]) == R"(- and so
+  - many other
+  - wonderful beers
+  )");
+  ```
+- Fix parsing of isolated `!`: should be an empty val tagged with `!` (UKK06-02) ([PR#215](https://github.com/biojppm/rapidyaml/pulls/215)).
+- Fix [#193](https://github.com/biojppm/rapidyaml/issues/193): amalgamated header missing `#include <stdarg.h>` which prevented compilation in bare-metal `arm-none-eabi` ([PR #195](https://github.com/biojppm/rapidyaml/pull/195), requiring also [c4core #64](https://github.com/biojppm/c4core/pull/64)).
+- Accept `infinity`,`inf` and `nan` as special float values (but not mixed case: eg `InFiNiTy` or `Inf` or `NaN` are not accepted) ([PR #186](https://github.com/biojppm/rapidyaml/pull/186)).
+- Accept special float values with upper or mixed case: `.Inf`, `.INF`, `.NaN`, `.NAN`. Previously, only low-case `.inf` and `.nan` were accepted ([PR #186](https://github.com/biojppm/rapidyaml/pull/186)).
+- Accept `null` with upper or mixed case: `Null` or `NULL`. Previously, only low-case `null` was accepted ([PR #186](https://github.com/biojppm/rapidyaml/pull/186)).
+- Fix [#182](https://github.com/biojppm/rapidyaml/issues/182): add missing export of DLL symbols, and document requirements for compiling shared library from the amalgamated header. [PR #183](https://github.com/biojppm/rapidyaml/pull/183), also [PR c4core#56](https://github.com/biojppm/c4core/pull/56) and [PR c4core#57](https://github.com/biojppm/c4core/pull/57).
+- Fix [#185](https://github.com/biojppm/rapidyaml/issues/185): compilation failures in earlier Xcode versions ([PR #187](https://github.com/biojppm/rapidyaml/pull/187) and [PR c4core#61](https://github.com/biojppm/c4core/pull/61)):
+  - `c4/substr_fwd.hpp`: (failure in Xcode 12 and earlier) forward declaration for `std::allocator` is inside the `inline namespace __1`, unlike later versions.
+  - `c4/error.hpp`: (failure in debug mode in Xcode 11 and earlier) `__clang_major__` does not mean the same as in the common clang, and as a result the warning `-Wgnu-inline-cpp-without-extern` does not exist there.
+- Ensure error messages do not wrap around the buffer when the YAML source line is too long ([PR#210](https://github.com/biojppm/rapidyaml/pulls/210)).
+- Ensure error is emitted on unclosed flow sequence characters eg `[[[` ([PR#210](https://github.com/biojppm/rapidyaml/pulls/210)). Same thing for `[]]`.
+- Refactor error message building and parser debug logging to use the new dump facilities in c4core ([PR#212](https://github.com/biojppm/rapidyaml/pulls/212)).
+- Parse: fix read-after-free when duplicating a parser state node, when pushing to the stack requires a stack buffer resize ([PR#210](https://github.com/biojppm/rapidyaml/pulls/210)).
+- Add support for legacy gcc 4.8 ([PR#217](https://github.com/biojppm/rapidyaml/pulls/217)).
+
+
+### Improvements
+
+- Rewrite filtering of scalars to improve parsing performance ([PR #188](https://github.com/biojppm/rapidyaml/pull/188)). Previously the scalar strings were filtered in place, which resulted in quadratic complexity in terms of scalar length. This did not matter for small scalars fitting the cache (which is the more frequent case), but grew in cost as the scalars grew larger. To achieve linearity, the code was changed so that the strings are now filtered to a temporary scratch space in the parser, and copied back to the output buffer after filtering, if any change occurred. The improvements were large for the folded scalars; the table below shows the benchmark results of throughput (MB/s) for several files containing large scalars of a single type:
+  | scalar type	| before |	after |	improvement |
+  |:------------|-------:|-------:|---------:|
+  | block folded   | 276	| 561	| 103% |
+  | block literal  | 331	| 611	| 85% |
+  | single quoted  | 247	| 267	| 8% |
+  | double quoted  | 212	| 230	| 8% |
+  | plain (unquoted) | 173	| 186	| 8% |
+  
+  The cost for small scalars is negligible, with benchmark improvement in the interval of -2% to 5%, so well within the margin of benchmark variability in a regular OS. In the future, this will be optimized again by copying each character in place, thus completely avoiding the staging arena.
+- `Callbacks`: add `operator==()` and `operator!=()` ([PR #168](https://github.com/biojppm/rapidyaml/pull/168)).
+- `Tree`: on error or assert prefer the error callback stored into the tree's current `Callbacks`, rather than the global `Callbacks` ([PR #168](https://github.com/biojppm/rapidyaml/pull/168)).
+- `detail::stack<>`: improve behavior when assigning from objects `Callbacks`, test all rule-of-5 scenarios ([PR #168](https://github.com/biojppm/rapidyaml/pull/168)).
+- Improve formatting of error messages.
+
+
+### Thanks
+
+- @ingydotnet
+- @perlpunk
+- @cschreib
+- @fargies
+- @Xeonacid
+- @aviktorov
+- @xTVaser

+ 0 - 0
3rdparty/rapidyaml/changelog/current.md


+ 11 - 0
3rdparty/rapidyaml/compat.cmake

@@ -0,0 +1,11 @@
+
+# old gcc-4.8 support
+if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") AND
+  (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 4.8) AND
+  (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0))
+
+  # c++17 compiler required
+  set(C4RYML_BUILD_BENCHMARKS OFF CACHE BOOL "" FORCE)
+  # LLVM required
+  set(C4RYML_SANITIZE OFF CACHE BOOL "" FORCE)
+endif()

+ 9 - 0
3rdparty/rapidyaml/ext/c4core/.github/.old/log.hpp

@@ -0,0 +1,9 @@
+#ifndef _C4_LOG_HPP_
+#define _C4_LOG_HPP_
+
+// FIXME - these are just dumb placeholders
+#define C4_LOGF_ERR(...) fprintf(stderr, __VA_ARGS__)
+#define C4_LOGF_WARN(...) fprintf(stderr, __VA_ARGS__)
+#define C4_LOGP(msg, ...) printf(msg)
+
+#endif /* _C4_LOG_HPP_ */

+ 4091 - 0
3rdparty/rapidyaml/ext/c4core/.github/.old/util.hpp

@@ -0,0 +1,4091 @@
+#ifndef _C4_UTIL_HPP_
+#define _C4_UTIL_HPP_
+
+//------------------------------------------------------------
+//------------------------------------------------------------
+//------------------------------------------------------------
+// CONFIG
+
+//#define C4_DEBUG
+//#define C4_USE_XASSERT
+//#define C4_NO_ALLOC_DEFAULTS
+//#define C4_REDEFINE_CPPNEW
+//#define C4_LOG_THREAD_SAFE
+#ifndef C4_LOG_MAX_CHANNELS
+#   define C4_LOG_MAX_CHANNELS 32
+#endif
+#ifndef C4_LOG_BUFFER_INITIAL_SIZE
+#   define C4_LOG_BUFFER_INITIAL_SIZE 128
+#endif
+#ifndef C4_LOG_BUFFER_REF_SIZE
+#   define C4_LOG_BUFFER_REF_SIZE 256
+#endif
+
+//------------------------------------------------------------
+//------------------------------------------------------------
+//------------------------------------------------------------
+// INCLUDES
+
+#include <stddef.h>
+#include <stdint.h> // put uint32_t et al into the :: namespace
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <stdarg.h>
+#include <algorithm>
+#include <assert.h>
+
+#include <limits>
+
+#include "c4/preprocessor.hpp"
+#include "c4/platform.hpp"
+#include "c4/cpu.hpp"
+
+#include "c4/windows.hpp"
+
+#if !defined(C4_WIN) && !defined(C4_POSIX)
+#   include <chrono>
+#elif defined(C4_POSIX)
+#   include <time.h>
+#endif
+
+#ifdef C4_LOG_THREAD_SAFE
+#   include <thread>
+#   include <mutex>
+#endif
+
+#if !defined(C4_NO_ALLOC_DEFAULTS) && defined(C4_POSIX)
+#   include <errno.h>
+#endif
+
+#include <memory>
+
+#include "c4/language.hpp"
+
+
+//------------------------------------------------------------
+//------------------------------------------------------------
+//------------------------------------------------------------
+// error reporting
+
+#define C4_STATIC_ASSERT(cond) static_assert(cond, "static assert failed")
+#define C4_STATIC_ASSERT_MSG(cond, msg) static_assert(cond, msg)
+
+C4_BEGIN_NAMESPACE(c4)
+
+using error_callback_type = void (*)();
+
+/** Defaults to abort() */
+inline error_callback_type& get_error_callback()
+{
+    static error_callback_type cb = &::abort;
+    return cb;
+}
+/** Set the function which is called when an error occurs. */
+inline void set_error_callback(error_callback_type cb)
+{
+    get_error_callback() = cb;
+}
+
+void report_error(const char *file, int line, const char *func, const char *fmt, ...);
+C4_END_NAMESPACE(c4)
+
+/** Raise an error, and report a printf-formatted message.
+ * If an error callback was set, it will be called.
+ * @see set_error_callback() */
+#define C4_ERROR(msg, ...)                                              \
+    c4::report_error(__FILE__, __LINE__, C4_PRETTY_FUNC, msg, ## __VA_ARGS__)
+
+/** Report a warning with a printf-formatted message. */
+#define C4_WARNING(msg, ...)                                \
+    {                                                       \
+        C4_LOG_WARN("\n%s:%d: WARNING: " msg                \
+                    "\n%s:%d: WARNING: %s\n",               \
+                    __FILE__, __LINE__, ## __VA_ARGS__,     \
+                    __FILE__, __LINE__, C4_PRETTY_FUNC);    \
+    }
+
+// error checking - always turned on
+/** Check that a condition is true, or raise an error when not true. */
+#define C4_CHECK(cond)                          \
+    if(C4_UNLIKELY(!(cond)))                    \
+    {                                           \
+        C4_ERROR("check failed: " #cond);       \
+    }
+
+/** like C4_CHECK(), and additionally log a printf-style message.
+ * @see C4_CHECK */
+#define C4_CHECK_MSG(cond, fmt, ...)                                \
+    if(C4_UNLIKELY(!(cond)))                                        \
+    {                                                               \
+        C4_ERROR("check failed: " #cond "\n" fmt, ## __VA_ARGS__);  \
+    }
+
+// assertions - only in debug builds
+#ifdef NDEBUG // turn off assertions
+#   define C4_ASSERT(cond)
+#   define C4_ASSERT_MSG(cond, fmt, ...)
+#else
+#   define C4_ASSERT(cond) C4_CHECK(cond)
+#   define C4_ASSERT_MSG(cond, fmt, ...) C4_CHECK_MSG(cond, fmt, ## __VA_ARGS__)
+#endif
+
+// Extreme assertion: can be switched off independently of the regular assertion.
+// Use eg for bounds checking in hot code.
+#ifdef C4_USE_XASSERT
+#   define C4_XASSERT(cond) C4_CHECK(cond)
+#   define C4_XASSERT_MSG(cond, fmt, ...) C4_CHECK_MSG(cond, fmt, ## __VA_ARGS__)
+#else
+#   define C4_XASSERT(cond)
+#   define C4_XASSERT_MSG(cond, fmt, ...)
+#endif
+
+// Common error conditions
+#define C4_NOT_IMPLEMENTED() C4_ERROR("NOT IMPLEMENTED")
+#define C4_NOT_IMPLEMENTED_MSG(msg, ...) C4_ERROR("NOT IMPLEMENTED: " msg, ## __VA_ARGS__)
+
+#define C4_NEVER_REACH() C4_UNREACHABLE(); C4_ERROR("never reach this point")
+#define C4_NEVER_REACH_MSG(msg, ...) C4_UNREACHABLE(); C4_ERROR("never reach this point: " msg, ## __VA_ARGS__)
+
+//------------------------------------------------------------
+//------------------------------------------------------------
+//------------------------------------------------------------
+// TIMING
+
+C4_BEGIN_NAMESPACE(c4)
+
+/** converts automatically from/to microseconds. */
+class Time
+{
+public:
+
+    C4_ALWAYS_INLINE Time() : m_microsecs(0) {}
+    C4_ALWAYS_INLINE Time(double microsecs) : m_microsecs(microsecs) {}
+
+    C4_ALWAYS_INLINE operator double () const { return m_microsecs; }
+
+    C4_ALWAYS_INLINE void operator= (double t) { m_microsecs = t; }
+
+    C4_ALWAYS_INLINE void m(double minutes) { m_microsecs = minutes * 60.e6; }
+    C4_ALWAYS_INLINE double m() const { return m_microsecs / 60.e6; }
+
+    C4_ALWAYS_INLINE void s(double seconds) { m_microsecs = seconds * 1.e6; }
+    C4_ALWAYS_INLINE double s() const { return m_microsecs * 1.e-6; }
+
+    C4_ALWAYS_INLINE void ms(double miliseconds) { m_microsecs = miliseconds * 1.e3; }
+    C4_ALWAYS_INLINE double ms() const { return m_microsecs * 1.e-3; }
+
+    C4_ALWAYS_INLINE void us(double microseconds) { m_microsecs = microseconds; }
+    C4_ALWAYS_INLINE double us() const { return m_microsecs; }
+
+    C4_ALWAYS_INLINE void ns(double nanoseconds) { m_microsecs = nanoseconds * 1.e-3; }
+    C4_ALWAYS_INLINE double ns() const { return m_microsecs * 1.e3; }
+
+private:
+
+    double m_microsecs;
+
+};
+
+C4_BEGIN_NAMESPACE(time_suffixes)
+Time operator"" _s(long double seconds) { Time t; t.s((double)seconds); return t; }
+Time operator"" _ms(long double milliseconds) { Time t; t.ms((double)milliseconds); return t; }
+Time operator"" _us(long double microseconds) { Time t; t.us((double)microseconds); return t; }
+Time operator"" _ns(long double nanoseconds) { Time t; t.ns((double)nanoseconds); return t; }
+C4_END_NAMESPACE(time_suffixes)
+
+/** a general-use time stamp in microseconds (usecs).
+ * Although this is timed precisely, there may be some issues.
+ * Eg, concurrent or heavy use may cause penalties.
+ * @see https://www.strchr.com/performance_measurements_with_rdtsc
+ * @see https://msdn.microsoft.com/en-us/library/windows/desktop/ee417693(v=vs.85).aspx */
+inline double currtime()
+{
+#ifdef C4_WIN
+    static bool gotfreq = false;
+    static double ifreq = 0.;
+    if(C4_UNLIKELY(!gotfreq))
+    {
+        static LARGE_INTEGER freq = {};
+        QueryPerformanceFrequency(&freq);
+        ifreq = 1.e9 / double(freq.QuadPart);
+        gotfreq = true;
+    }
+    LARGE_INTEGER ts;
+    QueryPerformanceCounter(&ts);
+    double usecs = ts.QuadPart * ifreq;
+    return usecs;
+#elif defined(C4_POSIX)
+    struct timespec ts;
+    clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
+    double usecs = 1.e6 * double(ts.tv_sec) + 1.e-3 * double(ts.tv_nsec);
+    return usecs;
+#else
+    std::chrono::time_point< hrc_type, usec_type > tp = std::chrono::high_resolution_clock::now();
+    double nsecs = tp.time_since_epoch().count();
+    return 1.e-3 * nsecs;
+#endif
+}
+
+/** execution time */
+inline double exetime()
+{
+    static const double atstart = currtime();
+    double now = currtime() - atstart;
+    return now;
+}
+
+/** do a spin loop for at least the given time */
+inline void busy_wait(double microsecs)
+{
+    double start = currtime();
+    while(currtime() - start < microsecs)
+    {
+        C4_KEEP_EMPTY_LOOP;
+    }
+}
+
+C4_END_NAMESPACE(c4)
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+// Some memory utilities.
+
+C4_BEGIN_NAMESPACE(c4)
+
+C4_ALWAYS_INLINE void zero_mem(void* mem, size_t num_bytes)
+{
+    ::memset(mem, 0, num_bytes);
+}
+template< class T >
+C4_ALWAYS_INLINE void zero_mem(T* mem, size_t num_elms)
+{
+    ::memset(mem, 0, sizeof(T) * num_elms);
+}
+template< class T >
+C4_ALWAYS_INLINE void zero_mem(T* mem)
+{
+    ::memset(mem, 0, sizeof(T));
+}
+
+
+inline bool mem_overlaps(void const* a, void const* b, size_t sza, size_t szb)
+{
+    if(a < b)
+    {
+        if(size_t(a) + sza > size_t(b)) return true;
+    }
+    else if(a > b)
+    {
+        if(size_t(b) + szb > size_t(a)) return true;
+    }
+    else if(a == b)
+    {
+        if(sza != 0 && szb != 0) return true;
+    }
+    return false;
+}
+
+/** Fills 'dest' with the first 'pattern_size' bytes at 'pattern', 'num_times'. */
+inline void mem_repeat(void* dest, void const* pattern, size_t pattern_size, size_t num_times)
+{
+    if(C4_UNLIKELY(num_times == 0)) return;
+    C4_ASSERT(!mem_overlaps(dest, pattern, num_times*pattern_size, pattern_size));
+    char *begin = (char*)dest;
+    char *end   = begin + num_times * pattern_size;
+    // copy the pattern once
+    ::memcpy(begin, pattern, pattern_size);
+    // now copy from dest to itself, doubling up every time
+    size_t n = pattern_size;
+    while(begin + 2*n < end)
+    {
+        ::memcpy(begin + n, begin, n);
+        n <<= 1; // double n
+    }
+    // copy the missing part
+    if(begin + n < end)
+    {
+        ::memcpy(begin + n, begin, end - (begin + n));
+    }
+}
+
+C4_END_NAMESPACE(c4)
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+// some traits and type utility classes/macros.
+
+C4_BEGIN_NAMESPACE(c4)
+
+/** use this macro to enable a function overload based on a compile-time condition.
+@code
+// define an overload for a non-pod type
+template< class T, C4_REQUIRE_R(std::is_pod< T >::value) >
+void foo() { std::cout << "pod type\n"; }
+
+// define an overload for a non-pod type
+template< class T, C4_REQUIRE_R(!std::is_pod< T >::value) >
+void foo() { std::cout << "nonpod type\n"; }
+
+struct non_pod
+{
+    non_pod() : name("asdfkjhasdkjh") {}
+    const char *name;
+};
+
+int main()
+{
+    foo< float >(); // prints "pod type"
+    foo< non_pod >(); // prints "nonpod type"
+}
+@endcode */
+#define C4_REQUIRE_T(cond) typename std::enable_if< cond, bool >::type* = nullptr
+
+/** enable_if for a return type */
+#define C4_REQUIRE_R(cond, type_) typename std::enable_if< cond, type_ >::type
+
+//-----------------------------------------------------------------------------
+/** declare a traits class telling whether a type provides a member typedef */
+#define C4_DEFINE_HAS_TYPEDEF(member_typedef)                   \
+template< typename T >                                          \
+struct has_##stype                                              \
+{                                                               \
+private:                                                        \
+                                                                \
+    typedef char                      yes;                      \
+    typedef struct { char array[2]; } no;                       \
+                                                                \
+    template< typename C >                                      \
+    static yes _test(typename C::member_typedef*);              \
+                                                                \
+    template< typename C >                                      \
+    static no  _test(...);                                      \
+                                                                \
+public:                                                         \
+                                                                \
+    enum { value = (sizeof(_test< T >(0)) == sizeof(yes)) };    \
+                                                                \
+}
+
+//-----------------------------------------------------------------------------
+/** declare a traits class telling whether a type provides a method */
+#define C4_DEFINE_HAS_METHOD(ret_type, method_name, const_qualifier, ...) \
+template< typename T >                                                  \
+struct has_##method_name##_method                                       \
+{                                                                       \
+private:                                                                \
+                                                                        \
+    typedef char                      &yes;                             \
+    typedef struct { char array[2]; } &no;                              \
+                                                                        \
+    template< typename C >                                              \
+    static yes _test                                                    \
+    (                                                                   \
+        C const_qualifier* v,                                           \
+        typename std::enable_if                                         \
+        <                                                               \
+            std::is_same< decltype(v->method_name(__VA_ARGS__)), ret_type >::value \
+            ,                                                           \
+            void /* this is defined only if the bool above is true. */  \
+                 /* so when it fails, SFINAE is triggered */            \
+        >                                                               \
+        ::type*                                                         \
+    );                                                                  \
+                                                                        \
+    template< typename C >                                              \
+    static no _test(...);                                               \
+                                                                        \
+public:                                                                 \
+                                                                        \
+    enum { value = (sizeof(_test< T >((typename std::remove_reference< T >::type*)0, 0)) == sizeof(yes)) }; \
+                                                                        \
+};
+
+//--------------------------------------------------
+
+/** whether a value should be used in place of a const-reference in argument passing. */
+template< class T >
+struct cref_uses_val
+{
+    enum { value = (
+    std::is_scalar< T >::value
+    ||
+    (std::is_pod< T >::value && sizeof(T) <= sizeof(size_t))) };
+};
+/** override the default behaviour for c4::fastcref< T >
+ @see fastcref */
+#define C4_CREF_USES_VAL(T) \
+template<>                  \
+struct cref_uses_val< T >   \
+{                           \
+    enum { value = true };  \
+};
+
+/** Whether to use pass-by-value or pass-by-const-reference in a function argument
+ * or return type. */
+template< class T >
+using fastcref = typename std::conditional< c4::cref_uses_val< T >::value, T, T const& >::type;
+
+//--------------------------------------------------
+
+/** Just what its name says. Useful sometimes as a default empty policy class. */
+struct EmptyStruct
+{
+    template< class... T > EmptyStruct(T && ...){}
+};
+
+/** Just what its name says. Useful sometimes as a default policy class to
+ * be inherited from. */
+struct EmptyStructVirtual
+{
+    virtual ~EmptyStructVirtual() = default;
+    template< class... T > EmptyStructVirtual(T && ...){}
+};
+
+
+//--------------------------------------------------
+// Utilities to make a class obey size restrictions (eg, min size or size multiple of).
+// DirectX usually makes this restriction with uniform buffers.
+// This is also useful for padding to prevent false-sharing.
+
+/** force the following class to be tightly packed.
+ * @see http://stackoverflow.com/questions/21092415/force-c-structure-to-pack-tightly */
+#pragma pack(push, 1)
+/** pad a class with more bytes at the end. */
+template< class T, size_t BytesToPadAtEnd >
+struct Padded : public T
+{
+    using T::T;
+public:
+    char ___c4padspace___[BytesToPadAtEnd];
+};
+#pragma pack(pop)
+/** When the padding argument is 0, we cannot declare the char[] array. */
+template< class T >
+struct Padded< T, 0 > : public T
+{
+    using T::T;
+};
+
+/** how many bytes must be added to size such that the result is at least minsize? */
+constexpr inline size_t min_remainder(size_t size, size_t minsize)
+{
+    return size < minsize ? minsize-size : 0;
+}
+
+/** how many bytes must be added to size such that the result is a multiple of multipleOf?  */
+constexpr inline size_t mult_remainder(size_t size, size_t multipleof)
+{
+    return (((size % multipleof) != 0) ? (multipleof-(size % multipleof)) : 0);
+}
+
+/** make T have a size which is at least Min bytes */
+template< class T, size_t Min >
+using MinSized = Padded< T, min_remainder(sizeof(T), Min) >;
+
+/** make T have a size which is a multiple of Mult bytes */
+template< class T, size_t Mult >
+using MultSized = Padded< T, mult_remainder(sizeof(T), Mult) >;
+
+/** make T have a size which is simultaneously:
+ *  -bigger or equal than Min
+ *  -a multiple of Mult */
+template< class T, size_t Min, size_t Mult >
+using MinMultSized = MultSized< MinSized< T, Min >, Mult >;
+
+/** make T be suitable for use as a uniform buffer. (at least with DirectX). */
+template< class T >
+using UbufSized = MinMultSized< T, 64, 16 >;
+
+//-----------------------------------------------------------------------------
+// construct
+
+#define _C4REQUIRE(cond) \
+C4_ALWAYS_INLINE typename std::enable_if< cond, void >::type
+
+
+/** default-construct an object, trivial version */
+template< class U > _C4REQUIRE(std::is_trivially_default_constructible< U >::value)
+construct(U* ptr) noexcept
+{
+    memset(ptr, 0, sizeof(U));
+}
+/** default-construct an object, non-trivial version */
+template< class U > _C4REQUIRE( ! std::is_trivially_default_constructible< U >::value)
+construct(U* ptr) noexcept
+{
+    new (ptr) U();
+}
+
+/** default-construct n objects, trivial version */
+template< class U, class I > _C4REQUIRE(std::is_trivially_default_constructible< U >::value)
+construct_n(U* ptr, I n) noexcept
+{
+    memset(ptr, 0, n * sizeof(U));
+}
+/** default-construct n objects, non-trivial version */
+template< class U, class I > _C4REQUIRE( ! std::is_trivially_default_constructible< U >::value)
+construct_n(U* ptr, I n) noexcept
+{
+    for(I i = 0; i < n; ++i)
+    {
+        new (ptr+i) U();
+    }
+}
+
+template< class U, class ...Args >
+inline void construct(U* ptr, Args&&... args)
+{
+    new ((void*)ptr) U(std::forward< Args >(args)...);
+}
+template< class U, class I, class ...Args >
+inline void construct_n(U* ptr, I n, Args&&... args)
+{
+    for(I i = 0; i < n; ++i)
+    {
+        new ((void*)(ptr + i)) U(std::forward< Args >(args)...);
+    }
+}
+
+//-----------------------------------------------------------------------------
+// copy-construct
+
+template< class U > _C4REQUIRE(std::is_trivially_copy_constructible< U >::value)
+copy_construct(U* dst, U const* src) noexcept
+{
+    memcpy(dst, src, sizeof(U));
+}
+template< class U > _C4REQUIRE( ! std::is_trivially_copy_constructible< U >::value)
+copy_construct(U* dst, U const* src)
+{
+    new (dst) U(*src);
+}
+template< class U, class I > _C4REQUIRE(std::is_trivially_copy_constructible< U >::value)
+copy_construct_n(U* dst, U const* src, I n) noexcept
+{
+    memcpy(dst, src, n * sizeof(U));
+}
+template< class U, class I > _C4REQUIRE( ! std::is_trivially_copy_constructible< U >::value)
+copy_construct_n(U* dst, U const* src, I n)
+{
+    for(I i = 0; i < n; ++i)
+    {
+        new (dst + i) U(*(src + i));
+    }
+}
+
+template< class U > _C4REQUIRE(std::is_scalar< U >::value)
+copy_construct(U* dst, U src) noexcept // pass by value for scalar types
+{
+    *dst = src;
+}
+template< class U > _C4REQUIRE( ! std::is_scalar< U >::value)
+copy_construct(U* dst, U const& src) // pass by reference for non-scalar types
+{
+    new (dst) U(src);
+}
+template< class U, class I > _C4REQUIRE(std::is_scalar< U >::value)
+copy_construct_n(U* dst, U src, I n) noexcept // pass by value for scalar types
+{
+    for(I i = 0; i < n; ++i)
+    {
+        dst[i] = src;
+    }
+}
+template< class U, class I > _C4REQUIRE( ! std::is_scalar< U >::value)
+copy_construct_n(U* dst, U const& src, I n) // pass by reference for non-scalar types
+{
+    for(I i = 0; i < n; ++i)
+    {
+        new (dst + i) U(src);
+    }
+}
+
+template< class U, size_t N >
+C4_ALWAYS_INLINE void copy_construct(U (&dst)[N], U const (&src)[N]) noexcept
+{
+    copy_construct_n(dst, src, N);
+}
+
+//-----------------------------------------------------------------------------
+// copy-assign
+
+template< class U > _C4REQUIRE(std::is_trivially_copy_assignable< U >::value)
+copy_assign(U* dst, U const* src) noexcept
+{
+    memcpy(dst, src, sizeof(U));
+}
+template< class U > _C4REQUIRE( ! std::is_trivially_copy_assignable< U >::value)
+copy_assign(U* dst, U const* src) noexcept
+{
+    *dst = *src;
+}
+template< class U, class I > _C4REQUIRE(std::is_trivially_copy_assignable< U >::value)
+copy_assign_n(U* dst, U const* src, I n) noexcept
+{
+    memcpy(dst, src, n * sizeof(U));
+}
+template< class U, class I > _C4REQUIRE( ! std::is_trivially_copy_assignable< U >::value)
+copy_assign_n(U* dst, U const* src, I n) noexcept
+{
+    for(I i = 0; i < n; ++i)
+    {
+        dst[i] = src[i];
+    }
+}
+
+template< class U > _C4REQUIRE(std::is_scalar< U >::value)
+copy_assign(U* dst, U src) noexcept // pass by value for scalar types
+{
+    *dst = src;
+}
+template< class U > _C4REQUIRE( ! std::is_scalar< U >::value)
+copy_assign(U* dst, U const& src) noexcept // pass by reference for non-scalar types
+{
+    *dst = src;
+}
+template< class U, class I > _C4REQUIRE(std::is_scalar< U >::value)
+copy_assign_n(U* dst, U src, I n) noexcept // pass by value for scalar types
+{
+    for(I i = 0; i < n; ++i)
+    {
+        dst[i] = src;
+    }
+}
+template< class U, class I > _C4REQUIRE( ! std::is_scalar< U >::value)
+copy_assign_n(U* dst, U const& src, I n) noexcept // pass by reference for non-scalar types
+{
+    for(I i = 0; i < n; ++i)
+    {
+        dst[i] = src;
+    }
+}
+
+template< class U, size_t N >
+C4_ALWAYS_INLINE void copy_assign(U (&dst)[N], U const (&src)[N]) noexcept
+{
+    copy_assign_n(dst, src, N);
+}
+
+//-----------------------------------------------------------------------------
+// move-construct
+
+template< class U > _C4REQUIRE(std::is_trivially_move_constructible< U >::value)
+move_construct(U* dst, U* src) noexcept
+{
+    memcpy(dst, src, sizeof(U));
+}
+template< class U > _C4REQUIRE( ! std::is_trivially_move_constructible< U >::value)
+move_construct(U* dst, U* src) noexcept
+{
+    new (dst) U(std::move(*src));
+}
+template< class U, class I > _C4REQUIRE(std::is_trivially_move_constructible< U >::value)
+move_construct_n(U* dst, U* src, I n) noexcept
+{
+    memcpy(dst, src, n * sizeof(U));
+}
+template< class U, class I > _C4REQUIRE( ! std::is_trivially_move_constructible< U >::value)
+move_construct_n(U* dst, U* src, I n) noexcept
+{
+    for(I i = 0; i < n; ++i)
+    {
+        new (dst + i) U(std::move(src[i]));
+    }
+}
+
+//-----------------------------------------------------------------------------
+// move-assign
+
+template< class U > _C4REQUIRE(std::is_trivially_move_assignable< U >::value)
+move_assign(U* dst, U* src) noexcept
+{
+    memcpy(dst, src, sizeof(U));
+}
+template< class U > _C4REQUIRE( ! std::is_trivially_move_assignable< U >::value)
+move_assign(U* dst, U* src) noexcept
+{
+    *dst = std::move(*src);
+}
+template< class U, class I > _C4REQUIRE(std::is_trivially_move_assignable< U >::value)
+move_assign_n(U* dst, U* src, I n) noexcept
+{
+    memcpy(dst, src, n * sizeof(U));
+}
+template< class U, class I > _C4REQUIRE( ! std::is_trivially_move_assignable< U >::value)
+move_assign_n(U* dst, U* src, I n) noexcept
+{
+    for(I i = 0; i < n; ++i)
+    {
+        *(dst + i) = std::move(*(src + i));
+    }
+}
+
+//-----------------------------------------------------------------------------
+// destroy
+
+template< class U > _C4REQUIRE(std::is_trivially_destructible< U >::value)
+destroy(U* ptr) noexcept
+{
+    C4_UNUSED(ptr); // nothing to do
+}
+template< class U > _C4REQUIRE( ! std::is_trivially_destructible< U >::value)
+destroy(U* ptr) noexcept
+{
+    ptr->~U();
+}
+template< class U, class I > _C4REQUIRE(std::is_trivially_destructible< U >::value)
+destroy_n(U* ptr, I n) noexcept
+{
+    C4_UNUSED(ptr);
+    C4_UNUSED(n); // nothing to do
+}
+template< class U, class I > _C4REQUIRE( ! std::is_trivially_destructible< U >::value)
+destroy_n(U* ptr, I n) noexcept
+{
+    for(I i = 0; i < n; ++i)
+    {
+        ptr[i].~U();
+    }
+}
+
+#undef _C4REQUIRE
+
+C4_END_NAMESPACE(c4)
+
+
+//------------------------------------------------------------
+//------------------------------------------------------------
+//------------------------------------------------------------
+// MEMORY ALLOCATION
+
+C4_BEGIN_NAMESPACE(c4)
+
+// c-style allocation ---------------------------------------------------------
+
+// this API provides unaligned as well as aligned allocation functions.
+// These functions forward the call to a modifiable function.
+
+// aligned allocation. Thread-safe.
+void* aalloc(size_t sz, size_t alignment);
+void afree(void* ptr);
+void* arealloc(void* ptr, size_t oldsz, size_t newsz, size_t alignment);
+
+// classic, unaligned allocation. Thread-safe.
+void* alloc(size_t sz);
+void free(void* ptr);
+void* realloc(void* ptr, size_t oldsz, size_t newsz);
+
+// allocation setup facilities
+using aalloc_type   = void* (*)(size_t size, size_t alignment);
+using afree_type    = void  (*)(void *ptr);
+using arealloc_type = void* (*)(void *ptr, size_t oldsz, size_t newsz, size_t alignment);
+
+using alloc_type   = void* (*)(size_t size);
+using free_type    = void  (*)(void *ptr);
+using realloc_type = void* (*)(void *ptr, size_t oldsz, size_t newsz);
+
+// set the function to be called
+void set_aalloc(aalloc_type fn);
+void set_afree(afree_type fn);
+void set_arealloc(arealloc_type fn);
+
+void set_alloc(alloc_type fn);
+void set_free(free_type fn);
+void set_realloc(realloc_type fn);
+
+// get the function which will be called
+alloc_type get_alloc();
+free_type get_free();
+realloc_type get_realloc();
+
+aalloc_type get_aalloc();
+free_type get_afree();
+arealloc_type get_arealloc();
+
+// c++-style allocation -------------------------------------------------------
+
+/** C++17-style memory_resource. See http://en.cppreference.com/w/cpp/experimental/memory_resource */
+struct MemoryResource
+{
+    const char *name = nullptr;
+    virtual ~MemoryResource() {}
+
+    void* allocate(size_t sz, size_t alignment = alignof(max_align_t))
+    {
+        void *mem = this->do_allocate(sz, alignment);
+        C4_CHECK_MSG(mem != nullptr, "could not allocate %lu bytes", sz);
+        return mem;
+    }
+    void* reallocate(void* ptr, size_t oldsz, size_t newsz, size_t alignment = alignof(max_align_t))
+    {
+        void *mem = this->do_reallocate(ptr, oldsz, newsz, alignment);
+        C4_CHECK_MSG(mem != nullptr, "could not reallocate from %lu to %lu bytes", oldsz, newsz);
+        return mem;
+    }
+    void deallocate(void* ptr, size_t sz, size_t alignment = alignof(max_align_t))
+    {
+        this->do_deallocate(ptr, sz, alignment);
+    }
+
+protected:
+
+    virtual void* do_allocate(size_t sz, size_t alignment) = 0;
+    virtual void* do_reallocate(void* ptr, size_t oldsz, size_t newsz, size_t alignment) = 0;
+    virtual void  do_deallocate(void* ptr, size_t sz, size_t alignment) = 0;
+
+};
+
+/** A c4::aalloc-based memory resource. Thread-safe if the implementation called by
+ * c4::aalloc() is safe. */
+struct MemoryResourceMalloc : public MemoryResource
+{
+
+    MemoryResourceMalloc() { name = "malloc"; }
+    virtual ~MemoryResourceMalloc() {}
+
+protected:
+
+    virtual void* do_allocate(size_t sz, size_t alignment) override
+    {
+        return c4::aalloc(sz, alignment);
+    }
+
+    virtual void  do_deallocate(void* ptr, size_t sz, size_t alignment) override
+    {
+        C4_UNUSED(sz);
+        C4_UNUSED(alignment);
+        c4::afree(ptr);
+    }
+
+    virtual void* do_reallocate(void* ptr, size_t oldsz, size_t newsz, size_t alignment) override
+    {
+        return c4::arealloc(ptr, oldsz, newsz, alignment);
+    }
+
+};
+
+
+C4_ALWAYS_INLINE MemoryResource* get_memory_resource_malloc()
+{
+    static MemoryResourceMalloc mr;
+    return &mr;
+}
+
+C4_BEGIN_NAMESPACE(detail)
+C4_ALWAYS_INLINE MemoryResource* & get_memory_resource()
+{
+    static MemoryResource* mr = get_memory_resource_malloc();
+    return mr;
+}
+C4_END_NAMESPACE(detail)
+
+MemoryResource* get_memory_resource()
+{
+    return detail::get_memory_resource();
+}
+void set_memory_resource(MemoryResource* mr)
+{
+    C4_ASSERT(mr != nullptr);
+    detail::get_memory_resource() = mr;
+}
+
+
+struct AllocationCounts
+{
+    size_t curr_allocs = 0;
+    size_t curr_size = 0;
+    size_t max_allocs = 0;
+    size_t max_size = 0;
+    size_t total_allocs = 0;
+    size_t sum_size = 0;
+
+    void clear_counts()
+    {
+        zero_mem(this);
+    }
+
+    void add_counts(void* ptr, size_t sz)
+    {
+        if(ptr == nullptr) return;
+        ++curr_allocs;
+        curr_size += sz;
+        max_allocs = curr_allocs > max_allocs ? curr_allocs : max_allocs;
+        max_size = curr_size > max_size ? curr_size : max_size;
+        ++total_allocs;
+        sum_size += sz;
+    }
+    void rem_counts(void *ptr, size_t sz)
+    {
+        if(ptr == nullptr) return;
+        --curr_allocs;
+        curr_size -= sz;
+    }
+
+    AllocationCounts operator- (AllocationCounts const& that)
+    {
+        AllocationCounts r(*this);
+        r.curr_allocs -= that.curr_allocs;
+        r.curr_size -= that.curr_size;
+        r.max_allocs = max_allocs > that.max_allocs ? max_allocs : that.max_allocs;
+        r.max_size = max_size > that.max_size ? max_size : that.max_size;
+        r.total_allocs -= that.total_allocs;
+        r.sum_size -= that.sum_size;
+        return r;
+    }
+    AllocationCounts operator+ (AllocationCounts const& that)
+    {
+        AllocationCounts r(*this);
+        r.curr_allocs += that.curr_allocs;
+        r.curr_size += that.curr_size;
+        r.max_allocs += max_allocs > that.max_allocs ? max_allocs : that.max_allocs;
+        r.max_size += max_size > that.max_size ? max_size : that.max_size;
+        r.total_allocs += that.total_allocs;
+        r.sum_size += that.sum_size;
+        return r;
+    }
+
+};
+
+
+/** a MemoryResource which latches onto another MemoryResource
+ * and counts allocations and sizes. */
+class MemoryResourceCounts : public MemoryResource
+{
+public:
+
+    MemoryResourceCounts() : m_resource(get_memory_resource()) { name = m_resource->name; }
+    MemoryResourceCounts(MemoryResource *res) : m_resource(res) { name = m_resource->name; }
+
+protected:
+
+    MemoryResource *m_resource;
+
+public:
+
+    AllocationCounts counts;
+
+protected:
+
+    virtual void* do_allocate(size_t sz, size_t alignment) override
+    {
+        void *ptr = m_resource->allocate(sz, alignment);
+        counts.add_counts(ptr, sz);
+        return ptr;
+    }
+
+    virtual void  do_deallocate(void* ptr, size_t sz, size_t alignment) override
+    {
+        counts.rem_counts(ptr, sz);
+        m_resource->deallocate(ptr, sz, alignment);
+    }
+
+    virtual void* do_reallocate(void* ptr, size_t oldsz, size_t newsz, size_t alignment) override
+    {
+        counts.rem_counts(ptr, oldsz);
+        void* nptr = m_resource->reallocate(ptr, oldsz, newsz, alignment);
+        counts.add_counts(nptr, newsz);
+        return nptr;
+    }
+
+};
+
+struct AllocatorBase
+{
+protected:
+
+    MemoryResource *m_resource;
+
+    AllocatorBase() : m_resource(get_memory_resource()) {}
+    AllocatorBase(MemoryResource* mem) noexcept : m_resource(mem) {}
+
+public:
+
+    MemoryResource* resource() const { return m_resource; }
+
+    /** for construct:
+     * @see http://en.cppreference.com/w/cpp/experimental/polymorphic_allocator/construct */
+
+/** SFINAE: enable the function with a void return type when a condition is verified */
+#define _c4_void_if(cond) C4_ALWAYS_INLINE typename std::enable_if< cond, void >::type
+/** @see http://en.cppreference.com/w/cpp/memory/uses_allocator */
+#define _c4_uses_allocator(U) std::uses_allocator< U, MemoryResource* >::value
+/** @see http://en.cppreference.com/w/cpp/types/is_constructible */
+#define _c4_is_constructible(...) std::is_constructible< __VA_ARGS__ >::value
+
+    // 1. types with no allocators
+    template< class U, class... Args >
+    _c4_void_if( ! _c4_uses_allocator(U) && _c4_is_constructible(U, Args...) )
+    construct(U* ptr, Args&&... args)
+    {
+        c4::construct(ptr, std::forward< Args >(args)...);
+    }
+    template< class U, class I, class... Args >
+    _c4_void_if( ! _c4_uses_allocator(U) && _c4_is_constructible(U, Args...) )
+    construct_n(U* ptr, I n, Args&&... args)
+    {
+        c4::construct_n(ptr, n, std::forward< Args >(args)...);
+    }
+
+    // 2. types using allocators (ie, containers)
+
+    // 2.1. can construct(std::allocator_arg_t, MemoryResource*, Args...)
+    template< class U, class... Args >
+    _c4_void_if(_c4_uses_allocator(U) && _c4_is_constructible(U, std::allocator_arg_t, MemoryResource*, Args...))
+    construct(U* ptr, Args&&... args)
+    {
+        c4::construct(ptr, std::allocator_arg, m_resource, std::forward< Args >(args)...);
+    }
+    template< class U, class I, class... Args >
+    _c4_void_if(_c4_uses_allocator(U) && _c4_is_constructible(U, std::allocator_arg_t, MemoryResource*, Args...))
+    construct_n(U* ptr, I n, Args&&... args)
+    {
+        c4::construct_n(ptr, n, std::allocator_arg, m_resource, std::forward< Args >(args)...);
+    }
+
+    // 2.2. can construct(Args..., MemoryResource*)
+    template< class U, class... Args >
+    _c4_void_if(_c4_uses_allocator(U) && _c4_is_constructible(U, Args..., MemoryResource*))
+    construct(U* ptr, Args&&... args)
+    {
+        c4::construct(ptr, std::forward< Args >(args)..., m_resource);
+    }
+    template< class U, class I, class... Args >
+    _c4_void_if(_c4_uses_allocator(U) && _c4_is_constructible(U, Args..., MemoryResource*))
+    construct_n(U* ptr, I n, Args&&... args)
+    {
+        c4::construct_n(ptr, n, std::forward< Args >(args)..., m_resource);
+    }
+
+    template< class U >
+    static C4_ALWAYS_INLINE void destroy(U* ptr)
+    {
+        c4::destroy(ptr);
+    }
+    template< class U, class I >
+    static C4_ALWAYS_INLINE void destroy_n(U* ptr, I n)
+    {
+        c4::destroy_n(ptr, n);
+    }
+
+#undef _c4_void_if
+#undef _c4_is_constructible
+#undef _c4_uses_allocator
+
+};
+
+
+/** A polymorphic allocator, acting as a proxy to a memory resource */
+template< class T >
+class Allocator : public AllocatorBase
+{
+public:
+
+    template< class U > using rebind = Allocator< U >;
+    template< class U > Allocator< U > rebound() { return Allocator< U >(*this); }
+
+    Allocator() : AllocatorBase() {}
+    Allocator(MemoryResource *r) : AllocatorBase(r) {}
+    template< class U > Allocator(Allocator<U> const& that) : AllocatorBase(that.m_resource) {}
+
+    Allocator(Allocator const&) = default;
+    Allocator(Allocator     &&) = default;
+
+    Allocator& operator= (Allocator const&) = delete; // WTF? why? @see http://en.cppreference.com/w/cpp/memory/polymorphic_allocator
+    Allocator& operator= (Allocator     &&) = default;
+
+    /** returns a default-constructed polymorphic allocator object
+     * @see http://en.cppreference.com/w/cpp/memory/polymorphic_allocator/select_on_container_copy_construction      */
+    Allocator select_on_container_copy_construct() const { return Allocator(*this); }
+
+    T* allocate(size_t num_objs, size_t alignment = alignof(T))
+    {
+        C4_ASSERT(m_resource != nullptr);
+        void* vmem = m_resource->allocate(num_objs * sizeof(T), alignment);
+        T* mem = static_cast< T* >(vmem);
+        return mem;
+    }
+
+    void deallocate(T * ptr, size_t num_objs, size_t alignment = alignof(T))
+    {
+        C4_ASSERT(m_resource != nullptr);
+        m_resource->deallocate(ptr, num_objs * sizeof(T), alignment);
+    }
+
+    T* reallocate(T* ptr, size_t oldnum, size_t newnum, size_t alignment = alignof(T))
+    {
+        C4_ASSERT(m_resource != nullptr);
+        void* vmem = m_resource->reallocate(ptr, oldnum * sizeof(T), newnum * sizeof(T), alignment);
+        T* mem = static_cast< T* >(vmem);
+        return mem;
+    }
+
+};
+
+
+template< class T, size_t N = 16, size_t Alignment = alignof(T) >
+class SmallAllocator : public AllocatorBase
+{
+
+    union {
+        alignas(Alignment) char _m_arr[N * sizeof(T)];
+        alignas(Alignment) T m_arr[N];
+    };
+
+public:
+
+    template< class U > using rebind = SmallAllocator< U >;
+    template< class U > SmallAllocator< U > rebound() { return SmallAllocator< U >(*this); }
+
+    SmallAllocator() : AllocatorBase() {}
+    SmallAllocator(MemoryResource *r) : AllocatorBase(r) {}
+    template< class U > SmallAllocator(SmallAllocator<U> const& that) : AllocatorBase(that.m_resource) {}
+
+    SmallAllocator(SmallAllocator const&) = default;
+    SmallAllocator(SmallAllocator     &&) = default;
+
+    SmallAllocator& operator= (SmallAllocator const&) = delete; // WTF? why? @see http://en.cppreference.com/w/cpp/memory/polymorphic_allocator
+    SmallAllocator& operator= (SmallAllocator     &&) = default;
+
+    /** returns a default-constructed polymorphic allocator object
+     * @see http://en.cppreference.com/w/cpp/memory/polymorphic_allocator/select_on_container_copy_construction      */
+    SmallAllocator select_on_container_copy_construct() const { return SmallAllocator(*this); }
+
+    T* allocate(size_t num_objs, size_t alignment = alignof(T))
+    {
+        C4_ASSERT(m_resource != nullptr);
+        if(num_objs <= N)
+        {
+            return m_arr;
+        }
+        void* vmem = m_resource->allocate(num_objs * sizeof(T), alignment);
+        T* mem = static_cast< T* >(vmem);
+        return mem;
+    }
+
+    void deallocate(T * ptr, size_t num_objs, size_t alignment = alignof(T))
+    {
+        if(ptr == &m_arr[0])
+        {
+            return;
+        }
+        C4_ASSERT(m_resource != nullptr);
+        m_resource->deallocate(ptr, num_objs * sizeof(T), alignment);
+    }
+
+    T* reallocate(T* ptr, size_t oldnum, size_t newnum, size_t alignment = alignof(T))
+    {
+        C4_ASSERT(m_resource != nullptr);
+        if(oldnum <= N && newnum <= N)
+        {
+            return m_arr;
+        }
+        else if(oldnum <= N && newnum > N)
+        {
+            return allocate(newnum, alignment);
+        }
+        else if(oldnum > N && newnum <= N)
+        {
+            deallocate(ptr, oldnum, alignment);
+            return m_arr;
+        }
+        void* vmem = m_resource->reallocate(ptr, oldnum * sizeof(T), newnum * sizeof(T), alignment);
+        T* mem = static_cast< T* >(vmem);
+        return mem;
+    }
+
+private:
+
+    MemoryResource* m_resource;
+
+};
+
+C4_END_NAMESPACE(c4)
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+// Storage facilities.
+
+
+C4_BEGIN_NAMESPACE(c4)
+
+//-----------------------------------------------------------------------------
+#define _c4_DEFINE_ARRAY_TYPES(T, I) \
+\
+    using value_type = T;\
+    using size_type = I;\
+\
+    using pointer = T*;\
+    using const_pointer = T const*;\
+\
+    using reference = T&;\
+    using const_reference = T const&;\
+\
+    using iterator = T*;\
+    using const_iterator = T const*;\
+\
+    using difference_type = ptrdiff_t;
+
+//-----------------------------------------------------------------------------
+template< class T, class I = uint32_t >
+class array_view
+{
+protected:
+
+    T *m_ptr;
+    I m_size;
+
+public:
+
+    _c4_DEFINE_ARRAY_TYPES(T, I);
+
+    C4_ALWAYS_INLINE operator array_view< const T, I > () const { return array_view< const T, I >((T const*)m_ptr, m_size); }
+
+    array_view(array_view const&) = default;
+    array_view(array_view     &&) = default;
+
+    array_view& operator= (array_view const&) = default;
+    array_view& operator= (array_view     &&) = default;
+
+public:
+
+    C4_ALWAYS_INLINE array_view() : m_ptr{}, m_size{0} {}
+    C4_ALWAYS_INLINE array_view(T *p, I sz) : m_ptr{p}, m_size{sz} {}
+    template< size_t N >
+    C4_ALWAYS_INLINE array_view(T (&arr)[N]) : m_ptr{arr}, m_size{N} {}
+
+    C4_ALWAYS_INLINE constexpr I type_size() const { return sizeof(T); }
+    C4_ALWAYS_INLINE           I byte_size() const { return m_size*sizeof(T); }
+
+    C4_ALWAYS_INLINE bool empty()    const noexcept { return m_size == 0; }
+    C4_ALWAYS_INLINE I    size()     const noexcept { return m_size; }
+    C4_ALWAYS_INLINE I    capacity() const noexcept { return m_size; }
+
+    C4_ALWAYS_INLINE T      * data()       noexcept { return m_ptr; }
+    C4_ALWAYS_INLINE T const* data() const noexcept { return m_ptr; }
+
+    C4_ALWAYS_INLINE       iterator  begin()       noexcept { return m_ptr; }
+    C4_ALWAYS_INLINE const_iterator  begin() const noexcept { return m_ptr; }
+    C4_ALWAYS_INLINE const_iterator cbegin() const noexcept { return m_ptr; }
+
+    C4_ALWAYS_INLINE       iterator  end()       noexcept { return m_ptr + m_size; }
+    C4_ALWAYS_INLINE const_iterator  end() const noexcept { return m_ptr + m_size; }
+    C4_ALWAYS_INLINE const_iterator cend() const noexcept { return m_ptr + m_size; }
+
+    C4_ALWAYS_INLINE T&          front()       noexcept { C4_XASSERT(!empty()); return m_ptr[0]; }
+    C4_ALWAYS_INLINE fastcref<T> front() const noexcept { C4_XASSERT(!empty()); return m_ptr[0]; }
+
+    C4_ALWAYS_INLINE T&          back()       noexcept { C4_XASSERT(!empty()); return m_ptr[m_size - 1]; }
+    C4_ALWAYS_INLINE fastcref<T> back() const noexcept { C4_XASSERT(!empty()); return m_ptr[m_size - 1]; }
+
+    C4_ALWAYS_INLINE T&          operator[] (I i)       noexcept { C4_XASSERT(i >= 0 && i < m_size); return m_ptr[i]; }
+    C4_ALWAYS_INLINE fastcref<T> operator[] (I i) const noexcept { C4_XASSERT(i >= 0 && i < m_size); return m_ptr[i]; }
+
+    C4_ALWAYS_INLINE void clear() noexcept { m_size = 0; }
+
+    C4_ALWAYS_INLINE void ltrim(I n) noexcept { C4_XASSERT(n >= 0 && n < m_size); m_ptr += n; }
+    C4_ALWAYS_INLINE void rtrim(I n) noexcept { C4_XASSERT(n >= 0 && n < m_size); m_size -= n; }
+
+    array_view view(I first = 0) const noexcept
+    {
+        C4_ASSERT(first >= 0 && first < m_size);
+        return array_view(m_ptr + first, m_size - first);
+    }
+    array_view view(I first, I num) const noexcept
+    {
+        C4_ASSERT(first >= 0 && first < m_size);
+        C4_ASSERT(first + num >= 0 && first + num < m_size);
+        return array_view(m_ptr + first, num);
+    }
+
+};
+
+C4_BEGIN_NAMESPACE(stg) // stg==SToraGe
+
+struct growth_default;
+
+//-----------------------------------------------------------------------------
+/** a tag type for specifying the initial capacity of allocatable contiguous storage */
+struct with_capacity_t {};
+/** a tag type for initializing the containers with variadic arguments a la
+ * initializer_list, minus the initializer_list problems.
+ * @see */
+struct aggregate_t {};
+
+//-----------------------------------------------------------------------------
+// Raw storage classes
+
+/** @defgroup raw_storage_classes Raw storage classes
+ *
+ * These classes are a building block for the several flavours of
+ * contiguous containers. They offer a convenient way to hold a
+ * number of (almost-)contiguous objects (via the index-based [] operator).
+ *
+ *     - The memory used by these objects is NOT automatically allocated or\n
+ *       freed. Use the protected methods _raw_reserve(I n), _raw_trim(I n)
+ *     - The elements in the raw storage are NOT automatically constructed\n
+ *       or destroyed.
+ */
+
+//-----------------------------------------------------------------------------
+/** Type traits for raw storage classes.
+ * @ingroup raw_storage_classes */
+template< class T >
+struct raw_storage_traits
+{
+    using storage_type = T;
+    using value_type = typename T::value_type;
+    using size_type = typename T::size_type;
+
+    constexpr static const bool fixed = false;
+    constexpr static const bool contiguous = false;
+}
+
+#define C4_DEFINE_STORAGE_TRAITS(type, is_fixed, is_contiguous, ...)    \
+    template< __VA_ARGS__ >                                             \
+    struct raw_storage_traits< type >                                   \
+    {                                                                   \
+        using storage_type = T;                                         \
+        using value_type = typename T::value_type;                      \
+        using size_type = typename T::size_type;                        \
+                                                                        \
+        constexpr static const bool fixed = is_fixed;                   \
+        constexpr static const bool contiguous = is_contiguous;         \
+    }
+
+//-----------------------------------------------------------------------------
+/** Utility class which uses SFINAE to dispatch construction/destruction
+ * to the appropriate functions. This class is meant to be used by container
+ * implementations to aid in object management.
+ * @ingroup raw_storage_classes */
+template< class T >
+struct raw_storage_util : public raw_storage_traits< T >
+{
+    using raw_storage_traits< T >::contiguous;
+
+#define _c4require(what) C4_ALWAYS_INLINE static typename std::enable_if< what, void >::type
+
+    template< class ...Args >
+    _c4require(contiguous) construct(T& dest, I first, I n, Args&&... args)
+    {
+        dest.m_allocator.construct_n(dest.data() + first, n, std::forward< Args >(args)...);
+    }
+    template< class ...Args >
+    _c4require( ! contiguous) construct(T& dest, I first, I n, Args&&... args)
+    {
+        dest._raw_construct(first, n, std::forward< Args >(args)...);
+    }
+
+    _c4require(contiguous) destroy(T& dest, I first, I n)
+    {
+        dest.m_allocator.destroy_n(dest.data() + first, n);
+    }
+    _c4require( ! contiguous) destroy(T& dest, I first, I n)
+    {
+        dest._raw_destroy(first, n);
+    }
+
+    _c4require(contiguous) move_construct(T& dest, T const& src, I first, I n)
+    {
+        c4::move_construct_n(dest.data() + first, src.data() + first, n);
+    }
+    _c4require( ! contiguous) move_construct(T& dest, T const& src, I first, I n)
+    {
+        dest._raw_move_construct(src, first, n);
+    }
+
+    _c4require(contiguous) copy_construct(T& dest, T const& src, I first, I n)
+    {
+        c4::copy_construct_n(dest.data() + first, src.data() + first, n);
+    }
+    _c4require( ! contiguous) copy_construct(T& dest, T const& src, I first, I n)
+    {
+        dest._raw_copy_construct(src, first, n);
+    }
+
+    _c4require(contiguous) move_assign(T& dest, T const& src, I first, I n)
+    {
+        c4::move_assign_n(dest.data() + first, src.data() + first, n);
+    }
+    _c4require( ! contiguous) move_assign(T& dest, T const& src, I first, I n)
+    {
+        dest._raw_move_assign(src, first, n);
+    }
+
+    _c4require(contiguous) copy_assign(T& dest, T const& src, I first, I n)
+    {
+        c4::copy_assign_n(dest.data() + first, src.data() + first, n);
+    }
+    _c4require( ! contiguous) copy_assign(T& dest, T const& src, I first, I n)
+    {
+        dest._raw_copy_assign(src, first, n);
+    }
+
+
+#undef _c4require
+};
+
+//-----------------------------------------------------------------------------
+/** @ingroup raw_storage_classes */
+template< class T, size_t N, class I = uint32_t, I Alignment = alignof(T) >
+struct raw_fixed
+{
+
+    union {
+        alignas(Alignment) char _m_buf[N * sizeof(T)];
+        alignas(Alignment) T m_ptr[N];
+    };
+
+    _c4_DEFINE_ARRAY_TYPES(T, I);
+
+    C4_ALWAYS_INLINE T& operator[] (I i) { return m_ptr[i]; }
+    C4_ALWAYS_INLINE constexpr fastcref<T> operator[] (I i) const { return m_ptr[i]; }
+
+    C4_ALWAYS_INLINE T      * data()       noexcept { return m_ptr; }
+    C4_ALWAYS_INLINE T const* data() const noexcept { return m_ptr; }
+
+    C4_ALWAYS_INLINE constexpr I max_capacity() const noexcept { return N; }
+    C4_ALWAYS_INLINE constexpr I capacity() const noexcept { return N; }
+    C4_ALWAYS_INLINE constexpr I next_capacity(I cap) const noexcept { return N; }
+
+    void _raw_reserve(I n) { C4_XASSERT(n <= N); }
+    void _raw_trim(I n) { C4_XASSERT(n <= N); }
+};
+
+C4_DEFINE_STORAGE_TRAITS(raw_fixed< T, N, I, Alignment >,
+                         true, // fixed
+                         true, // contiguous
+                         class T, size_t N, class I, I Alignment);
+
+//-----------------------------------------------------------------------------
+
+/** raw storage variable size: allocatable, contiguous
+ * @ingroup raw_storage_classes */
+template
+<
+    class T,
+    class I = uint32_t,
+    I Alignment = alignof(T),
+    class Alloc = Allocator< T >,
+    class GrowthType = growth_default
+>
+struct raw
+{
+
+    T*    m_ptr;
+    I     m_capacity;
+    Alloc m_allocator;
+
+    _c4_DEFINE_ARRAY_TYPES(T, I);
+
+    using allocator_type = Alloc;
+
+    raw() : m_ptr(nullptr), m_capacity(0), m_allocator() {}
+    raw(Alloc const& a) : m_ptr(nullptr), m_capacity(0), m_allocator(a) {}
+    ~raw()
+    {
+        C4_ASSERT_MSG(m_ptr == nullptr, "the container using this did not free the storage");
+    }
+
+    /** @todo implement this */
+    raw(raw const& that) = delete;
+    raw(raw     && that) = default;
+
+    /** @todo implement this */
+    raw& operator=(raw const& that) = delete;
+    raw& operator=(raw     && that) = default;
+
+    C4_ALWAYS_INLINE T& operator[] (I i) { return m_ptr[i]; }
+    C4_ALWAYS_INLINE constexpr fastcref<T> operator[] (I i) const { return m_ptr[i]; }
+
+    C4_ALWAYS_INLINE T      * data()       noexcept { return m_ptr; }
+    C4_ALWAYS_INLINE T const* data() const noexcept { return m_ptr; }
+
+    C4_ALWAYS_INLINE I max_capacity() const noexcept { return std::numeric_limits< I >::max() - 1; }
+    C4_ALWAYS_INLINE I capacity() const noexcept { return m_capacity; }
+    C4_ALWAYS_INLINE I next_capacity(I desired) const
+    {
+        return GrowthType::next_size(m_capacity, desired);
+    }
+
+    void _raw_reserve(I cap)
+    {
+
+    }
+
+    void _raw_trim(I cap)
+    {
+
+    }
+};
+
+C4_DEFINE_STORAGE_TRAITS(raw< T, I, Alignment, Alloc, GrowthType >,
+                         false, // fixed
+                         true,  // contiguous
+                         class T, class I, I Alignment, class Alloc, class GrowthType);
+
+
+//-----------------------------------------------------------------------------
+/** raw storage: allocatable and paged. This is NOT a contiguous storage structure.
+  * However, it does behave as such, offering the [] operator with contiguous
+  * range indices. This is useful for minimizing allocations and data copies in
+  * dynamic array-based containers as flat_list.
+  *
+  * @ingroup raw_storage_classes
+  * @todo add a raw structure with a runtime-determined page size */
+template
+<
+    class T,
+    size_t PageSize,      //< The page size. Must be a power of two.
+    class I = uint32_t,
+    I Alignment = alignof(T),
+    class Alloc = Allocator< T >
+>
+struct raw_paged
+{
+    static_assert(PageSize > 1, "PageSize must be > 1")
+    static_assert(PageSize & (PageSize - 1) == 0, "PageSize must be a power of two")
+
+    //! id mask: all the bits up to PageSize. Use to extract the position of an index within a page.
+    constexpr static const I _raw_idmask = (I)PageSize - 1;
+
+    //! page mask: bits complementary to PageSize. Use to extract the page of an index.
+    constexpr static const I _raw_pgmask = ~ ((I)PageSize - 1); //< all the bits
+
+    T    **m_pages;      //< array containing the pages
+    I      m_num_pages;  //< number of current pages in the array
+    Alloc  m_allocator;
+
+public:
+
+    _c4_DEFINE_ARRAY_TYPES(T, I);
+    using allocator_type = Alloc;
+
+    raw_paged() : m_pages(nullptr), m_num_pages(0), m_allocator() {}
+    raw_paged(Alloc const& a) : m_pages(nullptr), m_num_pages(0), m_allocator(a) {}
+
+    raw_paged(raw_paged const& that) = delete;
+    raw_paged(raw_paged     && that) = default;
+    raw_paged& operator=(raw_paged const& that) = delete;
+    raw_paged& operator=(raw_paged     && that) = default;
+
+    C4_ALWAYS_INLINE T& operator[] (I i)
+    {
+        C4_XASSERT(i < capacity());
+        I pg = i & _raw_pgmask;
+        I id = i & _raw_idmask;
+        C4_XASSERT(pg < m_num_pages);
+        C4_XASSERT(id < PageSize);
+        return m_pages[pg][id];
+    }
+    C4_ALWAYS_INLINE constexpr fastcref<T> operator[] (I i) const
+    {
+        C4_XASSERT(i < capacity());
+        I pg = i & _raw_pgmask;
+        I id = i & _raw_id;
+        C4_XASSERT(pg < m_num_pages);
+        C4_XASSERT(id < PageSize);
+        return m_pages[pg][id];
+    }
+
+    C4_ALWAYS_INLINE I max_capacity() const noexcept { return std::numeric_limits< I >::max() - 1; }
+    C4_ALWAYS_INLINE I capacity() const noexcept { return m_num_pages * PageSize; }
+    C4_ALWAYS_INLINE I next_capacity(I desired) const
+    {
+        I cap = capacity();
+        if(desired < cap) return cap;
+        I np = desired / PageSize;
+        I cap = np * PageSize;
+        return cap;
+    }
+
+protected:
+
+    void _raw_reserve(I cap)
+    {
+        if(cap <= capacity()) return;
+        I np = cap / PageSize;
+        C4_XASSERT(np * PageSize >= capacity());
+        if(np > m_num_pages)
+        {
+            auto at = m_allocator.rebound< T* >();
+            m_pages = at.reallocate(m_pages, m_num_pages, np);
+            for(I i = m_num_pages; i < np; ++i)
+            {
+                m_pages[i] = m_allocator.allocate(PageSize, Alignment);
+            }
+        }
+        m_num_pages = np;
+    }
+    void _raw_trim(I to)
+    {
+        if(m_pages == nullptr) return;
+        I np = to / PageSize;
+        if(np >= m_num_pages) return;
+        for(I i = np; i < m_num_pages; ++i)
+        {
+            m_allocator.deallocate(m_pages[i], PageSize, Alignment);
+        }
+        auto at = m_allocator.rebound< T* >();
+        if(to == 0)
+        {
+            at.deallocate(m_pages, m_num_pages);
+            m_pages = nullptr;
+        }
+        else
+        {
+            m_pages = at.reallocate(m_pages, m_num_pages, np);
+        }
+        m_num_pages = np;
+    }
+    void _raw_copy(raw_paged const& that, I first, I num)
+    {
+        C4_NOT_IMPLEMENTED();
+    }
+};
+
+
+C4_DEFINE_STORAGE_TRAITS(raw_paged< T, I, PageSize, Alignment, Alloc >,
+                         false, // fixed
+                         false, // contiguous
+                         true, // paged
+                         class T, class I, I PageSize, I Alignment, class Alloc);
+
+//-----------------------------------------------------------------------------
+/* CRTP bases for contiguous storage.
+ * These serve as a development scaffold until the implementation is validated
+ * and stable. Afterwards, the member functions in these base classes
+ * should be copied and adapted into each of the contiguous storage classes. */
+
+
+#define _c4this  static_cast< S* >(this)
+#define _c4cthis static_cast< S const* >(this)
+
+/** CRTP base for non-resizeable contiguous storage */
+template< class T, class I, class S >
+struct contiguous_base
+{
+protected:
+
+    // prevent construction/destruction of an object of this type unless through derived types
+
+    contiguous_base() {}
+    ~contiguous_base() {}
+
+public:
+
+    _c4_DEFINE_ARRAY_TYPES(T, I);
+
+    using view_type = array_view< T, I >;
+    using const_view_type = array_view< const T, I >;
+
+    C4_ALWAYS_INLINE constexpr I type_size() const { return sizeof(T); }
+    C4_ALWAYS_INLINE           I byte_size() const { return _c4this->size() * sizeof(T); }
+
+public:
+
+    C4_ALWAYS_INLINE operator view_type () { return view_type(_c4this->m_ptr, _c4this->size()); }
+    C4_ALWAYS_INLINE operator const_view_type () const { return const_view_type(_c4this->m_ptr, _c4this->size()); }
+
+    view_type view(I first = 0)
+    {
+        C4_ASSERT(first >= 0 && first < _c4this->size());
+        return view_type(_c4this->m_ptr + first, _c4this->size() - first);
+    }
+    const_view_type view(I first = 0) const
+    {
+        C4_ASSERT(first >= 0 && first < _c4this->size());
+        return view_type(_c4this->m_ptr + first, _c4this->size() - first);
+    }
+    view_type view(I first, I num)
+    {
+        C4_ASSERT(first >= 0 && first < _c4this->size());
+        C4_ASSERT(first + num >= 0 && first + num < _c4this->size());
+        return view_type(_c4this->m_ptr + first, num);
+    }
+    const_view_type view(I first, I num) const
+    {
+        C4_ASSERT(first >= 0 && first < _c4this->size());
+        C4_ASSERT(first + num >= 0 && first + num < _c4this->size());
+        return view_type(_c4this->m_ptr + first, num);
+    }
+
+    void fill(T const& v)
+    {
+        C4_ASSERT(_c4this->m_size > 0);
+        copy_assign_n(_c4this->m_ptr, v, _c4this->m_size);
+    }
+
+    void assign(T const* v, I sz)
+    {
+        if(v == _c4this->m_ptr && sz == _c4this->size()) return;
+        _c4this->resize(sz); // resize() for fixed-size storage just asserts whether the size is the same
+        copy_assign_n(_c4this->m_ptr, v, sz);
+    }
+
+    void assign(array_view< T, I > v)
+    {
+        assign(v.data(), v.size());
+    }
+
+    void assign(aggregate_t, std::initializer_list< T > il)
+    {
+        assign(il.begin(), il.size());
+    }
+
+    C4_ALWAYS_INLINE bool is_valid_iterator(const_iterator it) const noexcept
+    {
+        return it >= _c4cthis->m_ptr && it <= _c4cthis->m_ptr + _c4cthis->size();
+    }
+    C4_ALWAYS_INLINE bool is_valid_index(I i) const noexcept
+    {
+        return i >= 0 && i < _c4cthis->size();
+    }
+
+};
+
+
+/** CRTP base for resizeable contiguous storage */
+template< class T, class I, class S >
+struct contiguous_base_rs : public contiguous_base< T, I, S >
+{
+    using contiguous_base< T, I, S >::is_valid_iterator;
+
+protected:
+
+    // prevent construction/destruction of an object of this type unless through derived types
+
+    contiguous_base_rs() {}
+    ~contiguous_base_rs() {}
+
+public:
+
+    _c4_DEFINE_ARRAY_TYPES(T, I);
+
+public:
+
+    // emplace
+    template< class... Args >
+    iterator emplace(const_iterator pos, Args&&... args)
+    {
+        C4_XASSERT(pos >= _c4this->m_ptr && pos <= _c4this->m_ptr + _c4this->m_size);
+        I ipos = I(pos - _c4this->m_ptr);
+        _c4this->_growto(_c4this->m_size + 1, pos);
+        pos = _c4this->m_ptr + ipos;
+        _c4this->_construct(pos, std::forward< Args >(args)...);
+        ++_c4this->m_size;
+        return (iterator)pos;
+    }
+    template< class... Args >
+    void emplace_back(Args&& ...a)
+    {
+        _c4this->_growto(_c4this->m_size + 1);
+        _c4this->_construct(_c4this->m_ptr + _c4this->m_size, std::forward< Args >(a)...);
+        ++_c4this->m_size;
+    }
+    template< class... Args >
+    void emplace_front(Args&& ...a)
+    {
+        _c4this->_growto(_c4this->m_size + 1);
+        _c4this->_construct(_c4this->m_ptr, std::forward< Args >(a)...);
+        ++_c4this->m_size;
+    }
+
+    // push
+
+    void push_back(T const& val)
+    {
+        _c4this->_growto(_c4this->m_size + 1);
+        _c4this->_construct(_c4this->m_ptr + _c4this->m_size, val);
+        ++_c4this->m_size;
+    }
+    void push_back(T && val)
+    {
+        _c4this->_growto(_c4this->m_size + 1);
+        _c4this->_construct(_c4this->m_ptr + _c4this->m_size, std::move(val));
+        ++_c4this->m_size;
+    }
+
+    void push_front(T const& val)
+    {
+        _c4this->_growto(_c4this->m_size + 1, _c4this->m_ptr);
+        _c4this->_construct(_c4this->m_ptr, val);
+        ++_c4this->m_size;
+    }
+    void push_front(T && val)
+    {
+        _c4this->_growto(_c4this->m_size + 1, _c4this->m_ptr);
+        _c4this->_construct(_c4this->m_ptr, std::move(val));
+        ++_c4this->m_size;
+    }
+
+    // pop
+
+    void pop_back()
+    {
+        C4_XASSERT(_c4this->m_size > 0);
+        _c4this->_growto(_c4this->m_size - 1);
+        --_c4this->m_size;
+    }
+    void pop_front()
+    {
+        C4_XASSERT(_c4this->m_size > 0);
+        _c4this->_growto(_c4this->m_size - 1, _c4this->m_ptr);
+        --_c4this->m_size;
+    }
+
+    // insert
+
+    iterator insert(const_iterator pos, T const& value)
+    {
+        C4_XASSERT(is_valid_iterator(pos));
+        I ipos = I(pos - _c4this->m_ptr);
+        _c4this->_growto(_c4this->m_size + 1, pos);
+        pos = _c4this->m_ptr + ipos;
+        _c4this->_construct(pos, value);
+        ++_c4this->m_size;
+        return (iterator)pos;
+    }
+    iterator insert(const_iterator pos, T&& value)
+    {
+        C4_XASSERT(is_valid_iterator(pos));
+        I ipos = I(pos - _c4this->m_ptr);
+        _c4this->_growto(_c4this->m_size + 1, pos);
+        pos = _c4this->m_ptr + ipos;
+        _c4this->_construct(pos, std::move(value));
+        ++_c4this->m_size;
+        return (iterator)pos;
+    }
+    iterator insert(const_iterator pos, I count, T const& value)
+    {
+        C4_XASSERT(is_valid_iterator(pos));
+        I ipos = I(pos - _c4this->m_ptr);
+        _c4this->_growto(_c4this->m_size + count, pos);
+        pos = _c4this->m_ptr + ipos;
+        _c4this->_construct_n(pos, value, count);
+        _c4this->m_size += count;
+        return (iterator)pos;
+    }
+    template< class InputIt >
+    iterator insert(const_iterator pos, InputIt first, InputIt last)
+    {
+        C4_XASSERT(is_valid_iterator(pos));
+        I ipos = I(pos - _c4this->m_ptr);
+        I count = (I)std::distance(first, last);
+        _c4this->_growto(_c4this->m_size + count, pos);
+        pos = _c4this->m_ptr + ipos;
+        for(I i = 0; first != last; ++first, ++i)
+        {
+            _c4this->_construct(pos + i, first);
+        }
+        _c4this->m_size += count;
+        return (iterator)pos;
+    }
+    iterator insert(const_iterator pos, aggregate_t, std::initializer_list<T> ilist)
+    {
+        C4_XASSERT(is_valid_iterator(pos));
+        I ipos = I(pos - _c4this->m_ptr);
+        _c4this->_growto(_c4this->m_size + ilist.size(), pos);
+        pos = _c4this->m_ptr + ipos;
+        I i = 0;
+        for(auto const& v : ilist)
+        {
+            _c4this->_construct((pos++) + i, v);
+        }
+        pos = _c4this->m_ptr + ipos;
+        _c4this->m_size += ilist.size();
+        return (iterator)pos;
+    }
+
+    /** removes the element at pos */
+    iterator erase(const_iterator pos)
+    {
+        C4_XASSERT(is_valid_iterator(pos) && _c4this->size() > 0);
+        I ipos = I(pos - _c4this->m_ptr);
+        _c4this->_grow_to(_c4this->size() - 1, pos);
+        pos = _c4this->m_ptr + ipos;
+        --_c4this->m_size;
+        return (iterator)pos;
+    }
+    /** removes the elements in the range [first; last). */
+    iterator erase(const_iterator first, const_iterator last)
+    {
+        I dist = (I)std::distance(first, last);
+        if(!dist) return (iterator)first;
+        C4_XASSERT(is_valid_iterator(first) && _c4this->size() >= dist);
+        I ipos = I(first - _c4this->m_ptr);
+        _c4this->_grow_to(_c4this->size() - dist, first);
+        first = _c4this->m_ptr + ipos;
+        _c4this->m_size -= dist;
+        return (iterator)first;
+    }
+
+};
+
+#undef _c4this
+#undef _c4cthis
+
+//-----------------------------------------------------------------------------
+/** contiguous storage, fixed size+capacity (cannot resize down) */
+template< class T, size_t N, class I = uint32_t, I Alignment = alignof(T) >
+class contiguous_fixed_size : public contiguous_base< T, I, contiguous_fixed_size< T, N, I, Alignment > >
+{
+    C4_STATIC_ASSERT(N <= std::numeric_limits< I >::max());
+
+    friend struct contiguous_base< T, I, contiguous_fixed_size< T, N, I, Alignment > >;
+    using base_type = contiguous_base< T, I, contiguous_fixed_size< T, N, I, Alignment > >;
+
+protected:
+
+    alignas(Alignment) T m_ptr[N];
+
+public:
+
+    _c4_DEFINE_ARRAY_TYPES(T, I);
+
+    enum { alignment = Alignment };
+
+public:
+
+    C4_ALWAYS_INLINE operator array_view< T, I > () { return array_view< T, I >(m_ptr, N); }
+    C4_ALWAYS_INLINE operator array_view< const T, I > () const { return array_view< const T, I >(m_ptr, N); }
+
+public:
+
+    contiguous_fixed_size() {}
+    contiguous_fixed_size(array_view< T, I > const& v) { assign(v); }
+    contiguous_fixed_size(array_view< T, I >     && v) { assign(std::move(v)); }
+    //contiguous_fixed_size(std::initializer_list< T > il) { assign(il); }
+
+    // provided for compatibility with other contiguous storages
+    contiguous_fixed_size(I sz) { C4_ASSERT(sz == N); }
+    contiguous_fixed_size(I sz, with_capacity_t, I cap) { C4_ASSERT(sz == N && cap == N); }
+
+    C4_ALWAYS_INLINE constexpr bool empty()    const { return false; }
+    C4_ALWAYS_INLINE constexpr I    size()     const { return N; }
+    C4_ALWAYS_INLINE constexpr I    max_size() const { return N; }
+    C4_ALWAYS_INLINE constexpr I    capacity() const { return N; }
+
+    C4_ALWAYS_INLINE                 iterator  begin()       { return m_ptr; }
+    C4_ALWAYS_INLINE constexpr const_iterator  begin() const { return m_ptr; }
+    C4_ALWAYS_INLINE constexpr const_iterator cbegin() const { return m_ptr; }
+
+    C4_ALWAYS_INLINE                 iterator  end()       { return m_ptr + I(N); }
+    C4_ALWAYS_INLINE constexpr const_iterator  end() const { return m_ptr + I(N); }
+    C4_ALWAYS_INLINE constexpr const_iterator cend() const { return m_ptr + I(N); }
+
+    C4_ALWAYS_INLINE           T&          front()       { return m_ptr[0]; }
+    C4_ALWAYS_INLINE constexpr fastcref<T> front() const { return m_ptr[0]; }
+
+    C4_ALWAYS_INLINE           T&          back()       { return m_ptr[I(N) - 1]; }
+    C4_ALWAYS_INLINE constexpr fastcref<T> back() const { return m_ptr[I(N) - 1]; }
+
+    C4_ALWAYS_INLINE           T      * data()       { return m_ptr; }
+    C4_ALWAYS_INLINE constexpr T const* data() const { return m_ptr; }
+
+    C4_ALWAYS_INLINE T&          operator[] (I i)       { C4_XASSERT(i >= 0 && i < N); return m_ptr[i]; }
+    C4_ALWAYS_INLINE fastcref<T> operator[] (I i) const { C4_XASSERT(i >= 0 && i < N); return m_ptr[i]; }
+
+private:
+
+    // these functions are provided for compatibility with the scaffold CRTP
+
+    C4_ALWAYS_INLINE void reserve(I cap) { C4_ASSERT(cap == I(N)); }
+    C4_ALWAYS_INLINE void shrink_to_fit() {}
+    C4_ALWAYS_INLINE void resize(I sz) { C4_ASSERT(sz == I(N)); }
+    C4_ALWAYS_INLINE void clear() { C4_NEVER_REACH(); }
+
+    template< class U, class... Args >
+    C4_ALWAYS_INLINE static void _construct(U *ptr, Args&&... args)
+    {
+        ::c4::construct(ptr, std::forward< Args >(args)...);
+    }
+
+};
+
+//-----------------------------------------------------------------------------
+/** contiguous storage, variable size, fixed capacity. */
+template< class T, size_t N, class I = uint32_t, I Alignment = alignof(T) >
+class contiguous_fixed_capacity : public contiguous_base_rs< T, I, contiguous_fixed_capacity< T, N, I, Alignment > >
+{
+    C4_STATIC_ASSERT(N <= std::numeric_limits< I >::max());
+
+    friend struct contiguous_base< T, I, contiguous_fixed_capacity< T, N, I, Alignment > >;
+    friend struct contiguous_base_rs< T, I, contiguous_fixed_capacity< T, N, I, Alignment > >;
+    using base_type = contiguous_base_rs< T, I, contiguous_fixed_capacity< T, N, I, Alignment > >;
+
+protected:
+
+    union {
+        alignas(Alignment) char _m_buf[N * sizeof(T)];
+        alignas(Alignment) T m_ptr[N];
+    };
+
+    I m_size;
+
+public:
+
+    _c4_DEFINE_ARRAY_TYPES(T, I);
+
+    enum { alignment = Alignment };
+
+public:
+
+    contiguous_fixed_capacity() : m_size{0} {}
+    contiguous_fixed_capacity(I sz) : contiguous_fixed_capacity() { resize(sz); }
+
+    // provided for compatibility
+    contiguous_fixed_capacity(with_capacity_t, I cap) : contiguous_fixed_capacity() { C4_ASSERT_MSG(cap == N, "capacity: asked %u, should be %u", (uint32_t)cap, (uint32_t)N); }
+    contiguous_fixed_capacity(I sz, with_capacity_t, I cap) : contiguous_fixed_capacity() { C4_ASSERT_MSG(cap == N, "capacity: asked %u, should be %u", (uint32_t)cap, (uint32_t)N); resize(sz); }
+
+    contiguous_fixed_capacity(aggregate_t, std::initializer_list< T > il) { assign(aggregate_t{}, il); }
+
+    C4_ALWAYS_INLINE           bool empty()    const { return m_size == 0; }
+    C4_ALWAYS_INLINE           I    size()     const { return m_size; }
+    C4_ALWAYS_INLINE constexpr I    max_size() const { return N; }
+    C4_ALWAYS_INLINE constexpr I    capacity() const { return N; }
+
+    C4_ALWAYS_INLINE           T      * data()       { return m_ptr; }
+    C4_ALWAYS_INLINE constexpr T const* data() const { return m_ptr; }
+
+    C4_ALWAYS_INLINE                 iterator  begin()       { return m_ptr; }
+    C4_ALWAYS_INLINE constexpr const_iterator  begin() const { return m_ptr; }
+    C4_ALWAYS_INLINE constexpr const_iterator cbegin() const { return m_ptr; }
+
+    C4_ALWAYS_INLINE                 iterator  end()       { return m_ptr + m_size; }
+    C4_ALWAYS_INLINE           const_iterator  end() const { return m_ptr + m_size; }
+    C4_ALWAYS_INLINE           const_iterator cend() const { return m_ptr + m_size; }
+
+    C4_ALWAYS_INLINE T&          front()       { C4_XASSERT(!empty()); return m_ptr[0]; }
+    C4_ALWAYS_INLINE fastcref<T> front() const { C4_XASSERT(!empty()); return m_ptr[0]; }
+
+    C4_ALWAYS_INLINE T&          back()       { C4_XASSERT(!empty()); return m_ptr[m_size - 1]; }
+    C4_ALWAYS_INLINE fastcref<T> back() const { C4_XASSERT(!empty()); return m_ptr[m_size - 1]; }
+
+    C4_ALWAYS_INLINE T&          operator[] (I i)       { C4_XASSERT(i >= 0 && i < m_size); return m_ptr[i]; }
+    C4_ALWAYS_INLINE fastcref<T> operator[] (I i) const { C4_XASSERT(i >= 0 && i < m_size); return m_ptr[i]; }
+
+    C4_ALWAYS_INLINE void resize(I sz) { C4_ASSERT(sz >= 0 && sz <= N); m_size = sz; }
+    C4_ALWAYS_INLINE void clear() { m_size = 0; }
+
+protected:
+
+    // these functions are provided for compatibility with the scaffold CRTP
+
+    C4_ALWAYS_INLINE void reserve(I cap) { C4_ASSERT(cap == N); }
+    C4_ALWAYS_INLINE void shrink_to_fit() {}
+
+    template< class U, class... Args >
+    C4_ALWAYS_INLINE static void _construct(U *ptr, Args&&... args)
+    {
+        ::c4::construct(ptr, std::forward< Args >(args)...);
+    }
+
+    void _growto(I sz)
+    {
+        C4_ASSERT(sz <= N);
+    }
+    void _growto(I sz, const_iterator before_this)
+    {
+        C4_ASSERT(sz <= N);
+        C4_NOT_IMPLEMENTED();
+    }
+};
+
+
+//-----------------------------------------------------------------------------
+/** contiguous storage, allocatable */
+template
+<
+    class T,
+    class I = uint32_t,
+    I Alignment = alignof(T),
+    class Alloc = c4::Allocator< T >,
+    class GrowthType = growth_default
+>
+struct contiguous : public contiguous_base_rs< T, I, contiguous< T, I, Alignment, Alloc, GrowthType > >
+{
+    friend struct contiguous_base< T, I, contiguous< T, I, Alignment, Alloc, GrowthType > >;
+    friend struct contiguous_base_rs< T, I, contiguous< T, I, Alignment, Alloc, GrowthType > >;
+    using base_type = contiguous_base_rs< T, I, contiguous< T, I, Alignment, Alloc, GrowthType > >;
+protected:
+
+    T*    m_ptr;
+    I     m_size;
+    I     m_capacity;
+    Alloc m_allocator;
+
+public:
+
+    _c4_DEFINE_ARRAY_TYPES(T, I);
+
+    enum { alignment = Alignment };
+    using allocator_type = Alloc;
+    using growth_type = GrowthType;
+
+    C4_ALWAYS_INLINE operator array_view< T, I > () { return array_view< T, I >(m_ptr, m_size); }
+    C4_ALWAYS_INLINE operator array_view< const T, I > () const { return array_view< const T, I >(m_ptr, m_size); }
+
+public:
+
+    allocator_type get_allocator() const { return m_allocator; }
+
+    C4_ALWAYS_INLINE bool empty()    const { return m_size == 0; }
+    C4_ALWAYS_INLINE I    size()     const { return m_size; }
+    C4_ALWAYS_INLINE I    max_size() const { return allocator_type::max_size(); }
+    C4_ALWAYS_INLINE I    capacity() const { return m_capacity; }
+
+    C4_ALWAYS_INLINE T      * data()       { return m_ptr; }
+    C4_ALWAYS_INLINE T const* data() const { return m_ptr; }
+
+    C4_ALWAYS_INLINE       iterator  begin()       { return m_ptr; }
+    C4_ALWAYS_INLINE const_iterator  begin() const { return m_ptr; }
+    C4_ALWAYS_INLINE const_iterator cbegin() const { return m_ptr; }
+
+    C4_ALWAYS_INLINE       iterator  end()       { return m_ptr + m_size; }
+    C4_ALWAYS_INLINE const_iterator  end() const { return m_ptr + m_size; }
+    C4_ALWAYS_INLINE const_iterator cend() const { return m_ptr + m_size; }
+
+    C4_ALWAYS_INLINE T&          front()       { C4_XASSERT(!empty()); return *m_ptr; }
+    C4_ALWAYS_INLINE fastcref<T> front() const { C4_XASSERT(!empty()); return *m_ptr; }
+
+    C4_ALWAYS_INLINE T&          back()       { C4_XASSERT(!empty()); return *(m_ptr + (m_size - 1)); }
+    C4_ALWAYS_INLINE fastcref<T> back() const { C4_XASSERT(!empty()); return *(m_ptr + (m_size - 1)); }
+
+    C4_ALWAYS_INLINE T&          operator[] (I i)       { C4_XASSERT(i >= 0 && i < m_size); return m_ptr[i]; }
+    C4_ALWAYS_INLINE fastcref<T> operator[] (I i) const { C4_XASSERT(i >= 0 && i < m_size); return m_ptr[i]; }
+
+    contiguous()                        : m_ptr(nullptr), m_size(0), m_capacity(0), m_allocator( ) {}
+    contiguous(allocator_type const& a) : m_ptr(nullptr), m_size(0), m_capacity(0), m_allocator(a) {}
+
+    contiguous(I sz)                          : contiguous( ) { resize(sz); }
+    contiguous(I sz, allocator_type const& a) : contiguous(a) { resize(sz); }
+
+    contiguous(with_capacity_t, I cap)                          : contiguous( ) { reserve(cap); }
+    contiguous(with_capacity_t, I cap, allocator_type const& a) : contiguous(a) { reserve(cap); }
+
+    contiguous(I sz, with_capacity_t, I cap)                          : contiguous( ) { reserve(cap); resize(sz); }
+    contiguous(I sz, with_capacity_t, I cap, allocator_type const& a) : contiguous(a) { reserve(cap); resize(sz); }
+
+    contiguous(contiguous const& that) : contiguous(that.m_allocator) { assign(that); }
+    contiguous(contiguous     && that) : contiguous(that.m_allocator) { assign(std::move(that)); }
+
+    contiguous& operator= (contiguous const& that) { assign(that); return *this; }
+    contiguous& operator= (contiguous     && that) { assign(std::move(that)); return *this; }
+
+    ~contiguous()
+    {
+        _free();
+    }
+
+    void clear()
+    {
+        resize(0);
+    }
+
+    using base_type::assign;
+    void assign(contiguous const& that)
+    {
+        if(&that == this) return;
+        if(that.m_size == 0)
+        {
+            clear();
+        }
+        else if(that.m_size > 0)
+        {
+            if(that.m_size == m_size)
+            {
+                copy_assign_n(m_ptr, that.m_ptr, that.m_size);
+            }
+            else if(that.m_size < m_size)
+            {
+                copy_assign_n(m_ptr, that.m_ptr, that.m_size);
+                m_allocator.destroy_n(m_ptr+that.m_size, m_size - that.m_size);
+            }
+            else if(that.m_size > m_size)
+            {
+                reserve(that.m_size);
+                copy_assign_n(m_ptr, that.m_ptr, m_size);
+                copy_construct_n(m_ptr+m_size, that.m_ptr, that.m_size - m_size);
+            }
+            m_size = that.m_size;
+        }
+    }
+    void assign(contiguous && that)
+    {
+        if(&that == this) return;
+        C4_ASSERT(m_ptr != that.m_ptr);
+        clear();
+        m_allocator.deallocate(m_ptr, m_capacity, Alignment);
+        m_ptr = that.m_ptr;
+        m_size = that.m_size;
+        m_capacity = that.m_capacity;
+        that.m_ptr = nullptr;
+        that.m_size = 0;
+        that.m_capacity = 0;
+    }
+
+    void resize(I sz)
+    {
+        if(sz == m_size) return;
+        if(sz > m_capacity)
+        {
+            reserve(sz);
+        }
+        if(sz > m_size)
+        {
+            m_allocator.construct_n(m_ptr + m_size, sz - m_size);
+        }
+        else if(sz < m_size)
+        {
+            m_allocator.destroy_n(m_ptr + sz, m_size - sz);
+        }
+        m_size = sz;
+    }
+    void resize(I sz, fastcref<T> value)
+    {
+        if(sz == m_size) return;
+        if(sz > m_capacity)
+        {
+            reserve(sz);
+        }
+        if(sz > m_size)
+        {
+            m_allocator.construct_n(m_ptr + m_size, sz - m_size, value);
+        }
+        else if(sz < m_size)
+        {
+            m_allocator.destroy_n(m_ptr + sz, m_size - sz);
+        }
+        m_size = sz;
+    }
+    void reserve(I cap)
+    {
+        _reserve(m_size, cap, m_ptr + m_size);
+    }
+    void shrink_to_fit()
+    {
+        if(m_size == m_capacity) return;
+        if(m_size == 0)
+        {
+            _free();
+        }
+        else
+        {
+            T *tmp = m_allocator.allocate(m_size, Alignment);
+            if(m_ptr)
+            {
+                move_construct_n(tmp, m_ptr, m_size);
+                m_allocator.deallocate(m_ptr, m_capacity, Alignment);
+            }
+            m_ptr = tmp;
+            m_capacity = m_size;
+        }
+    }
+
+protected:
+
+    void _growto(I sz)
+    {
+        C4_XASSERT(m_size <= m_capacity);
+        if(sz > m_capacity)
+        {
+            size_t next = growth_type::next_size(m_capacity, sz);
+            C4_XASSERT(next < size_t(std::numeric_limits< I >::max()));
+            _reserve(sz, (I)next, m_ptr + m_capacity);
+        }
+        if(sz < m_size)
+        {
+            C4_NOT_IMPLEMENTED();
+        }
+    }
+    void _growto(I sz, const_iterator before_this)
+    {
+        C4_XASSERT(m_size <= m_capacity);
+        if(sz > m_capacity)
+        {
+            size_t next = growth_type::next_size(m_capacity, sz);
+            C4_XASSERT(next < size_t(std::numeric_limits< I >::max()));
+            _reserve(sz, (I)next, before_this);
+        }
+        else
+        {
+            if(sz > m_size)
+            {
+                I num_after = m_ptr+m_size - before_this;
+                for(I i = 0; i < num_after; ++i)
+                {
+                    T* curr = m_ptr + m_size - i;
+                    move_construct(curr, curr-1);
+                }
+            }
+            else if(sz < m_size)
+            {
+                C4_NOT_IMPLEMENTED();
+            }
+        }
+    }
+    void _reserve(I sz, I cap, const_iterator before_this)
+    {
+        if(cap <= m_capacity) return;
+        T *tmp = m_allocator.allocate(cap, Alignment);
+        if(m_ptr)
+        {
+            if(sz >= m_size) // add more before_this
+            {
+                I num_more = sz - m_size; // to construct (later)
+                I num_before = before_this - m_ptr; // to move before
+                I num_after = m_ptr+m_size - before_this; // to move after
+                move_construct_n(tmp, m_ptr, num_before);
+                move_construct_n(tmp + num_before + num_more, m_ptr + num_before, num_after);
+            }
+            else // remove some before_this
+            {
+                I num_less = m_size - sz; // to remove
+                I num_before = before_this-num_less - m_ptr; // to move before
+                I num_after = m_ptr+m_size - before_this; // to move after
+                m_allocator.destroy_n(m_ptr + num_before, num_less);
+                move_construct_n(tmp, m_ptr, num_before);
+                move_construct_n(tmp + num_before, m_ptr + num_before + num_less, num_after);
+            }
+            m_allocator.deallocate(m_ptr, m_capacity, Alignment);
+        }
+        m_ptr = tmp;
+        m_capacity = cap;
+    }
+
+    void _free()
+    {
+        m_allocator.destroy_n(m_ptr, m_size);
+        m_allocator.deallocate(m_ptr, m_capacity, Alignment);
+        m_size = 0;
+        m_ptr = nullptr;
+        m_capacity = 0;
+    }
+
+
+    template< class U, class... Args >
+    C4_ALWAYS_INLINE void _construct(U *ptr, Args&&... args)
+    {
+        m_allocator.construct(ptr, std::forward< Args >(args)...);
+    }
+
+    template< class U, class... Args >
+    C4_ALWAYS_INLINE void _construct_n(U *ptr, Args&&... args)
+    {
+        m_allocator.construct_n(ptr, std::forward< Args >(args)...);
+    }
+};
+
+
+//-----------------------------------------------------------------------------
+
+/** @todo add search algorithm transparency (binary/ternary/golden)*/
+template
+<
+    class T,
+    class Compare = std::less< T >,
+    class Storage = c4::stg::contiguous< T >
+>
+class contiguous_sorted : protected Storage
+{
+public:
+
+    using value_type = T;
+    using size_type = typename Storage::size_type;
+    using iterator = typename Storage::iterator;
+    using const_iterator = typename Storage::const_iterator;
+    using reference = value_type&;
+    using const_reference = value_type const&;
+    using pointer = value_type*;
+    using const_pointer = value_type const*;
+
+public:
+
+    contiguous_sorted() : m_valid(true) {}
+    contiguous_sorted(aggregate_t, std::initializer_list< T > il) { Storage::assign(aggregate_t{}, il); }
+
+    using Storage::empty;
+    using Storage::size;
+    using Storage::capacity;
+    using Storage::reserve;
+    using Storage::shrink_to_fit;
+
+    using Storage::type_size;
+    using Storage::byte_size;
+
+    using Storage::begin;
+    using Storage::end;
+    using Storage::data;
+
+    using Storage::front;
+    using Storage::back;
+
+    using Storage::operator[];
+
+    bool valid() const { return m_valid; }
+
+    template< class L >
+    iterator find(L const& v)
+    {
+        C4_STATIC_ASSERT(m_valid);
+        auto it = lower_bound< L >(v);
+        if(it == end()) return it;
+        if( ! m_compare(*it, v) && ! m_compare(v, *it)) return it;
+        return end();
+    }
+    iterator find(T const& v)
+    {
+        C4_STATIC_ASSERT(m_valid);
+        auto it = lower_bound(v);
+        if(it == end()) return it;
+        if( ! m_compare(*it, v) && ! m_compare(v, *it)) return it;
+        return end();
+    }
+
+    template< class L >
+    const_iterator find(L const& v) const
+    {
+        C4_STATIC_ASSERT(m_valid);
+        auto it = lower_bound< L >(v);
+        if(it == end()) return it;
+        if( ! m_compare(*it, v) && ! m_compare(v, *it)) return it;
+        return end();
+    }
+    const_iterator find(T const& v) const
+    {
+        C4_STATIC_ASSERT(m_valid);
+        auto it = lower_bound(v);
+        if(it == end()) return it;
+        if( ! m_compare(*it, v) && ! m_compare(v, *it)) return it;
+        return end();
+    }
+
+    // insert into the proper place. order remains valid.
+
+    iterator insert(T const& v)
+    {
+        if(empty())
+        {
+            Storage::push_back(v);
+            return end() - 1;
+        }
+        if( ! m_valid) fix();
+        auto lb = lower_bound(v);
+        auto it = Storage::insert(lb, v);
+        return it;
+    }
+    iterator insert(T && v)
+    {
+        if(empty())
+        {
+            Storage::push_back(std::move(v));
+            return end() - 1;
+        }
+        if( ! m_valid) fix();
+        auto lb = lower_bound(v);
+        auto it = Storage::insert(lb, std::move(v));
+        return it;
+    }
+    template< class... Args >
+    C4_ALWAYS_INLINE iterator emplace(Args&&... args)
+    {
+        if(empty())
+        {
+            Storage::emplace_back(std::forward< Args >(args)...);
+            return end() - 1;
+        }
+        if( ! m_valid) fix();
+        auto lb = lower_bound(v);
+        auto it = Storage::emplace(lb, std::forward< Args >(args)...);
+        return it;
+    }
+
+    // insert at the end. order continues valid if and only if the value is not less than back()
+
+    iterator push_back_nosort(T const& v)
+    {
+        if( ! empty())
+        {
+            m_valid = m_compare(v, back());
+        }
+        this->push_back(v);
+        return end() - 1;
+    }
+    iterator push_back_nosort(T && v)
+    {
+        if( ! empty())
+        {
+            m_valid = m_compare(v, back());
+        }
+        this->push_back(std::move(v));
+        return end() - 1;
+    }
+    template< class... Args >
+    C4_ALWAYS_INLINE iterator emplace_back_nosort(Args&&... args)
+    {
+        return this->push_back_nosort(std::move(T(std::forward< Args >(args)...)));
+    }
+
+    // insert before the given position. Order continues valid if and only if the value is not less than pos->first
+
+    iterator insert_nosort(const_iterator pos, T const& v)
+    {
+        C4_XASSERT(this->is_valid_iterator(pos));
+        if( ! empty() && pos != end())
+        {
+            m_valid = m_compare(v, *pos);
+        }
+        size_type i = (size_type)(pos - begin());
+        Storage::insert(pos, v);
+        return begin() + i;
+    }
+    iterator insert_nosort(const_iterator pos, T && v)
+    {
+        C4_XASSERT(this->is_valid_iterator(pos));
+        if( ! empty() && pos != end())
+        {
+            m_valid = m_compare(v, *pos);
+        }
+        size_type i = (size_type)(pos - begin());
+        Storage::insert(pos, std::move(v));
+        return begin() + i;
+    }
+    template< class... Args >
+    C4_ALWAYS_INLINE iterator emplace_nosort(const_iterator pos, Args&&... args)
+    {
+        return insert_nosort(pos, std::move(T(std::forward< Args >(args)...)));
+    }
+
+    bool fix()
+    {
+        if(m_valid) return false;
+        std::sort(begin(), end(), m_compare);
+        m_valid = true;
+        return true;
+    }
+
+
+    void assign(T const* mem, size_type sz)
+    {
+        Storage::assign(mem, sz);
+        m_valid = _check(); // O(N) pass
+        fix(); // O(N log N) pass
+    }
+    void assign_nosort(T const* mem, size_type sz)
+    {
+        Storage::assign(mem, sz);
+        m_valid = _check(); // O(N) pass
+    }
+    void assign_nocheck(T const* mem, size_type sz)
+    {
+        Storage::assign(mem, sz);
+    }
+    void assign(aggregate_t a, std::initializer_list< T > il)
+    {
+        Storage::assign(a, il);
+        m_valid = _check(); // O(N) pass
+        fix(); // O(N log N) pass
+    }
+    void assign_nosort(aggregate_t a, std::initializer_list< T > il)
+    {
+        Storage::assign(a, il);
+        m_valid = _check(); // O(N) pass
+    }
+    void assign_nocheck(aggregate_t a, std::initializer_list< T > il)
+    {
+        Storage::assign(a, il);
+    }
+
+    /** Returns an iterator pointing to the first element that is _NOT LESS THAN_ key.*/
+    template< class U >
+    C4_ALWAYS_INLINE const_iterator lower_bound(U const& v) const { return std::lower_bound(begin(), end(), v, m_compare); }
+    /** Returns an iterator pointing to the first element that is _NOT LESS THAN_ key.*/
+    C4_ALWAYS_INLINE const_iterator lower_bound(T const& v) const { return std::lower_bound(begin(), end(), v, m_compare); }
+
+    /** Returns an iterator pointing to the first element that is _NOT LESS THAN_ key.*/
+    template< class U >
+    C4_ALWAYS_INLINE iterator lower_bound(U const& v) { return std::lower_bound(begin(), end(), v, m_compare); }
+    /** Returns an iterator pointing to the first element that is _NOT LESS THAN_ key.*/
+    C4_ALWAYS_INLINE iterator lower_bound(T const& v) { return std::lower_bound(begin(), end(), v, m_compare); }
+
+    /** Returns an iterator pointing to the first element that is _NOT MORE THAN_ key.*/
+    template< class U >
+    C4_ALWAYS_INLINE const_iterator upper_bound(U const& v) const { return std::upper_bound(begin(), end(), v, m_compare); }
+    /** Returns an iterator pointing to the first element that is _NOT MORE THAN_ key.*/
+    C4_ALWAYS_INLINE const_iterator upper_bound(T const& v) const { return std::upper_bound(begin(), end(), v, m_compare); }
+
+    /** Returns an iterator pointing to the first element that is _NOT MORE THAN_ key.*/
+    template< class U >
+    C4_ALWAYS_INLINE iterator upper_bound(U const& v) { return std::upper_bound(begin(), end(), v, m_compare); }
+    /** Returns an iterator pointing to the first element that is _NOT MORE THAN_ key.*/
+    C4_ALWAYS_INLINE iterator upper_bound(T const& v) { return std::upper_bound(begin(), end(), v, m_compare); }
+
+protected:
+
+    bool _check() const
+    {
+        for(auto itm1 = begin(), it = itm1+1, e = end(); it < e; ++itm1, ++it)
+        {
+            if( ! m_compare(itm1, it))
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+
+private:
+
+    Compare m_compare;
+    bool    m_valid;
+
+};
+
+//-----------------------------------------------------------------------------
+
+// Capacity growth policies
+
+/** Grow by the least possible amount. */
+struct growth_least
+{
+    static size_t next_size(size_t curr, size_t at_least) noexcept
+    {
+        if(at_least <= curr) return curr;
+        return at_least;
+    }
+};
+/** Grow to the double of the current size if it is bigger than at_least;
+ * if not, then just to at_least. */
+struct growth_pot
+{
+    static size_t next_size(size_t curr, size_t at_least) noexcept
+    {
+        if(at_least <= curr) return curr;
+        size_t nxt = (curr << 1);
+        return nxt > at_least ? nxt : at_least;
+    }
+};
+/** Grow by the Fibonacci ratio if the result is bigger than at_least;
+ * if not, then just to at_least. */
+struct growth_phi
+{
+    static size_t next_size(size_t curr, size_t at_least) noexcept
+    {
+        if(at_least <= curr) return curr;
+        size_t nxt = size_t(float(curr) * 1.618f);
+        nxt = nxt > 0 ? nxt : 1;
+        return nxt > at_least ? nxt : at_least;
+    }
+};
+/** grow another growth policy in fixed chunk sizes. Useful for SIMD buffers. */
+template< class Growth, size_t PowerOfTwoChunkSize >
+struct growth_by_chunks
+{
+    C4_STATIC_ASSERT(PowerOfTwoChunkSize > 1);
+    C4_STATIC_ASSERT_MSG((PowerOfTwoChunkSize & (PowerOfTwoChunkSize - 1)) == 0, "chunk size must be a power of two");
+
+    constexpr static const size_t chunk_size = PowerOfTwoChunkSize;
+
+    static size_t next_size(size_t curr, size_t at_least) noexcept
+    {
+        size_t next = Growth::next_size(curr, at_least);
+        size_t rem = (next & (PowerOfTwoChunkSize-1));
+        next += rem ? PowerOfTwoChunkSize - rem : 0;
+        C4_ASSERT((next % PowerOfTwoChunkSize) == 0);
+        return next;
+    }
+};
+/** first, powers of 2, then Fibonacci ratio */
+struct growth_default
+{
+    static size_t next_size(size_t curr, size_t at_least) noexcept
+    {
+        if(at_least <= 1024)
+            return growth_pot::next_size(curr, at_least);
+        else
+            return growth_phi::next_size(curr, at_least);
+    }
+};
+
+C4_END_NAMESPACE(stg)
+
+//-----------------------------------------------------------------------------
+using stg::with_capacity_t;
+constexpr const stg::with_capacity_t with_capacity;
+
+using stg::aggregate_t;
+constexpr const stg::aggregate_t aggregate;
+
+template< class T, size_t N, class I = uint32_t >
+using array = c4::stg::contiguous_fixed_size< T, N, I, alignof(T) >;
+
+template< class T, size_t N, class I = uint32_t >
+using static_vector = c4::stg::contiguous_fixed_capacity< T, N, I, alignof(T) >;
+
+template< class T, class I = uint32_t >
+using vector = c4::stg::contiguous< T, I, alignof(T), c4::Allocator< T >, c4::stg::growth_default >;
+
+template< class T, size_t N = 16, class I = uint8_t >
+using small_vector = c4::stg::contiguous< T, I, alignof(T), c4::SmallAllocator< T, N, alignof(T) >, c4::stg::growth_default >;
+
+template< class T, class Compare = std::less< T >, class Storage = vector< T > >
+using sorted_vector = c4::stg::contiguous_sorted< T, Compare, Storage >;
+
+//-----------------------------------------------------------------------------
+
+template< class K, class T, class Compare = std::less< K > >
+struct flat_map_compare
+{
+    using value_type = std::pair<K,T>;
+
+    Compare comp;
+
+    flat_map_compare() : comp() {}
+    flat_map_compare(Compare const& c) : comp(c) {}
+
+    // compare value vs value
+    C4_ALWAYS_INLINE bool operator() (value_type const& l, value_type const& r) const { return comp(l.first, r.first); }
+
+    // compare key vs value
+    template< class L >
+    C4_ALWAYS_INLINE bool operator() (L const& k, value_type const& v) const { return comp(k, v.first); }
+    C4_ALWAYS_INLINE bool operator() (K const& k, value_type const& v) const { return comp(k, v.first); }
+
+    // compare value vs key
+    template< class L >
+    C4_ALWAYS_INLINE bool operator() (value_type const& v, L const& k) const { return comp(v.first, k); }
+    C4_ALWAYS_INLINE bool operator() (value_type const& v, K const& k) const { return comp(v.first, k); }
+};
+
+//-----------------------------------------------------------------------------
+/** A map based on a sorted array. Lookup is O(log N) BUT insertion/removal are O(N).
+ * This associative container is good for any of the following scenarios:
+ *  -frequent lookups with infrequent modifications.
+ *  -OR small sizes. Depending on the size, the array's better cache
+ *    properties compensate the more expensive linear modification time.
+ * Of course, YMMV. */
+template
+<
+    class K,
+    class T,
+    class Compare = std::less< K >,
+    class Storage = c4::stg::contiguous< std::pair< K, T > >
+>
+class flat_map : protected stg::contiguous_sorted< std::pair< K, T >, flat_map_compare< K, T, Compare >, Storage >
+{
+    C4_STATIC_ASSERT((std::is_same< std::pair< K, T >, typename Storage::value_type >::value == true));
+    using base_type = stg::contiguous_sorted< std::pair< K, T >, flat_map_compare< K, T, Compare >, Storage >;
+
+public:
+
+    using key_type = K;
+    using mapped_type = T;
+    using size_type      = typename Storage::size_type;
+    using allocator_type = typename Storage::allocator_type;
+    using value_type     = typename Storage::value_type;
+    using iterator       = typename Storage::iterator;
+    using const_iterator = typename Storage::const_iterator;
+
+public:
+
+    using base_type::base_type;
+
+    using base_type::empty;
+    using base_type::size;
+    using base_type::capacity;
+    using base_type::reserve;
+    using base_type::shrink_to_fit;
+
+    using base_type::type_size;
+    using base_type::byte_size;
+
+    using base_type::data;
+
+    using base_type::begin;
+    using base_type::end;
+
+    using base_type::front;
+    using base_type::back;
+
+    using base_type::valid;
+    using base_type::find;
+
+    using base_type::lower_bound;
+    using base_type::upper_bound;
+
+    using base_type::clear;
+
+    using base_type::insert;
+    using base_type::emplace;
+
+    using base_type::push_back_nosort;
+    using base_type::emplace_back_nosort;
+
+    using base_type::insert_nosort;
+    using base_type::emplace_nosort;
+
+    using base_type::fix;
+
+    using base_type::assign;
+    using base_type::assign_nosort;
+    using base_type::assign_nocheck;
+
+    template< class L >
+    T& operator[] (L const& key)
+    {
+        iterator it = this->template lower_bound< L >(key);
+        if(it == this->end() || it->first != key)
+        {
+            it = this->emplace_nosort(it, key, std::move(T()));
+        }
+        C4_XASSERT(it->first == key);
+        return it->second;
+    }
+    T& operator[] (K const& key)
+    {
+        auto it = this->lower_bound(key);
+        if(it == this->end() || it->first != key)
+        {
+            it = this->emplace_nosort(it, key, std::move(T()));
+        }
+        C4_XASSERT(it->first == key);
+        return it->second;
+    }
+
+    template< class L >
+    fastcref< T > operator[] (L const& key) const
+    {
+        auto it = this->template find< L >(key);
+        C4_XASSERT(it != this->end());
+        C4_XASSERT(it->first == key);
+        return it->second;
+    }
+    fastcref< T > operator[] (K const& key) const
+    {
+        auto it = this->find(key);
+        C4_XASSERT(it != this->end());
+        C4_XASSERT(it->first == key);
+        return it->second;
+    }
+
+};
+
+//-----------------------------------------------------------------------------
+
+template< class T, class I >
+struct flat_forward_list_node
+{
+    using value_type = T;
+    using size_type = I;
+    T val;
+    I next;
+};
+template< class T, class I >
+struct flat_list_node
+{
+    using value_type = T;
+    using size_type = I;
+    T val;
+    I prev;
+    I next;
+};
+
+//-----------------------------------------------------------------------------
+
+template< class T, class I = uint32_t, class RawStorage = stg::raw< flat_forward_list_node<T,I>, I > >
+struct flat_forward_list
+{
+    C4_STATIC_ASSERT((std::is_same< I, typename RawStorage::size_type >::value));
+    C4_STATIC_ASSERT((std::is_same< flat_forward_list_node<T,I>, typename RawStorage::value_type >::value));
+public:
+
+    using node_type = flat_forward_list_node< T, I >;
+    using value_type = T;
+    using size_type = I;
+    using storage_type = RawStorage;
+
+public:
+
+    flat_forward_list() : m_storage(), m_size(0), m_head(0), m_tail(0), m_fhead(0)
+    {
+    }
+
+    I size() const { return m_size; }
+    I capacity() const { return m_storage.capacity(); }
+
+    bool empty() const { return m_size == 0; }
+    bool full() const { return m_fhead == m_storage.capacity(); }
+
+    void push_front(T const& val)
+    {
+        auto *n = _push_front();
+        n->val = val;
+    }
+    void push_front(T && val)
+    {
+        auto *n = _push_front();
+        n->val = std::move(val);
+    }
+    template< class... Args >
+    void emplace_front(Args&&... args)
+    {
+        auto *n = _push_front();
+        construct(&n->val, std::forward< Args >(args)...);
+    }
+    void pop_front()
+    {
+        C4_XASSERT(m_size > 0);
+        auto *c = n(m_head);
+        m_head = c->next;
+        c->val.~T();
+        --m_size;
+        C4_NOT_IMPLEMENTED();
+    }
+
+    void push_back(T const& val)
+    {
+        auto *c = _push_back();
+        c->val = val;
+    }
+    void push_back(T && val)
+    {
+        auto *c = _push_back();
+        c->val = std::move(val);
+    }
+    template< class... Args >
+    void emplace_back(Args&&... args)
+    {
+        auto *c = _push_back();
+        construct(&c->val, std::forward< Args >(args)...);
+    }
+    void pop_back()
+    {
+        C4_XASSERT(m_size > 1);
+        C4_NOT_IMPLEMENTED();
+    }
+
+    void clear()
+    {
+        if(m_size > 0)
+        {
+            n(m_tail)->next = m_fhead;
+            m_fhead = m_head;
+            m_head = 0;
+            m_tail = 0;
+        }
+        m_size = 0;
+    }
+
+    /** reorders the elements in memory in order of appearance on the list */
+    void sort()
+    {
+        node_type *d = m_storage.data();
+        I curr = m_head;
+        for(I i = 0; i < m_size; ++i)
+        {
+            while(curr < i)
+            {
+                curr = d[curr].next;
+            }
+            std::swap(d[i], d[curr]);
+            std::swap(d[i].next, curr);
+        }
+        I cap = m_storage.capacity();
+        for(I i = 0; i < cap; ++i)
+        {
+            d[i].next = i+1;
+        }
+        m_head = 0;
+        m_tail = 0;
+        if(m_size > 0)
+        {
+            d[m_size-1].next = cap;
+            m_tail = m_size - 1;
+        }
+        m_fhead = m_storage.capacity();
+        m_fhead = m_size < m_fhead ? m_size : m_fhead;
+    }
+
+protected:
+
+    C4_ALWAYS_INLINE node_type      * n(I i)       { C4_XASSERT(i >= 0 && i <= m_storage.capacity()); return m_storage.data() + i; }
+    C4_ALWAYS_INLINE node_type const* n(I i) const { C4_XASSERT(i >= 0 && i <= m_storage.capacity()); return m_storage.data() + i; }
+
+    node_type* _push_back()
+    {
+        if(C4_UNLIKELY(full()))
+        {
+            _growto(m_size + 1);
+        }
+        node_type* d = m_storage.data();
+        node_type* c = d + m_fhead;
+        I fhead = c->next;
+        c->next = m_storage.capacity();
+        if(C4_LIKELY(m_size > 0))
+        {
+            d[m_tail].next = m_fhead;
+        }
+        m_tail = m_fhead;
+        m_fhead = fhead;
+        ++m_size;
+        return c;
+    }
+    node_type* _push_front()
+    {
+        if(C4_UNLIKELY(full()))
+        {
+            _growto(m_size + 1);
+        }
+        node_type* d = m_storage.data();
+        node_type* c = d + m_fhead;
+        I fhead = c->next;
+        if(C4_LIKELY(m_size > 0))
+        {
+            c->next = m_head;
+        }
+        else
+        {
+            c->next = m_storage.capacity();
+            m_tail = m_fhead;
+        }
+        m_head = m_fhead;
+        m_fhead = fhead;
+        ++m_size;
+        return c;
+    }
+    node_type* _insert_after(I after_this)
+    {
+        C4_XASSERT(!empty());
+        C4_XASSERT(after_this >= 0 && after_this < m_storage.capacity());
+        if(C4_UNLIKELY(full()))
+        {
+            _growto(m_size + 1);
+        }
+        node_type* d = m_storage.data();
+        node_type* c = d + m_fhead;
+        I next_fhead = c->next;
+        c->next = d[after_this].next;
+        d[after_this].next = m_fhead;
+        m_tail = (after_this == m_tail) ? m_fhead : m_tail;
+        m_fhead = next_fhead;
+        ++m_size;
+        return c;
+    }
+
+    void _growto(I cap)
+    {
+        if(cap <= m_storage.capacity()) return;
+        C4_CHECK_MSG(cap <= m_storage.max_capacity(), "asked %lu, max is %lu", (uint64_t)cap, (uint64_t)m_storage.max_capacity());
+        I next_cap = m_storage.next_capacity(cap);
+        C4_CHECK_MSG(next_cap >= cap, "could not allocate storage for more elements. next=%lu cap=%lu", (uint64_t)next_cap, (uint64_t)cap);
+        _sort_into(m_storage.alloc(next_cap), next_cap);
+        C4_ASSERT(m_fhead != m_storage.capacity());
+    }
+
+    /** swaps the list into another array, pasting in list order */
+    void _sort_into(node_type* dst, I next_cap)
+    {
+        if(m_size != 0)
+        {
+            node_type * src = m_storage.data();
+            node_type * sc = src + m_head;
+            node_type * e  = src + m_storage.capacity();
+            node_type * dc = dst;
+            I curr = 0;
+            while(sc != e)
+            {
+                move_construct(&dc->val, &sc->val);
+                sc = src + sc->next;
+                dc->next = 1 + curr++;
+                ++dc;
+            }
+        }
+        m_head = 0;
+        m_tail = 0;
+        if(m_size > 0)
+        {
+            m_tail = m_size - 1;
+            dst[m_tail].next = next_cap;
+        }
+        m_fhead = m_size;
+        m_storage.reset(dst, next_cap);
+        for(I i = m_fhead; i < next_cap; ++i)
+        {
+            (dst + i)->next = i+1;
+        }
+    }
+
+protected:
+
+    RawStorage m_storage;
+    I m_size;
+    I m_head;
+    I m_tail;
+    I m_fhead; // free head: first free element
+
+protected:
+
+    template< class U >
+    friend class iterator_impl;
+
+    template< class U >
+    class iterator_impl
+    {
+        flat_forward_list *list;
+        I node;
+
+        C4_ALWAYS_INLINE bool _valid() const { return list != nullptr && list->_valid_node(node); }
+
+    public:
+
+        using value_type = typename U::value_type;
+        using size_type = typename U::size_type;
+
+        iterator_impl(flat_forward_list *li, I n) : list(li), node(n) {}
+
+        value_type& operator*  () { return list->n(node)->val; }
+        value_type* operator-> () { return &list->n(node)->val; }
+
+        iterator_impl& operator++ () { C4_XASSERT(_valid()); node = list->n(node)->next; return *this; }
+        iterator_impl& operator++ (int) { C4_XASSERT(_valid()); iterator_impl it = *this; node = list->n(node).next; return it; }
+
+        bool operator== (iterator_impl const& that) const { return list == that.list && node == that.node; }
+        bool operator!= (iterator_impl const& that) const { return list != that.list || node != that.node; }
+
+    };
+
+public:
+
+    using iterator = iterator_impl< node_type >;
+    using const_iterator = iterator_impl< const node_type >;
+
+    C4_ALWAYS_INLINE bool _valid_node(I node)
+    {
+        node_type const* c = n(node);
+        return c >= m_storage.data() && c < m_storage.data() + m_storage.capacity();
+    }
+
+    C4_ALWAYS_INLINE iterator begin() { return iterator(this, m_head); }
+    C4_ALWAYS_INLINE iterator end  () { return iterator(this, m_storage.capacity()); }
+
+    C4_ALWAYS_INLINE const_iterator begin() const { return iterator(this, m_head); }
+    C4_ALWAYS_INLINE const_iterator end  () const { return iterator(this, m_storage.capacity()); }
+
+};
+
+C4_END_NAMESPACE(c4)
+
+//------------------------------------------------------------
+//------------------------------------------------------------
+//------------------------------------------------------------
+// THREAD UTILS
+
+#ifdef C4_LOG_THREAD_SAFE
+
+C4_BEGIN_NAMESPACE(c4)
+
+uint32_t thread_number()
+{
+    static std::mutex mtx;
+    {
+        std::lock_guard< std::mutex > lock(mtx);
+        static flat_map< std::thread::id, uint32_t > ids;
+        static uint32_t count = 0;
+
+        auto id = std::this_thread::get_id();
+        auto it = ids.find(id);
+        if(it == ids.end())
+        {
+            it = ids.emplace(id, count++);
+        }
+
+        return it->second;
+    }
+}
+
+/** A simple class for implementing thread local vars.
+ * @warning It is inefficient for heavy use. */
+template< class T >
+class ThreadLocal : private c4::flat_map< std::thread::id, T >
+{
+    typedef c4::flat_map< std::thread::id, T > map_type;
+public:
+
+    using map_type::map_type;
+
+    C4_ALWAYS_INLINE operator T& () { return get(); }
+    C4_ALWAYS_INLINE T& get()
+    {
+        std::lock_guard< std::mutex > lock(m_mtx);
+        T& obj = (*this)[std::this_thread::get_id()];
+        return obj;
+    }
+
+private:
+
+    std::mutex m_mtx;
+
+};
+
+#if 0
+#   define C4_THREAD_LOCAL(type) c4::ThreadLocal< type >
+#else
+#   define C4_THREAD_LOCAL(type) thread_local type
+#endif
+
+C4_END_NAMESPACE(c4)
+
+#endif //  C4_LOG_THREAD_SAFE
+
+//------------------------------------------------------------
+//------------------------------------------------------------
+//------------------------------------------------------------
+// LOGGING
+
+C4_BEGIN_NAMESPACE(c4)
+
+// a stringstream output buffer used by the logger
+template< class I = uint16_t >
+struct LogBuffer
+{
+    typedef I index_type;
+
+    vector< char, I > buf;
+    I pos;
+
+    LogBuffer() : buf(C4_LOG_BUFFER_INITIAL_SIZE), pos(0) {}
+
+    // string to read from
+    C4_ALWAYS_INLINE const char* rd() const { return buf.data(); }
+    // string to write into
+    C4_ALWAYS_INLINE char* wt() { return buf.data() + pos; }
+    // remaining size
+    C4_ALWAYS_INLINE I rem()
+    {
+        C4_XASSERT(pos <= buf.size());
+        return buf.size() - pos;
+    }
+
+    void clear()
+    {
+        pos = 0;
+        buf[0] = '\0';
+    }
+    void growto(uint16_t sz) // grow by the max of sz and the golden ratio
+    {
+        float n = 1.618f * float(buf.size());
+        assert(size_t(n) < max_idx);
+        auto next = I(n);
+        next = next > sz ? next : sz;
+        buf.resize(next);
+    }
+    void write(const char *cstr)
+    {
+        write(cstr, strlen(cstr));
+    }
+    void write(const char *str, size_t sz)
+    {
+        assert(sz <= max_idx);
+        assert(sz + size_t(pos + 1) < max_idx);
+        if(sz+1 > rem()) growto(pos + sz + 1);
+        ::strncpy(wt(), str, sz);
+        pos += sz;
+        buf[pos] = '\0';
+    }
+    void printf(const char *fmt, ...)
+    {
+        va_list args;
+        va_start(args, fmt);
+        int inum = ::vsnprintf(wt(), rem(), fmt, args);
+        I num = abs(inum); // silently skip output errors
+        assert(num < max_idx);
+        if(num >= rem()) // not enough space?
+        {
+            va_start(args, fmt);
+            growto(pos + num + 1);
+            assert(size_t(pos) + size_t(num) + 1 < max_idx);
+            inum = ::vsnprintf(wt(), rem(), fmt, args);
+            num = abs(inum);
+            assert(num < max_idx);
+        }
+        assert(size_t(pos) + size_t(num) < max_idx);
+        pos += num;
+        buf[pos] = '\0';
+        va_end(args);
+    }
+    void vprintf(const char *fmt, va_list args)
+    {
+        va_list args2;
+        va_copy(args2, args);
+        int inum = ::vsnprintf(wt(), rem(), fmt, args);
+        I num = abs(inum); // silently skip output errors
+        assert(num < max_idx);
+        if(num >= rem()) // not enough space?
+        {
+            assert(num < max_idx);
+            assert(size_t(pos) + size_t(num) + 1 < max_idx);
+            growto(buf.size() + num + 1);
+            inum = ::vsnprintf(wt(), rem(), fmt, args2);
+            num = abs(inum);
+        }
+        assert(size_t(pos) + size_t(num) + 1 < max_idx);
+        pos += num;
+        buf[pos] = '\0';
+        va_end(args2);
+    }
+
+    constexpr static const size_t max_idx = std::numeric_limits< I >::max();
+};
+
+//-----------------------------------------------------------------------------
+class Log
+{
+public:
+
+    typedef enum {
+        ALWAYS = -10,
+        ERR = -2,
+        WARN = -1,
+        INFO = 0,
+        DEBUG = 1,
+        TRACE1 = 2,
+        TRACE2 = 3,
+        TRACE3 = 4,
+    } Level_e;
+
+    typedef enum {
+        SHOW_TIMESTAMP = 1 << 0,
+        TO_TERM = 1 << 1,
+        TO_FILE = 1 << 2,
+        TO_STR  = 1 << 3,
+        DEFAULT_MODE = SHOW_TIMESTAMP|TO_TERM,
+    } Mode_e;
+
+    struct Channel
+    {
+        uint8_t level;
+        uint8_t name_len;
+        char    name[30];
+
+        Channel() : level{INFO}, name_len{0}, name{0} {}
+        Channel(const char *str, Level_e lev)
+        {
+            level = lev;
+            name_len = strlen(str);
+            if(name_len > (sizeof(name) - 1)) abort();
+            if(name_len > 0)
+            {
+                snprintf(name, sizeof(name), "%s", str);
+            }
+            else
+            {
+                name[name_len] = '\0';
+            }
+        }
+        inline bool cmp(const char *str) const
+        {
+            if(name_len == 0) return str[0] == '\0';
+            return strncmp(name, str, name_len) == 0;
+        }
+        C4_ALWAYS_INLINE bool skip(Level_e lev) const
+        {
+            return lev > level && lev != ALWAYS;
+        }
+    };
+
+    static uint8_t& _mode() { static uint8_t m = DEFAULT_MODE; return m; }
+    static uint8_t mode() { return _mode(); }
+    static void mode(uint8_t mode_flags) { _mode() = mode_flags; }
+
+    static FILE*& _file() { static FILE *f = nullptr; return f; }
+    static FILE* file() { return _file(); }
+    static void file(FILE* f) { _file() = f; }
+
+    static LogBuffer< size_t >& strbuf() { static LogBuffer< size_t > b; return b; }
+#ifdef C4_LOG_THREAD_SAFE
+    static std::mutex& strbuf_mtx() { static std::mutex m; return m; }
+#endif
+
+    class StrReader
+    {
+#ifdef C4_LOG_THREAD_SAFE
+        std::lock_guard< std::mutex > l;
+        const char* s;
+    public:
+        C4_ALWAYS_INLINE operator const char* () const { return s; }
+        StrReader(std::mutex &m, const char* s_) : l(m, std::adopt_lock_t{}), s(s_) {}
+#else
+        const char* s;
+    public:
+        C4_ALWAYS_INLINE operator const char* () const { return s; }
+        StrReader(const char* s_) : s(s_) {}
+#endif
+    };
+
+    static StrReader&& str()
+    {
+#ifdef C4_LOG_THREAD_SAFE
+        auto& m = strbuf_mtx();
+        m.lock();
+abort();
+        return std::move(StrReader(m, (mode() & TO_STR) ? strbuf().rd() : ""));
+#else
+        return std::move(StrReader((mode() & TO_STR) ? strbuf().rd() : ""));
+#endif
+    }
+
+    static void str_clear()
+    {
+        if(mode() & TO_STR)
+        {
+#ifdef C4_LOG_THREAD_SAFE
+            std::lock_guard< std::mutex > lock(strbuf_mtx());
+#endif
+            strbuf().clear();
+        }
+    }
+
+    /** return the buffer for this thread */
+    static LogBuffer< uint16_t >& buf()
+    {
+        // using static vars here saves us the need to declare them
+        // in a source file, allowing to use this as a drop-in header.
+#ifndef C4_LOG_THREAD_SAFE
+        static LogBuffer< uint16_t > b;
+        return b;
+#else // C4_LOG_THREAD_SAFE
+        // using a thread-local buffer saves us from locking when formatting
+        static C4_THREAD_LOCAL(LogBuffer< uint16_t >) s_buffer;
+        return s_buffer;
+#endif // C4_LOG_THREAD_SAFE
+    }
+    static Channel* main_channel()
+    {
+        return &_channels()[0];
+    }
+    static Channel* channel(uint8_t i)
+    {
+        assert(i < _channels().size());
+        return &_channels()[i];
+    }
+    static Channel* channel(const char *name)
+    {
+        for(auto &ch : _channels())
+        {
+            if(ch.cmp(name))
+            {
+                return &ch;
+            }
+        }
+        return nullptr;
+    }
+    static Channel* add_channel(const char *name, Level_e lev = INFO)
+    {
+        auto& chs = _channels();
+        assert(chs.size() < C4_LOG_MAX_CHANNELS);
+        assert(channel(name) == nullptr);
+        chs.emplace_back(name, lev);
+        return &chs.back();
+    }
+
+private:
+
+    static inline c4::vector< Channel >& _channels()
+    {
+        static c4::vector< Channel > s_channels(1, c4::with_capacity, C4_LOG_MAX_CHANNELS);
+        return s_channels;
+    }
+
+public:
+
+    /** set the level of all channels */
+    static void level(Level_e l)
+    {
+        for(auto &ch : _channels())
+        {
+            ch.level = l;
+        }
+    }
+
+    template< class I >
+    static void _print_prefix(Channel const& ch, LogBuffer< I > &buf)
+    {
+        uint8_t md = mode();
+        if((md & SHOW_TIMESTAMP) && (ch.name_len > 0))
+        {
+            buf.printf("%lfms[%s]: ", exetime()/1.e3, ch.name);
+        }
+        else if((md & SHOW_TIMESTAMP))
+        {
+            buf.printf("%lfms: ", exetime()/1.e3, ch.name);
+        }
+        else if((ch.name_len > 0))
+        {
+            buf.printf("[%s]: ", ch.name);
+        }
+    }
+
+    /** print formatted output to the main channel, at INFO level */
+    static void printf(const char *fmt, ...)
+    {
+        Channel &ch = *main_channel();
+        if(ch.skip(INFO)) return;
+        va_list args;
+        va_start(args, fmt);
+        auto& b = buf();
+        _print_prefix(ch, b);
+        b.vprintf(fmt, args);
+        pump(b.rd(), b.pos);
+        b.clear();
+    }
+    /** print formatted output to the main channel, at the given level */
+    static void printfl(Level_e level, const char *fmt, ...)
+    {
+        Channel &ch = *main_channel();
+        if(ch.skip(level)) return;
+        va_list args;
+        va_start(args, fmt);
+        auto& b = buf();
+        _print_prefix(ch, b);
+        b.vprintf(fmt, args);
+        pump(b.rd(), b.pos);
+        b.clear();
+    }
+    /** print formatted output to the given channel at the given level */
+    static void printfcl(Channel *ch, Level_e level, const char *fmt, ...)
+    {
+        if(ch->skip(level)) return;
+        va_list args;
+        va_start(args, fmt);
+        auto& b = buf();
+        _print_prefix(*ch, b);
+        b.vprintf(fmt, args);
+        pump(b.rd(), b.pos);
+        b.clear();
+    }
+
+    /** directly print a string to the main channel at INFO level */
+    static void write(const char *s) { write(s, strlen(s)); }
+    /** directly print a string with specified size to the main channel at INFO level */
+    static void write(const char *s, size_t sz)
+    {
+        Channel &ch = *main_channel();
+        if(ch.skip(INFO)) return;
+        auto& b = buf();
+        _print_prefix(ch, b);
+        b.write(s, sz);
+        pump(b.rd(), sz);
+        b.clear();
+    }
+    /** directly print a string to the main channel at the given level */
+    static void writel(Level_e level, const char *s) { writel(level, s, strlen(s)); }
+    /** directly print a string with specified size to the main channel at the given level */
+    static void writel(Level_e level, const char *s, size_t sz)
+    {
+        Channel &ch = *main_channel();
+        if(ch.skip(level)) return;
+        auto& b = buf();
+        _print_prefix(ch, b);
+        b.write(s, sz);
+        pump(b.rd(), sz);
+        b.clear();
+    }
+    /** directly print a string to the given channel at the given level */
+    static void writecl(Channel *ch, Level_e level, const char *s) { writel(level, s, strlen(s)); }
+    /** directly print a string with specified size to the given channel at the given level */
+    static void writecl(Channel *ch, Level_e level, const char *s, size_t sz)
+    {
+        if(ch->skip(level)) return;
+        auto& b = buf();
+        _print_prefix(*ch, b);
+        b.write(s, sz);
+        pump(b.rd(), sz);
+        b.clear();
+    }
+
+    static void pump(const char *str, size_t sz)
+    {
+        uint8_t md = mode();
+        if(md & TO_TERM)
+        {
+#ifndef _MSC_VER
+            ::printf("%.*s", (int)sz, str);
+#else
+            if( ! IsDebuggerPresent())
+            {
+                ::printf("%.*s", (int)sz, str);
+            }
+            else
+            {
+                OutputDebugStrA(str);
+            }
+#endif
+        }
+        if(md & TO_FILE)
+        {
+            if(file() == nullptr) abort();
+            fprintf(file(), "%.*s", (int)sz, str);
+        }
+        if(md & TO_STR)
+        {
+#ifdef C4_LOG_THREAD_SAFE
+            std::lock_guard< std::mutex > lock(strbuf_mtx());
+#endif
+            strbuf().write(str, sz);
+        }
+    }
+
+    static void flush()
+    {
+        uint8_t md = mode();
+        if(md & TO_TERM)
+        {
+#ifndef _MSC_VER
+            fflush(stdout);
+#else
+            if( ! IsDebuggerPresent())
+            {
+                fflush(stdout);
+            }
+#endif
+        }
+        if(md & TO_FILE)
+        {
+            fflush(file());
+        }
+    }
+
+    /** A proxy object which buffers prints to a log buffer.
+     * It accumulates << calls and outputs once after the last call.
+     * The buffer is set to NULL when the channel's log level
+     * is incompatible with the given log level. */
+    struct Proxy
+    {
+        Channel const& channel;
+        Level_e level;
+        LogBuffer< uint16_t >* buf;
+        Proxy(Channel const* ch, Level_e lev) : channel(*ch), level(lev), buf(nullptr)
+        {
+            if(C4_LIKELY(channel.skip(level))) return;
+            buf = &Log::buf();
+            Log::_print_prefix(channel, *buf);
+        }
+        ~Proxy()
+        {
+            if(C4_LIKELY(!buf)) return;
+            Log::pump(buf->rd(), buf->pos);
+            buf->clear();
+        }
+        template< typename T >
+        void printf(const char *fmt, T const& var) const
+        {
+            if(C4_LIKELY(!buf)) return;
+            buf->printf(fmt, var);
+            if(buf->pos > C4_LOG_BUFFER_REF_SIZE)
+            {
+                Log::pump(buf->rd(), buf->pos);
+                buf->clear();
+            }
+        }
+    };
+    Proxy operator() (Channel const *ch, Level_e lev) { return Proxy(ch, lev); }
+    Proxy operator() (Channel const *ch) { return Proxy(ch, INFO); }
+    Proxy operator() (Level_e lev) { return Proxy(&_channels()[0], INFO); }
+
+    /** create a temporary proxy object to handle all the calls to <<.
+     * It will accumulate the calls and output once after the last call. */
+    template< class T >
+    Proxy operator<< (T const& v)
+    {
+        Proxy s(main_channel(), INFO);
+        s << v;
+        return s;
+    }
+};
+
+using LogProxy = const Log::Proxy;
+
+C4_ALWAYS_INLINE LogProxy& operator<< (LogProxy& ss, void *      var) { ss.printf("%p",   var); return ss; }
+C4_ALWAYS_INLINE LogProxy& operator<< (LogProxy& ss, double      var) { ss.printf("%lg",  var); return ss; }
+C4_ALWAYS_INLINE LogProxy& operator<< (LogProxy& ss, float       var) { ss.printf("%g",   var); return ss; }
+C4_ALWAYS_INLINE LogProxy& operator<< (LogProxy& ss, char        var) { ss.printf("%c",   var); return ss; }
+C4_ALWAYS_INLINE LogProxy& operator<< (LogProxy& ss,  int64_t    var) { ss.printf("%lld", var); return ss; }
+C4_ALWAYS_INLINE LogProxy& operator<< (LogProxy& ss, uint64_t    var) { ss.printf("%llu", var); return ss; }
+C4_ALWAYS_INLINE LogProxy& operator<< (LogProxy& ss,  int32_t    var) { ss.printf("%d",   var); return ss; }
+C4_ALWAYS_INLINE LogProxy& operator<< (LogProxy& ss, uint32_t    var) { ss.printf("%u",   var); return ss; }
+C4_ALWAYS_INLINE LogProxy& operator<< (LogProxy& ss,  int16_t    var) { ss.printf("%hd",  var); return ss; }
+C4_ALWAYS_INLINE LogProxy& operator<< (LogProxy& ss, uint16_t    var) { ss.printf("%hu",  var); return ss; }
+C4_ALWAYS_INLINE LogProxy& operator<< (LogProxy& ss,  int8_t     var) { ss.printf("%hhd", var); return ss; }
+C4_ALWAYS_INLINE LogProxy& operator<< (LogProxy& ss, uint8_t     var) { ss.printf("%hhu", var); return ss; }
+C4_ALWAYS_INLINE LogProxy& operator<< (LogProxy& ss,       char *var) { ss.printf("%s",   var); return ss; }
+C4_ALWAYS_INLINE LogProxy& operator<< (LogProxy& ss, const char *var) { ss.printf("%s",   var); return ss; }
+//C4_ALWAYS_INLINE LogProxy& operator<< (LogProxy& ss, std::string const& var) { ss.printf(var.c_str(), var.size()); return ss; }
+template< size_t N >
+C4_ALWAYS_INLINE LogProxy& operator<< (LogProxy& ss, const char (&var)[N]) { ss.printf("%.*s", (int)(N-1), &var[0]); return ss; }
+
+#define c4_log c4::Log()
+
+#define C4_LOG(fmt, ...) C4_LOG_INFO(fmt, ## __VA_ARGS__)
+#define C4_LOG_ERR(fmt, ...) c4_log.printfl(c4::Log::ERR, fmt, ## __VA_ARGS__)
+#define C4_LOG_WARN(fmt, ...) c4_log.printfl(c4::Log::WARN, fmt, ## __VA_ARGS__)
+#define C4_LOG_INFO(fmt, ...) c4_log.printfl(c4::Log::INFO, fmt, ## __VA_ARGS__)
+#define C4_LOG_DEBUG(fmt, ...) c4_log.printfl(c4::Log::DEBUG, fmt, ## __VA_ARGS__)
+#define C4_LOG_TRACE1(fmt, ...) c4_log.printfl(c4::Log::TRACE1, fmt, ## __VA_ARGS__)
+#define C4_LOG_TRACE2(fmt, ...) c4_log.printfl(c4::Log::TRACE2, fmt, ## __VA_ARGS__)
+#define C4_LOG_TRACE3(fmt, ...) c4_log.printfl(c4::Log::TRACE3, fmt, ## __VA_ARGS__)
+
+#define C4_LOGC(channel, fmt, ...) C4_LOGC_INFO(channel, fmt, ## __VA_ARGS__)
+#define C4_LOGC_ERR(channel, fmt, ...) c4_log.printfcl(channel, c4::Log::ERR, fmt, ## __VA_ARGS__)
+#define C4_LOGC_WARN(channel, fmt, ...) c4_log.printfcl(channel, c4::Log::WARN, fmt, ## __VA_ARGS__)
+#define C4_LOGC_INFO(channel, fmt, ...) c4_log.printfcl(channel, c4::Log::INFO, fmt, ## __VA_ARGS__)
+#define C4_LOGC_DEBUG(channel, fmt, ...) c4_log.printfcl(channel, c4::Log::DEBUG, fmt, ## __VA_ARGS__)
+#define C4_LOGC_TRACE1(channel, fmt, ...) c4_log.printfcl(channel, c4::Log::TRACE1, fmt, ## __VA_ARGS__)
+#define C4_LOGC_TRACE2(channel, fmt, ...) c4_log.printfcl(channel, c4::Log::TRACE2, fmt, ## __VA_ARGS__)
+#define C4_LOGC_TRACE3(channel, fmt, ...) c4_log.printfcl(channel, 4::Log::TRACE3, fmt, ## __VA_ARGS__)
+
+
+C4_END_NAMESPACE(c4)
+
+//------------------------------------------------------------
+//------------------------------------------------------------
+//------------------------------------------------------------
+// ERROR REPORTING: implementation
+
+C4_BEGIN_NAMESPACE(c4)
+
+/** Raise an error, and report a printf-formatted message.
+ * If an error callback was set, it will be called.
+ * @see set_error_callback() */
+inline void report_error(const char *file, int line, const char *func, const char *fmt, ...)
+{
+    char msg[256];
+    va_list args;
+    va_start(args, fmt);
+    int num = vsnprintf(msg, sizeof(msg), fmt, args);
+    if(num > 0)
+    {
+        C4_LOG_ERR("\n%s:%d: ERROR: %s\n", file, line, msg);
+    }
+    C4_LOG_ERR("\n%s:%d: ERROR: %s\n", file, line, func);
+    C4_LOG_ERR("\n%s:%d: ERROR: ABORTING...\n", file, line);
+    c4_log.flush();
+    auto fn = get_error_callback();
+    if(fn)
+    {
+        fn();
+    }
+}
+
+C4_END_NAMESPACE(c4)
+
+//------------------------------------------------------------
+//------------------------------------------------------------
+//------------------------------------------------------------
+// ALLOCATIONS - implementation
+
+C4_BEGIN_NAMESPACE(c4)
+
+C4_BEGIN_NAMESPACE(detail)
+#ifndef C4_NO_ALLOC_DEFAULTS
+inline void free_impl(void *ptr)
+{
+    ::free(ptr);
+}
+inline void afree_impl(void *ptr)
+{
+#if defined(C4_WIN) || defined(C4_XBOX)
+    ::_aligned_free(ptr);
+#else
+    ::free(ptr);
+#endif
+}
+inline void* alloc_impl(size_t size)
+{
+    void* mem = ::malloc(size);
+    C4_CHECK(mem != nullptr || size == 0);
+    return mem;
+}
+inline void* aalloc_impl(size_t size, size_t alignment)
+{
+    void *mem;
+#if defined(C4_WIN) || defined(C4_XBOX)
+    mem = ::_aligned_malloc(size, alignment);
+    C4_CHECK(mem != nullptr || size == 0);
+#elif defined(C4_POSIX)
+    // NOTE: alignment needs to be sized in multiples of sizeof(void*)
+    size_t amult = alignment;
+    if(C4_UNLIKELY(alignment < sizeof(void*)))
+    {
+        amult = sizeof(void*);
+    }
+    int ret = ::posix_memalign(&mem, amult, size);
+    if(C4_UNLIKELY(ret))
+    {
+        if(ret == EINVAL)
+        {
+            C4_ERROR("The alignment argument %lu was not a power of two, "
+                     "or was not a multiple of sizeof(void*)",
+                     (uint64_t)alignment);
+        }
+        else if(ret == ENOMEM)
+        {
+            C4_ERROR("There was insufficient memory to fulfill the "
+                     "allocation request of %lu bytes (alignment=%lu)",
+                     (uint64_t)size, (uint64_t)size);
+        }
+        if(mem)
+        {
+            afree(mem);
+        }
+        return nullptr;
+    }
+#else
+    C4_NOT_IMPLEMENTED_MSG("need to implement an aligned allocation for this platform");
+#endif
+    C4_ASSERT_MSG((size_t(mem) & (alignment-1)) == 0, "address %p is not aligned to %lu boundary", mem, (uint64_t)alignment);
+    return mem;
+}
+inline void* realloc_impl(void* ptr, size_t oldsz, size_t newsz)
+{
+    C4_UNUSED(oldsz);
+    void *nptr = ::realloc(ptr, newsz);
+    return nptr;
+}
+inline void* arealloc_impl(void* ptr, size_t oldsz, size_t newsz, size_t alignment)
+{
+    /** @todo make this more efficient
+     * @see http://stackoverflow.com/a/9078627/5875572
+     * @see look for qReallocAligned() in http://code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/global/qmalloc.cpp
+     */
+    void *tmp = aalloc(newsz, alignment);
+    size_t min = newsz < oldsz ? newsz : oldsz;
+    ::memcpy(tmp, ptr, min);
+    afree(ptr);
+    return tmp;
+}
+
+#endif // C4_NO_ALLOC_DEFAULTS
+
+C4_ALWAYS_INLINE alloc_type& get_alloc()
+{
+#ifndef C4_NO_ALLOC_DEFAULTS
+    static alloc_type fn = &alloc_impl;
+#else
+    static alloc_type fn = nullptr;
+#endif
+    return fn;
+}
+C4_ALWAYS_INLINE aalloc_type& get_aalloc()
+{
+#ifndef C4_NO_ALLOC_DEFAULTS
+    static aalloc_type fn = &aalloc_impl;
+#else
+    static aalloc_type fn = nullptr;
+#endif
+    return fn;
+}
+
+C4_ALWAYS_INLINE free_type& get_free()
+{
+#ifndef C4_NO_ALLOC_DEFAULTS
+    static free_type fn = &free_impl;
+#else
+    static free_type fn = nullptr;
+#endif
+    return fn;
+}
+
+C4_ALWAYS_INLINE free_type& get_afree()
+{
+#ifndef C4_NO_ALLOC_DEFAULTS
+    static free_type fn = &afree_impl;
+#else
+    static free_type fn = nullptr;
+#endif
+    return fn;
+}
+
+C4_ALWAYS_INLINE realloc_type& get_realloc()
+{
+#ifndef C4_NO_ALLOC_DEFAULTS
+    static realloc_type fn = &realloc_impl;
+#else
+    static realloc_type fn = nullptr;
+#endif
+    return fn;
+}
+C4_ALWAYS_INLINE arealloc_type& get_arealloc()
+{
+#ifndef C4_NO_ALLOC_DEFAULTS
+    static arealloc_type fn = &arealloc_impl;
+#else
+    static arealloc_type fn = nullptr;
+#endif
+    return fn;
+}
+
+C4_END_NAMESPACE(detail)
+
+
+C4_ALWAYS_INLINE alloc_type get_alloc()
+{
+    return detail::get_alloc();
+}
+C4_ALWAYS_INLINE void set_alloc(alloc_type fn)
+{
+    detail::get_alloc() = fn;
+}
+
+C4_ALWAYS_INLINE aalloc_type get_aalloc()
+{
+    return detail::get_aalloc();
+}
+C4_ALWAYS_INLINE void set_aalloc(aalloc_type fn)
+{
+    detail::get_aalloc() = fn;
+}
+
+C4_ALWAYS_INLINE free_type get_free()
+{
+    return detail::get_free();
+}
+C4_ALWAYS_INLINE void set_free(free_type fn)
+{
+    detail::get_free() = fn;
+}
+
+C4_ALWAYS_INLINE free_type get_afree()
+{
+    return detail::get_afree();
+}
+C4_ALWAYS_INLINE void set_afree(free_type fn)
+{
+    detail::get_afree() = fn;
+}
+
+C4_ALWAYS_INLINE realloc_type get_realloc()
+{
+    return detail::get_realloc();
+}
+C4_ALWAYS_INLINE void set_realloc(realloc_type fn)
+{
+    detail::get_realloc() = fn;
+}
+
+C4_ALWAYS_INLINE arealloc_type get_arealloc()
+{
+    return detail::get_arealloc();
+}
+C4_ALWAYS_INLINE void set_arealloc(arealloc_type fn)
+{
+    detail::get_arealloc() = fn;
+}
+
+
+inline void* alloc(size_t sz)
+{
+    C4_ASSERT_MSG(c4::get_alloc() != nullptr, "did you forget to call set_alloc()?");
+    auto fn = c4::get_alloc();
+    void* ptr = fn(sz);
+    return ptr;
+}
+inline void* aalloc(size_t sz, size_t alignment)
+{
+    C4_ASSERT_MSG(c4::get_aalloc() != nullptr, "did you forget to call set_aalloc()?");
+    auto fn = c4::get_aalloc();
+    void* ptr = fn(sz, alignment);
+    return ptr;
+}
+inline void free(void* ptr)
+{
+    C4_ASSERT_MSG(c4::get_free() != nullptr, "did you forget to call set_free()?");
+    auto fn = c4::get_free();
+    fn(ptr);
+}
+inline void afree(void* ptr)
+{
+    C4_ASSERT_MSG(c4::get_afree() != nullptr, "did you forget to call set_afree()?");
+    auto fn = c4::get_afree();
+    fn(ptr);
+}
+
+inline void* realloc(void *ptr, size_t oldsz, size_t newsz)
+{
+    C4_ASSERT_MSG(c4::get_realloc() != nullptr, "did you forget to call set_realloc()?");
+    auto fn = c4::get_realloc();
+    void* nptr = fn(ptr, oldsz, newsz);
+    return nptr;
+}
+inline void* arealloc(void *ptr, size_t oldsz, size_t newsz, size_t alignment)
+{
+    C4_ASSERT_MSG(c4::get_arealloc() != nullptr, "did you forget to call set_arealloc()?");
+    auto fn = c4::get_arealloc();
+    void* nptr = fn(ptr, oldsz, newsz, alignment);
+    return nptr;
+}
+
+C4_END_NAMESPACE(c4)
+
+#ifdef C4_REDEFINE_CPPNEW
+#include <new>
+void* operator new(size_t size)
+{
+    return ::c4::alloc(size);
+}
+void operator delete(void *p) noexcept
+{
+    ::c4::free(p);
+}
+void operator delete(void *p, size_t)
+{
+    ::c4::free(p);
+}
+void* operator new[](size_t size)
+{
+    return operator new(size);
+}
+void operator delete[](void *p) noexcept
+{
+    operator delete(p);
+}
+void operator delete[](void *p, size_t)
+{
+    operator delete(p);
+}
+void* operator new(size_t size, std::nothrow_t)
+{
+    return operator new(size);
+}
+void operator delete(void *p, std::nothrow_t)
+{
+    operator delete(p);
+}
+void operator delete(void *p, size_t, std::nothrow_t)
+{
+    operator delete(p);
+}
+void* operator new[](size_t size, std::nothrow_t)
+{
+    return operator new(size);
+}
+void operator delete[](void *p, std::nothrow_t)
+{
+    operator delete(p);
+}
+void operator delete[](void *p, size_t, std::nothrow_t)
+{
+    operator delete(p);
+}
+#endif // C4_REDEFINE_CPPNEW
+
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+#endif //_C4_UTIL_HPP_

+ 108 - 0
3rdparty/rapidyaml/ext/c4core/.github/.travis.yml.old

@@ -0,0 +1,108 @@
+sudo: required
+dist: bionic
+language: cpp
+env:
+  global:
+    # cmake is installed into /usr/bin
+    - PATH=/usr/bin:/usr/local/bin:$PATH
+
+# we're not using combination parameters here to ensure that the builds
+# run in the order we want. (We want to perform the fastest tests first so
+# failed tests appear as early as possible).
+
+# NOTE: The compiler setting is unused. It simply makes the display on
+# travis-ci.org more readable.
+# WARNING: do not use the name CXX. Travis will ignore the value here.
+matrix:
+
+  include:
+    # every entry does both 64 and 32 bit
+    # SAN := sanitizers
+    # VG := valgrind
+
+    # coverage: in bionic, lcov is incompatible with g++8 and later
+    - env: CXX_=g++-7     BT=Coverage STD=11
+    - env: CXX_=g++-7     BT=Coverage STD=14
+    - env: CXX_=g++-7     BT=Coverage STD=17
+
+    - env: CXX_=g++-10     BT=Debug   STD=11         VG=ON
+    - env: CXX_=g++-10     BT=Release STD=11         VG=ON
+    - env: CXX_=g++-10     BT=Debug   STD=14         VG=ON
+    - env: CXX_=g++-10     BT=Release STD=14         VG=ON
+    - env: CXX_=g++-10     BT=Debug   STD=17         VG=ON
+    - env: CXX_=g++-10     BT=Release STD=17         VG=ON
+    - env: CXX_=g++-10     BT=Debug   STD=20         VG=ON
+    - env: CXX_=g++-10     BT=Release STD=20         VG=ON
+
+    - env: CXX_=clang++-10 BT=Debug   STD=11 SAN=ALL VG=ON
+    - env: CXX_=clang++-10 BT=Release STD=11 SAN=ALL VG=ON
+    - env: CXX_=clang++-10 BT=Debug   STD=14 SAN=ALL VG=ON
+    - env: CXX_=clang++-10 BT=Release STD=14 SAN=ALL VG=ON
+    - env: CXX_=clang++-10 BT=Debug   STD=17 SAN=ALL VG=ON
+    - env: CXX_=clang++-10 BT=Release STD=17 SAN=ALL VG=ON
+    - env: CXX_=clang++-10 BT=Debug   STD=20 SAN=ALL VG=ON
+    - env: CXX_=clang++-10 BT=Release STD=20 SAN=ALL VG=ON
+
+    - env: CXX_=g++-9      BT=Debug
+    - env: CXX_=g++-9      BT=Release
+    - env: CXX_=clang++-9  BT=Debug
+    - env: CXX_=clang++-9  BT=Release
+
+    - env: CXX_=g++-8      BT=Debug
+    - env: CXX_=g++-8      BT=Release
+    - env: CXX_=clang++-8  BT=Debug
+    - env: CXX_=clang++-8  BT=Release
+
+    - env: CXX_=g++-7      BT=Debug
+    - env: CXX_=g++-7      BT=Release
+    - env: CXX_=clang++-7  BT=Debug
+    - env: CXX_=clang++-7  BT=Release
+
+    - env: CXX_=g++-6       BT=Debug
+    - env: CXX_=g++-6       BT=Release
+    - env: CXX_=clang++-6.0 BT=Debug
+    - env: CXX_=clang++-6.0 BT=Release
+
+    - env: CXX_=g++-5       BT=Debug
+    - env: CXX_=g++-5       BT=Release
+    - env: CXX_=clang++-5.0 BT=Debug
+    - env: CXX_=clang++-5.0 BT=Release
+
+    # gcc 4.9 is not available in 18.04 -- https://stackoverflow.com/questions/48398475/
+    #- env: CXX_=g++-4.9     BT=Debug
+    #- env: CXX_=g++-4.9     BT=Release
+    - env: CXX_=clang++-4.0 BT=Debug
+    - env: CXX_=clang++-4.0 BT=Release
+
+    - env: CXX_=clang++-3.9 BT=Debug
+    - env: CXX_=clang++-3.9 BT=Release
+
+    # ----------- clang-tidy
+    #
+    - env: CXX_=clang++-9 BT=Debug   LINT=clang-tidy
+    - env: CXX_=clang++-9 BT=Release LINT=clang-tidy
+
+install:
+  - bash -x .ci/travis-install.sh
+
+script:
+  - source .ci/travis-setenv.sh
+  
+  - c4core_cfg_test 64 dynamic
+  - c4core_run_test 64 dynamic
+  
+  - c4core_cfg_test 64 static
+  - c4core_run_test 64 static
+  
+  - c4core_cfg_test 32 static
+  - c4core_run_test 32 static
+  
+  - echo "Success!"
+
+after_success:
+  - source .ci/travis-setenv.sh
+  # coveralls only accepts one submission per job
+  #- c4core_submit_coverage 32 static coveralls
+  - c4core_submit_coverage 64 static coveralls
+  - c4core_submit_coverage 32 static codecov
+  - c4core_submit_coverage 64 static codecov

+ 82 - 0
3rdparty/rapidyaml/ext/c4core/.github/appveyor.yml.old

@@ -0,0 +1,82 @@
+version: '{build}'
+
+image: Visual Studio 2019
+
+environment:
+  matrix:
+
+    - {GEN: Visual Studio 16 2019, ARCH: -A x64, CFG: Debug, compiler: msvc-16-seh}
+    - {GEN: Visual Studio 16 2019, ARCH: -A Win32, CFG: Debug, compiler: msvc-16-seh}
+    - {GEN: Visual Studio 16 2019, ARCH: -A x64, CFG: Release, compiler: msvc-16-seh}
+    - {GEN: Visual Studio 16 2019, ARCH: -A Win32, CFG: Release, compiler: msvc-16-seh}
+    - {GEN: Visual Studio 16 2019, ARCH: -A x64, STD: -D C4_CXX_STANDARD=20, CFG: Debug, compiler: msvc-16-seh}
+    - {GEN: Visual Studio 16 2019, ARCH: -A x64, STD: -D C4_CXX_STANDARD=17, CFG: Debug, compiler: msvc-16-seh}
+    - {GEN: Visual Studio 16 2019, ARCH: -A x64, STD: -D C4_CXX_STANDARD=14, CFG: Debug, compiler: msvc-16-seh}
+
+    - {GEN: Visual Studio 15 2017 Win64, CFG: Debug, APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017, compiler: msvc-15-seh}
+    - {GEN: Visual Studio 15 2017, CFG: Debug, APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017, compiler: msvc-15-seh}
+    - {GEN: Visual Studio 15 2017 Win64, CFG: Release, APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017, compiler: msvc-15-seh}
+    - {GEN: Visual Studio 15 2017, CFG: Release, APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017, compiler: msvc-15-seh}
+
+    #- compiler: gcc-5.3.0-posix
+    #  GEN: "MinGW Makefiles"
+    #  cxx_path: 'C:\mingw-w64\i686-5.3.0-posix-dwarf-rt_v4-rev0\mingw32\bin'
+    #  APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
+    #  CFG: Quicktest
+    #  externconfig: Debug
+
+
+matrix:
+  fast_finish: true
+
+install:
+  - git submodule update --init --recursive
+  # git bash conflicts with MinGW makefiles
+  - set "PATH=%PATH:C:\Program Files\Git\usr\bin;=%"
+  - if not "%cxx_path%"=="" (set "PATH=%PATH%;%cxx_path%")
+  - cmake --version
+
+build_script:
+  - echo %GEN%
+  - echo %ARCH%
+  - echo %CFG%
+  - echo %STD%
+  - set NUM_JOBS=3
+  - set PROJ_DIR=%cd%
+  - set BUILD_DIR=%PROJ_DIR%\build
+  - set INSTALL_DIR=%PROJ_DIR%\install
+  - set C4_EXTERN_DIR=%BUILD_DIR%\extern
+  - md %BUILD_DIR%
+  - md %BUILD_DIR%\static %BUILD_DIR%\shared %BUILD_DIR%\extern
+  - cmake -S %PROJ_DIR% -B %BUILD_DIR%\static %STD% -DC4CORE_DEV=ON -G "%GEN%" %ARCH% "-DCMAKE_BUILD_TYPE=%CFG%" "-DCMAKE_INSTALL_PREFIX=%INSTALL_DIR%\static" -DBUILD_SHARED_LIBS=OFF
+  - cmake -S %PROJ_DIR% -B %BUILD_DIR%\shared %STD% -DC4CORE_DEV=ON -G "%GEN%" %ARCH% "-DCMAKE_BUILD_TYPE=%CFG%" "-DCMAKE_INSTALL_PREFIX=%INSTALL_DIR%\shared" -DBUILD_SHARED_LIBS=ON
+  - cmake --build %BUILD_DIR%\static --config %configuration% --target test-build --parallel %NUM_JOBS%
+  - cmake --build %BUILD_DIR%\shared --config %configuration% --target test-build --parallel %NUM_JOBS%
+
+test_script:
+  - cmake --build %BUILD_DIR%\static --config %configuration% --target test
+  - cmake --build %BUILD_DIR%\shared --config %configuration% --target test
+
+#artifacts:
+#  - path: '_build/CMakeFiles/*.log'
+#    name: logs
+#  - path: '_build/Testing/**/*.xml'
+#    name: test_results
+
+skip_commits:
+  files:
+    - .gitignore
+    - .travis*
+    - .ci/travis*
+    - .ci/dev_*
+    - .ci/show_*
+    - .ci/vagrant*
+    - .ci/Vagrant*
+    - bm/html/*
+    - doc/*
+    - img/*
+    - CHANGELOG.md
+    - CONTRIBUTING.md
+    - LICENSE.txt
+    - README.*
+    - ROADMAP.*

+ 129 - 0
3rdparty/rapidyaml/ext/c4core/.github/release.sh

@@ -0,0 +1,129 @@
+#!/bin/bash
+
+
+# useful to iterate when fixing the release:
+# ver=0.2.1 ; ( set -x ; git tag -d v$ver ; git push origin :v$ver ) ; (set -x ; set -e ; tbump --only-patch --non-interactive $ver ; git add -u ; git commit --amend --no-edit ; git tag --annotate --message "v$ver" "v$ver" ; git push -f --tags origin )
+
+
+function c4_release_create()
+{
+    ( \
+      set -euxo pipefail ; \
+      ver=$(_c4_validate_ver $1) ; \
+      branch=$(_c4_validate_branch) ; \
+      c4_release_bump $ver ; \
+      c4_release_commit $ver $branch \
+      )
+}
+
+function c4_release_redo()
+{
+    ( \
+      set -euxo pipefail ; \
+      ver=$(_c4_validate_ver $1) ; \
+      branch=$(_c4_validate_branch) ; \
+      c4_release_delete $ver ; \
+      c4_release_bump $ver ; \
+      c4_release_amend $ver $branch \
+    )
+}
+
+function c4_release_bump()
+{
+    ( \
+      set -euxo pipefail ; \
+      ver=$(_c4_validate_ver $1) ; \
+      tbump --non-interactive --only-patch $ver \
+      )
+}
+
+function c4_release_commit()
+{
+    ( \
+      set -euxo pipefail ; \
+      ver=$(_c4_validate_ver $1) ; \
+      branch=$(_c4_validate_branch) ; \
+      tag=v$ver ; \
+      git add -u ; \
+      git commit -m $tag ; \
+      git tag --annotate --message $tag $tag ; \
+      )
+}
+
+function c4_release_amend()
+{
+    ( \
+      set -euxo pipefail ; \
+      ver=$(_c4_validate_ver $1) ; \
+      branch=$(_c4_validate_branch) ; \
+      tag=v$ver ; \
+      git add -u ; \
+      git commit --amend -m $tag ; \
+      git tag --annotate --message $tag $tag ; \
+    )
+}
+
+function c4_release_delete()
+{
+    ( \
+      set -euxo pipefail ; \
+      ver=$(_c4_validate_ver $1) ; \
+      git tag -d v$ver ; \
+      git push origin :v$ver \
+    )
+}
+
+function c4_release_push()
+{
+    ( \
+      set -euxo pipefail ; \
+      ver=$(_c4_validate_ver $1) ; \
+      branch=$(_c4_validate_branch) ; \
+      tag=v$ver ; \
+      git push origin $branch ; \
+      git push --tags origin $tag \
+      )
+}
+
+function c4_release_force_push()
+{
+    ( \
+      set -euxo pipefail ; \
+      ver=$(_c4_validate_ver $1) ; \
+      branch=$(_c4_validate_branch) ; \
+      tag=v$ver ; \
+      git push -f origin $branch ; \
+      git push -f --tags origin $tag \
+    )
+}
+
+function _c4_validate_ver()
+{
+    ver=$1
+    if [ -z "$ver" ] ; then \
+        exit 1
+    fi
+    ver=$(echo $ver | sed "s:v\(.*\):\1:")
+    #sver=$(echo $ver | sed "s:\([0-9]*\.[0-9]*\..[0-9]*\).*:\1:")
+    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
+        fi
+    fi
+    echo $ver
+}
+
+function _c4_validate_branch()
+{
+    branch=$(git rev-parse --abbrev-ref HEAD)
+    if [ "$branch" != "master" ] ; then
+        echo "ERROR: release branch must be master"
+        exit 1
+    fi
+    echo $branch
+}

+ 306 - 0
3rdparty/rapidyaml/ext/c4core/.github/reqs.sh

@@ -0,0 +1,306 @@
+#!/usr/bin/env bash
+
+set -x
+
+# input environment variables:
+# OS: the operating system
+# CXX_: the compiler version. eg, g++-9 or clang++-6.0
+# BT: the build type
+# VG: whether to install valgrind
+# ARM: whether to arm cross-compiler and emulator
+# GITHUB_WORKFLOW: when run from github
+# API: whether to install swig
+# CMANY: whether to install cmany
+
+
+
+#-------------------------------------------------------------------------------
+
+function c4_install_test_requirements()
+{
+    os=$1
+    case "$os" in
+        ubuntu*)
+            c4_install_test_requirements_ubuntu
+            return 0
+            ;;
+        macos*)
+            c4_install_test_requirements_macos
+            return 0
+            ;;
+        win*)
+            c4_install_test_requirements_windows
+            return 0
+            ;;
+        *)
+            return 0
+            ;;
+    esac
+}
+
+function c4_install_test_requirements_windows()
+{
+    if [ "$CMANY" == "ON" ] ; then
+        pip install cmany
+    fi
+    if [ "$API" == "ON" ] ; then
+        choco install swig
+        which swig
+    fi
+    # ensure chocolatey does not override cmake's cpack
+    which cpack
+    choco_cpack="/c/ProgramData/Chocolatey/bin/cpack.exe"
+    if [ -f $choco_cpack ] ; then
+        newname=$(echo $choco_cpack | sed 's:cpack:choco-cpack:')
+        mv -vf $choco_cpack $newname
+    fi
+    which cpack
+}
+
+function c4_install_test_requirements_macos()
+{
+    if [ "$CMANY" == "ON" ] ; then
+        sudo pip3 install cmany
+    fi
+}
+
+function c4_install_test_requirements_ubuntu()
+{
+    APT_PKG=""  # all
+    PIP_PKG=""
+    c4_gather_test_requirements_ubuntu
+    echo "apt packages: $APT_PKG"
+    echo "pip packages: $PIP_PKG"
+    c4_install_test_requirements_ubuntu_impl
+    echo 'INSTALL COMPLETE!'
+}
+
+
+function c4_install_all_possible_requirements_ubuntu()
+{
+    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"
+    echo "pip packages: $PIP_PKG"
+    c4_install_test_requirements_ubuntu_impl
+    echo 'INSTALL COMPLETE!'
+}
+
+
+function c4_gather_test_requirements_ubuntu()
+{
+    if [ "$GITHUB_WORKFLOW" != "" ] ; then
+        sudo dpkg --add-architecture i386
+    else
+        _add_apt build-essential
+        _add_apt cmake
+    fi
+
+    _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_"
+
+    _add_apt python3-setuptools
+    _add_apt python3-pip
+
+    #_add_apt iwyu
+    #_add_apt cppcheck
+    #_add_pip cpplint
+    # oclint?
+    if [ "$VG" == "ON" ] ; then
+        _add_apt valgrind
+    fi
+
+    if [ "$BT" == "Coverage" ]; then
+        _add_apt lcov
+        _add_apt libffi-dev
+        _add_apt libssl-dev
+        _add_pip requests[security]
+        _add_pip pyopenssl
+        _add_pip ndg-httpsclient
+        _add_pip pyasn1
+        _add_pip cpp-coveralls
+    fi
+
+    if [ "$CMANY" != "" ] ; then
+        _add_pip cmany
+    fi
+
+    case "$CXX_" in
+        arm*)
+            _c4_add_arm_compilers
+            ;;
+    esac
+}
+
+
+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/ bionic main'
+    sudo -E add-apt-repository --yes ppa:ubuntu-toolchain-r/test
+
+    if [ "$APT_PKG" != "" ] ; then
+        #sudo -E apt-get clean
+        sudo -E apt-get update
+        sudo -E apt-get install -y --force-yes $APT_PKG
+    fi
+
+    if [ "$PIP_PKG" != "" ]; then
+        sudo pip3 install $PIP_PKG
+    fi
+}
+
+
+#-------------------------------------------------------------------------------
+
+function _c4_add_arm_compilers()
+{
+    # 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
+
+    _add_apt gcc-arm-embedded
+    _add_apt g++-arm-linux-gnueabihf
+    _add_apt g++-multilib-arm-linux-gnueabihf
+    _add_apt qemu
+}
+
+
+function _c4_gather_compilers()
+{
+    cxx=$1
+    case $cxx in
+        g++-11     ) _c4_addgcc 11 ;;
+        g++-10     ) _c4_addgcc 10 ;;
+        g++-9      ) _c4_addgcc 9  ;;
+        g++-8      ) _c4_addgcc 8  ;;
+        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.8    ) _c4_addgcc 4.8 ;;
+        clang++-12 ) _c4_addclang 12  ;;
+        clang++-11 ) _c4_addclang 11  ;;
+        clang++-10 ) _c4_addclang 10  ;;
+        clang++-9  ) _c4_addclang 9   ;;
+        clang++-8  ) _c4_addclang 8   ;;
+        clang++-7  ) _c4_addclang 7   ;;
+        clang++-6.0) _c4_addclang 6.0 ;;
+        clang++-5.0) _c4_addclang 5.0 ;;
+        clang++-4.0) _c4_addclang 4.0 ;;
+        clang++-3.9) _c4_addclang 3.9 ;;
+        all)
+            all="g++-11 g++-10 g++-9 g++-8 g++-7 g++-6 g++-5 clang++-12 clang++-11 clang++-10 clang++-9 clang++-8 clang++-7 clang++-6.0 clang++-5.0 clang++-4.0 clang++-3.9"
+            echo "installing all compilers: $all"
+            for cxx in $all ; do
+                _c4_gather_compilers $cxx
+            done
+            ;;
+        "")
+            # use default compiler
+            ;;
+        arm*)
+            ;;
+        *)
+            echo "unknown compiler: $cxx"
+            exit 1
+            ;;
+    esac
+}
+
+# add a gcc compiler
+function _c4_addgcc()
+{
+    gccversion=$1
+    case $gccversion in
+        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"
+            ;;
+        *)
+            ;;
+    esac
+    _add_apt g++-$gccversion
+    _add_apt g++-$gccversion-multilib
+    _add_apt libstdc++-$gccversion-dev
+    _add_apt lib32stdc++-$gccversion-dev
+}
+
+# add a clang compiler
+function _c4_addclang()
+{
+    clversion=$1
+    case $clversion in
+        # in 18.04, clang9 and later require PPAs
+        9 | 10 | 11 | 12 )
+            _add_apt clang-$clversion "deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-$clversion main"
+            # libstdc++ is required
+            _c4_addgcc 11
+            _c4_addgcc 10
+            _c4_addgcc 9
+            ;;
+        *)
+            _add_apt clang-$clversion
+            ;;
+    esac
+    _add_apt g++-multilib  # this is required for 32 bit https://askubuntu.com/questions/1057341/unable-to-find-stl-headers-in-ubuntu-18-04
+    _add_apt clang-tidy-$clversion
+}
+
+# add libc++
+function _c4_addlibcxx()
+{
+    _add_apt libc++1
+    _add_apt libc++abi-dev
+    _add_apt libc++-dev
+    _add_apt libc++1:i386
+    _add_apt libc++abi-dev:i386
+    _add_apt libc++-dev:i386
+}
+
+
+#-------------------------------------------------------------------------------
+
+# add a pip package to the list
+function _add_pip()
+{
+    pkgs=$*
+    PIP_PKG="$PIP_PKG $pkgs"
+    echo "adding to pip packages: $pkgs"
+}
+
+# add a debian package to the list
+function _add_apt()
+{
+    pkgs=$1
+    sourceslist=$2
+    APT_PKG="$APT_PKG $pkgs"
+    echo "adding to apt packages: $pkgs"
+    _add_src "$sourceslist" "# for packages: $pkgs"
+}
+
+# add an apt source
+function _add_src()
+{
+    sourceslist=$1
+    comment=$2
+    if [ ! -z "$sourceslist" ] ; then
+        echo "adding apt source: $sourceslist"
+        sudo bash -c "cat >> /etc/apt/sources.list <<EOF
+$comment
+$sourceslist
+EOF"
+        #cat /etc/apt/sources.list
+    fi
+}

+ 410 - 0
3rdparty/rapidyaml/ext/c4core/.github/setenv.sh

@@ -0,0 +1,410 @@
+#!/usr/bin/env bash
+
+set -e
+set -x
+
+PROJ_DIR=$(pwd)
+
+function c4_show_info()
+{
+    set +x
+    env | sort
+    echo "PROJ_DIR=$PROJ_DIR"
+    echo "PROJ_PFX_TARGET=$PROJ_PFX_TARGET"
+    echo "PROJ_PFX_CMAKE=$PROJ_PFX_CMAKE"
+    echo "CMAKE_FLAGS=$CMAKE_FLAGS"
+    echo "NUM_JOBS_BUILD=$NUM_JOBS_BUILD"
+    echo "GITHUB_WORKSPACE=$GITHUB_WORKSPACE"
+    pwd
+    ls -lFhp
+    echo "BITLINKS=$BITLINKS"
+    for bl in shared64 static64 shared32 static32 ; do
+        if _c4skipbitlink $bl ; then
+            echo "skip $bl"
+        else
+            echo "exec $bl"
+        fi
+    done
+    echo "CXX_=$CXX_"
+    echo "BT=$BT"
+    echo "LINT=$LINT"
+    echo "SAN=$SAN"
+    echo "SAN_ONLY=$SAN"
+    echo "VG=$VG"
+    echo "BM=$BM"
+    echo "STD=$STD"
+    echo "ARM=$ARM"
+    echo "LIBCXX=$LIBCXX"
+    echo "VERBOSE_MAKEFILES=$VERBOSE_MAKEFILES"
+    which cmake
+    cmake --version
+    case "$CXX_" in
+        xcode)
+            # https://gist.github.com/nlutsenko/ee245fbd239087d22137
+            echo "number of cores=$(sysctl -n hw.ncpu)"
+            #defaults read com.apple.dt.xcodebuild | grep -i Number | grep -i Build
+            #defaults read com.apple.dt.Xcode | grep -i Number | grep -i Tasks
+            ;;
+        gcc*|g++*|*clang*)
+            echo "number of cores=$(nproc)"
+            $CXX_ --version
+            ;;
+    esac
+    set -x
+    git branch
+    git rev-parse HEAD
+    git tag || echo
+    git log -1 --format='%H'
+}
+
+function _c4bits()
+{
+    case "$1" in
+        shared64|static64|arm64) echo 64 ;;
+        shared32|static32|arm32|arm) echo 32 ;;
+        *) exit 1 ;;
+    esac
+}
+
+function _c4linktype()
+{
+    case "$1" in
+        shared64|shared32) echo shared ;;
+        static64|static32) echo static ;;
+        *) exit 1 ;;
+    esac
+}
+
+function _c4skipbitlink()
+{
+    bitlink___=$1
+    if [ -z "$BITLINKS" ] ; then
+        return 1  # return nonzero as failure, meaning DO NOT SKIP
+    fi
+    for bl___ in $BITLINKS ; do
+        if [ "${bl___}" == "${bitlink___}" ] ; then
+            return 1  # return nonzero as failure, meaning DO NOT SKIP
+        fi
+    done
+    return 0  # return nonzero as success, meaning DO SKIP
+}
+
+function c4_build_test()
+{
+    c4_build_target $* test-build
+}
+
+function c4_run_test()
+{
+    c4_run_target $* test
+}
+
+function c4_build_target()  # runs in parallel
+{
+    if _c4skipbitlink "$1" ; then return 0 ; fi
+    id=$1
+    target=$2
+    if [ ! -z "$target" ] ; then
+        target="--target $target"
+    fi
+    build_dir=`pwd`/build/$id
+    export CTEST_OUTPUT_ON_FAILURE=1
+    # watchout: the `--parallel` flag to `cmake --build` is broken:
+    # https://discourse.cmake.org/t/parallel-does-not-really-enable-parallel-compiles-with-msbuild/964/10
+    # https://gitlab.kitware.com/cmake/cmake/-/issues/20564
+    cmake --build $build_dir --config $BT $target -- $(_c4_generator_build_flags) $(_c4_parallel_build_flags)
+}
+
+function c4_run_target()  # does not run in parallel
+{
+    if _c4skipbitlink "$1" ; then return 0 ; fi
+    id=$1
+    target=$2
+    build_dir=`pwd`/build/$id
+    export CTEST_OUTPUT_ON_FAILURE=1
+    cmake --build $build_dir --config $BT --target $target -- $(_c4_generator_build_flags)
+}
+
+function c4_package()
+{
+    if _c4skipbitlink "$1" ; then return 0 ; fi
+    id=$1
+    generator=$2
+    build_dir=`pwd`/build/$id
+    if [ -z "$generator" ] ; then
+        c4_run_target $id package
+    else
+        ( cd $build_dir ; cpack -G $generator )
+    fi
+}
+
+function c4_submit_coverage()
+{
+    if [ "$BT" != "Coverage" ] ; then
+        echo "build type is \"$BT\": no coverage to submit"
+        return 0
+    fi
+    if _c4skipbitlink "$1" ; then return 0 ; fi
+    id=$1
+    coverage_service=$2
+    build_dir=`pwd`/build/$id
+    echo "Submitting coverage data: $build_dir --> $coverage_service"
+    cmake --build $build_dir --config $BT --target ${PROJ_PFX_TARGET}coverage-submit-$coverage_service
+}
+
+# WIP
+function c4_run_static_analysis()
+{
+    if _c4skipbitlink "$1" ; then return 0 ; fi
+    id=$1
+    linktype=$(_c4linktype $id)
+    build_dir=`pwd`/build/$id
+    # https://blog.kitware.com/static-checks-with-cmake-cdash-iwyu-clang-tidy-lwyu-cpplint-and-cppcheck/
+    pushd $PROJ_DIR
+}
+
+function c4_cfg_test()
+{
+    if _c4skipbitlink "$1" ; then return 0 ; fi
+    id=$1
+    #
+    build_dir=`pwd`/build/$id
+    install_dir=`pwd`/install/$id
+    mkdir -p $build_dir
+    mkdir -p $install_dir
+    #
+    if [ "$TOOLCHAIN" != "" ] ; then
+        toolchain_file=`pwd`/$TOOLCHAIN
+        if [ ! -f "$toolchain_file" ] ; then
+            echo "ERROR: toolchain not found: $toolchain_file"
+            exit 1
+        fi
+        _addcmkflags -DCMAKE_TOOLCHAIN_FILE=$toolchain_file
+    else
+        bits=$(_c4bits $id)
+        linktype=$(_c4linktype $id)
+        case "$linktype" in
+            static) _addcmkflags -DBUILD_SHARED_LIBS=OFF ;;
+            shared) _addcmkflags -DBUILD_SHARED_LIBS=ON ;;
+            *)
+                echo "ERROR: unknown linktype: $linktype"
+                exit 1
+                ;;
+        esac
+    fi
+    if [ "$STD" != "" ] ; then
+        _addcmkflags -DC4_CXX_STANDARD=$STD
+        _addprojflags CXX_STANDARD=$STD
+    fi
+    if [ "$LIBCXX" != "" ] ; then
+        _addprojflags USE_LIBCXX=$LIBCXX
+    fi
+    #
+    if [ "$DEV" != "OFF" ] ; then
+        _addprojflags DEV=ON
+    fi
+    case "$LINT" in
+        all       ) _addprojflags LINT=ON LINT_TESTS=ON LINT_CLANG_TIDY=ON  LINT_PVS_STUDIO=ON ;;
+        clang-tidy) _addprojflags LINT=ON LINT_TESTS=ON LINT_CLANG_TIDY=ON  LINT_PVS_STUDIO=OFF ;;
+        pvs-studio) _addprojflags LINT=ON LINT_TESTS=ON LINT_CLANG_TIDY=OFF LINT_PVS_STUDIO=ON ;;
+        *         ) _addprojflags LINT=OFF ;;
+    esac
+    case "$SAN" in
+        ALL) _addprojflags SANITIZE=ON ;;
+        A  ) _addprojflags SANITIZE=ON ASAN=ON  TSAN=OFF MSAN=OFF UBSAN=OFF ;;
+        T  ) _addprojflags SANITIZE=ON ASAN=OFF TSAN=ON  MSAN=OFF UBSAN=OFF ;;
+        M  ) _addprojflags SANITIZE=ON ASAN=OFF TSAN=OFF MSAN=ON  UBSAN=OFF ;;
+        UB ) _addprojflags SANITIZE=ON ASAN=OFF TSAN=OFF MSAN=OFF UBSAN=ON ;;
+        *  ) _addprojflags SANITIZE=OFF ;;
+    esac
+    case "$SAN_ONLY" in
+        ON) _addprojflags SANITIZE_ONLY=ON ;;
+        * ) _addprojflags SANITIZE_ONLY=OFF ;;
+    esac
+    case "$VG" in
+        ON) _addprojflags VALGRIND=ON VALGRIND_SGCHECK=OFF ;; # FIXME SGCHECK should be ON
+        * ) _addprojflags VALGRIND=OFF VALGRIND_SGCHECK=OFF ;;
+    esac
+    case "$BM" in
+        ON) _addprojflags BUILD_BENCHMARKS=ON ;;
+        * ) _addprojflags BUILD_BENCHMARKS=OFF ;;
+    esac
+    if [ "$BT" == "Coverage" ] ; then
+        # 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_COVERALLS=ON COVERAGE_COVERALLS_SILENT=ON
+    fi
+    if [ ! -z "$VERBOSE_MAKEFILES" ] ; then
+        _addcmkflags -DCMAKE_VERBOSE_MAKEFILES=$VERBOSE_MAKEFILES
+    fi
+    _addcmkflags -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
+    if [ ! -z "$CMAKE_FLAGS" ] ; then
+        _addcmkflags $CMAKE_FLAGS
+    fi
+
+    echo "building with additional cmake flags: $CMFLAGS"
+
+    export C4_EXTERN_DIR=`pwd`/build/extern
+    mkdir -p $C4_EXTERN_DIR
+
+    cmake --version
+    pwd
+
+    #
+    # bash quote handling is a fiasco, and I could not find a way of storing
+    # quoted strings in variables and then expand the variables with correct quotes
+    # so we have to do this precious jewell of chicanery:
+    case "$CXX_" in
+        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
+            ;;
+        vs2017)
+            case "$bits" in
+                64) g="Visual Studio 15 2017 Win64" ;;
+                32) g="Visual Studio 15 2017" ;;
+            esac
+            cmake -S $PROJ_DIR -B $build_dir -DCMAKE_INSTALL_PREFIX="$install_dir" \
+                  -DCMAKE_BUILD_TYPE=$BT -G "$g" $CMFLAGS
+            ;;
+        xcode)
+            g=Xcode
+            case "$bits" in
+                64) a="x86_64" ;;
+                32) a="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
+            ;;
+        *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="-std=c99 -m$bits" -DCMAKE_CXX_FLAGS="-m$bits"
+            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"
+            ;;
+        *)
+            echo "unknown compiler"
+            exit 1
+            ;;
+    esac
+}
+
+function _c4_choose_clang_tidy()
+{
+    cxx=$1
+    # only for clang compilers.
+    case $cxx in
+        clang*)
+            # try with version first
+            clang_tidy_ver=$(echo $cxx | sed "s:++:-tidy:")
+            clang_tidy=$(echo $cxx | sed "s:++.*:-tidy:")
+            for n in $clang_tidy_ver $clang_tidy ; do
+                exe=$(which $n)
+                echo "searching for $n: $exe"
+                if [ -z "$exe" ] ; then
+                    echo "could not find $clang_tidy"
+                else
+                    _addcmkflags "-DCLANG_TIDY=$exe"
+                    return 0
+                fi
+            done
+            echo "error: could not find clang-tidy for $cxx"
+            exit 1
+            ;;
+    esac
+}
+
+# add cmake flags without project prefix
+function _addcmkflags()
+{
+    for f in $* ; do
+        CMFLAGS="$CMFLAGS ${f}"
+    done
+}
+
+# add cmake flags with project prefix
+function _addprojflags()
+{
+    for f in $* ; do
+        CMFLAGS="$CMFLAGS -D${PROJ_PFX_CMAKE}${f}"
+    done
+}
+
+function _c4_parallel_build_flags()
+{
+    case "$CXX_" in
+        vs2019|vs2017|vs2015)
+            # https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild-command-line-reference?view=vs-2019
+            # https://stackoverflow.com/questions/2619198/how-to-get-number-of-cores-in-win32
+            if [ -z "$NUM_JOBS_BUILD" ] ; then
+                echo "/maxcpucount:$NUMBER_OF_PROCESSORS"
+            else
+                echo "/maxcpucount:$NUM_JOBS_BUILD"
+            fi
+            ;;
+        xcode)
+            # https://stackoverflow.com/questions/5417835/how-to-modify-the-number-of-parallel-compilation-with-xcode
+            # https://gist.github.com/nlutsenko/ee245fbd239087d22137
+            if [ -z "$NUM_JOBS_BUILD" ] ; then
+                echo "-IDEBuildOperationMaxNumberOfConcurrentCompileTasks=$(sysctl -n hw.ncpu)"
+            else
+                echo "-IDEBuildOperationMaxNumberOfConcurrentCompileTasks=$NUM_JOBS_BUILD"
+            fi
+            ;;
+        *g++*|*gcc*|*clang*|em++)
+            if [ -z "$NUM_JOBS_BUILD" ] ; then
+                echo "-j $(nproc)"
+            else
+                echo "-j $NUM_JOBS_BUILD"
+            fi
+            ;;
+        "") # allow empty compiler
+            ;;
+        *)
+            echo "unknown compiler"
+            exit 1
+            ;;
+    esac
+}
+
+function _c4_generator_build_flags()
+{
+    case "$CXX_" in
+        vs2019|vs2017|vs2015)
+            ;;
+        xcode)
+            # WTF???
+            # https://github.com/biojppm/rapidyaml/pull/97/checks?check_run_id=1504677928#step:7:964
+            # https://stackoverflow.com/questions/51153525/xcode-10-unable-to-attach-db-error
+            echo "-UseModernBuildSystem=NO"
+            ;;
+        *g++*|*gcc*|*clang*|em++)
+            ;;
+        "") # allow empty compiler
+            ;;
+        *)
+            echo "unknown compiler"
+            exit 1
+            ;;
+    esac
+}

+ 80 - 0
3rdparty/rapidyaml/ext/c4core/.github/vagrant/Vagrantfile

@@ -0,0 +1,80 @@
+# -*- mode: ruby -*-
+# vi: set ft=ruby :
+
+# 1) download and install vagrant: https://www.vagrantup.com/downloads.html
+#    (do not install ubuntu's 14.04 16.04 version, see https://stackoverflow.com/questions/22717428/vagrant-error-failed-to-mount-folders-in-linux-guest ):
+# 2) vagrant plugin install vagrant-vbguest
+# 3) vagrant up --provider virtualbox
+# 4) vagrant ssh
+
+# All Vagrant configuration is done below. The "2" in Vagrant.configure
+# configures the configuration version (we support older styles for
+# backwards compatibility). Please don't change it unless you know what
+# you're doing.
+Vagrant.configure(2) do |config|
+  # The most common configuration options are documented and commented below.
+  # For a complete reference, please see the online documentation at
+  # https://docs.vagrantup.com.
+
+  # Every Vagrant development environment requires a box. You can search for
+  # boxes at https://atlas.hashicorp.com/search.
+  config.vm.box = "generic/ubuntu1804"
+
+  # Disable automatic box update checking. If you disable this, then
+  # boxes will only be checked for updates when the user runs
+  # `vagrant box outdated`. This is not recommended.
+  # config.vm.box_check_update = false
+
+  # Create a forwarded port mapping which allows access to a specific port
+  # within the machine from a port on the host machine. In the example below,
+  # accessing "localhost:8080" will access port 80 on the guest machine.
+  # config.vm.network "forwarded_port", guest: 80, host: 8080
+
+  #config.ssh.username = 'travis'
+  #config.ssh.password = 'travis'
+
+  # Create a private network, which allows host-only access to the machine
+  # using a specific IP.
+  # config.vm.network "private_network", ip: "192.168.33.10"
+
+  # Create a public network, which generally matched to bridged network.
+  # Bridged networks make the machine appear as another physical device on
+  # your network.
+  # config.vm.network "public_network"
+
+  # Share an additional folder to the guest VM. The first argument is
+  # the path on the host to the actual folder. The second argument is
+  # the path on the guest to mount the folder. And the optional third
+  # argument is a set of non-required options.
+  config.vm.synced_folder "../../../..", "/vagrant"
+
+  #config.vm.synced_folder '.', '/vagrant', disabled: true
+
+  # Provider-specific configuration so you can fine-tune various
+  # backing providers for Vagrant. These expose provider-specific options.
+  # Example for VirtualBox:
+  #
+  # config.vm.provider "virtualbox" do |vb|
+  #   # Display the VirtualBox GUI when booting the machine
+  #   vb.gui = true
+  #
+  #   # Customize the amount of memory on the VM:
+  #   vb.memory = "1024"
+  # end
+  #
+  # View the documentation for the provider you are using for more
+  # information on available options.
+
+  # Define a Vagrant Push strategy for pushing to Atlas. Other push strategies
+  # such as FTP and Heroku are also available. See the documentation at
+  # https://docs.vagrantup.com/v2/push/atlas.html for more information.
+  # config.push.define "atlas" do |push|
+  #   push.app = "YOUR_ATLAS_USERNAME/YOUR_APPLICATION_NAME"
+  # end
+
+  # Enable provisioning with a shell script. Additional provisioners such as
+  # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the
+  # documentation for more information about their specific syntax and use.
+  #config.vm.provision "shell", path: "travis-install.sh"
+
+end

+ 71 - 0
3rdparty/rapidyaml/ext/c4core/.github/vagrant/macos/Vagrantfile

@@ -0,0 +1,71 @@
+# -*- mode: ruby -*-
+# vi: set ft=ruby :
+
+# All Vagrant configuration is done below. The "2" in Vagrant.configure
+# configures the configuration version (we support older styles for
+# backwards compatibility). Please don't change it unless you know what
+# you're doing.
+Vagrant.configure("2") do |config|
+  # The most common configuration options are documented and commented below.
+  # For a complete reference, please see the online documentation at
+  # https://docs.vagrantup.com.
+
+  # Every Vagrant development environment requires a box. You can search for
+  # boxes at https://vagrantcloud.com/search.
+  config.vm.box = "ramsey/macos-catalina"
+  config.vm.box_version = "1.0.0"
+
+  # Disable automatic box update checking. If you disable this, then
+  # boxes will only be checked for updates when the user runs
+  # `vagrant box outdated`. This is not recommended.
+  # config.vm.box_check_update = false
+
+  # Create a forwarded port mapping which allows access to a specific port
+  # within the machine from a port on the host machine. In the example below,
+  # accessing "localhost:8080" will access port 80 on the guest machine.
+  # NOTE: This will enable public access to the opened port
+  # config.vm.network "forwarded_port", guest: 80, host: 8080
+
+  # Create a forwarded port mapping which allows access to a specific port
+  # within the machine from a port on the host machine and only allow access
+  # via 127.0.0.1 to disable public access
+  # config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1"
+
+  # Create a private network, which allows host-only access to the machine
+  # using a specific IP.
+  # config.vm.network "private_network", ip: "192.168.33.10"
+
+  # Create a public network, which generally matched to bridged network.
+  # Bridged networks make the machine appear as another physical device on
+  # your network.
+  # config.vm.network "public_network"
+
+  # Share an additional folder to the guest VM. The first argument is
+  # the path on the host to the actual folder. The second argument is
+  # the path on the guest to mount the folder. And the optional third
+  # argument is a set of non-required options.
+  # config.vm.synced_folder "../data", "/vagrant_data"
+
+  # Provider-specific configuration so you can fine-tune various
+  # backing providers for Vagrant. These expose provider-specific options.
+  # Example for VirtualBox:
+  #
+  # config.vm.provider "virtualbox" do |vb|
+  #   # Display the VirtualBox GUI when booting the machine
+  #   vb.gui = true
+  #
+  #   # Customize the amount of memory on the VM:
+  #   vb.memory = "1024"
+  # end
+  #
+  # View the documentation for the provider you are using for more
+  # information on available options.
+
+  # Enable provisioning with a shell script. Additional provisioners such as
+  # Ansible, Chef, Docker, Puppet and Salt are also available. Please see the
+  # documentation for more information about their specific syntax and use.
+  # config.vm.provision "shell", inline: <<-SHELL
+  #   apt-get update
+  #   apt-get install -y apache2
+  # SHELL
+end

+ 71 - 0
3rdparty/rapidyaml/ext/c4core/.github/vagrant/vagrant-provision.sh

@@ -0,0 +1,71 @@
+#!/usr/bin/env bash
+
+set -x
+
+# https://askubuntu.com/questions/735201/installing-clang-3-8-on-ubuntu-14-04-3
+wget -O - http://llvm.org/apt/llvm-snapshot.gpg.key | sudo apt-key add -
+
+done=$(grep C4STL /etc/apt/sources.list)
+if [ -z "$done" ] ; then
+    cat >> /etc/apt/sources.list <<EOF
+
+# C4STL
+# http://apt.llvm.org/
+#deb http://llvm.org/apt/trusty/ llvm-toolchain-trusty-3.7 main
+#deb http://llvm.org/apt/trusty/ llvm-toolchain-trusty-3.8 main
+deb http://llvm.org/apt/trusty/ llvm-toolchain-trusty-3.9 main
+deb http://llvm.org/apt/trusty/ llvm-toolchain-trusty-4.0 main
+#deb http://llvm.org/apt/trusty/ llvm-toolchain-trusty-5.0 main
+EOF
+fi
+
+sudo -E apt-get install -y software-properties-common python-software-properties
+sudo -E add-apt-repository -y ppa:ubuntu-toolchain-r/test
+sudo -E add-apt-repository -y ppa:george-edison55/cmake-3.x
+sudo -E apt-get -yq update
+
+sudo -E apt-get install -yq --force-yes \
+     build-essential \
+     cmake \
+     g++-5 \
+     g++-5-multilib \
+     g++-6 \
+     g++-6-multilib \
+     g++-7 \
+     g++-7-multilib \
+     g++-8 \
+     g++-8-multilib \
+     g++-9 \
+     g++-9-multilib \
+     g++-10 \
+     g++-10-multilib \
+     g++-11 \
+     g++-11-multilib \
+     clang-3.7 \
+     clang-3.8 \
+     clang-3.9 \
+     clang-4.0 \
+     swig3.0 \
+     libssl-dev \
+     zlib1g-dev \
+     libbz2-dev \
+     libreadline-dev \
+     libsqlite3-dev \
+     wget \
+     curl \
+     llvm \
+     libncurses5-dev \
+     libncursesw5-dev \
+     xz-utils \
+     tk-dev \
+     libffi-dev \
+     liblzma-dev \
+     python-openssl \
+     git \
+     python3 \
+     python3-pip \
+     python3-venv
+
+sudo -E pip install cmany
+
+exit 0

+ 116 - 0
3rdparty/rapidyaml/ext/c4core/.github/workflows/arch.yml

@@ -0,0 +1,116 @@
+name: rarearchs
+
+defaults:
+  #if: "!contains(github.event.head_commit.message, 'skip ci')"  # SKIP
+  run:
+    # Use a bash shell so we can use the same syntax for environment variable
+    # access regardless of the host operating system
+    shell: bash -e -x {0}
+
+on:
+  # https://github.community/t/how-to-trigger-an-action-on-push-or-pull-request-but-not-both/16662
+  workflow_dispatch:
+  push:
+    branches:
+    - master
+  pull_request:
+    branches:
+    - master
+
+jobs:
+  rarearchs:
+    name: ${{matrix.arch}}/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-20.04
+    strategy:
+      fail-fast: false
+      matrix:
+        include:
+          - {std: 11, bt: Debug  , arch: aarch64, distro: ubuntu20.04}
+          - {std: 11, bt: Release, arch: aarch64, distro: ubuntu20.04}
+          - {std: 14, bt: Debug  , arch: aarch64, distro: ubuntu20.04}
+          - {std: 14, bt: Release, arch: aarch64, distro: ubuntu20.04}
+          - {std: 17, bt: Debug  , arch: aarch64, distro: ubuntu20.04}
+          - {std: 17, bt: Release, arch: aarch64, distro: ubuntu20.04}
+          #
+          - {std: 11, bt: Debug  , arch: ppc64le, distro: ubuntu20.04}
+          - {std: 11, bt: Release, arch: ppc64le, distro: ubuntu20.04}
+          - {std: 14, bt: Debug  , arch: ppc64le, distro: ubuntu20.04}
+          - {std: 14, bt: Release, arch: ppc64le, distro: ubuntu20.04}
+          - {std: 17, bt: Debug  , arch: ppc64le, distro: ubuntu20.04}
+          - {std: 17, bt: Release, arch: ppc64le, distro: ubuntu20.04}
+          #
+          - {std: 11, bt: Debug  , arch: s390x  , distro: ubuntu20.04}
+          - {std: 11, bt: Release, arch: s390x  , distro: ubuntu20.04}
+          - {std: 14, bt: Debug  , arch: s390x  , distro: ubuntu20.04}
+          - {std: 14, bt: Release, arch: s390x  , distro: ubuntu20.04}
+          - {std: 17, bt: Debug  , arch: s390x  , distro: ubuntu20.04}
+          - {std: 17, bt: Release, arch: s390x  , distro: ubuntu20.04}
+          #
+          #- {std: 11, bt: Debug  , arch: armv6  , distro: bullseye}
+          #- {std: 11, bt: Release, arch: armv6  , distro: bullseye}
+          #- {std: 14, bt: Debug  , arch: armv6  , distro: bullseye}
+          #- {std: 14, bt: Release, arch: armv6  , distro: bullseye}
+          #- {std: 17, bt: Debug  , arch: armv6  , distro: bullseye}
+          #- {std: 17, bt: Release, arch: armv6  , distro: bullseye}
+          #
+          #- {std: 11, bt: Debug  , arch: armv7  , distro: ubuntu20.04}
+          #- {std: 11, bt: Release, arch: armv7  , distro: ubuntu20.04}
+          #- {std: 14, bt: Debug  , arch: armv7  , distro: ubuntu20.04}
+          #- {std: 14, bt: Release, arch: armv7  , distro: ubuntu20.04}
+          #- {std: 17, bt: Debug  , arch: armv7  , distro: ubuntu20.04}
+          #- {std: 17, bt: Release, arch: armv7  , distro: ubuntu20.04}
+    steps:
+      - {name: checkout, uses: actions/checkout@v2, with: {submodules: recursive}}
+      - name: test
+        uses: uraimo/run-on-arch-action@v2.0.5
+        with:
+          arch: ${{matrix.arch}}
+          distro: ${{matrix.distro}}
+          install: |
+            set -x
+            apt-get update -y
+            apt-get install -y \
+              git \
+              build-essential
+            # arm platforms need an up-to-date cmake:
+            # https://gitlab.kitware.com/cmake/cmake/-/issues/20568
+            if [ "${{matrix.arch}}" == "armv6" ] || [ "${{matrix.arch}}" == "armv7" ] ; then
+              apt-get install -y \
+                gpg \
+                wget \
+                apt-transport-https
+              wget --no-check-certificate -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | gpg --dearmor - | tee /usr/share/keyrings/kitware-archive-keyring.gpg >/dev/null
+              echo 'deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ focal main' | tee /etc/apt/sources.list.d/kitware.list >/dev/null
+              apt-get update -y
+              rm /usr/share/keyrings/kitware-archive-keyring.gpg
+              apt-get install kitware-archive-keyring
+              apt-get update -y
+            fi
+            apt-get install -y cmake cmake-data
+            cmake --version
+          run: |
+            set -x
+            uname -a
+            pwd
+            ls -lFhp .
+            #
+            bdir=build_${{matrix.arch}}_${{matrix.bt}}_${{matrix.std}}
+            idir=install_${{matrix.arch}}_${{matrix.bt}}_${{matrix.std}}
+            mkdir -p $bdir
+            #
+            cmake -S . -B $bdir \
+              -DCMAKE_INSTALL_PREFIX=$idir \
+              -DCMAKE_BUILD_TYPE=${{matrix.bt}} \
+              -DC4_CXX_STANDARD=${{matrix.std}} \
+              -DCXX_STANDARD=${{matrix.std}} \
+              -DC4CORE_DEV=ON \
+              -DC4CORE_BUILD_BENCHMARKS=OFF \
+              -DC4CORE_SANITIZE=OFF \
+              -DC4CORE_LINT=OFF \
+              -DC4CORE_VALGRIND=OFF
+            #
+            cmake --build $bdir -j --target c4core-test-build
+            #
+            cmake --build $bdir    --target c4core-test-run

+ 87 - 0
3rdparty/rapidyaml/ext/c4core/.github/workflows/benchmarks.yml

@@ -0,0 +1,87 @@
+name: benchmarks
+
+defaults:
+  run:
+    # Use a bash shell so we can use the same syntax for environment variable
+    # access regardless of the host operating system
+    shell: bash -e -x {0}
+
+on:
+  # https://github.community/t/how-to-trigger-an-action-on-push-or-pull-request-but-not-both/16662
+  workflow_dispatch:
+  push:
+    branches:
+    - master
+  pull_request:
+    branches:
+    - master
+
+env:
+  PROJ_PFX_TARGET: c4core-
+  PROJ_PFX_CMAKE: C4CORE_
+  CMAKE_FLAGS:
+  NUM_JOBS_BUILD: # 4
+
+
+jobs:
+  benchmarks:
+    name: bm/c++${{matrix.std}}/${{matrix.cxx}}/${{matrix.bt}}
+    if: |
+      (!contains(github.event.head_commit.message, 'skip all')) ||
+      (!contains(github.event.head_commit.message, 'skip benchmarks')) ||
+      contains(github.event.head_commit.message, 'only benchmarks')
+    continue-on-error: true
+    runs-on: ${{matrix.os}}
+    strategy:
+      fail-fast: false
+      matrix:
+        include:
+          - {std: 11, cxx: g++-10, bt: Debug  , os: ubuntu-18.04  , bitlinks: static64 static32}
+          - {std: 11, cxx: g++-10, bt: Release, os: ubuntu-18.04  , bitlinks: static64 static32}
+          - {std: 17, cxx: g++-10, bt: Debug  , os: ubuntu-18.04  , bitlinks: static64 static32}
+          - {std: 17, cxx: g++-10, bt: Release, os: ubuntu-18.04  , bitlinks: static64 static32}
+          - {std: 20, cxx: g++-10, bt: Debug  , os: ubuntu-18.04  , bitlinks: static64 static32}
+          - {std: 20, cxx: g++-10, bt: Release, os: ubuntu-18.04  , bitlinks: static64 static32}
+          - {std: 11, cxx: vs2019, bt: Debug  , os: windows-latest, bitlinks: static64 static32}
+          - {std: 11, cxx: vs2019, bt: Release, os: windows-latest, bitlinks: static64 static32}
+          - {std: 17, cxx: vs2019, bt: Debug  , os: windows-latest, bitlinks: static64 static32}
+          - {std: 17, cxx: vs2019, bt: Release, os: windows-latest, bitlinks: static64 static32}
+          - {std: 20, cxx: vs2019, bt: Debug  , os: windows-latest, bitlinks: static64 static32}
+          - {std: 20, cxx: vs2019, bt: Release, os: windows-latest, bitlinks: static64 static32}
+    env: {BM: ON, 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:
+      # use fetch-depth to ensure all tags are fetched
+      - {name: checkout, uses: actions/checkout@v2, with: {submodules: recursive, fetch-depth: 0}}
+      - {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---------------------------------------------------
+        run: source .github/setenv.sh && c4_cfg_test shared64
+      - {name: shared64-build, run: source .github/setenv.sh && c4_build_target shared64 c4core-bm-build}
+      - {name: shared64-run, run: export NUM_JOBS_BUILD=1 && source .github/setenv.sh && c4_run_target shared64 c4core-bm-run}
+      - name: static64-configure---------------------------------------------------
+        run: source .github/setenv.sh && c4_cfg_test static64
+      - {name: static64-build, run: source .github/setenv.sh && c4_build_target static64 c4core-bm-build}
+      - {name: static64-run, run: export NUM_JOBS_BUILD=1 && source .github/setenv.sh && c4_run_target static64 c4core-bm-run}
+      - name: static32-configure---------------------------------------------------
+        run: source .github/setenv.sh && c4_cfg_test static32
+      - {name: static32-build, run: source .github/setenv.sh && c4_build_target static32 c4core-bm-build}
+      - {name: static32-run, run: export NUM_JOBS_BUILD=1 && source .github/setenv.sh && c4_run_target static32 c4core-bm-run}
+      - name: shared32-configure---------------------------------------------------
+        run: source .github/setenv.sh && c4_cfg_test shared32
+      - {name: shared32-build, run: source .github/setenv.sh && c4_build_target shared32 c4core-bm-build}
+      - {name: shared32-run, run: export NUM_JOBS_BUILD=1 && source .github/setenv.sh && c4_run_target shared32 c4core-bm-run}
+      - name: gather benchmark results
+        run: |
+          set -x
+          desc=$(git describe || git rev-parse --short HEAD)
+          for bl in ${{matrix.bitlinks}} ; do
+            dst=$(echo benchmark_results/$desc/${{matrix.cxx}}-${{matrix.bt}}-c++${{matrix.std}}-$bl | sed 's:++-:xx:g' | sed 's:+:x:g')
+            mkdir -p $dst
+            find build -name bm-results
+            mv -vf build/$bl/bm/bm-results/* $dst/.
+          done
+      - name: upload benchmark result artifacts
+        uses: actions/upload-artifact@v2
+        with:
+          name: benchmark_results
+          path: benchmark_results/

+ 692 - 0
3rdparty/rapidyaml/ext/c4core/.github/workflows/ci.yml

@@ -0,0 +1,692 @@
+name: ci
+
+defaults:
+  #if: "!contains(github.event.head_commit.message, 'skip ci')"  # SKIP
+  run:
+    # Use a bash shell so we can use the same syntax for environment variable
+    # access regardless of the host operating system
+    shell: bash -e -x {0}
+
+on:
+  - push
+  - pull_request
+  - workflow_dispatch
+
+env:
+  PROJ_PFX_TARGET: c4core-
+  PROJ_PFX_CMAKE: C4CORE_
+  CMAKE_FLAGS:
+  NUM_JOBS_BUILD: # 4
+
+
+# ubuntu-20.04:
+#   # https://github.com/actions/virtual-environments/blob/main/images/linux/Ubuntu2004-README.md
+#   gcc: 7.5.0, 8.4.0, 9.3.0, 10.2.0
+#   clang: 8.0.1, 9.0.1, 10.0.0
+# ubuntu-18.04:
+#   # https://github.com/actions/virtual-environments/blob/main/images/linux/Ubuntu1804-README.md
+#   gcc: 7.5.0, 8.4.0, 9.3.0, 10.1.0
+#   clang: 6.0.0, 8.0.0, 9.0.0
+# ubuntu-16.04:
+#   # https://github.com/actions/virtual-environments/blob/main/images/linux/Ubuntu1604-README.md
+#   gcc: 5.5.0, 7.5.0, 8.4.0, 9.3.0
+#   clang: 6.0.0, 8.0.0, 9.0.1
+# macos-11.0: macOS Big Sur 11.0
+#   # https://github.com/actions/virtual-environments/blob/main/images/macos/macos-11.0-Readme.md
+#   Xcode 12.1 11.7
+#   clang/LLVM 10.0.1
+#   gcc-8 gcc-9
+# macos-10.15: macOS Catalina 10.15
+#   # https://github.com/actions/virtual-environments/blob/main/images/macos/macos-10.15-Readme.md
+#   Xcode 12.1 11.7
+#   clang/LLVM 11.0.0
+#   gcc-8 gcc-9
+# windows-2019:
+#   # https://github.com/actions/virtual-environments/blob/main/images/win/Windows2019-Readme.md
+#   vs2019
+# windows-2016:
+#   # https://github.com/actions/virtual-environments/blob/main/images/win/Windows2019-Readme.md
+#   vs2017
+jobs:
+
+  #----------------------------------------------------------------------------
+  test_coverage:
+    # 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++-7, bt: Coverage, os: ubuntu-18.04}
+          - {std: 14, cxx: g++-7, bt: Coverage, os: ubuntu-18.04}
+          - {std: 17, cxx: g++-7, bt: Coverage, os: ubuntu-18.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}}"}
+    steps:
+      - {name: checkout, uses: actions/checkout@v2, 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: 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-submit
+        run: |
+          source .github/setenv.sh
+          c4_submit_coverage shared64 codecov
+          #c4_submit_coverage shared64 coveralls  # only accepts one submission per job
+      - 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-submit
+        run: |
+          source .github/setenv.sh
+          c4_submit_coverage static64 codecov
+          c4_submit_coverage static64 coveralls
+      - 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-submit
+        run: |
+          source .github/setenv.sh
+          c4_submit_coverage static32 codecov
+          #c4_submit_coverage static32 coveralls  # only accepts one submission per job
+      - 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-submit
+        run: |
+          source .github/setenv.sh
+          c4_submit_coverage shared32 codecov
+          #c4_submit_coverage shared32 coveralls  # only accepts one submission per job
+      - 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-submit
+        run: |
+          source .github/setenv.sh
+          c4_submit_coverage static32 codecov
+          #c4_submit_coverage static32 coveralls  # only accepts one submission per job
+
+  #----------------------------------------------------------------------------
+  test_windows:
+    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: vs2017, bt: Debug  , os: windows-2016, bitlinks: shared64 static32}
+          - {std: 11, cxx: vs2017, bt: Release, os: windows-2016, bitlinks: shared64 static32}
+          - {std: 14, cxx: vs2017, bt: Debug  , os: windows-2016, bitlinks: shared64 static32}
+          - {std: 14, cxx: vs2017, bt: Release, os: windows-2016, bitlinks: shared64 static32}
+          - {std: 11, cxx: vs2019, bt: Debug  , os: windows-2019, bitlinks: shared64 static32}
+          - {std: 11, cxx: vs2019, bt: Release, os: windows-2019, bitlinks: shared64 static32}
+          - {std: 14, cxx: vs2019, bt: Debug  , os: windows-2019, bitlinks: shared64 static32}
+          - {std: 14, cxx: vs2019, bt: Release, os: windows-2019, bitlinks: shared64 static32}
+          - {std: 17, cxx: vs2019, bt: Debug  , os: windows-2019, bitlinks: shared64 static32}
+          - {std: 17, cxx: vs2019, bt: Release, os: windows-2019, bitlinks: shared64 static32}
+          - {std: 20, cxx: vs2019, bt: Debug  , os: windows-2019, bitlinks: shared64 static32}
+          - {std: 20, cxx: vs2019, bt: Release, os: windows-2019, 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@v2, 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: 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: 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}
+      - 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}
+
+  #----------------------------------------------------------------------------
+  test_macosx:
+    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: xcode, bt: Debug  , os: macos-11.0, bitlinks: shared64 static64}
+          - {std: 11, cxx: xcode, bt: Release, os: macos-11.0, bitlinks: shared64 static64}
+          - {std: 14, cxx: xcode, bt: Debug  , os: macos-11.0, bitlinks: shared64 static64}
+          - {std: 14, cxx: xcode, bt: Release, os: macos-11.0, bitlinks: shared64 static64}
+          - {std: 17, cxx: xcode, bt: Debug  , os: macos-11.0, bitlinks: shared64 static64}
+          - {std: 17, cxx: xcode, bt: Release, os: macos-11.0, bitlinks: shared64 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@v2, 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: 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: 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}
+      - 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}
+
+  #----------------------------------------------------------------------------
+  test_gcc_canary:
+    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++-7      , bt: Debug  , os: ubuntu-18.04, bitlinks: shared64 static32}
+          - {std: 11, cxx: g++-7      , bt: Release, os: ubuntu-18.04, bitlinks: shared64 static32}
+          - {std: 20, cxx: g++-10     , bt: Debug  , os: ubuntu-18.04, bitlinks: shared64 static32}
+          - {std: 20, cxx: g++-10     , bt: Release, os: ubuntu-18.04, bitlinks: shared64 static32}
+          - {std: 11, cxx: g++-5      , bt: Debug  , os: ubuntu-16.04, bitlinks: shared64 static32}
+          - {std: 11, cxx: g++-5      , bt: Release, os: ubuntu-16.04, 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@v2, 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: 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}
+
+  #----------------------------------------------------------------------------
+  test_clang_canary:
+    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: 20, cxx: clang++-10 , bt: Debug  , os: ubuntu-18.04, bitlinks: shared64 static32}
+          - {std: 20, cxx: clang++-10 , bt: Release, os: ubuntu-18.04, bitlinks: shared64 static32}
+          - {std: 11, cxx: clang++-6.0, bt: Debug  , os: ubuntu-16.04, bitlinks: shared64 static32}
+          - {std: 11, cxx: clang++-6.0, bt: Release, os: ubuntu-16.04, 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@v2, 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: 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}
+
+  #----------------------------------------------------------------------------
+  test_clang_tidy:
+    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:
+          # clang tidy takes a long time, so don't do multiple bits/linktypes
+          - {std: 11, cxx: clang++-9, bt: Debug             , lint: clang-tidy, bitlinks: shared64, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-9, bt: Debug             , lint: clang-tidy, bitlinks: shared32, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-9, bt: Debug             , lint: clang-tidy, bitlinks: static64, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-9, bt: Debug             , lint: clang-tidy, bitlinks: static32, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-9, bt: ReleaseWithDebInfo, lint: clang-tidy, bitlinks: shared64, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-9, bt: ReleaseWithDebInfo, lint: clang-tidy, bitlinks: shared32, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-9, bt: ReleaseWithDebInfo, lint: clang-tidy, bitlinks: static64, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-9, bt: ReleaseWithDebInfo, lint: clang-tidy, bitlinks: static32, os: ubuntu-18.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@v2, 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: 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}
+
+  #----------------------------------------------------------------------------
+  test_gcc_extended:
+    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:
+          #                                     VALGRIND
+          - {std: 11, cxx: g++-10, bt: Debug  , vg: ON, os: ubuntu-18.04}
+          - {std: 11, cxx: g++-10, bt: Release, vg: ON, os: ubuntu-18.04}
+          - {std: 14, cxx: g++-10, bt: Debug  , vg: ON, os: ubuntu-18.04}
+          - {std: 14, cxx: g++-10, bt: Release, vg: ON, os: ubuntu-18.04}
+          - {std: 17, cxx: g++-10, bt: Debug  , vg: ON, os: ubuntu-18.04}
+          - {std: 17, cxx: g++-10, bt: Release, vg: ON, os: ubuntu-18.04}
+          - {std: 20, cxx: g++-10, bt: Debug  , vg: ON, os: ubuntu-18.04}
+          - {std: 20, cxx: g++-10, bt: Release, vg: ON, os: ubuntu-18.04}
+          #
+          - {std: 11, cxx: g++-9, bt: Debug  , os: ubuntu-18.04}
+          - {std: 11, cxx: g++-9, bt: Release, os: ubuntu-18.04}
+          - {std: 11, cxx: g++-8, bt: Debug  , os: ubuntu-18.04}
+          - {std: 11, cxx: g++-8, bt: Release, os: ubuntu-18.04}
+          - {std: 11, cxx: g++-7, bt: Debug  , os: ubuntu-18.04}
+          - {std: 11, cxx: g++-7, bt: Release, os: ubuntu-18.04}
+          - {std: 11, cxx: g++-6, bt: Debug  , os: ubuntu-18.04}
+          - {std: 11, cxx: g++-6, bt: Release, os: ubuntu-18.04}
+          - {std: 11, cxx: g++-5, bt: Debug  , os: ubuntu-18.04}
+          - {std: 11, cxx: g++-5, bt: Release, os: ubuntu-18.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@v2, 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: 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}
+
+  #----------------------------------------------------------------------------
+  test_clang_extended:
+    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: 20, cxx: clang++-10 , bt: Debug  , vg: on, os: ubuntu-18.04}
+          - {std: 20, cxx: clang++-10 , bt: Release, vg: on, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-9  , bt: Debug  , vg: on, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-9  , bt: Release, vg: on, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-8  , bt: Debug  , vg: on, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-8  , bt: Release, vg: on, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-7  , bt: Debug  , vg: on, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-7  , bt: Release, vg: on, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-6.0, bt: Debug  , vg: on, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-6.0, bt: Release, vg: on, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-5.0, bt: Debug  , vg: on, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-5.0, bt: Release, vg: on, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-4.0, bt: Debug  , vg: on, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-4.0, bt: Release, vg: on, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-3.9, bt: Debug  , vg: on, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-3.9, bt: Release, vg: on, os: ubuntu-18.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@v2, 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: 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}
+
+  #----------------------------------------------------------------------------
+  test_clang_sanitize:
+    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:
+          # 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 static64, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-10 , bt: Debug  , vg: ON, san: ALL, bitlinks: shared32 static32, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-10 , bt: Release, vg: ON, san: ALL, bitlinks: shared64 static64, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-10 , bt: Release, vg: ON, san: ALL, bitlinks: shared32 static32, os: ubuntu-18.04}
+          - {std: 14, cxx: clang++-10 , bt: Debug  , vg: ON, san: ALL, bitlinks: shared64 static64, os: ubuntu-18.04}
+          - {std: 14, cxx: clang++-10 , bt: Debug  , vg: ON, san: ALL, bitlinks: shared32 static32, os: ubuntu-18.04}
+          - {std: 14, cxx: clang++-10 , bt: Release, vg: ON, san: ALL, bitlinks: shared64 static64, os: ubuntu-18.04}
+          - {std: 14, cxx: clang++-10 , bt: Release, vg: ON, san: ALL, bitlinks: shared32 static32, os: ubuntu-18.04}
+          - {std: 17, cxx: clang++-10 , bt: Debug  , vg: ON, san: ALL, bitlinks: shared64 static64, os: ubuntu-18.04}
+          - {std: 17, cxx: clang++-10 , bt: Debug  , vg: ON, san: ALL, bitlinks: shared32 static32, os: ubuntu-18.04}
+          - {std: 17, cxx: clang++-10 , bt: Release, vg: ON, san: ALL, bitlinks: shared64 static64, os: ubuntu-18.04}
+          - {std: 17, cxx: clang++-10 , bt: Release, vg: ON, san: ALL, bitlinks: shared32 static32, os: ubuntu-18.04}
+          - {std: 20, cxx: clang++-10 , bt: Debug  , vg: ON, san: ALL, bitlinks: shared64 static64, os: ubuntu-18.04}
+          - {std: 20, cxx: clang++-10 , bt: Debug  , vg: ON, san: ALL, bitlinks: shared32 static32, os: ubuntu-18.04}
+          - {std: 20, cxx: clang++-10 , bt: Release, vg: ON, san: ALL, bitlinks: shared64 static64, os: ubuntu-18.04}
+          - {std: 20, cxx: clang++-10 , bt: Release, vg: ON, san: ALL, bitlinks: shared32 static32, os: ubuntu-18.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@v2, 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: 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}
+
+  #----------------------------------------------------------------------------
+  test_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: ${{matrix.os}}
+    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: cmake/Toolchain-Arm-ubuntu.cmake, cxx: arm-linux-gnueabihf-gcc, os: ubuntu-18.04}
+          - {std: 11, bt: Release, toolchain: cmake/Toolchain-Arm-ubuntu.cmake, cxx: arm-linux-gnueabihf-gcc, os: ubuntu-18.04}
+          - {std: 14, bt: Debug  , toolchain: cmake/Toolchain-Arm-ubuntu.cmake, cxx: arm-linux-gnueabihf-gcc, os: ubuntu-18.04}
+          - {std: 14, bt: Release, toolchain: cmake/Toolchain-Arm-ubuntu.cmake, cxx: arm-linux-gnueabihf-gcc, os: ubuntu-18.04}
+          - {std: 17, bt: Debug  , toolchain: cmake/Toolchain-Arm-ubuntu.cmake, cxx: arm-linux-gnueabihf-gcc, os: ubuntu-18.04}
+          - {std: 17, bt: Release, toolchain: cmake/Toolchain-Arm-ubuntu.cmake, cxx: arm-linux-gnueabihf-gcc, os: ubuntu-18.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@v2, 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: 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}
+
+#  #----------------------------------------------------------------------------
+#  # https://blog.kitware.com/static-checks-with-cmake-cdash-iwyu-clang-tidy-lwyu-cpplint-and-cppcheck/
+#  static_analysis:
+#    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:
+#          # these jobs take much longer, so run only one bitlink pair per job to profit from parallelism
+#          - {std: 11, cxx: clang++-10, bt: Debug  , bitlinks: shared64, os: ubuntu-18.04}
+#          - {std: 11, cxx: clang++-10, bt: Release, bitlinks: shared64, os: ubuntu-18.04}
+#          - {std: 14, cxx: clang++-10, bt: Debug  , bitlinks: shared64, os: ubuntu-18.04}
+#          - {std: 14, cxx: clang++-10, bt: Release, bitlinks: shared64, os: ubuntu-18.04}
+#          - {std: 17, cxx: clang++-10, bt: Debug  , bitlinks: shared64, os: ubuntu-18.04}
+#          - {std: 17, cxx: clang++-10, bt: Release, bitlinks: shared64, os: ubuntu-18.04}
+#          - {std: 20, cxx: clang++-10, bt: Debug  , bitlinks: shared64, os: ubuntu-18.04}
+#          - {std: 20, cxx: clang++-10, bt: Release, bitlinks: shared64, os: ubuntu-18.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@v2, 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: shared64-configure---------------------------------------------------
+#        run: source .github/setenv.sh && c4_cfg_test shared64
+#      - {name: shared64-build, run: source .github/setenv.sh && c4_build_test shared64}
+#      - {name: clang-tidy, run: cmake "-DCMAKE_CXX_CLANG_TIDY=/usr/bin/clang-tidy-3.9;-checks=*" ../path/to/source}
+#      - {name: cppcheck, run: cmake "-DCMAKE_CXX_CPPCHECK=/usr/bin/cppcheck;--std=c++11" ../path/to/source}
+#      - {name: cpplint, run: cmake "-DCMAKE_CXX_CPPLINT=/usr/local/bin/cpplint;--linelength=179" ..}
+#      - {name: include-what-you-use, run: cmake "-DCMAKE_CXX_INCLUDE_WHAT_YOU_USE=/usr/bin/iwyu;--transitive_includes_only" ..}
+#      - {name: link-what-you-use, run: cmake -DCMAKE_LINK_WHAT_YOU_USE=TRUE ..}
+
+  #----------------------------------------------------------------------------
+  # useful to iterate when fixing the release
+  # ver=0.0.0-rc1 ; ( set -x ; git tag -d v$ver ; git push origin :v$ver ) ; (set -x ; set -e ; git add -u ; git commit --amend --no-edit ; git tag --annotate --message "v$ver" "v$ver" ; git push -f --tags origin gh_actions )
+
+  release:
+    if: contains(github.ref, 'tags/v')
+    runs-on: ubuntu-latest
+    #needs: [test_coverage, test_windows, test_macosx, test_gcc_canary, test_clang_canary, test_clang_tidy, test_gcc_extended, test_clang_extended, test_clang_sanitize, test_api]
+    steps:
+      - name: Install requirements
+        run: |
+          sudo -E pip install git-archive-all
+      - name: Get version
+        id: get_version
+        # https://github.community/t/how-to-get-just-the-tag-name/16241/11
+        run: |
+          echo ::set-output name=SRC_TAG::${GITHUB_REF#refs/tags/}
+          echo ::set-output name=SRC_VERSION::${GITHUB_REF#refs/tags/v}
+          echo SRC_TAG=${GITHUB_REF#refs/tags/}
+          echo SRC_VERSION=${GITHUB_REF#refs/tags/v}
+      - {name: checkout, uses: actions/checkout@v2, with: {submodules: recursive}}
+      - name: Create Release
+        id: create_release
+        uses: actions/create-release@v1 # https://github.com/marketplace/actions/create-a-release
+        env:
+          GITHUB_TOKEN: "${{secrets.GITHUB_TOKEN}}"
+          SRC_TAG: "${{steps.get_version.outputs.SRC_TAG}}"
+          SRC_VERSION: "${{steps.get_version.outputs.SRC_VERSION}}"
+        with:
+          tag_name: ${{github.ref}}
+          release_name: Release ${{steps.get_version.outputs.SRC_VERSION}}
+          draft: true  # to create a draft (unpublished) release, false to create a published one. Default: false
+          prerelease: ${{contains(github.ref, '-rc')}}
+          body_path: ${{github.workspace}}/changelog/${{steps.get_version.outputs.SRC_VERSION}}.md
+      - name: Create source packs
+        id: src_pack
+        run: |
+          version=${{steps.get_version.outputs.SRC_VERSION}}
+          name=${PROJ_PFX_TARGET}src-$version
+          git-archive-all --prefix $name $name.tgz
+          git-archive-all --prefix $name $name.zip
+          echo ::set-output name=TGZ::$name.tgz
+          echo ::set-output name=ZIP::$name.zip
+      - name: Upload tgz source pack
+        id: upload_src_tgz_to_release
+        uses: actions/upload-release-asset@v1.0.1
+        env: {GITHUB_TOKEN: "${{secrets.GITHUB_TOKEN}}"}
+        with:
+          upload_url: ${{steps.create_release.outputs.upload_url}}
+          asset_path: ${{steps.src_pack.outputs.TGZ}}
+          asset_name: ${{steps.src_pack.outputs.TGZ}}
+          asset_content_type: application/gzip
+      - name: Upload zip source pack
+        id: upload_src_zip_to_release
+        uses: actions/upload-release-asset@v1.0.1
+        env: {GITHUB_TOKEN: "${{secrets.GITHUB_TOKEN}}"}
+        with:
+          upload_url: ${{steps.create_release.outputs.upload_url}}
+          asset_path: ${{steps.src_pack.outputs.ZIP}}
+          asset_name: ${{steps.src_pack.outputs.ZIP}}
+          asset_content_type: application/zip
+      - name: Save Release URL for uploading binary artifacts
+        run: |
+          echo "UPLOAD_URL: ${{steps.create_release.outputs.upload_url}}"
+          echo "${{steps.create_release.outputs.upload_url}}" > ./upload_url
+      - name: Upload Release URL
+        uses: actions/upload-artifact@v1
+        with:
+          path: ./upload_url
+          name: upload_url
+
+# since this is a library, we just provide the source packages (done above)
+#  #----------------------------------------------------------------------------
+#  publish:
+#    needs: release
+#    name: publish/${{matrix.config.os}}/${{matrix.config.gen}}
+#    runs-on: ${{matrix.config.os}}
+#    env: {DEV: OFF, BT: Release, OS: "${{matrix.config.os}}", CXX_: "${{matrix.config.cxx}}", GEN: "${{matrix.config.gen}}"}
+#    strategy:
+#      fail-fast: false
+#      matrix:
+#        config:
+#          #  name of the artifact    | suffix         | cpack gen | mime type                      | os              | cxx
+#          - {name: Ubuntu 20.04 deb  , sfx: unix64.deb, gen: DEB  , mime: vnd.debian.binary-package, os: ubuntu-20.04             }
+#          - {name: Ubuntu 20.04 sh   , sfx: unix64.sh , gen: STGZ , mime: x-sh                     , os: ubuntu-20.04             }
+#          - {name: Ubuntu 18.04 deb  , sfx: unix64.deb, gen: DEB  , mime: vnd.debian.binary-package, os: ubuntu-18.04             }
+#          - {name: Ubuntu 18.04 sh   , sfx: unix64.sh , gen: STGZ , mime: x-sh                     , os: ubuntu-18.04             }
+#          - {name: Ubuntu 16.04 deb  , sfx: unix64.deb, gen: DEB  , mime: vnd.debian.binary-package, os: ubuntu-16.04             }
+#          - {name: Ubuntu 16.04 sh   , sfx: unix64.sh , gen: STGZ , mime: x-sh                     , os: ubuntu-16.04             }
+#          - {name: Windows VS2017 zip, sfx: win64.zip , gen: ZIP  , mime: zip                      , os: windows-2016, cxx: vs2017}
+#          - {name: Windows VS2019 zip, sfx: win64.zip , gen: ZIP  , mime: zip                      , os: windows-2019, cxx: vs2019}
+#          - {name: MacOSX sh         , sfx: apple64.sh, gen: STGZ , mime: x-sh                     , os: macos-11.0  , cxx: xcode }
+#    steps:
+#      - name: Get version
+#        id: get_version
+#        # https://github.community/t/how-to-get-just-the-tag-name/16241/11
+#        run: |
+#          echo ::set-output name=SRC_VERSION::${GITHUB_REF#refs/tags/v}
+#          echo SRC_VERSION=${GITHUB_REF#refs/tags/v}
+#          echo GEN=$GEN
+#      - name: Download upload URL
+#        uses: actions/download-artifact@v1
+#        with: {name: upload_url, path: ./}
+#      - name: Preprocess
+#        id: preprocess
+#        run: |
+#          upload_url=`cat ./upload_url`
+#          echo ::set-output name=upload_url::$upload_url
+#          # the package has the same name in multiple same-platform+same-sfx
+#          # instances, but the uploaded asset needs to have different names:
+#          sfx=${{matrix.config.sfx}}
+#          case "${{matrix.config.os}}" in
+#            ubuntu*)
+#              sfx=$(echo $sfx | sed "s:unix64:${{matrix.config.os}}:")
+#              ;;
+#            windows*)
+#              sfx=$(echo $sfx | sed "s:win64:win64-${{matrix.config.cxx}}:")
+#              ;;
+#            macos*)
+#              sfx=$(echo $sfx | sed "s:apple64:macosx-${{matrix.config.cxx}}:")
+#              ;;
+#          esac
+#          asset_name=${PROJ_PFX_TARGET}${{steps.get_version.outputs.SRC_VERSION}}-$sfx
+#          echo ::set-output name=asset_name::$asset_name
+#      - {name: checkout, uses: actions/checkout@v2, 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: shared64-configure---------------------------------------------------
+#        run: source .github/setenv.sh && c4_cfg_test shared64
+#      - {name: shared64-build, run: source .github/setenv.sh && c4_build_target shared64 all}
+#      - name: shared64-pack
+#        run: |
+#          source .github/setenv.sh && c4_package shared64 $GEN
+#          src=./build/shared64/${PROJ_PFX_TARGET}${{steps.get_version.outputs.SRC_VERSION}}-${{matrix.config.sfx}}
+#          dst=${{steps.preprocess.outputs.asset_name}}
+#          cp -fav $src $dst
+#      - name: Upload artifact
+#        id: upload_to_release
+#        uses: actions/upload-release-asset@v1.0.1
+#        env: {GITHUB_TOKEN: "${{secrets.GITHUB_TOKEN}}"}
+#        with:
+#          upload_url: ${{steps.preprocess.outputs.upload_url}}
+#          asset_path: ${{steps.preprocess.outputs.asset_name}}
+#          asset_name: ${{steps.preprocess.outputs.asset_name}}
+#          asset_content_type: application/${{matrix.config.mime}}
+#      #- name: Report artifact URL
+#      #  run: echo "artifact uploaded successfully: ${{steps.upload_to_release.outputs.browser_download_url}}"

+ 95 - 0
3rdparty/rapidyaml/ext/c4core/.github/workflows/emscripten.yml

@@ -0,0 +1,95 @@
+name: emscripten
+
+defaults:
+  #if: "!contains(github.event.head_commit.message, 'skip ci')"  # SKIP
+  run:
+    # Use a bash shell so we can use the same syntax for environment variable
+    # access regardless of the host operating system
+    shell: bash -e -x {0}
+
+on:
+  # https://github.community/t/how-to-trigger-an-action-on-push-or-pull-request-but-not-both/16662
+  workflow_dispatch:
+  push:
+    branches:
+    - master
+  pull_request:
+    branches:
+    - master
+
+env:
+  PROJ_PFX_TARGET: c4core-
+  PROJ_PFX_CMAKE: C4CORE_
+  CMAKE_FLAGS:
+  NUM_JOBS_BUILD: # 4
+  EMSCRIPTEN_CACHE_FOLDER: 'emsdk-cache'
+
+
+# ubuntu-20.04:
+#   # https://github.com/actions/virtual-environments/blob/main/images/linux/Ubuntu2004-README.md
+#   gcc: 7.5.0, 8.4.0, 9.3.0, 10.2.0
+#   clang: 8.0.1, 9.0.1, 10.0.0
+# ubuntu-18.04:
+#   # https://github.com/actions/virtual-environments/blob/main/images/linux/Ubuntu1804-README.md
+#   gcc: 7.5.0, 8.4.0, 9.3.0, 10.1.0
+#   clang: 6.0.0, 8.0.0, 9.0.0
+# macos-11.0: macOS Big Sur 11.0
+#   # https://github.com/actions/virtual-environments/blob/main/images/macos/macos-11.0-Readme.md
+#   Xcode 12.1 11.7
+#   clang/LLVM 10.0.1
+#   gcc-8 gcc-9
+# macos-10.15: macOS Catalina 10.15
+#   # https://github.com/actions/virtual-environments/blob/main/images/macos/macos-10.15-Readme.md
+#   Xcode 12.1 11.7
+#   clang/LLVM 11.0.0
+#   gcc-8 gcc-9
+# windows-2019:
+#   # https://github.com/actions/virtual-environments/blob/main/images/win/Windows2019-Readme.md
+#   vs2019
+# windows-2016:
+#   # https://github.com/actions/virtual-environments/blob/main/images/win/Windows2016-Readme.md
+#   vs2017
+
+jobs:
+
+  #----------------------------------------------------------------------------
+  emscripten:
+    name: emscripten/${{matrix.emver}}/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: ${{matrix.os}}
+    strategy:
+      fail-fast: false
+      matrix:
+        include:
+          - {std: 11, cxx: em++, emver: 2.0.34, bt: Debug  , os: ubuntu-latest, bitlinks: static32}
+          - {std: 11, cxx: em++, emver: 2.0.34, bt: Release, os: ubuntu-latest, bitlinks: static32}
+          - {std: 20, cxx: em++, emver: 2.0.34, bt: Debug  , os: ubuntu-latest, bitlinks: static32}
+          - {std: 20, cxx: em++, emver: 2.0.34, bt: Release, os: ubuntu-latest, bitlinks: static32}
+          - {std: 11, cxx: em++, emver: 3.0.0 , bt: Debug  , os: ubuntu-latest, bitlinks: static32}
+          - {std: 11, cxx: em++, emver: 3.0.0 , bt: Release, os: ubuntu-latest, bitlinks: static32}
+          - {std: 20, cxx: em++, emver: 3.0.0 , bt: Debug  , os: ubuntu-latest, bitlinks: static32}
+          - {std: 20, cxx: em++, emver: 3.0.0 , bt: Release, os: ubuntu-latest, 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@v2, with: {submodules: recursive}}
+      - name: setup emscripten cache
+        id: cache-system-libraries
+        uses: actions/cache@v2
+        with: {path: "${{env.EMSCRIPTEN_CACHE_FOLDER}}", key: "${{matrix.emver}}-${{runner.os}}"}
+      - name: setup emscripten
+        uses: mymindstorm/setup-emsdk@v11
+        with: {version: "${{matrix.emver}}", actions-cache-folder: "${{env.EMSCRIPTEN_CACHE_FOLDER}}"}
+      - {name: show info, run: source .github/setenv.sh && c4_show_info}
+      - 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}

+ 111 - 0
3rdparty/rapidyaml/ext/c4core/.github/workflows/libcxx.yml

@@ -0,0 +1,111 @@
+name: libcxx
+
+defaults:
+  #if: "!contains(github.event.head_commit.message, 'skip ci')"  # SKIP
+  run:
+    # Use a bash shell so we can use the same syntax for environment variable
+    # access regardless of the host operating system
+    shell: bash -e -x {0}
+
+on:
+  # https://github.community/t/how-to-trigger-an-action-on-push-or-pull-request-but-not-both/16662
+  workflow_dispatch:
+  push:
+    branches:
+    - master
+  pull_request:
+    branches:
+    - master
+
+env:
+  PROJ_PFX_TARGET: c4core-
+  PROJ_PFX_CMAKE: C4CORE_
+  CMAKE_FLAGS:
+  NUM_JOBS_BUILD: # 4
+
+
+# ubuntu-20.04:
+#   # https://github.com/actions/virtual-environments/blob/main/images/linux/Ubuntu2004-README.md
+#   gcc: 7.5.0, 8.4.0, 9.3.0, 10.2.0
+#   clang: 8.0.1, 9.0.1, 10.0.0
+# ubuntu-18.04:
+#   # https://github.com/actions/virtual-environments/blob/main/images/linux/Ubuntu1804-README.md
+#   gcc: 7.5.0, 8.4.0, 9.3.0, 10.1.0
+#   clang: 6.0.0, 8.0.0, 9.0.0
+# macos-11.0: macOS Big Sur 11.0
+#   # https://github.com/actions/virtual-environments/blob/main/images/macos/macos-11.0-Readme.md
+#   Xcode 12.1 11.7
+#   clang/LLVM 10.0.1
+#   gcc-8 gcc-9
+# macos-10.15: macOS Catalina 10.15
+#   # https://github.com/actions/virtual-environments/blob/main/images/macos/macos-10.15-Readme.md
+#   Xcode 12.1 11.7
+#   clang/LLVM 11.0.0
+#   gcc-8 gcc-9
+# windows-2019:
+#   # https://github.com/actions/virtual-environments/blob/main/images/win/Windows2019-Readme.md
+#   vs2019
+# windows-2016:
+#   # https://github.com/actions/virtual-environments/blob/main/images/win/Windows2016-Readme.md
+#   vs2017
+
+jobs:
+
+  #----------------------------------------------------------------------------
+  libcxx:
+    name: libc++/${{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: ${{matrix.os}}
+    strategy:
+      fail-fast: false
+      matrix:
+        include:
+          - {std: 20, cxx: clang++-10 , bt: Debug  , os: ubuntu-18.04, bitlinks: shared64 static32}
+          - {std: 20, cxx: clang++-10 , bt: Release, os: ubuntu-18.04, bitlinks: shared64 static32}
+          - {std: 17, cxx: clang++-10 , bt: Debug  , os: ubuntu-18.04, bitlinks: shared64 static32}
+          - {std: 17, cxx: clang++-10 , bt: Release, os: ubuntu-18.04, bitlinks: shared64 static32}
+          - {std: 14, cxx: clang++-10 , bt: Debug  , os: ubuntu-18.04, bitlinks: shared64 static32}
+          - {std: 14, cxx: clang++-10 , bt: Release, os: ubuntu-18.04, bitlinks: shared64 static32}
+          - {std: 11, cxx: clang++-10 , bt: Debug  , os: ubuntu-18.04, bitlinks: shared64 static32}
+          - {std: 11, cxx: clang++-10 , bt: Release, os: ubuntu-18.04, bitlinks: shared64 static32}
+          - {std: 17, cxx: clang++-6.0, bt: Debug  , os: ubuntu-18.04, bitlinks: shared64 static32}
+          - {std: 17, cxx: clang++-6.0, bt: Release, os: ubuntu-18.04, bitlinks: shared64 static32}
+          - {std: 14, cxx: clang++-6.0, bt: Debug  , os: ubuntu-18.04, bitlinks: shared64 static32}
+          - {std: 14, cxx: clang++-6.0, bt: Release, os: ubuntu-18.04, bitlinks: shared64 static32}
+          - {std: 11, cxx: clang++-6.0, bt: Debug  , os: ubuntu-18.04, bitlinks: shared64 static32}
+          - {std: 11, cxx: clang++-6.0, bt: Release, os: ubuntu-18.04, bitlinks: shared64 static32}
+    env:
+      LIBCXX: ON  # <---- enable libc++
+      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@v2, 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: 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}

+ 103 - 0
3rdparty/rapidyaml/ext/c4core/.github/workflows/macosx.yml

@@ -0,0 +1,103 @@
+name: macosx
+
+defaults:
+  #if: "!contains(github.event.head_commit.message, 'skip ci')"  # SKIP
+  run:
+    # Use a bash shell so we can use the same syntax for environment variable
+    # access regardless of the host operating system
+    shell: bash -e -x {0}
+
+on:
+  # https://github.community/t/how-to-trigger-an-action-on-push-or-pull-request-but-not-both/16662
+  workflow_dispatch:
+  push:
+    branches:
+    - master
+  pull_request:
+    branches:
+    - master
+
+env:
+  PROJ_PFX_TARGET: c4core-
+  PROJ_PFX_CMAKE: C4CORE_
+  CMAKE_FLAGS:
+  NUM_JOBS_BUILD: # 4
+
+
+# ubuntu-20.04:
+#   # https://github.com/actions/virtual-environments/blob/main/images/linux/Ubuntu2004-README.md
+#   gcc: 7.5.0, 8.4.0, 9.3.0, 10.2.0
+#   clang: 8.0.1, 9.0.1, 10.0.0
+# ubuntu-18.04:
+#   # https://github.com/actions/virtual-environments/blob/main/images/linux/Ubuntu1804-README.md
+#   gcc: 7.5.0, 8.4.0, 9.3.0, 10.1.0
+#   clang: 6.0.0, 8.0.0, 9.0.0
+# macos-11.0: macOS Big Sur 11.0
+#   # https://github.com/actions/virtual-environments/blob/main/images/macos/macos-11.0-Readme.md
+#   Xcode 12.1 11.7
+#   clang/LLVM 10.0.1
+#   gcc-8 gcc-9
+# macos-10.15: macOS Catalina 10.15
+#   # https://github.com/actions/virtual-environments/blob/main/images/macos/macos-10.15-Readme.md
+#   Xcode 12.1 11.7
+#   clang/LLVM 11.0.0
+#   gcc-8 gcc-9
+# windows-2019:
+#   # https://github.com/actions/virtual-environments/blob/main/images/win/Windows2019-Readme.md
+#   vs2019
+# windows-2016:
+#   # https://github.com/actions/virtual-environments/blob/main/images/win/Windows2016-Readme.md
+#   vs2017
+
+jobs:
+
+  #----------------------------------------------------------------------------
+  xcode:
+    name: xcode${{matrix.xcver}}/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: ${{matrix.os}}
+    strategy:
+      fail-fast: false
+      matrix:
+        include:
+          - {std: 11, cxx: xcode, xcver: 13, bt: Debug  , os: macos-11, bitlinks: shared64 static64}
+          - {std: 11, cxx: xcode, xcver: 13, bt: Release, os: macos-11, bitlinks: shared64 static64}
+          - {std: 17, cxx: xcode, xcver: 13, bt: Debug  , os: macos-11, bitlinks: shared64 static64}
+          - {std: 17, cxx: xcode, xcver: 13, bt: Release, os: macos-11, bitlinks: shared64 static64}
+          #
+          - {std: 11, cxx: xcode, xcver: 12, bt: Debug  , os: macos-11, bitlinks: shared64 static64}
+          - {std: 11, cxx: xcode, xcver: 12, bt: Release, os: macos-11, bitlinks: shared64 static64}
+          - {std: 17, cxx: xcode, xcver: 12, bt: Debug  , os: macos-11, bitlinks: shared64 static64}
+          - {std: 17, cxx: xcode, xcver: 12, bt: Release, os: macos-11, bitlinks: shared64 static64}
+          #
+          - {std: 11, cxx: xcode, xcver: 11, bt: Debug  , os: macos-11, bitlinks: shared64 static64}
+          - {std: 11, cxx: xcode, xcver: 11, bt: Release, os: macos-11, bitlinks: shared64 static64}
+          - {std: 17, cxx: xcode, xcver: 11, bt: Debug  , os: macos-11, bitlinks: shared64 static64}
+          - {std: 17, cxx: xcode, xcver: 11, bt: Release, os: macos-11, bitlinks: shared64 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@v2, with: {submodules: recursive}}
+      - {name: xcode, uses: maxim-lobanov/setup-xcode@v1, with: {xcode-version: "${{matrix.xcver}}" }}
+      - {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---------------------------------------------------
+        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: 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}
+      - 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}

+ 199 - 0
3rdparty/rapidyaml/ext/c4core/.github/workflows/release.yml

@@ -0,0 +1,199 @@
+name: release
+
+defaults:
+  #if: "!contains(github.event.head_commit.message, 'skip ci')"  # SKIP
+  run:
+    # Use a bash shell so we can use the same syntax for environment variable
+    # access regardless of the host operating system
+    shell: bash -e -x {0}
+
+on:
+  # https://github.community/t/how-to-trigger-an-action-on-push-or-pull-request-but-not-both/16662
+  workflow_dispatch:
+  push:
+    tags:
+      - v0.*
+      - v1.*
+      - v2.*
+    branches:
+      - master
+  pull_request:
+    branches:
+      - master
+
+env:
+  PROJ_PKG_NAME: c4core-
+  PROJ_PFX_TARGET: c4core-
+  PROJ_PFX_CMAKE: C4CORE_
+  CMAKE_FLAGS:
+  NUM_JOBS_BUILD: # 4
+
+
+# useful to iterate when fixing the release:
+# ver=0.2.1 ; ( set -x ; git tag -d v$ver ; git push origin :v$ver ) ; (set -x ; set -e ; tbump --only-patch --non-interactive $ver ; git add -u ; git commit --amend --no-edit ; git tag --annotate --message "v$ver" "v$ver" ; git push -f --tags origin )
+
+jobs:
+
+  gettag:
+    runs-on: ubuntu-latest
+    steps:
+      # use fetch-depth to ensure all tags are fetched
+      - {name: checkout, uses: actions/checkout@v2, with: {submodules: recursive, fetch-depth: 0}}
+      - name: Variables (from tag)
+        if: contains(github.ref, 'tags/v')
+        run: |
+          # https://github.community/t/how-to-get-just-the-tag-name/16241/11
+          SRC_TAG=${GITHUB_REF#refs/tags/}
+          SRC_VERSION=${GITHUB_REF#refs/tags/v}
+          cat <<EOF > vars.sh
+          export SRC_TAG=$SRC_TAG
+          export SRC_VERSION=$SRC_VERSION
+          EOF
+      - name: Variables (from commit, no tag)
+        if: ${{ !contains(github.ref, 'tags/v') }}
+        run: |
+          set -x
+          branch_name=${GITHUB_REF#refs/heads/}
+          # builds triggered from PRs have the branch_name like this: refs/pull/150/merge
+          # so filter to eg pr0150_merge
+          branch_name=`echo $branch_name | sed "s:refs/pull/\([0-9]*\)/\(.*\):pr0\1_\2:"`
+          # sanitize the branch name; eg merge/foo-bar -> merge_foo_bar
+          branch_name=`echo $branch_name | sed 's:[/.-]:_:g'`
+          SRC_TAG=$(git describe || git rev-parse --short HEAD)  # eg v0.2.0-110-gda837e0
+          SRC_VERSION="${branch_name}-${SRC_TAG}"
+          cat <<EOF > vars.sh
+          export SRC_TAG=$SRC_TAG
+          export SRC_VERSION=$SRC_VERSION
+          EOF
+      - name: Verify vars.sh
+        run: cat vars.sh ; source vars.sh ; echo $SRC_TAG ; echo $SRC_VERSION
+      - name: Save vars.sh
+        uses: actions/upload-artifact@v1
+        with: {name: vars.sh, path: ./vars.sh}
+
+  #----------------------------------------------------------------------------
+  # create source packages
+  src:
+    needs: gettag
+    runs-on: ubuntu-latest
+    steps:
+      - {name: checkout, uses: actions/checkout@v2, with: {submodules: recursive}}
+      - name: Download vars.sh
+        uses: actions/download-artifact@v1
+        with: {name: vars.sh, path: ./}
+      - name: Install python 3.9
+        uses: actions/setup-python@v2
+        with: { python-version: 3.9 }
+      - name: Install requirements
+        run: |
+          sudo -E pip install git-archive-all
+      - name: Create source packages
+        run: |
+          pwd
+          ls -lFhp
+          source vars.sh
+          echo SRC_TAG=$SRC_TAG
+          echo SRC_VERSION=$SRC_VERSION
+          id=${PROJ_PKG_NAME}${SRC_VERSION}
+          name=${id}-src
+          mkdir -p assets
+          git-archive-all --prefix $name assets/$name.tgz
+          git-archive-all --prefix $name assets/$name.zip
+          python --version
+          python tools/amalgamate.py assets/$id.hpp
+      - name: Save source artifacts
+        uses: actions/upload-artifact@v1
+        with: {name: assets, path: assets}
+
+  #----------------------------------------------------------------------------
+  # create c++ packages
+  cpp:
+    name: cpp/${{matrix.config.os}}/${{matrix.config.gen}}
+    needs: gettag
+    runs-on: ${{matrix.config.os}}
+    env: {DEV: OFF, BT: Release, OS: "${{matrix.config.os}}", CXX_: "${{matrix.config.cxx}}", GEN: "${{matrix.config.gen}}"}
+    strategy:
+      fail-fast: false
+      matrix:
+        config:
+          #  name of the artifact    | suffix (gen)    | suffix (package)         | cpack gen | mime type                      | os              | cxx
+          # ubuntu 20.04 is disabled because of a problem installing libc++:i386:
+          #- {name: Ubuntu 20.04 deb  , sfxg: unix64-shared-Release.deb, sfxp: ubuntu-20.04.deb   , gen: DEB  , mime: vnd.debian.binary-package, os: ubuntu-20.04             }
+          - {name: Ubuntu 18.04 deb  , sfxg: unix64-shared-Release.deb, sfxp: ubuntu-18.04.deb   , gen: DEB  , mime: vnd.debian.binary-package, os: ubuntu-18.04             }
+          - {name: Windows VS2019 zip, sfxg: win64-shared-Release.zip , sfxp: windows-vs2019.zip , gen: ZIP  , mime: zip                      , os: windows-2019, cxx: vs2019}
+          - {name: MacOSX sh         , sfxg: apple64-shared-Release.sh, sfxp: macosx-xcode.sh    , gen: STGZ , mime: x-sh                     , os: macos-11.0  , cxx: xcode }
+    steps:
+      - {name: checkout, uses: actions/checkout@v2, with: {submodules: recursive}}
+      - name: Download vars.sh
+        uses: actions/download-artifact@v1
+        with: {name: vars.sh, path: ./}
+      - {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---------------------------------------------------
+        run: source .github/setenv.sh && c4_cfg_test shared64
+      - {name: shared64-build, run: source .github/setenv.sh && c4_build_target shared64}
+      - name: shared64-pack
+        run: source .github/setenv.sh && c4_package shared64 $GEN
+      - name: shared64-normalize
+        run: |
+          set -x
+          source vars.sh
+          mkdir -p assets
+          asset_src=`ls -1 ./build/shared64/${PROJ_PFX_TARGET}*-${{matrix.config.sfxg}}`
+          asset_dst=./assets/${PROJ_PKG_NAME}${SRC_VERSION}-${{matrix.config.sfxp}}
+          [ ! -f $asset_src ] && exit 1
+          cp -fav $asset_src $asset_dst
+      - name: Save artifacts
+        uses: actions/upload-artifact@v1
+        with: {name: assets, path: assets}
+
+  #----------------------------------------------------------------------------
+  release:
+    runs-on: ubuntu-latest
+    needs:
+      - src
+      - cpp
+    steps:
+      - {name: checkout, uses: actions/checkout@v2, with: {submodules: recursive}}
+      - name: Gather artifacts - ./assets
+        uses: actions/download-artifact@v2
+        with: {name: assets, path: assets}
+      - name: Verify existing artifacts
+        run: |
+          ls -lFhp assets/
+      #
+      # Github
+      - name: Restore vars.sh
+        if: contains(github.ref, 'tags/v')
+        uses: actions/download-artifact@v1
+        with: {name: vars.sh, path: ./}
+      - name: Save vars for following steps
+        if: contains(github.ref, 'tags/v')
+        id: vars
+        run: |
+          source vars.sh
+          version_body=${{github.workspace}}/changelog/$SRC_VERSION.md
+          if [ ! -f $version_body ] ; then
+            echo "version body file was not found: $version_body"
+            exit 1
+          fi
+          echo "::set-output name=VERSION::$SRC_VERSION"
+          echo "::set-output name=VERSION_BODY::$version_body"
+      - name: Create Github Release
+        if: contains(github.ref, 'tags/v')
+        id: create_release
+        uses: actions/create-release@v1
+        env: { GITHUB_TOKEN: "${{secrets.GITHUB_TOKEN}}" }
+        with:
+          tag_name: ${{github.ref}}
+          release_name: Release ${{steps.vars.outputs.VERSION}}
+          body_path: ${{steps.vars.outputs.VERSION_BODY}}
+          draft: true
+          prerelease: ${{contains(github.ref, 'rc')}}
+      - name: Upload assets to Github Release
+        if: contains(github.ref, 'tags/v')
+        uses: dwenegar/upload-release-assets@v1
+        env: { GITHUB_TOKEN: "${{secrets.GITHUB_TOKEN}}" }
+        with:
+          release_id: ${{steps.create_release.outputs.id}}
+          assets_path: ./assets/

+ 576 - 0
3rdparty/rapidyaml/ext/c4core/.github/workflows/test.yml

@@ -0,0 +1,576 @@
+name: test
+
+defaults:
+  #if: "!contains(github.event.head_commit.message, 'skip ci')"  # SKIP
+  run:
+    # Use a bash shell so we can use the same syntax for environment variable
+    # access regardless of the host operating system
+    shell: bash -e -x {0}
+
+on:
+  # https://github.community/t/how-to-trigger-an-action-on-push-or-pull-request-but-not-both/16662
+  workflow_dispatch:
+  push:
+    branches:
+    - master
+  pull_request:
+    branches:
+    - master
+
+env:
+  PROJ_PFX_TARGET: c4core-
+  PROJ_PFX_CMAKE: C4CORE_
+  CMAKE_FLAGS:
+  NUM_JOBS_BUILD: # 4
+
+
+# ubuntu-20.04:
+#   # https://github.com/actions/virtual-environments/blob/main/images/linux/Ubuntu2004-README.md
+#   gcc: 7.5.0, 8.4.0, 9.3.0, 10.2.0
+#   clang: 8.0.1, 9.0.1, 10.0.0
+# ubuntu-18.04:
+#   # https://github.com/actions/virtual-environments/blob/main/images/linux/Ubuntu1804-README.md
+#   gcc: 7.5.0, 8.4.0, 9.3.0, 10.1.0
+#   clang: 6.0.0, 8.0.0, 9.0.0
+# macos-11.0: macOS Big Sur 11.0
+#   # https://github.com/actions/virtual-environments/blob/main/images/macos/macos-11.0-Readme.md
+#   Xcode 12.1 11.7
+#   clang/LLVM 10.0.1
+#   gcc-8 gcc-9
+# macos-10.15: macOS Catalina 10.15
+#   # https://github.com/actions/virtual-environments/blob/main/images/macos/macos-10.15-Readme.md
+#   Xcode 12.1 11.7
+#   clang/LLVM 11.0.0
+#   gcc-8 gcc-9
+# windows-2019:
+#   # https://github.com/actions/virtual-environments/blob/main/images/win/Windows2019-Readme.md
+#   vs2019
+# windows-2016:
+#   # https://github.com/actions/virtual-environments/blob/main/images/win/Windows2016-Readme.md
+#   vs2017
+
+jobs:
+
+  #----------------------------------------------------------------------------
+  coverage:
+    name: coverage/c++${{matrix.std}}
+    # 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++-7, cc: gcc-7, bt: Coverage, os: ubuntu-18.04}
+          - {std: 14, cxx: g++-7, cc: gcc-7, bt: Coverage, os: ubuntu-18.04}
+          - {std: 17, cxx: g++-7, cc: gcc-7, bt: Coverage, os: ubuntu-18.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}}"}
+    steps:
+      - {name: checkout, uses: actions/checkout@v2, 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: 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-submit
+        run: |
+          source .github/setenv.sh
+          c4_submit_coverage shared64 codecov
+          #c4_submit_coverage shared64 coveralls  # only accepts one submission per job
+      - 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-submit
+        run: |
+          source .github/setenv.sh
+          c4_submit_coverage static64 codecov
+          c4_submit_coverage static64 coveralls
+      - 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-submit
+        run: |
+          source .github/setenv.sh
+          c4_submit_coverage static32 codecov
+          #c4_submit_coverage static32 coveralls  # only accepts one submission per job
+      - 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-submit
+        run: |
+          source .github/setenv.sh
+          c4_submit_coverage shared32 codecov
+          #c4_submit_coverage shared32 coveralls  # only accepts one submission per job
+      - 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-submit
+        run: |
+          source .github/setenv.sh
+          c4_submit_coverage static32 codecov
+          #c4_submit_coverage static32 coveralls  # only accepts one submission per job
+
+  #----------------------------------------------------------------------------
+  coverage_nofastfloat:
+    name: coverage/c++${{matrix.std}}/nofastfloat
+    # 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++-7, cc: gcc-7, bt: Coverage, os: ubuntu-18.04}
+          - {std: 14, cxx: g++-7, cc: gcc-7, bt: Coverage, os: ubuntu-18.04}
+          - {std: 17, cxx: g++-7, cc: gcc-7, bt: Coverage, os: ubuntu-18.04}
+    env: {
+      STD: "${{matrix.std}}",
+      CXX_: "${{matrix.cxx}}",
+      BT: "${{matrix.bt}}",
+      OS: "${{matrix.os}}",
+      CODECOV_TOKEN: "${{secrets.CODECOV_TOKEN}}",
+      COVERALLS_REPO_TOKEN: "${{secrets.COVERALLS_REPO_TOKEN}}",
+      BDIR:   "build/nofastfloat-${{matrix.cxx}}-cxx${{matrix.std}}",
+      IDIR: "install/nofastfloat-${{matrix.cxx}}-cxx${{matrix.std}}",
+    }
+    steps:
+      - {name: checkout, uses: actions/checkout@v2, 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: nofastfloat-configure------------------------------------------------
+        run: |
+          set -x
+          mkdir -p $BDIR
+          mkdir -p $IDIR
+          cmake -S . -B $BDIR \
+            -DC4CORE_WITH_FASTFLOAT=OFF \
+            -DC4_CXX_STANDARD=${{matrix.std}} \
+            -DC4CORE_CXX_STANDARD=${{matrix.std}} \
+            -DC4CORE_BUILD_TESTS=ON \
+            -DC4CORE_VALGRIND=OFF \
+            -DC4CORE_COVERAGE_CODECOV=ON \
+            -DC4CORE_COVERAGE_CODECOV_SILENT=ON \
+            -DC4CORE_COVERAGE_COVERALLS=ON \
+            -DC4CORE_COVERAGE_COVERALLS_SILENT=ON \
+            -DCMAKE_INSTALL_PREFIX=$IDIR \
+            -DCMAKE_BUILD_TYPE=Coverage \
+            -DCMAKE_CXX_COMPILER=${{matrix.cxx}} \
+            -DCMAKE_C_COMPILER=${{matrix.cc}}
+      - name: nofastfloat-build
+        run: |
+          cmake --build $BDIR --config Coverage --target c4core-test-build -j
+      - name: nofastfloat-run
+        run: |
+          cmake --build $BDIR --config Coverage --target c4core-test-run
+      - name: nofastfloat-submit
+        run: |
+          cmake --build $BDIR --config Coverage --target c4core-coverage-submit-codecov
+          #cmake --build $BDIR --config Coverage --target c4core-coverage-submit-coveralls
+
+
+  #----------------------------------------------------------------------------
+  windows:
+    name: win/${{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: ${{matrix.os}}
+    strategy:
+      fail-fast: false
+      matrix:
+        include:
+          - {std: 11, cxx: vs2017, bt: Debug  , os: windows-2016, bitlinks: shared64 static32}
+          - {std: 11, cxx: vs2017, bt: Release, os: windows-2016, bitlinks: shared64 static32}
+          - {std: 14, cxx: vs2017, bt: Debug  , os: windows-2016, bitlinks: shared64 static32}
+          - {std: 14, cxx: vs2017, bt: Release, os: windows-2016, bitlinks: shared64 static32}
+          - {std: 11, cxx: vs2019, bt: Debug  , os: windows-2019, bitlinks: shared64 static32}
+          - {std: 11, cxx: vs2019, bt: Release, os: windows-2019, bitlinks: shared64 static32}
+          - {std: 14, cxx: vs2019, bt: Debug  , os: windows-2019, bitlinks: shared64 static32}
+          - {std: 14, cxx: vs2019, bt: Release, os: windows-2019, bitlinks: shared64 static32}
+          - {std: 17, cxx: vs2019, bt: Debug  , os: windows-2019, bitlinks: shared64 static32}
+          - {std: 17, cxx: vs2019, bt: Release, os: windows-2019, bitlinks: shared64 static32}
+          - {std: 20, cxx: vs2019, bt: Debug  , os: windows-2019, bitlinks: shared64 static32}
+          - {std: 20, cxx: vs2019, bt: Release, os: windows-2019, 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@v2, 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: 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: 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}
+      - 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}
+
+  #----------------------------------------------------------------------------
+  gcc_canary:
+    name: gcc_canary/${{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: ${{matrix.os}}
+    strategy:
+      fail-fast: false
+      matrix:
+        include:
+          - {std: 11, cxx: g++-7      , bt: Debug  , os: ubuntu-18.04, bitlinks: shared64 static32}
+          - {std: 11, cxx: g++-7      , bt: Release, os: ubuntu-18.04, bitlinks: shared64 static32}
+          - {std: 20, cxx: g++-10     , bt: Debug  , os: ubuntu-18.04, bitlinks: shared64 static32}
+          - {std: 20, cxx: g++-10     , bt: Release, os: ubuntu-18.04, bitlinks: shared64 static32}
+          - {std: 11, cxx: g++-5      , bt: Debug  , os: ubuntu-18.04, bitlinks: shared64 static32}
+          - {std: 11, cxx: g++-5      , bt: Release, os: ubuntu-18.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}
+    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@v2, 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: 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_canary:
+    name: clang_canary/${{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: ${{matrix.os}}
+    strategy:
+      fail-fast: false
+      matrix:
+        include:
+          - {std: 20, cxx: clang++-10 , bt: Debug  , os: ubuntu-18.04, bitlinks: shared64 static32}
+          - {std: 20, cxx: clang++-10 , bt: Release, os: ubuntu-18.04, bitlinks: shared64 static32}
+          - {std: 11, cxx: clang++-6.0, bt: Debug  , os: ubuntu-18.04, bitlinks: shared64 static32}
+          - {std: 11, cxx: clang++-6.0, bt: Release, os: ubuntu-18.04, 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@v2, 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: 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_tidy:
+    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: ${{matrix.os}}
+    strategy:
+      fail-fast: false
+      matrix:
+        include:
+          # clang tidy takes a long time, so don't do multiple bits/linktypes
+          - {std: 11, cxx: clang++-9, bt: Debug             , lint: clang-tidy, bitlinks: shared64, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-9, bt: Debug             , lint: clang-tidy, bitlinks: shared32, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-9, bt: Debug             , lint: clang-tidy, bitlinks: static64, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-9, bt: Debug             , lint: clang-tidy, bitlinks: static32, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-9, bt: ReleaseWithDebInfo, lint: clang-tidy, bitlinks: shared64, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-9, bt: ReleaseWithDebInfo, lint: clang-tidy, bitlinks: shared32, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-9, bt: ReleaseWithDebInfo, lint: clang-tidy, bitlinks: static64, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-9, bt: ReleaseWithDebInfo, lint: clang-tidy, bitlinks: static32, os: ubuntu-18.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@v2, 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: 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_extended:
+    name: gcc_extended/${{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: ${{matrix.os}}
+    strategy:
+      fail-fast: false
+      matrix:
+        include:
+          #                                     VALGRIND
+          - {std: 11, cxx: g++-10, bt: Debug  , vg: ON, os: ubuntu-18.04}
+          - {std: 11, cxx: g++-10, bt: Release, vg: ON, os: ubuntu-18.04}
+          - {std: 14, cxx: g++-10, bt: Debug  , vg: ON, os: ubuntu-18.04}
+          - {std: 14, cxx: g++-10, bt: Release, vg: ON, os: ubuntu-18.04}
+          - {std: 17, cxx: g++-10, bt: Debug  , vg: ON, os: ubuntu-18.04}
+          - {std: 17, cxx: g++-10, bt: Release, vg: ON, os: ubuntu-18.04}
+          - {std: 20, cxx: g++-10, bt: Debug  , vg: ON, os: ubuntu-18.04}
+          - {std: 20, cxx: g++-10, bt: Release, vg: ON, os: ubuntu-18.04}
+          #
+          - {std: 11, cxx: g++-9, bt: Debug  , os: ubuntu-18.04}
+          - {std: 11, cxx: g++-9, bt: Release, os: ubuntu-18.04}
+          - {std: 11, cxx: g++-8, bt: Debug  , os: ubuntu-18.04}
+          - {std: 11, cxx: g++-8, bt: Release, os: ubuntu-18.04}
+          - {std: 11, cxx: g++-7, bt: Debug  , os: ubuntu-18.04}
+          - {std: 11, cxx: g++-7, bt: Release, os: ubuntu-18.04}
+          - {std: 11, cxx: g++-6, bt: Debug  , os: ubuntu-18.04}
+          - {std: 11, cxx: g++-6, bt: Release, os: ubuntu-18.04}
+          - {std: 11, cxx: g++-5, bt: Debug  , os: ubuntu-18.04}
+          - {std: 11, cxx: g++-5, bt: Release, os: ubuntu-18.04}
+          - {std: 11, cxx: g++-4.8, bt: Debug, os: ubuntu-18.04}
+          - {std: 11, cxx: g++-4.8, bt: Release, os: ubuntu-18.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@v2, 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: 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_extended:
+    name: clang_extended/${{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: ${{matrix.os}}
+    strategy:
+      fail-fast: false
+      matrix:
+        include:
+          - {std: 20, cxx: clang++-10 , bt: Debug  , vg: on, os: ubuntu-18.04}
+          - {std: 20, cxx: clang++-10 , bt: Release, vg: on, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-9  , bt: Debug  , vg: on, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-9  , bt: Release, vg: on, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-8  , bt: Debug  , vg: on, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-8  , bt: Release, vg: on, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-7  , bt: Debug  , vg: on, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-7  , bt: Release, vg: on, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-6.0, bt: Debug  , vg: on, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-6.0, bt: Release, vg: on, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-5.0, bt: Debug  , vg: on, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-5.0, bt: Release, vg: on, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-4.0, bt: Debug  , vg: on, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-4.0, bt: Release, vg: on, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-3.9, bt: Debug  , vg: on, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-3.9, bt: Release, vg: on, os: ubuntu-18.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@v2, 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: 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_sanitize:
+    name: clang_sanitize/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: ${{matrix.os}}
+    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 static64, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-10 , bt: Debug  , vg: ON, san: ALL, bitlinks: shared32 static32, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-10 , bt: Release, vg: ON, san: ALL, bitlinks: shared64 static64, os: ubuntu-18.04}
+          - {std: 11, cxx: clang++-10 , bt: Release, vg: ON, san: ALL, bitlinks: shared32 static32, os: ubuntu-18.04}
+          - {std: 14, cxx: clang++-10 , bt: Debug  , vg: ON, san: ALL, bitlinks: shared64 static64, os: ubuntu-18.04}
+          - {std: 14, cxx: clang++-10 , bt: Debug  , vg: ON, san: ALL, bitlinks: shared32 static32, os: ubuntu-18.04}
+          - {std: 14, cxx: clang++-10 , bt: Release, vg: ON, san: ALL, bitlinks: shared64 static64, os: ubuntu-18.04}
+          - {std: 14, cxx: clang++-10 , bt: Release, vg: ON, san: ALL, bitlinks: shared32 static32, os: ubuntu-18.04}
+          - {std: 17, cxx: clang++-10 , bt: Debug  , vg: ON, san: ALL, bitlinks: shared64 static64, os: ubuntu-18.04}
+          - {std: 17, cxx: clang++-10 , bt: Debug  , vg: ON, san: ALL, bitlinks: shared32 static32, os: ubuntu-18.04}
+          - {std: 17, cxx: clang++-10 , bt: Release, vg: ON, san: ALL, bitlinks: shared64 static64, os: ubuntu-18.04}
+          - {std: 17, cxx: clang++-10 , bt: Release, vg: ON, san: ALL, bitlinks: shared32 static32, os: ubuntu-18.04}
+          - {std: 20, cxx: clang++-10 , bt: Debug  , vg: ON, san: ALL, bitlinks: shared64 static64, os: ubuntu-18.04}
+          - {std: 20, cxx: clang++-10 , bt: Debug  , vg: ON, san: ALL, bitlinks: shared32 static32, os: ubuntu-18.04}
+          - {std: 20, cxx: clang++-10 , bt: Release, vg: ON, san: ALL, bitlinks: shared64 static64, os: ubuntu-18.04}
+          - {std: 20, cxx: clang++-10 , bt: Release, vg: ON, san: ALL, bitlinks: shared32 static32, os: ubuntu-18.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@v2, 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: 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: ${{matrix.os}}
+    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: cmake/Toolchain-Arm-ubuntu.cmake, cxx: arm-linux-gnueabihf-gcc, os: ubuntu-18.04}
+          - {std: 11, bt: Release, toolchain: cmake/Toolchain-Arm-ubuntu.cmake, cxx: arm-linux-gnueabihf-gcc, os: ubuntu-18.04}
+          - {std: 14, bt: Debug  , toolchain: cmake/Toolchain-Arm-ubuntu.cmake, cxx: arm-linux-gnueabihf-gcc, os: ubuntu-18.04}
+          - {std: 14, bt: Release, toolchain: cmake/Toolchain-Arm-ubuntu.cmake, cxx: arm-linux-gnueabihf-gcc, os: ubuntu-18.04}
+          - {std: 17, bt: Debug  , toolchain: cmake/Toolchain-Arm-ubuntu.cmake, cxx: arm-linux-gnueabihf-gcc, os: ubuntu-18.04}
+          - {std: 17, bt: Release, toolchain: cmake/Toolchain-Arm-ubuntu.cmake, cxx: arm-linux-gnueabihf-gcc, os: ubuntu-18.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@v2, 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: 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}
+
+  #----------------------------------------------------------------------------
+#  # https://blog.kitware.com/static-checks-with-cmake-cdash-iwyu-clang-tidy-lwyu-cpplint-and-cppcheck/
+#  static_analysis:
+#    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:
+#          # these jobs take much longer, so run only one bitlink pair per job to profit from parallelism
+#          - {std: 11, cxx: clang++-10, bt: Debug  , bitlinks: shared64, os: ubuntu-18.04}
+#          - {std: 11, cxx: clang++-10, bt: Release, bitlinks: shared64, os: ubuntu-18.04}
+#          - {std: 14, cxx: clang++-10, bt: Debug  , bitlinks: shared64, os: ubuntu-18.04}
+#          - {std: 14, cxx: clang++-10, bt: Release, bitlinks: shared64, os: ubuntu-18.04}
+#          - {std: 17, cxx: clang++-10, bt: Debug  , bitlinks: shared64, os: ubuntu-18.04}
+#          - {std: 17, cxx: clang++-10, bt: Release, bitlinks: shared64, os: ubuntu-18.04}
+#          - {std: 20, cxx: clang++-10, bt: Debug  , bitlinks: shared64, os: ubuntu-18.04}
+#          - {std: 20, cxx: clang++-10, bt: Release, bitlinks: shared64, os: ubuntu-18.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@v2, 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: shared64-configure---------------------------------------------------
+#        run: source .github/setenv.sh && c4_cfg_test shared64
+#      - {name: shared64-build, run: source .github/setenv.sh && c4_build_test shared64}
+#      - {name: clang-tidy, run: cmake "-DCMAKE_CXX_CLANG_TIDY=/usr/bin/clang-tidy-3.9;-checks=*" ../path/to/source}
+#      - {name: cppcheck, run: cmake "-DCMAKE_CXX_CPPCHECK=/usr/bin/cppcheck;--std=c++11" ../path/to/source}
+#      - {name: cpplint, run: cmake "-DCMAKE_CXX_CPPLINT=/usr/local/bin/cpplint;--linelength=179" ..}
+#      - {name: include-what-you-use, run: cmake "-DCMAKE_CXX_INCLUDE_WHAT_YOU_USE=/usr/bin/iwyu;--transitive_includes_only" ..}
+#      - {name: link-what-you-use, run: cmake -DCMAKE_LINK_WHAT_YOU_USE=TRUE ..}

+ 104 - 0
3rdparty/rapidyaml/ext/c4core/.github/workflows/test_install.yml

@@ -0,0 +1,104 @@
+name: test_install
+
+defaults:
+  #if: "!contains(github.event.head_commit.message, 'skip ci')"  # SKIP
+  run:
+    # Use a bash shell so we can use the same syntax for environment variable
+    # access regardless of the host operating system
+    shell: bash -e -x {0}
+
+on:
+  # https://github.community/t/how-to-trigger-an-action-on-push-or-pull-request-but-not-both/16662
+  workflow_dispatch:
+  push:
+    branches:
+    - master
+  pull_request:
+    branches:
+    - master
+
+env:
+  PROJ_PFX_TARGET: c4core-
+  PROJ_PFX_CMAKE: C4CORE_
+  CMAKE_FLAGS:
+  NUM_JOBS_BUILD: # 4
+
+jobs:
+
+  #----------------------------------------------------------------------------
+  install_tests:
+    name: ${{matrix.name}}/${{matrix.bt}}
+    # 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:
+          - {name: find_package/linux       , sdir: test/test_install     , os: ubuntu-18.04, cxx: g++-10   , gen: "-DCMAKE_CXX_COMPILER=g++-10"              , tgt: all      , bt: Release, vars: "-Dc4core_DIR=$GITHUB_WORKSPACE/$PDIR/lib/cmake/c4core -DC4CORE_TEST_INSTALL_PACKAGE_MODE=ON", commonvars: }
+          - {name: find_package/linux       , sdir: test/test_install     , os: ubuntu-18.04, cxx: g++-10   , gen: "-DCMAKE_CXX_COMPILER=g++-10"              , tgt: all      , bt: Debug  , vars: "-Dc4core_DIR=$GITHUB_WORKSPACE/$PDIR/lib/cmake/c4core -DC4CORE_TEST_INSTALL_PACKAGE_MODE=ON", commonvars: }
+          - {name: find_package/linux/libcxx, sdir: test/test_install     , os: ubuntu-18.04, cxx: clang++-9, gen: "-DCMAKE_CXX_COMPILER=clang++-9"           , tgt: all      , bt: Release, vars: "-Dc4core_DIR=$GITHUB_WORKSPACE/$PDIR/lib/cmake/c4core -DC4CORE_TEST_INSTALL_PACKAGE_MODE=ON", commonvars: "-DC4CORE_USE_LIBCXX=ON"}
+          - {name: find_package/linux/libcxx, sdir: test/test_install     , os: ubuntu-18.04, cxx: clang++-9, gen: "-DCMAKE_CXX_COMPILER=clang++-9"           , tgt: all      , bt: Debug  , vars: "-Dc4core_DIR=$GITHUB_WORKSPACE/$PDIR/lib/cmake/c4core -DC4CORE_TEST_INSTALL_PACKAGE_MODE=ON", commonvars: "-DC4CORE_USE_LIBCXX=ON"}
+          - {name: find_package/macos       , sdir: test/test_install     , os: macos-11.0  , cxx: xcode    , gen: "-G Xcode -DCMAKE_OSX_ARCHITECTURES=x86_64", tgt: ALL_BUILD, bt: Release, vars: "-Dc4core_DIR=$GITHUB_WORKSPACE/$PDIR/lib/cmake/c4core -DC4CORE_TEST_INSTALL_PACKAGE_MODE=ON", commonvars: }
+          - {name: find_package/macos       , sdir: test/test_install     , os: macos-11.0  , cxx: xcode    , gen: "-G Xcode -DCMAKE_OSX_ARCHITECTURES=x86_64", tgt: ALL_BUILD, bt: Debug  , vars: "-Dc4core_DIR=$GITHUB_WORKSPACE/$PDIR/lib/cmake/c4core -DC4CORE_TEST_INSTALL_PACKAGE_MODE=ON", commonvars: }
+          - {name: find_package/win         , sdir: test/test_install     , os: windows-2019, cxx: vs2019   , gen: "-G 'Visual Studio 16 2019' -A x64"        , tgt: ALL_BUILD, bt: Release, vars: "-Dc4core_DIR=$GITHUB_WORKSPACE/$PDIR/cmake            -DC4CORE_TEST_INSTALL_PACKAGE_MODE=ON", commonvars: }
+          - {name: find_package/win         , sdir: test/test_install     , os: windows-2019, cxx: vs2019   , gen: "-G 'Visual Studio 16 2019' -A x64"        , tgt: ALL_BUILD, bt: Debug  , vars: "-Dc4core_DIR=$GITHUB_WORKSPACE/$PDIR/cmake            -DC4CORE_TEST_INSTALL_PACKAGE_MODE=ON", commonvars: }
+          #
+          - {name: find_library/linux       , sdir: test/test_install     , os: ubuntu-18.04, cxx: g++-10   , gen: "-DCMAKE_CXX_COMPILER=g++-10"              , tgt: all      , bt: Release, vars: "-DCMAKE_PREFIX_PATH=$GITHUB_WORKSPACE/$PDIR           -DC4CORE_TEST_INSTALL_PACKAGE_MODE=OFF", commonvars: }
+          - {name: find_library/linux       , sdir: test/test_install     , os: ubuntu-18.04, cxx: g++-10   , gen: "-DCMAKE_CXX_COMPILER=g++-10"              , tgt: all      , bt: Debug  , vars: "-DCMAKE_PREFIX_PATH=$GITHUB_WORKSPACE/$PDIR           -DC4CORE_TEST_INSTALL_PACKAGE_MODE=OFF", commonvars: }
+          - {name: find_library/linux/libcxx, sdir: test/test_install     , os: ubuntu-18.04, cxx: clang++-9, gen: "-DCMAKE_CXX_COMPILER=clang++-9"           , tgt: all      , bt: Release, vars: "-DCMAKE_PREFIX_PATH=$GITHUB_WORKSPACE/$PDIR           -DC4CORE_TEST_INSTALL_PACKAGE_MODE=OFF", commonvars: "-DC4CORE_USE_LIBCXX=ON"}
+          - {name: find_library/linux/libcxx, sdir: test/test_install     , os: ubuntu-18.04, cxx: clang++-9, gen: "-DCMAKE_CXX_COMPILER=clang++-9"           , tgt: all      , bt: Debug  , vars: "-DCMAKE_PREFIX_PATH=$GITHUB_WORKSPACE/$PDIR           -DC4CORE_TEST_INSTALL_PACKAGE_MODE=OFF", commonvars: "-DC4CORE_USE_LIBCXX=ON"}
+          - {name: find_library/macos       , sdir: test/test_install     , os: macos-11.0  , cxx: xcode    , gen: "-G Xcode -DCMAKE_OSX_ARCHITECTURES=x86_64", tgt: ALL_BUILD, bt: Release, vars: "-DCMAKE_PREFIX_PATH=$GITHUB_WORKSPACE/$PDIR           -DC4CORE_TEST_INSTALL_PACKAGE_MODE=OFF", commonvars: }
+          - {name: find_library/macos       , sdir: test/test_install     , os: macos-11.0  , cxx: xcode    , gen: "-G Xcode -DCMAKE_OSX_ARCHITECTURES=x86_64", tgt: ALL_BUILD, bt: Debug  , vars: "-DCMAKE_PREFIX_PATH=$GITHUB_WORKSPACE/$PDIR           -DC4CORE_TEST_INSTALL_PACKAGE_MODE=OFF", commonvars: }
+          - {name: find_library/win         , sdir: test/test_install     , os: windows-2019, cxx: vs2019   , gen: "-G 'Visual Studio 16 2019' -A x64"        , tgt: ALL_BUILD, bt: Release, vars: "-DCMAKE_PREFIX_PATH=$GITHUB_WORKSPACE/$PDIR           -DC4CORE_TEST_INSTALL_PACKAGE_MODE=OFF", commonvars: }
+          - {name: find_library/win         , sdir: test/test_install     , os: windows-2019, cxx: vs2019   , gen: "-G 'Visual Studio 16 2019' -A x64"        , tgt: ALL_BUILD, bt: Debug  , vars: "-DCMAKE_PREFIX_PATH=$GITHUB_WORKSPACE/$PDIR           -DC4CORE_TEST_INSTALL_PACKAGE_MODE=OFF", commonvars: }
+          #
+          - {name: singleheader/linux       , sdir: test/test_singleheader, os: ubuntu-18.04, cxx: g++-10   , gen: "-DCMAKE_CXX_COMPILER=g++-10"              , tgt: all      , bt: Release, vars: , commonvars: }
+          - {name: singleheader/linux       , sdir: test/test_singleheader, os: ubuntu-18.04, cxx: g++-10   , gen: "-DCMAKE_CXX_COMPILER=g++-10"              , tgt: all      , bt: Debug  , vars: , commonvars: }
+          - {name: singleheader/linux/libcxx, sdir: test/test_singleheader, os: ubuntu-18.04, cxx: clang++-9, gen: "-DCMAKE_CXX_COMPILER=clang++-9"           , tgt: all      , bt: Release, vars: , commonvars: "-DC4CORE_USE_LIBCXX=ON"}
+          - {name: singleheader/linux/libcxx, sdir: test/test_singleheader, os: ubuntu-18.04, cxx: clang++-9, gen: "-DCMAKE_CXX_COMPILER=clang++-9"           , tgt: all      , bt: Debug  , vars: , commonvars: "-DC4CORE_USE_LIBCXX=ON"}
+          - {name: singleheader/macos       , sdir: test/test_singleheader, os: macos-11.0  , cxx: xcode    , gen: "-G Xcode -DCMAKE_OSX_ARCHITECTURES=x86_64", tgt: ALL_BUILD, bt: Release, vars: , commonvars: }
+          - {name: singleheader/macos       , sdir: test/test_singleheader, os: macos-11.0  , cxx: xcode    , gen: "-G Xcode -DCMAKE_OSX_ARCHITECTURES=x86_64", tgt: ALL_BUILD, bt: Debug  , vars: , commonvars: }
+          - {name: singleheader/win         , sdir: test/test_singleheader, os: windows-2019, cxx: vs2019   , gen: "-G 'Visual Studio 16 2019' -A x64"        , tgt: ALL_BUILD, bt: Release, vars: , commonvars: }
+          - {name: singleheader/win         , sdir: test/test_singleheader, os: windows-2019, cxx: vs2019   , gen: "-G 'Visual Studio 16 2019' -A x64"        , tgt: ALL_BUILD, bt: Debug  , vars: , commonvars: }
+    env:
+      CXX_: "${{matrix.cxx}}"
+      BT: "${{matrix.bt}}"
+      OS: "${{matrix.os}}"
+      BDIR:   "build/${{matrix.name}}-${{matrix.bt}}"
+      IDIR: "install/${{matrix.name}}-${{matrix.bt}}"
+      PDIR:  "prefix/${{matrix.name}}-${{matrix.bt}}"
+    steps:
+      - {name: checkout, uses: actions/checkout@v2, 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
+        uses: actions/setup-python@v2
+        with: { python-version: 3.9 }
+      - name: preinstall
+        run: |
+          if [ "${{matrix.sdir}}" == "test/test_install" ] ; then
+            mkdir -p $BDIR-staging
+            cmake -S . -B $BDIR-staging -DCMAKE_INSTALL_PREFIX=$PDIR -DCMAKE_BUILD_TYPE=${{matrix.bt}} ${{matrix.gen}} ${{matrix.commonvars}}
+            cmake --build $BDIR-staging --config ${{matrix.bt}} --target ${{matrix.tgt}} -j
+            cmake --build $BDIR-staging --config ${{matrix.bt}} --target install
+          fi
+      - name: configure
+        run: |
+          mkdir -p $BDIR
+          mkdir -p $IDIR
+          cmake -S ${{matrix.sdir}} -B $BDIR \
+            -DC4CORE_BUILD_TESTS=ON \
+            -DC4CORE_VALGRIND=OFF \
+            -DCMAKE_BUILD_TYPE=${{matrix.bt}} \
+            -DCMAKE_INSTALL_PREFIX=$IDIR \
+            ${{matrix.gen}} \
+            ${{matrix.vars}} \
+            ${{matrix.commonvars}}
+      - name: build
+        run: |
+          cmake --build $BDIR --config ${{matrix.bt}} --target c4core-test-build -j
+      - name: run
+        run: |
+          cmake --build $BDIR --config ${{matrix.bt}} --target c4core-test-run

+ 34 - 0
3rdparty/rapidyaml/ext/c4core/.gitignore

@@ -0,0 +1,34 @@
+# text editor files
+*.bck
+\#*
+*~
+.ccls-cache/
+.clangd/
+.cache/
+.cquery_cached_index/
+__pycache__/
+
+# Visual Studio files
+.vs/
+.vscode/
+# QtCreator files
+CMakeLists.txt.user
+# Eclipse
+.project
+.cproject
+/.settings/
+
+# build files
+build/
+install/
+.python-version
+compile_commands.json
+
+# test files
+/Testing/
+
+# continuous integration files
+.github/vagrant/*.log
+.github/vagrant/.vagrant
+.github/vagrant/macos/.vagrant
+src_singleheader/

+ 9 - 0
3rdparty/rapidyaml/ext/c4core/.gitmodules

@@ -0,0 +1,9 @@
+[submodule "cmake"]
+	path = cmake
+	url = https://github.com/biojppm/cmake
+[submodule "extern/debugbreak"]
+	path = src/c4/ext/debugbreak
+	url = https://github.com/biojppm/debugbreak
+[submodule "src/c4/ext/fast_float"]
+	path = src/c4/ext/fast_float
+	url = https://github.com/fastfloat/fast_float

+ 107 - 0
3rdparty/rapidyaml/ext/c4core/CMakeLists.txt

@@ -0,0 +1,107 @@
+cmake_minimum_required(VERSION 3.13 FATAL_ERROR)
+include(./cmake/c4Project.cmake)
+project(c4core
+    DESCRIPTION "Multiplatform low-level C++ utilities"
+    HOMEPAGE_URL "https://github.com/biojppm/c4core"
+    LANGUAGES CXX)
+include(./compat.cmake)
+
+c4_project(VERSION 0.1.8
+    AUTHOR "Joao Paulo Magalhaes <dev@jpmag.me>")
+
+option(C4CORE_WITH_FASTFLOAT "use fastfloat to parse floats" ON)
+
+set(C4CORE_SRC_FILES
+    c4/allocator.hpp
+    c4/base64.hpp
+    c4/base64.cpp
+    c4/blob.hpp
+    c4/bitmask.hpp
+    c4/charconv.hpp
+    c4/c4_pop.hpp
+    c4/c4_push.hpp
+    c4/char_traits.cpp
+    c4/char_traits.hpp
+    c4/common.hpp
+    c4/compiler.hpp
+    c4/config.hpp
+    c4/cpu.hpp
+    c4/ctor_dtor.hpp
+    c4/dump.hpp
+    c4/enum.hpp
+    c4/error.cpp
+    c4/error.hpp
+    c4/export.hpp
+    c4/format.hpp
+    c4/format.cpp
+    c4/hash.hpp
+    c4/language.hpp
+    c4/language.cpp
+    c4/memory_resource.cpp
+    c4/memory_resource.hpp
+    c4/memory_util.cpp
+    c4/memory_util.hpp
+    c4/platform.hpp
+    c4/preprocessor.hpp
+    c4/restrict.hpp
+    c4/span.hpp
+    c4/std/std.hpp
+    c4/std/std_fwd.hpp
+    c4/std/string.hpp
+    c4/std/string_fwd.hpp
+    c4/std/tuple.hpp
+    c4/std/vector.hpp
+    c4/std/vector_fwd.hpp
+    c4/substr.hpp
+    c4/substr_fwd.hpp
+    c4/szconv.hpp
+    c4/type_name.hpp
+    c4/types.hpp
+    c4/unrestrict.hpp
+    c4/utf.hpp
+    c4/utf.cpp
+    c4/windows.hpp
+    c4/windows_pop.hpp
+    c4/windows_push.hpp
+    c4/c4core.natvis
+    #
+    c4/ext/debugbreak/debugbreak.h
+    c4/ext/rng/rng.hpp
+    c4/ext/sg14/inplace_function.h
+    )
+if(C4CORE_WITH_FASTFLOAT)
+    list(APPEND C4CORE_SRC_FILES
+        c4/ext/fast_float.hpp
+        c4/ext/fast_float_all.h
+        )
+endif()
+set(C4CORE_AMALGAMATED ${C4CORE_SRC_DIR}/../src_singleheader/c4/c4core_all.hpp)
+list(TRANSFORM C4CORE_SRC_FILES PREPEND "${C4CORE_SRC_DIR}/" OUTPUT_VARIABLE C4CORE_SRC_FILES_FULL)
+
+add_custom_target(c4core-amalgamate
+    python ${CMAKE_CURRENT_LIST_DIR}/tools/amalgamate.py ${C4CORE_AMALGAMATED}
+    COMMENT "${CMAKE_CURRENT_LIST_DIR}/tools/amalgamate.py ${C4CORE_AMALGAMATED}"
+    BYPRODUCTS ${C4CORE_AMALGAMATED}
+    DEPENDS ${C4CORE_SRC_FILES_FULL}
+    )
+
+c4_add_library(c4core
+    INC_DIRS
+       $<BUILD_INTERFACE:${C4CORE_SRC_DIR}> $<INSTALL_INTERFACE:include>
+    SOURCE_ROOT ${C4CORE_SRC_DIR}
+    SOURCES ${C4CORE_SRC_FILES}
+)
+
+if(NOT C4CORE_WITH_FASTFLOAT)
+    target_compile_definitions(c4core PUBLIC -DC4CORE_NO_FAST_FLOAT)
+endif()
+
+
+#-------------------------------------------------------
+
+c4_install_target(c4core)
+c4_install_exports()
+
+c4_add_dev_targets()
+
+c4_pack_project(TYPE LIBRARY)

+ 26 - 0
3rdparty/rapidyaml/ext/c4core/LICENSE-BOOST.txt

@@ -0,0 +1,26 @@
+src/c4/ext/sg14/inplace_function.h is distributed under the following terms:
+----------------------------------------------------------------------------
+
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.

+ 20 - 0
3rdparty/rapidyaml/ext/c4core/LICENSE.txt

@@ -0,0 +1,20 @@
+Copyright (c) 2018, Joao Paulo Magalhaes <dev@jpmag.me>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+

+ 232 - 0
3rdparty/rapidyaml/ext/c4core/README.md

@@ -0,0 +1,232 @@
+# c4core - C++ core utilities
+
+[![MIT Licensed](https://img.shields.io/badge/License-MIT-green.svg)](https://github.com/biojppm/c4core/blob/master/LICENSE.txt)
+[![Docs](https://img.shields.io/badge/docs-docsforge-blue)](https://c4core.docsforge.com/)
+[![ci](https://github.com/biojppm/c4core/workflows/ci/badge.svg)](https://github.com/biojppm/c4core/actions?query=ci)
+[![Coveralls](https://coveralls.io/repos/github/biojppm/c4core/badge.svg)](https://coveralls.io/github/biojppm/c4core)
+[![Codecov](https://codecov.io/gh/biojppm/c4core/branch/master/graph/badge.svg)](https://codecov.io/gh/biojppm/c4core)
+[![LGTM alerts](https://img.shields.io/lgtm/alerts/g/biojppm/c4core.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/biojppm/c4core/alerts/)
+[![LGTM grade: C/C++](https://img.shields.io/lgtm/grade/cpp/g/biojppm/c4core.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/biojppm/c4core/context:cpp)
+
+
+c4core is a library of low-level C++ utilities, written with low-latency
+projects in mind.
+
+Some of the utilities provided by c4core have already equivalent
+functionality in the C++ standard, but they are provided as the existing C++
+equivalent may be insufficient (eg, std::string_view), inefficient (eg,
+std::string), heavy (eg streams), or plainly unusable on some
+platforms/projects, (eg exceptions); some other utilities have equivalent
+under consideration for C++ standardisation; and yet some other utilities
+have (to my knowledge) no equivalent under consideration. Be that as it may,
+I've been using these utilities in this or similar forms for some years now,
+and I've found them incredibly useful in my projects. I'm packing these as a
+separate library, as all of my projects use it.
+
+c4core is [extensively unit-tested in Linux, Windows and
+MacOS](https://github.com/biojppm/c4core/actions). The tests cover
+x64, x86, arm, wasm (emscripten), aarch64, ppc64le and s390x
+architectures, and include analysing c4core with:
+  * valgrind
+  * clang-tidy
+  * clang sanitizers:
+    * memory
+    * address
+    * undefined behavior
+    * thread
+  * [LGTM.com](https://lgtm.com/projects/g/biojppm/c4core)
+
+c4core also works [in
+bare-metal](https://github.com/biojppm/c4core/issues/63) as well as
+[in RISC-V](https://github.com/biojppm/c4core/pull/69) but at the
+moment it's not easy to add automated tests to the CI, so for now
+these are not in the list of official architectures.
+
+
+## Obtaining c4core
+
+c4core uses git submodules. It is best to clone c4core with the `--recursive`
+option:
+
+```bash
+# using --recursive makes sure git submodules are also cloned at the same time
+git clone --recursive https://github.com/biojppm/c4core
+```
+
+If you ommit the `--recursive` option, then after cloning you will have to
+make git checkout the current version of the submodules, using `git submodule
+init` followed by `git submodule update`.
+
+
+## Using c4core in your project
+
+c4core is built with cmake, and assumes you also use cmake. Although c4core
+is NOT header-only, and currently has no install target, you can very easily
+use c4core in your project by using
+`add_subdirectory(${path_to_c4core_root})` in your CMakeLists.txt; this will
+add c4core as a subproject of your project. Doing this is not intrusive to
+your cmake project because c4core is fast to build (typically under 10s), and
+it also prefixes every cmake variable with `C4CORE_`. But more importantly
+this will enable you to compile c4core with the exact same compile settings
+used by your project.
+
+Here's a very quick complete example of setting up your project to use
+c4core:
+
+```cmake
+project(foo)
+
+add_subdirectory(c4core)
+
+add_library(foo foo.cpp)
+target_link_libraries(foo PUBLIC c4core) # that's it!
+```
+
+Note above that the call to `target_link_libraries()` is using PUBLIC
+linking. This is required to make sure the include directories from `c4core`
+are transitively used.
+
+
+## Quick tour
+
+All of the utilities in this library are under the namespace `c4`; any
+exposed macros use the prefix `C4_`: eg `C4_ASSERT()`.
+
+### Multi-platform / multi-compiler utilities
+
+```c++
+// TODO: elaborate on the topics:
+#include <c4/error.hpp>
+
+C4_LIKELY()/C4_UNLIKELY()
+
+C4_RESTRICT, $, c$, $$, c$$
+#include <c4/restrict.hpp>
+#include <c4/unrestrict.hpp>
+
+#include <c4/windows_push.hpp>
+#include <c4/windows_pop.hpp>
+
+C4_UNREACHABLE()
+
+c4::type_name()
+```
+
+### Runtime assertions and error handling
+
+```c++
+// TODO: elaborate on the topics:
+
+error callback
+
+C4_ASSERT()
+C4_XASSERT()
+C4_CHECK()
+
+C4_ERROR()
+C4_NOT_IMPLEMENTED()
+```
+
+### Memory allocation
+
+```c++
+// TODO: elaborate on the topics:
+
+c4::aalloc(), c4::afree() // aligned allocation
+
+c4::MemoryResource // global and scope
+
+c4::Allocator
+```
+
+### Mass initialization/construction/destruction
+
+```c++
+// TODO: elaborate on the topics:
+
+c4::construct()/c4::construct_n()
+
+c4::destroy()/c4::destroy_n()
+
+c4::copy_construct()/c4::copy_construct_n()
+
+c4::copy_assign()/c4::copy_assign_n()
+
+c4::move_construct()/c4::move_construct_n()
+
+c4::move_assign()/c4::move_assign_n()
+
+c4::make_room()/c4::destroy_room()
+```
+
+
+### Writeable string views: c4::substr and c4::csubstr
+
+Here: [`#include <c4/substr.hpp>`](src/c4/substr.hpp)
+
+
+### Value <-> character interoperation
+
+Here: [`#include <c4/charconv.hpp>`](src/c4/charconv.hpp)
+
+```c++
+// TODO: elaborate on the topics:
+
+c4::utoa(), c4::atou()
+c4::itoa(), c4::atoi()
+c4::ftoa(), c4::atof()
+c4::dtoa(), c4::atod()
+
+c4::to_chars(), c4::from_chars()
+c4::to_chars_sub()
+c4::to_chars_first()
+```
+
+### String formatting and parsing
+
+* [`#include <c4/format.hpp>`](src/c4/format.hpp)
+
+```c++
+// TODO: elaborate on the topics:
+
+c4::cat(), c4::uncat()
+
+c4::catsep(), c4::uncatsep()
+
+c4::format(), c4::unformat()
+
+// formatting:
+c4::raw, c4::craw
+```
+
+### `c4::span` and `c4::blob`
+
+* [`#include <c4/span.hpp>`](src/c4/span.hpp)
+* [`#include <c4/blob.hpp>`](src/c4/blob.hpp)
+
+
+### Enums and enum symbols
+
+[`#include <c4/enum.hpp>`](src/c4/enum.hpp)
+
+```c++
+// TODO: elaborate on the topics:
+
+c4::e2str(), c4::str2e()
+```
+
+### Bitmasks and bitmask symbols
+
+[`#include <c4/bitmask.hpp>`](src/c4/bitmask.hpp)
+
+```c++
+// TODO: elaborate on the topics:
+
+c4::bm2str(), c4::str2bm()
+```
+
+### Base64 encoding / decoding
+
+[`#include <c4/base64.hpp>`](src/c4/base64.hpp)
+
+### Fuzzy float comparison

+ 23 - 0
3rdparty/rapidyaml/ext/c4core/ROADMAP.md

@@ -0,0 +1,23 @@
+# ROADMAP
+
+## New features
+
+These changes will provide new features, and client code can be kept
+unchanged.
+
+
+## API changes
+
+These changes will require client code to be updated.
+
+* [breaking] drop use of C-style sprintf() formats in error messages and
+  assertions. Change the implementation to use c4::format()
+  ```c++
+  C4_ASSERT_MSG(sz > s.size(), "sz=%zu s.size()=%zu", sz, s.size());
+  // ... the above changes to:
+  C4_ASSERT_MSG(sz > s.size(), "sz={} s.size()={}", sz, s.size());
+  ```
+
+## Implementation changes
+
+* drop calls to sprintf() in charconv.hpp.

+ 3 - 0
3rdparty/rapidyaml/ext/c4core/changelog/0.1.0.md

@@ -0,0 +1,3 @@
+# 0.1.0
+
+First release.

+ 5 - 0
3rdparty/rapidyaml/ext/c4core/changelog/0.1.1.md

@@ -0,0 +1,5 @@
+# 0.1.1
+
+- Fix parsing of hexadecimal floats ([2d5c3f0](https://github.com/biojppm/c4core/commits/2d5c3f0))
+- Fix `csubstr::reverse_sub()` ([902c5b9](https://github.com/biojppm/c4core/commits/902c5b9))
+- Fix [#35](https://github.com/biojppm/c4core/issues/35): add SO_VERSION

+ 4 - 0
3rdparty/rapidyaml/ext/c4core/changelog/0.1.2.md

@@ -0,0 +1,4 @@
+- Fix error macros (ie `C4_ERROR()`, `C4_CHECK()`, `C4_ASSERT()`, etc) such that they are a single statement
+- `is_debugger_attached()`: add MacOSX version
+- Add support for Visual Studio 2022
+- Ensure `C4_LITTLE_ENDIAN` is always defined, even with mixed endianness

+ 1 - 0
3rdparty/rapidyaml/ext/c4core/changelog/0.1.3.md

@@ -0,0 +1 @@
+- Update fast_float to [3.2.1](https://github.com/fastfloat/fast_float/releases/tag/v3.2.0)

+ 6 - 0
3rdparty/rapidyaml/ext/c4core/changelog/0.1.4.md

@@ -0,0 +1,6 @@
+- [PR #38](https://github.com/biojppm/c4core/pull/38): add s390x architecture feature macros. 
+- Fix compiler warnings after update of fast_float to [3.2.1](https://github.com/fastfloat/fast_float/releases/tag/v3.2.0).
+
+### Thanks
+
+@musicinmybrain

+ 2 - 0
3rdparty/rapidyaml/ext/c4core/changelog/0.1.5.md

@@ -0,0 +1,2 @@
+- Add support for aarch64, s390x, ppc64le CPU architectures
+- Update debugbreak header (added support for the above architectures)

+ 2 - 0
3rdparty/rapidyaml/ext/c4core/changelog/0.1.6.md

@@ -0,0 +1,2 @@
+- Fix wrong version names in version 0.1.5 (was saying 0.1.4, should be 0.1.5)
+

+ 5 - 0
3rdparty/rapidyaml/ext/c4core/changelog/0.1.7.md

@@ -0,0 +1,5 @@
+- Fix build with C4CORE_NO_FAST_FLOAT ([#42](https://github.com/biojppm/c4core/pull/42)).
+- Fix clang warning in AIX/xlclang ([#44](https://github.com/biojppm/c4core/pull/44)).
+
+### Thanks
+--- @mbs-c

+ 45 - 0
3rdparty/rapidyaml/ext/c4core/changelog/0.1.8.md

@@ -0,0 +1,45 @@
+
+### New features
+
+- Add amalgamation into a single header file ([PR #48](https://github.com/biojppm/c4core/pull/48)):
+  - The amalgamated header will be available together with the deliverables from each release.
+  - To generate the amalgamated header:
+    ```
+    $ python tools/amalgamate.py c4core_all.hpp
+    ```
+  - To use the amalgamated header:
+    - Include at will in any header of your project.
+    - In one - and only one - of your project source files, `#define C4CORE_SINGLE_HDR_DEFINE_NOW` and then `#include <c4core_all.hpp>`. This will enable the function and class definitions in the header file. For example, here's a sample program:
+      ```c++
+      #include <iostream>
+      #define C4CORE_SINGLE_HDR_DEFINE_NOW // do this before the include
+      #include <c4core_all.hpp>
+      int main()
+      {
+          for(c4::csubstr s : c4::csubstr("a/b/c/d").split('/'))
+              std::cout << s << "\n";
+      }
+      ```
+- Add `csubstr::is_unsigned_integer()` and `csubstr::is_real()` ([PR #49](https://github.com/biojppm/c4core/pull/49)).
+- CMake: add alias target c4core::c4core, guaranteeing that the same code can be used with `add_subdirectory()` and `find_package()`. (see [rapidyaml #173](https://github.com/biojppm/rapidyaml/issues/173))
+- Add support for compilation with emscripten (WebAssembly+javascript) ([PR #52](https://github.com/biojppm/c4core/pull/52)).
+
+
+### Fixes
+
+- Fix edge cases with empty strings in `span::first()`, `span::last()` and `span::range()`  ([PR #49](https://github.com/biojppm/c4core/pull/49)).
+- Accept octal numbers in `substr::first_real_span()` and `substr::is_real()` ([PR #49](https://github.com/biojppm/c4core/pull/49)).
+- `substr`: fix coverage misses in number query methods ([PR #49](https://github.com/biojppm/c4core/pull/49)).
+- Use single-header version of fast_float ([PR #49](https://github.com/biojppm/c4core/pull/47)).
+- Suppress warnings triggered from fast_float in clang (`-Wfortify-source`) ([PR #49](https://github.com/biojppm/c4core/pull/47)).
+- Add missing `inline` in [src/c4/ext/rng/rng.hpp](src/c4/ext/rng/rng.hpp) ([PR #49](https://github.com/biojppm/c4core/pull/47)).
+- Fix compilation of [src/c4/ext/rng/inplace_function.h](src/c4/ext/inplace_function.h) in C++11 ([PR #49](https://github.com/biojppm/c4core/pull/47)).
+- Change order of headers, notably in `windows_push.hpp` ([PR #47](https://github.com/biojppm/c4core/pull/47)).
+- In `c4/charconv.hpp`: do not use C4_ASSERT in `to_c_fmt()`, which is `constexpr`.
+- Fix [#53](https://github.com/biojppm/c4core/issues/53): cmake install targets were missing call to `export()` ([PR #55](https://github.com/biojppm/c4core/pull/55)).
+- Fix linking of subprojects with libc++: flags should be forwarded through `CMAKE_***_FLAGS` instead of being set explicitly per-target ([PR #54](https://github.com/biojppm/c4core/pull/54)).
+
+
+### Thanks
+
+- @cschreib

+ 31 - 0
3rdparty/rapidyaml/ext/c4core/changelog/current.md

@@ -0,0 +1,31 @@
+### Breaking changes
+
+- fix [#63](https://github.com/biojppm/c4core/issues/63): remove `c4/time.hpp` and `c4/time.cpp` which prevented compilation in bare-metal mode ([PR #64](https://github.com/biojppm/c4core/issues/64)).
+
+### New features
+
+- Added decoding of UTF codepoints: `c4::decode_code_point()` ([PR #65](https://github.com/biojppm/c4core/issues/65)).
+- Experimental feature: add formatted-dumping facilities: using semantics like `c4::cat()`, `c4::catsep()` and `c4::format()`, where the subject is not a string buffer but a dump callback accepting strings. This still requires a string buffer for serialization of non-string types, but the buffer's required size is now limited to the max serialized size of non-string arguments, in contrast to the requirement in `c4::cat()` et al which is the total serialized size of every argument. This enables very efficient and generic printf-like semantics with reuse of a single small buffer, and allows direct-printing to terminal or file ([PR #67](https://github.com/biojppm/c4core/issues/67)). This feature is still experimental and a minor amount of changes to the API is possible.
+- Added macro `C4_IF_CONSTEXPR` resolving to `if constexpr (...)` if the c++ standard is at least c++17.
+- `csubstr`: add `count(csubstr)` overload.
+- Add support for RISC-V architectures ([PR #69](https://github.com/biojppm/c4core/issues/69)).
+- Add support for bare-metal compilation ([PR #64](https://github.com/biojppm/c4core/issues/64)).
+- gcc >= 4.8 support using polyfills for missing templates and features ([PR #74](https://github.com/biojppm/c4core/pull/74) and [PR #68](https://github.com/biojppm/c4core/pull/68)).
+
+### Fixes
+
+- `csubstr::operator==(std::nullptr_t)` now returns true if either `.str==nullptr` or `.len==0`.
+- Fix: `bool operator==(const char (&s)[N], csubstr)`  and `operator==(const char (&s)[N], substr)`. The template declaration for these functions had an extra `const` which prevented these functions to participate in overload resolution, which in some cases resulted in calls resolving to `operator==(std::string const&, csubstr)` if that header was visible ([PR #64](https://github.com/biojppm/c4core/issues/64)).
+- Fix `csubstr::last_not_of()`: optional positional parameter was ignored [PR #62](https://github.com/biojppm/c4core/pull/62).
+- `atof()`, `atod()`, `atox()`, `substr::is_real()`, `substr::first_real_span()`: accept `infinity`, `inf` and `nan` as valid reals [PR #60](https://github.com/biojppm/c4core/pull/60).
+- Add missing export symbols [PR #56](https://github.com/biojppm/c4core/pull/56), [PR #57](https://github.com/biojppm/c4core/pull/57).
+- `c4/substr_fwd.hpp`: fix compilation failure in Xcode 12 and earlier, where the forward declaration for `std::allocator` is inside the `inline namespace __1`, unlike later versions [PR #61](https://github.com/biojppm/c4core/pull/61), reported in [rapidyaml#185](https://github.com/biojppm/rapidyaml/issues/185).
+- `c4/error.hpp`: fix compilation failure in debug mode in Xcode 12 and earlier: `__clang_major__` does not mean the same as in the common clang, and as a result the warning `-Wgnu-inline-cpp-without-extern` does not exist there.
+
+
+### Thanks
+
+- @danngreen
+- @Xeonacid
+- @aviktorov
+- @fargies

+ 1 - 0
3rdparty/rapidyaml/ext/c4core/cmake/.gitignore

@@ -0,0 +1 @@
+__pycache__

+ 120 - 0
3rdparty/rapidyaml/ext/c4core/cmake/ConfigurationTypes.cmake

@@ -0,0 +1,120 @@
+
+
+# this function works both with multiconfig and single-config generators.
+function(set_default_build_type which)
+    # CMAKE_CONFIGURATION_TYPES is available only for multiconfig generators.
+    # so set the build type only if CMAKE_CONFIGURATION_TYPES does not exist.
+    if(NOT CMAKE_CONFIGURATION_TYPES) # not a multiconfig generator?
+        if(NOT CMAKE_BUILD_TYPE)
+            if(NOT which)
+                set(which RelWithDebInfo)
+            endif()
+            message("Defaulting to ${which} build.")
+            set(CMAKE_BUILD_TYPE ${which} CACHE STRING "")
+        endif()
+    endif()
+endfunction()
+
+
+# https://stackoverflow.com/questions/31546278/where-to-set-cmake-configuration-types-in-a-project-with-subprojects
+function(setup_configuration_types)
+    set(options0arg
+    )
+    set(options1arg
+        DEFAULT
+    )
+    set(optionsnarg
+        TYPES
+    )
+    cmake_parse_arguments("" "${options0arg}" "${options1arg}" "${optionsnarg}" ${ARGN})
+
+    if(NOT TYPES)
+        set(TYPES Release Debug RelWithDebInfo MinSizeRel)
+    endif()
+
+    # make it safe to call repeatedly
+    if(NOT _setup_configuration_types_done)
+        set(_setup_configuration_types_done 1 CACHE INTERNAL "")
+
+        # No reason to set CMAKE_CONFIGURATION_TYPES if it's not a multiconfig generator
+        # Also no reason mess with CMAKE_BUILD_TYPE if it's a multiconfig generator.
+
+        if(CMAKE_CONFIGURATION_TYPES) # multiconfig generator?
+            set(CMAKE_CONFIGURATION_TYPES "${TYPES}" CACHE STRING "")
+        else() # single-config generator
+            set_property(CACHE CMAKE_BUILD_TYPE PROPERTY HELPSTRING "Choose the type of build")
+            set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "${TYPES}")
+            # set the valid options for cmake-gui drop-down list
+        endif()
+    endif()
+endfunction()
+
+
+# https://stackoverflow.com/questions/31546278/where-to-set-cmake-configuration-types-in-a-project-with-subprojects
+function(add_configuration_type name)
+    set(flag_vars
+        C_FLAGS
+        CXX_FLAGS
+        SHARED_LINKER_FLAGS
+        STATIC_LINKER_FLAGS
+        MODULE_LINKER_FLAGS
+        EXE_LINKER_FLAGS
+        RC_FLAGS
+    )
+
+    set(options0arg
+        PREPEND  # when defaulting to a config, prepend to it instead of appending to it
+        SET_MAIN_FLAGS # eg, set CMAKE_CXX_FLAGS from CMAKE_CXX_FLAGS_${name}
+    )
+    set(options1arg
+        DEFAULT_FROM # take the initial value of the flags from this config
+    )
+    set(optionsnarg
+        C_FLAGS
+        CXX_FLAGS
+        SHARED_LINKER_FLAGS
+        STATIC_LINKER_FLAGS
+        MODULE_LINKER_FLAGS
+        EXE_LINKER_FLAGS
+        RC_FLAGS
+    )
+    cmake_parse_arguments(_act "${options0arg}" "${options1arg}" "${optionsnarg}" ${ARGN})
+
+    string(TOUPPER ${name} UNAME)
+
+    # make it safe to call repeatedly
+    if(NOT _add_configuration_type_${name})
+        set(_add_configuration_type_${name} 1 CACHE INTERNAL "")
+
+        setup_configuration_types()
+
+        if(CMAKE_CONFIGURATION_TYPES) # multiconfig generator?
+            set(CMAKE_CONFIGURATION_TYPES "${CMAKE_CONFIGURATION_TYPES};${name}" CACHE STRING "" FORCE)
+        else() # single-config generator
+            set_property(CACHE CMAKE_BUILD_TYPE PROPERTY HELPSTRING "Choose the type of build" FORCE)
+            set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "${CMAKE_BUILD_TYPES};${name}" FORCE)
+            # set the valid options for cmake-gui drop-down list
+        endif()
+
+        # now set up the configuration
+        message(STATUS "config: CMAKE_${f}_${UNAME} --- ${val}")
+        foreach(f ${flag_vars})
+            set(val ${_act_${f}})
+            message(STATUS "config: ${name}: ${f} --- ${val}")
+            if(_act_DEFAULT_FROM)
+                if(_act_PREPEND)
+                    set(val "${val} ${CMAKE_${f}_${_act_DEFAULT_FROM}}")
+                else()
+                    set(val "${CMAKE_${f}_${_act_DEFAULT_FROM}} ${val}")
+                endif()
+            endif()
+            message(STATUS "config: CMAKE_${f}_${UNAME} --- ${val}")
+            set(CMAKE_${f}_${UNAME} "${val}" CACHE STRING "" FORCE)
+            mark_as_advanced(CMAKE_${f}_${UNAME})
+            if(_act_SET_MAIN_FLAGS)
+                set(CMAKE_${f} "${CMAKE_${f}_${UNAME}}" CACHE STRING "" FORCE)
+            endif()
+        endforeach()
+    endif()
+
+endfunction()

+ 30 - 0
3rdparty/rapidyaml/ext/c4core/cmake/CreateSourceGroup.cmake

@@ -0,0 +1,30 @@
+# create hierarchical source groups based on a dir tree
+#
+# EXAMPLE USAGE:
+#
+#    create_source_group("src" "${SRC_ROOT}" "${SRC_LIST}")
+#
+# Visual Studio usually has the equivalent to this:
+#
+#    create_source_group("Header Files" ${PROJ_SRC_DIR} "${PROJ_HEADERS}")
+#    create_source_group("Source Files" ${PROJ_SRC_DIR} "${PROJ_SOURCES}")
+#
+# TODO: <jpmag> this was taken from a stack overflow answer. Need to find it
+# and add a link here.
+
+macro(create_source_group GroupPrefix RootDir ProjectSources)
+  set(DirSources ${ProjectSources})
+  foreach(Source ${DirSources})
+    #message(STATUS "s=${Source}")
+    string(REGEX REPLACE "${RootDir}" "" RelativePath "${Source}")
+    #message(STATUS "  ${RelativePath}")
+    string(REGEX REPLACE "[\\\\/][^\\\\/]*$" "" RelativePath "${RelativePath}")
+    #message(STATUS "  ${RelativePath}")
+    string(REGEX REPLACE "^[\\\\/]" "" RelativePath "${RelativePath}")
+    #message(STATUS "  ${RelativePath}")
+    string(REGEX REPLACE "/" "\\\\\\\\" RelativePath "${RelativePath}")
+    #message(STATUS "  ${RelativePath}")
+    source_group("${GroupPrefix}\\${RelativePath}" FILES ${Source})
+    #message(STATUS "  ${Source}")
+  endforeach(Source)
+endmacro(create_source_group)

+ 2566 - 0
3rdparty/rapidyaml/ext/c4core/cmake/Doxyfile.full.in

@@ -0,0 +1,2566 @@
+# Doxyfile 1.8.15
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the configuration
+# file that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# https://www.gnu.org/software/libiconv/ for the list of possible encodings.
+# The default value is: UTF-8.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
+
+PROJECT_NAME           = @_PROJ@
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
+
+PROJECT_NUMBER         = @_VERSION@
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF          = @_PROJ_BRIEF@
+
+# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
+# in the documentation. The maximum height of the logo should not exceed 55
+# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
+# the logo to the output directory.
+
+PROJECT_LOGO           =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
+# into which the generated documentation will be written. If a relative path is
+# entered, it will be relative to the location where doxygen was started. If
+# left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = @_OUTPUT_DIR@
+
+# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
+# directories (in 2 levels) under the output directory of each output format and
+# will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system.
+# The default value is: NO.
+
+CREATE_SUBDIRS         = NO
+
+# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
+# characters to appear in the names of generated files. If set to NO, non-ASCII
+# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
+# U+3044.
+# The default value is: NO.
+
+ALLOW_UNICODE_NAMES    = YES
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
+# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
+# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
+# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
+# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
+# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
+# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
+# Ukrainian and Vietnamese.
+# The default value is: English.
+
+OUTPUT_LANGUAGE        = English
+
+# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all generated output in the proper direction.
+# Possible values are: None, LTR, RTL and Context.
+# The default value is: None.
+
+OUTPUT_TEXT_DIRECTION  = None
+
+# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
+# descriptions after the members that are listed in the file and class
+# documentation (similar to Javadoc). Set to NO to disable this.
+# The default value is: YES.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief
+# description of a member or function before the detailed description
+#
+# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+# The default value is: YES.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator that is
+# used to form the text in various listings. Each string in this list, if found
+# as the leading text of the brief description, will be stripped from the text
+# and the result, after processing the whole list, is used as the annotated
+# text. Otherwise, the brief description is used as-is. If left blank, the
+# following values are used ($name is automatically replaced with the name of
+# the entity):The $name class, The $name widget, The $name file, is, provides,
+# specifies, contains, represents, a, an and the.
+
+ABBREVIATE_BRIEF       = "The $name class" \
+                         "The $name widget" \
+                         "The $name file" \
+                         is \
+                         provides \
+                         specifies \
+                         contains \
+                         represents \
+                         a \
+                         an \
+                         the
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# doxygen will generate a detailed section even if there is only a brief
+# description.
+# The default value is: NO.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+# The default value is: NO.
+
+INLINE_INHERITED_MEMB  = YES
+
+# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
+# before files name in the file list and in the header files. If set to NO the
+# shortest path that makes the file name unique will be used
+# The default value is: YES.
+
+FULL_PATH_NAMES        = YES
+
+# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
+# Stripping is only done if one of the specified strings matches the left-hand
+# part of the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the path to
+# strip.
+#
+# Note that you can specify absolute paths here, but also relative paths, which
+# will be relative from the directory where doxygen is started.
+# This tag requires that the tag FULL_PATH_NAMES is set to YES.
+
+STRIP_FROM_PATH        = @_STRIP_FROM_PATH@
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
+# path mentioned in the documentation of a class, which tells the reader which
+# header file to include in order to use a class. If left blank only the name of
+# the header file containing the class definition is used. Otherwise one should
+# specify the list of include paths that are normally passed to the compiler
+# using the -I flag.
+
+STRIP_FROM_INC_PATH    = @_STRIP_FROM_INC_PATH@
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
+# less readable) file names. This can be useful is your file systems doesn't
+# support long names like on DOS, Mac, or CD-ROM.
+# The default value is: NO.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
+# first line (until the first dot) of a Javadoc-style comment as the brief
+# description. If set to NO, the Javadoc-style will behave just like regular Qt-
+# style comments (thus requiring an explicit @brief command for a brief
+# description.)
+# The default value is: NO.
+
+JAVADOC_AUTOBRIEF      = YES
+
+# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
+# line (until the first dot) of a Qt-style comment as the brief description. If
+# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
+# requiring an explicit \brief command for a brief description.)
+# The default value is: NO.
+
+QT_AUTOBRIEF           = YES
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
+# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
+# a brief description. This used to be the default behavior. The new default is
+# to treat a multi-line C++ comment block as a detailed description. Set this
+# tag to YES if you prefer the old behavior instead.
+#
+# Note that setting this tag to YES also means that rational rose comments are
+# not recognized any more.
+# The default value is: NO.
+
+MULTILINE_CPP_IS_BRIEF = YES
+
+# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
+# documentation from any documented member that it re-implements.
+# The default value is: YES.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new
+# page for each member. If set to NO, the documentation of a member will be part
+# of the file/class/namespace that contains it.
+# The default value is: NO.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
+# uses this value to replace tabs by spaces in code fragments.
+# Minimum value: 1, maximum value: 16, default value: 4.
+
+TAB_SIZE               = 4
+
+# This tag can be used to specify a number of aliases that act as commands in
+# the documentation. An alias has the form:
+# name=value
+# For example adding
+# "sideeffect=@par Side Effects:\n"
+# will allow you to put the command \sideeffect (or @sideeffect) in the
+# documentation, which will result in a user-defined paragraph with heading
+# "Side Effects:". You can put \n's in the value part of an alias to insert
+# newlines (in the resulting output). You can put ^^ in the value part of an
+# alias to insert a newline as if a physical newline was in the original file.
+# When you need a literal { or } or , in the value part of an alias you have to
+# escape them by means of a backslash (\), this can lead to conflicts with the
+# commands \{ and \} for these it is advised to use the version @{ and @} or use
+# a double escape (\\{ and \\})
+
+ALIASES                =
+
+# This tag can be used to specify a number of word-keyword mappings (TCL only).
+# A mapping has the form "name=value". For example adding "class=itcl::class"
+# will allow you to use the command class in the itcl::class meaning.
+
+TCL_SUBST              =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C. For
+# instance, some of the names that are used will be different. The list of all
+# members will be omitted, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
+# Python sources only. Doxygen will then generate output that is more tailored
+# for that language. For instance, namespaces will be presented as packages,
+# qualified scopes will look different, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources. Doxygen will then generate output that is tailored for Fortran.
+# The default value is: NO.
+
+OPTIMIZE_FOR_FORTRAN   = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for VHDL.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_VHDL   = NO
+
+# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice
+# sources only. Doxygen will then generate output that is more tailored for that
+# language. For instance, namespaces will be presented as modules, types will be
+# separated into more groups, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_SLICE  = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given
+# extension. Doxygen has a built-in mapping, but you can override or extend it
+# using this tag. The format is ext=language, where ext is a file extension, and
+# language is one of the parsers supported by doxygen: IDL, Java, Javascript,
+# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice,
+# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
+# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
+# tries to guess whether the code is fixed or free formatted code, this is the
+# default for Fortran type files), VHDL, tcl. For instance to make doxygen treat
+# .inc files as Fortran files (default is PHP), and .f files as C (default is
+# Fortran), use: inc=Fortran f=C.
+#
+# Note: For files without extension you can use no_extension as a placeholder.
+#
+# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
+# the files are not read by doxygen.
+
+EXTENSION_MAPPING      =
+
+# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
+# according to the Markdown format, which allows for more readable
+# documentation. See https://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you can
+# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
+# case of backward compatibilities issues.
+# The default value is: YES.
+
+MARKDOWN_SUPPORT       = YES
+
+# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up
+# to that level are automatically included in the table of contents, even if
+# they do not have an id attribute.
+# Note: This feature currently applies only to Markdown headings.
+# Minimum value: 0, maximum value: 99, default value: 0.
+# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
+
+TOC_INCLUDE_HEADINGS   = 4
+
+# When enabled doxygen tries to link words that correspond to documented
+# classes, or namespaces to their corresponding documentation. Such a link can
+# be prevented in individual cases by putting a % sign in front of the word or
+# globally by setting AUTOLINK_SUPPORT to NO.
+# The default value is: YES.
+
+AUTOLINK_SUPPORT       = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should set this
+# tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string);
+# versus func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+# The default value is: NO.
+
+BUILTIN_STL_SUPPORT    = YES
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+# The default value is: NO.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
+# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen
+# will parse them like normal C++ but will assume all classes use public instead
+# of private inheritance when no explicit protection keyword is present.
+# The default value is: NO.
+
+SIP_SUPPORT            = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate
+# getter and setter methods for a property. Setting this option to YES will make
+# doxygen to replace the get and set methods by a property in the documentation.
+# This will only work if the methods are indeed getting or setting a simple
+# type. If this is not the case, or you want to show the methods anyway, you
+# should set this option to NO.
+# The default value is: YES.
+
+IDL_PROPERTY_SUPPORT   = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+# The default value is: NO.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# If one adds a struct or class to a group and this option is enabled, then also
+# any nested class or struct is added to the same group. By default this option
+# is disabled and one has to add nested compounds explicitly via \ingroup.
+# The default value is: NO.
+
+GROUP_NESTED_COMPOUNDS = YES
+
+# Set the SUBGROUPING tag to YES to allow class member groups of the same type
+# (for instance a group of public functions) to be put as a subgroup of that
+# type (e.g. under the Public Functions section). Set it to NO to prevent
+# subgrouping. Alternatively, this can be done per class using the
+# \nosubgrouping command.
+# The default value is: YES.
+
+SUBGROUPING            = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
+# are shown inside the group in which they are included (e.g. using \ingroup)
+# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
+# and RTF).
+#
+# Note that this feature does not work in combination with
+# SEPARATE_MEMBER_PAGES.
+# The default value is: NO.
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
+# with only public data fields or simple typedef fields will be shown inline in
+# the documentation of the scope in which they are defined (i.e. file,
+# namespace, or group documentation), provided this scope is documented. If set
+# to NO, structs, classes, and unions are shown on a separate page (for HTML and
+# Man pages) or section (for LaTeX and RTF).
+# The default value is: NO.
+
+INLINE_SIMPLE_STRUCTS  = YES
+
+# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
+# enum is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically be
+# useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+# The default value is: NO.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
+# cache is used to resolve symbols given their name and scope. Since this can be
+# an expensive process and often the same symbol appears multiple times in the
+# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
+# doxygen will become slower. If the cache is too large, memory is wasted. The
+# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
+# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
+# symbols. At the end of a run doxygen will report the cache usage and suggest
+# the optimal cache size from a speed point of view.
+# Minimum value: 0, maximum value: 9, default value: 0.
+
+LOOKUP_CACHE_SIZE      = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
+# documentation are documented, even if no documentation was available. Private
+# class members and static file members will be hidden unless the
+# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
+# Note: This will also disable the warnings about undocumented members that are
+# normally produced when WARNINGS is set to YES.
+# The default value is: NO.
+
+EXTRACT_ALL            = YES
+
+# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
+# be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIVATE        = YES
+
+# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
+# scope will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PACKAGE        = YES
+
+# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
+# included in the documentation.
+# The default value is: NO.
+
+EXTRACT_STATIC         = YES
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
+# locally in source files will be included in the documentation. If set to NO,
+# only classes defined in header files are included. Does not have any effect
+# for Java sources.
+# The default value is: YES.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. If set to YES, local methods,
+# which are defined in the implementation section but not in the interface are
+# included in the documentation. If set to NO, only methods in the interface are
+# included.
+# The default value is: NO.
+
+EXTRACT_LOCAL_METHODS  = YES
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base name of
+# the file that contains the anonymous namespace. By default anonymous namespace
+# are hidden.
+# The default value is: NO.
+
+EXTRACT_ANON_NSPACES   = YES
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
+# undocumented members inside documented classes or files. If set to NO these
+# members will be included in the various overviews, but no documentation
+# section is generated. This option has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy. If set
+# to NO, these classes will be included in the various overviews. This option
+# has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
+# (class|struct|union) declarations. If set to NO, these declarations will be
+# included in the documentation.
+# The default value is: NO.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
+# documentation blocks found inside the body of a function. If set to NO, these
+# blocks will be appended to the function's detailed documentation block.
+# The default value is: NO.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation that is typed after a
+# \internal command is included. If the tag is set to NO then the documentation
+# will be excluded. Set it to YES to include the internal documentation.
+# The default value is: NO.
+
+INTERNAL_DOCS          = YES
+
+# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
+# names in lower-case letters. If set to YES, upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+# The default value is: system dependent.
+
+CASE_SENSE_NAMES       = NO
+
+# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
+# their full class and namespace scopes in the documentation. If set to YES, the
+# scope will be hidden.
+# The default value is: NO.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
+# append additional text to a page's title, such as Class Reference. If set to
+# YES the compound reference will be hidden.
+# The default value is: NO.
+
+HIDE_COMPOUND_REFERENCE= NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
+# the files that are included by a file in the documentation of that file.
+# The default value is: YES.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
+# grouped member an include statement to the documentation, telling the reader
+# which file to include in order to use the member.
+# The default value is: NO.
+
+SHOW_GROUPED_MEMB_INC  = YES
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
+# files with double quotes in the documentation rather than with sharp brackets.
+# The default value is: NO.
+
+FORCE_LOCAL_INCLUDES   = NO
+
+# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
+# documentation for inline members.
+# The default value is: YES.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
+# (detailed) documentation of file and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order.
+# The default value is: YES.
+
+SORT_MEMBER_DOCS       = NO
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
+# descriptions of file, namespace and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order. Note that
+# this will also influence the order of the classes in the class list.
+# The default value is: NO.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
+# (brief and detailed) documentation of class members so that constructors and
+# destructors are listed first. If set to NO the constructors will appear in the
+# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
+# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
+# member documentation.
+# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
+# detailed member documentation.
+# The default value is: NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
+# of group names into alphabetical order. If set to NO the group names will
+# appear in their defined order.
+# The default value is: NO.
+
+SORT_GROUP_NAMES       = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
+# fully-qualified names, including namespaces. If set to NO, the class list will
+# be sorted only by class name, not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the alphabetical
+# list.
+# The default value is: NO.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
+# type resolution of all parameters of a function it will reject a match between
+# the prototype and the implementation of a member function even if there is
+# only one candidate or it is obvious which candidate to choose by doing a
+# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
+# accept a match between prototype and implementation in such cases.
+# The default value is: NO.
+
+STRICT_PROTO_MATCHING  = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo
+# list. This list is created by putting \todo commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test
+# list. This list is created by putting \test commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug
+# list. This list is created by putting \bug commands in the documentation.
+# The default value is: YES.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)
+# the deprecated list. This list is created by putting \deprecated commands in
+# the documentation.
+# The default value is: YES.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional documentation
+# sections, marked by \if <section_label> ... \endif and \cond <section_label>
+# ... \endcond blocks.
+
+ENABLED_SECTIONS       =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
+# initial value of a variable or macro / define can have for it to appear in the
+# documentation. If the initializer consists of more lines than specified here
+# it will be hidden. Use a value of 0 to hide initializers completely. The
+# appearance of the value of individual variables and macros / defines can be
+# controlled using \showinitializer or \hideinitializer command in the
+# documentation regardless of this setting.
+# Minimum value: 0, maximum value: 10000, default value: 30.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
+# the bottom of the documentation of classes and structs. If set to YES, the
+# list will mention the files that were used to generate the documentation.
+# The default value is: YES.
+
+SHOW_USED_FILES        = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
+# will remove the Files entry from the Quick Index and from the Folder Tree View
+# (if specified).
+# The default value is: YES.
+
+SHOW_FILES             = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
+# page. This will remove the Namespaces entry from the Quick Index and from the
+# Folder Tree View (if specified).
+# The default value is: YES.
+
+SHOW_NAMESPACES        = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command command input-file, where command is the value of the
+# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
+# by doxygen. Whatever the program writes to standard output is used as the file
+# version. For an example see the documentation.
+
+FILE_VERSION_FILTER    =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option. You can
+# optionally specify a file name after the option, if omitted DoxygenLayout.xml
+# will be used as the name of the layout file.
+#
+# Note that if you run doxygen from a directory containing a file called
+# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
+# tag is left empty.
+
+LAYOUT_FILE            =
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
+# the reference definitions. This must be a list of .bib files. The .bib
+# extension is automatically appended if omitted. This requires the bibtex tool
+# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info.
+# For LaTeX the style of the bibliography can be controlled using
+# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
+# search path. See also \cite for info how to create references.
+
+CITE_BIB_FILES         =
+
+#---------------------------------------------------------------------------
+# Configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated to
+# standard output by doxygen. If QUIET is set to YES this implies that the
+# messages are off.
+# The default value is: NO.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
+# this implies that the warnings are on.
+#
+# Tip: Turn warnings on while writing the documentation.
+# The default value is: YES.
+
+WARNINGS               = YES
+
+# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
+# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: YES.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some parameters
+# in a documented function, or documenting parameters that don't exist or using
+# markup commands wrongly.
+# The default value is: YES.
+
+WARN_IF_DOC_ERROR      = YES
+
+# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
+# are documented, but have no documentation for their parameters or return
+# value. If set to NO, doxygen will only warn about wrong or incomplete
+# parameter documentation, but not about the absence of documentation. If
+# EXTRACT_ALL is set to YES then this flag will automatically be disabled.
+# The default value is: NO.
+
+WARN_NO_PARAMDOC       = NO
+
+# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
+# a warning is encountered.
+# The default value is: NO.
+
+WARN_AS_ERROR          = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that doxygen
+# can produce. The string should contain the $file, $line, and $text tags, which
+# will be replaced by the file and line number from which the warning originated
+# and the warning text. Optionally the format may contain $version, which will
+# be replaced by the version of the file (if it could be obtained via
+# FILE_VERSION_FILTER)
+# The default value is: $file:$line: $text.
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning and error
+# messages should be written. If left blank the output is written to standard
+# error (stderr).
+
+WARN_LOGFILE           =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag is used to specify the files and/or directories that contain
+# documented source files. You may enter file names like myfile.cpp or
+# directories like /usr/src/myproject. Separate the files or directories with
+# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
+# Note: If this tag is empty the current directory is searched.
+
+INPUT                  = @_INPUT@
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
+# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
+# documentation (see: https://www.gnu.org/software/libiconv/) for the list of
+# possible encodings.
+# The default value is: UTF-8.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
+# *.h) to filter out the source-files in the directories.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# read by doxygen.
+#
+# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
+# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
+# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
+# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,
+# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice.
+
+FILE_PATTERNS          = *.c \
+                         *.cc \
+                         *.cxx \
+                         *.cpp \
+                         *.c++ \
+                         *.java \
+                         *.ii \
+                         *.ixx \
+                         *.ipp \
+                         *.i++ \
+                         *.inl \
+                         *.idl \
+                         *.ddl \
+                         *.odl \
+                         *.h \
+                         *.hh \
+                         *.hxx \
+                         *.hpp \
+                         *.h++ \
+                         *.cs \
+                         *.d \
+                         *.php \
+                         *.php4 \
+                         *.php5 \
+                         *.phtml \
+                         *.inc \
+                         *.m \
+                         *.markdown \
+                         *.md \
+                         *.mm \
+                         *.dox \
+                         *.py \
+                         *.pyw \
+                         *.f90 \
+                         *.f95 \
+                         *.f03 \
+                         *.f08 \
+                         *.f \
+                         *.for \
+                         *.tcl \
+                         *.vhd \
+                         *.vhdl \
+                         *.ucf \
+                         *.qsf \
+                         *.ice \
+                         @_FILE_PATTERNS@
+
+# The RECURSIVE tag can be used to specify whether or not subdirectories should
+# be searched for input files as well.
+# The default value is: NO.
+
+RECURSIVE              = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+#
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE                = @_EXCLUDE@
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+# The default value is: NO.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       = @_EXCLUDE_PATTERNS@
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories use the pattern */test/*
+
+EXCLUDE_SYMBOLS        = @_EXCLUDE_SYMBOLS@
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or directories
+# that contain example code fragments that are included (see the \include
+# command).
+
+EXAMPLE_PATH           = @_EXAMPLE_PATH@
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank all
+# files are included.
+
+EXAMPLE_PATTERNS       = *
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude commands
+# irrespective of the value of the RECURSIVE tag.
+# The default value is: NO.
+
+EXAMPLE_RECURSIVE      = YES
+
+# The IMAGE_PATH tag can be used to specify one or more files or directories
+# that contain images that are to be included in the documentation (see the
+# \image command).
+
+IMAGE_PATH             =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command:
+#
+# <filter> <input-file>
+#
+# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
+# name of an input file. Doxygen will then use the output that the filter
+# program writes to standard output. If FILTER_PATTERNS is specified, this tag
+# will be ignored.
+#
+# Note that the filter must not add or remove lines; it is applied before the
+# code is scanned, but not when the output code is generated. If lines are added
+# or removed, the anchors will not be placed correctly.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+INPUT_FILTER           =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form: pattern=filter
+# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
+# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
+# patterns match the file name, INPUT_FILTER is applied.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+FILTER_PATTERNS        =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will also be used to filter the input files that are used for
+# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# The default value is: NO.
+
+FILTER_SOURCE_FILES    = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
+# it is also possible to disable source filtering for a specific pattern using
+# *.ext= (so without naming a filter).
+# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
+
+FILTER_SOURCE_PATTERNS =
+
+# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
+# is part of the input, its contents will be placed on the main page
+# (index.html). This can be useful if you have a project on for instance GitHub
+# and want to reuse the introduction page also for the doxygen output.
+
+USE_MDFILE_AS_MAINPAGE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
+# generated. Documented entities will be cross-referenced with these sources.
+#
+# Note: To get rid of all source code in the generated output, make sure that
+# also VERBATIM_HEADERS is set to NO.
+# The default value is: NO.
+
+SOURCE_BROWSER         = YES
+
+# Setting the INLINE_SOURCES tag to YES will include the body of functions,
+# classes and enums directly into the documentation.
+# The default value is: NO.
+
+INLINE_SOURCES         = YES
+
+# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
+# special comment blocks from generated source code fragments. Normal C, C++ and
+# Fortran comments will always remain visible.
+# The default value is: YES.
+
+STRIP_CODE_COMMENTS    = NO
+
+# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
+# entity all documented functions referencing it will be listed.
+# The default value is: NO.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES then for each documented function
+# all documented entities called/used by that function will be listed.
+# The default value is: NO.
+
+REFERENCES_RELATION    = YES
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
+# to YES then the hyperlinks from functions in REFERENCES_RELATION and
+# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
+# link to the documentation.
+# The default value is: YES.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
+# source code will show a tooltip with additional information such as prototype,
+# brief description and links to the definition and documentation. Since this
+# will make the HTML file larger and loading of large files a bit slower, you
+# can opt to disable this feature.
+# The default value is: YES.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+SOURCE_TOOLTIPS        = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code will
+# point to the HTML generated by the htags(1) tool instead of doxygen built-in
+# source browser. The htags tool is part of GNU's global source tagging system
+# (see https://www.gnu.org/software/global/global.html). You will need version
+# 4.8.6 or higher.
+#
+# To use it do the following:
+# - Install the latest version of global
+# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file
+# - Make sure the INPUT points to the root of the source tree
+# - Run doxygen as normal
+#
+# Doxygen will invoke htags (and that will in turn invoke gtags), so these
+# tools must be available from the command line (i.e. in the search path).
+#
+# The result: instead of the source browser generated by doxygen, the links to
+# source code will now point to the output of htags.
+# The default value is: NO.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
+# verbatim copy of the header file for each class for which an include is
+# specified. Set to NO to disable this.
+# See also: Section \class.
+# The default value is: YES.
+
+VERBATIM_HEADERS       = YES
+
+# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the
+# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the
+# cost of reduced performance. This can be particularly helpful with template
+# rich C++ code for which doxygen's built-in parser lacks the necessary type
+# information.
+# Note: The availability of this option depends on whether or not doxygen was
+# generated with the -Duse_libclang=ON option for CMake.
+# The default value is: NO.
+
+CLANG_ASSISTED_PARSING = YES
+
+# If clang assisted parsing is enabled you can provide the compiler with command
+# line options that you would normally use when invoking the compiler. Note that
+# the include paths will already be set by doxygen for the files and directories
+# specified with INPUT and INCLUDE_PATH.
+# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
+
+CLANG_OPTIONS          =
+
+# If clang assisted parsing is enabled you can provide the clang parser with the
+# path to the compilation database (see:
+# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) used when the files
+# were built. This is equivalent to specifying the "-p" option to a clang tool,
+# such as clang-check. These options will then be passed to the parser.
+# Note: The availability of this option depends on whether or not doxygen was
+# generated with the -Duse_libclang=ON option for CMake.
+
+CLANG_DATABASE_PATH    = @_CLANG_DATABASE_PATH@
+
+#---------------------------------------------------------------------------
+# Configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
+# compounds will be generated. Enable this if the project contains a lot of
+# classes, structs, unions or interfaces.
+# The default value is: YES.
+
+ALPHABETICAL_INDEX     = YES
+
+# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in
+# which the alphabetical index list will be split.
+# Minimum value: 1, maximum value: 20, default value: 5.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all classes will
+# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
+# can be used to specify a prefix (or a list of prefixes) that should be ignored
+# while generating the index headers.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+IGNORE_PREFIX          =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
+# The default value is: YES.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
+# generated HTML page (for example: .htm, .php, .asp).
+# The default value is: .html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
+# each generated HTML page. If the tag is left blank doxygen will generate a
+# standard header.
+#
+# To get valid HTML the header file that includes any scripts and style sheets
+# that doxygen needs, which is dependent on the configuration options used (e.g.
+# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
+# default header using
+# doxygen -w html new_header.html new_footer.html new_stylesheet.css
+# YourConfigFile
+# and then modify the file new_header.html. See also section "Doxygen usage"
+# for information on how to generate the default header that doxygen normally
+# uses.
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. For a description
+# of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_HEADER            =
+
+# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
+# generated HTML page. If the tag is left blank doxygen will generate a standard
+# footer. See HTML_HEADER for more information on how to generate a default
+# footer and what special commands can be used inside the footer. See also
+# section "Doxygen usage" for information on how to generate the default footer
+# that doxygen normally uses.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FOOTER            =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
+# sheet that is used by each HTML page. It can be used to fine-tune the look of
+# the HTML output. If left blank doxygen will generate a default style sheet.
+# See also section "Doxygen usage" for information on how to generate the style
+# sheet that doxygen normally uses.
+# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
+# it is more robust and this tag (HTML_STYLESHEET) will in the future become
+# obsolete.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_STYLESHEET        =
+
+# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# cascading style sheets that are included after the standard style sheets
+# created by doxygen. Using this option one can overrule certain style aspects.
+# This is preferred over using HTML_STYLESHEET since it does not replace the
+# standard style sheet and is therefore more robust against future updates.
+# Doxygen will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list). For an example see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_STYLESHEET  =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
+# files will be copied as-is; there are no commands or markers available.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_FILES       =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
+# will adjust the colors in the style sheet and background images according to
+# this color. Hue is specified as an angle on a colorwheel, see
+# https://en.wikipedia.org/wiki/Hue for more information. For instance the value
+# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
+# purple, and 360 is red again.
+# Minimum value: 0, maximum value: 359, default value: 220.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_HUE    = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
+# in the HTML output. For a value of 0 the output will use grayscales only. A
+# value of 255 will produce the most vivid colors.
+# Minimum value: 0, maximum value: 255, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_SAT    = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
+# luminance component of the colors in the HTML output. Values below 100
+# gradually make the output lighter, whereas values above 100 make the output
+# darker. The value divided by 100 is the actual gamma applied, so 80 represents
+# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
+# change the gamma.
+# Minimum value: 40, maximum value: 240, default value: 80.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_GAMMA  = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting this
+# to YES can help to show when doxygen was last run and thus if the
+# documentation is up to date.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_TIMESTAMP         = YES
+
+# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
+# documentation will contain a main index with vertical navigation menus that
+# are dynamically created via Javascript. If disabled, the navigation index will
+# consists of multiple levels of tabs that are statically embedded in every HTML
+# page. Disable this option to support browsers that do not have Javascript,
+# like the Qt help browser.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_MENUS     = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
+# shown in the various tree structured indices initially; the user can expand
+# and collapse entries dynamically later on. Doxygen will expand the tree to
+# such a level that at most the specified number of entries are visible (unless
+# a fully collapsed tree already exceeds this amount). So setting the number of
+# entries 1 will produce a full collapsed tree by default. 0 is a special value
+# representing an infinite number of entries and will result in a full expanded
+# tree by default.
+# Minimum value: 0, maximum value: 9999, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files will be
+# generated that can be used as input for Apple's Xcode 3 integrated development
+# environment (see: https://developer.apple.com/xcode/), introduced with OSX
+# 10.5 (Leopard). To create a documentation set, doxygen will generate a
+# Makefile in the HTML output directory. Running make will produce the docset in
+# that directory and running make install will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
+# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy
+# genXcode/_index.html for more information.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_DOCSET        = NO
+
+# This tag determines the name of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# The default value is: Doxygen generated docs.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+
+# This tag specifies a string that should uniquely identify the documentation
+# set bundle. This should be a reverse domain-name style string, e.g.
+# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+
+# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+# The default value is: org.doxygen.Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_ID    = org.doxygen.Publisher
+
+# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
+# The default value is: Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_NAME  = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
+# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
+# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
+# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on
+# Windows.
+#
+# The HTML Help Workshop contains a compiler that can convert all HTML output
+# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
+# files are now used as the Windows 98 help format, and will replace the old
+# Windows help format (.hlp) on all Windows platforms in the future. Compressed
+# HTML files also contain an index, a table of contents, and you can search for
+# words in the documentation. The HTML workshop also contains a viewer for
+# compressed HTML files.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_HTMLHELP      = NO
+
+# The CHM_FILE tag can be used to specify the file name of the resulting .chm
+# file. You can add a path in front of the file if the result should not be
+# written to the html output directory.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_FILE               =
+
+# The HHC_LOCATION tag can be used to specify the location (absolute path
+# including file name) of the HTML help compiler (hhc.exe). If non-empty,
+# doxygen will try to run the HTML help compiler on the generated index.hhp.
+# The file has to be specified with full path.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+HHC_LOCATION           =
+
+# The GENERATE_CHI flag controls if a separate .chi index file is generated
+# (YES) or that it should be included in the master .chm file (NO).
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+GENERATE_CHI           = NO
+
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
+# and project file content.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_INDEX_ENCODING     =
+
+# The BINARY_TOC flag controls whether a binary table of contents is generated
+# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it
+# enables the Previous and Next buttons.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members to
+# the table of contents of the HTML help documentation and to the tree view.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+TOC_EXPAND             = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
+# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
+# (.qch) of the generated HTML documentation.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_QHP           = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
+# the file name of the resulting .qch file. The path specified is relative to
+# the HTML output folder.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QCH_FILE               =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
+# Project output. For more information please see Qt Help Project / Namespace
+# (see: http://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_NAMESPACE          = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
+# Help Project output. For more information please see Qt Help Project / Virtual
+# Folders (see: http://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-
+# folders).
+# The default value is: doc.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_VIRTUAL_FOLDER     = doc
+
+# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
+# filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_NAME   =
+
+# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_ATTRS  =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's filter section matches. Qt Help Project / Filter Attributes (see:
+# http://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_SECT_FILTER_ATTRS  =
+
+# The QHG_LOCATION tag can be used to specify the location of Qt's
+# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
+# generated .qhp file.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHG_LOCATION           =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
+# generated, together with the HTML files, they form an Eclipse help plugin. To
+# install this plugin and make it available under the help contents menu in
+# Eclipse, the contents of the directory containing the HTML and XML files needs
+# to be copied into the plugins directory of eclipse. The name of the directory
+# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
+# After copying Eclipse needs to be restarted before the help appears.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_ECLIPSEHELP   = NO
+
+# A unique identifier for the Eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have this
+# name. Each documentation set should have its own identifier.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
+
+ECLIPSE_DOC_ID         = org.doxygen.Project
+
+# If you want full control over the layout of the generated HTML pages it might
+# be necessary to disable the index and replace it with your own. The
+# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
+# of each HTML page. A value of NO enables the index and the value YES disables
+# it. Since the tabs in the index contain the same information as the navigation
+# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+DISABLE_INDEX          = NO
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information. If the tag
+# value is set to YES, a side panel will be generated containing a tree-like
+# index structure (just like the one that is generated for HTML Help). For this
+# to work a browser that supports JavaScript, DHTML, CSS and frames is required
+# (i.e. any modern browser). Windows users are probably better off using the
+# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
+# further fine-tune the look of the index. As an example, the default style
+# sheet generated by doxygen has an example that shows how to put an image at
+# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
+# the same information as the tab index, you could consider setting
+# DISABLE_INDEX to YES when enabling this option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_TREEVIEW      = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
+# doxygen will group on one line in the generated HTML documentation.
+#
+# Note that a value of 0 will completely suppress the enum values from appearing
+# in the overview section.
+# Minimum value: 0, maximum value: 20, default value: 4.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
+# to set the initial width (in pixels) of the frame in which the tree is shown.
+# Minimum value: 0, maximum value: 1500, default value: 250.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+TREEVIEW_WIDTH         = 250
+
+# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
+# external symbols imported via tag files in a separate window.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+EXT_LINKS_IN_WINDOW    = NO
+
+# Use this tag to change the font size of LaTeX formulas included as images in
+# the HTML documentation. When you change the font size after a successful
+# doxygen run you need to manually remove any form_*.png images from the HTML
+# output directory to force them to be regenerated.
+# Minimum value: 8, maximum value: 50, default value: 10.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_FONTSIZE       = 10
+
+# Use the FORMULA_TRANSPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are not
+# supported properly for IE 6.0, but are supported on all modern browsers.
+#
+# Note that when changing this option you need to delete any form_*.png files in
+# the HTML output directory before the changes have effect.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_TRANSPARENT    = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
+# https://www.mathjax.org) which uses client side Javascript for the rendering
+# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
+# installed or if you want to formulas look prettier in the HTML output. When
+# enabled you may also need to install MathJax separately and configure the path
+# to it using the MATHJAX_RELPATH option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+USE_MATHJAX            = NO
+
+# When MathJax is enabled you can set the default output format to be used for
+# the MathJax output. See the MathJax site (see:
+# http://docs.mathjax.org/en/latest/output.html) for more details.
+# Possible values are: HTML-CSS (which is slower, but has the best
+# compatibility), NativeMML (i.e. MathML) and SVG.
+# The default value is: HTML-CSS.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_FORMAT         = HTML-CSS
+
+# When MathJax is enabled you need to specify the location relative to the HTML
+# output directory using the MATHJAX_RELPATH option. The destination directory
+# should contain the MathJax.js script. For instance, if the mathjax directory
+# is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
+# Content Delivery Network so you can quickly see the result without installing
+# MathJax. However, it is strongly recommended to install a local copy of
+# MathJax from https://www.mathjax.org before deployment.
+# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_RELPATH        = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
+# extension names that should be enabled during MathJax rendering. For example
+# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_EXTENSIONS     =
+
+# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
+# of code that will be used on startup of the MathJax code. See the MathJax site
+# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
+# example see the documentation.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_CODEFILE       =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
+# the HTML output. The underlying search engine uses javascript and DHTML and
+# should work on any modern browser. Note that when using HTML help
+# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
+# there is already a search function so this one should typically be disabled.
+# For large projects the javascript based search engine can be slow, then
+# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
+# search using the keyboard; to jump to the search box use <access key> + S
+# (what the <access key> is depends on the OS and browser, but it is typically
+# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
+# key> to jump into the search results window, the results can be navigated
+# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
+# the search. The filter options can be selected when the cursor is inside the
+# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
+# to select a filter and <Enter> or <escape> to activate or cancel the filter
+# option.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+SEARCHENGINE           = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a web server instead of a web client using Javascript. There
+# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
+# setting. When disabled, doxygen will generate a PHP script for searching and
+# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
+# and searching needs to be provided by external tools. See the section
+# "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SERVER_BASED_SEARCH    = NO
+
+# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
+# script for searching. Instead the search results are written to an XML file
+# which needs to be processed by an external indexer. Doxygen will invoke an
+# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
+# search results.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: https://xapian.org/).
+#
+# See the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH        = NO
+
+# The SEARCHENGINE_URL should point to a search engine hosted by a web server
+# which will return the search results when EXTERNAL_SEARCH is enabled.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: https://xapian.org/). See the section "External Indexing and
+# Searching" for details.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHENGINE_URL       =
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
+# search data is written to a file for indexing by an external tool. With the
+# SEARCHDATA_FILE tag the name of this file can be specified.
+# The default file is: searchdata.xml.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHDATA_FILE        = searchdata.xml
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
+# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
+# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
+# projects and redirect the results back to the right project.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH_ID     =
+
+# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
+# projects other than the one defined by this configuration file, but that are
+# all added to the same external search index. Each project needs to have a
+# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
+# to a relative location where the documentation can be found. The format is:
+# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTRA_SEARCH_MAPPINGS  =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
+# The default value is: YES.
+
+GENERATE_LATEX         = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked.
+#
+# Note that when not enabling USE_PDFLATEX the default is latex when enabling
+# USE_PDFLATEX the default is pdflatex and when in the later case latex is
+# chosen this is overwritten by pdflatex. For specific output languages the
+# default can have been set differently, this depends on the implementation of
+# the output language.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_CMD_NAME         =
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
+# index for LaTeX.
+# Note: This tag is used in the Makefile / make.bat.
+# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file
+# (.tex).
+# The default file is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to
+# generate index for LaTeX.
+# Note: This tag is used in the generated output file (.tex).
+# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat.
+# The default value is: \makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_MAKEINDEX_CMD    = \makeindex
+
+# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used by the
+# printer.
+# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
+# 14 inches) and executive (7.25 x 10.5 inches).
+# The default value is: a4.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PAPER_TYPE             = a4
+
+# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
+# that should be included in the LaTeX output. The package can be specified just
+# by its name or with the correct syntax as to be used with the LaTeX
+# \usepackage command. To get the times font for instance you can specify :
+# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}
+# To use the option intlimits with the amsmath package you can specify:
+# EXTRA_PACKAGES=[intlimits]{amsmath}
+# If left blank no extra packages will be included.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+EXTRA_PACKAGES         =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
+# generated LaTeX document. The header should contain everything until the first
+# chapter. If it is left blank doxygen will generate a standard header. See
+# section "Doxygen usage" for information on how to let doxygen write the
+# default header to a separate file.
+#
+# Note: Only use a user-defined header if you know what you are doing! The
+# following commands have a special meaning inside the header: $title,
+# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
+# $projectbrief, $projectlogo. Doxygen will replace $title with the empty
+# string, for the replacement values of the other commands the user is referred
+# to HTML_HEADER.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HEADER           =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
+# generated LaTeX document. The footer should contain everything after the last
+# chapter. If it is left blank doxygen will generate a standard footer. See
+# LATEX_HEADER for more information on how to generate a default footer and what
+# special commands can be used inside the footer.
+#
+# Note: Only use a user-defined footer if you know what you are doing!
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_FOOTER           =
+
+# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# LaTeX style sheets that are included after the standard style sheets created
+# by doxygen. Using this option one can overrule certain style aspects. Doxygen
+# will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list).
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_STYLESHEET =
+
+# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the LATEX_OUTPUT output
+# directory. Note that the files will be copied as-is; there are no commands or
+# markers available.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_FILES      =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
+# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
+# contain links (just like the HTML output) instead of page references. This
+# makes the output suitable for online browsing using a PDF viewer.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PDF_HYPERLINKS         = YES
+
+# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
+# the PDF file directly from the LaTeX files. Set this option to YES, to get a
+# higher quality PDF documentation.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+USE_PDFLATEX           = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
+# command to the generated LaTeX files. This will instruct LaTeX to keep running
+# if errors occur, instead of asking the user for help. This option is also used
+# when generating formulas in HTML.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BATCHMODE        = NO
+
+# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
+# index chapters (such as File Index, Compound Index, etc.) in the output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HIDE_INDICES     = NO
+
+# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
+# code with syntax highlighting in the LaTeX output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_SOURCE_CODE      = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. See
+# https://en.wikipedia.org/wiki/BibTeX and \cite for more info.
+# The default value is: plain.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BIB_STYLE        = plain
+
+# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
+# page will contain the date and time when the page was generated. Setting this
+# to NO can help when comparing the output of multiple runs.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_TIMESTAMP        = NO
+
+# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)
+# path from which the emoji images will be read. If a relative path is entered,
+# it will be relative to the LATEX_OUTPUT directory. If left blank the
+# LATEX_OUTPUT directory will be used.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EMOJI_DIRECTORY  =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The
+# RTF output is optimized for Word 97 and may not look too pretty with other RTF
+# readers/editors.
+# The default value is: NO.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: rtf.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
+# contain hyperlink fields. The RTF file will contain links (just like the HTML
+# output) instead of page references. This makes the output suitable for online
+# browsing using Word or some other Word compatible readers that support those
+# fields.
+#
+# Note: WordPad (write) and others do not support links.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# configuration file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+#
+# See also section "Doxygen usage" for information on how to generate the
+# default style sheet that doxygen normally uses.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_STYLESHEET_FILE    =
+
+# Set optional variables used in the generation of an RTF document. Syntax is
+# similar to doxygen's configuration file. A template extensions file can be
+# generated using doxygen -e rtf extensionFile.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_EXTENSIONS_FILE    =
+
+# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code
+# with syntax highlighting in the RTF output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_SOURCE_CODE        = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for
+# classes and files.
+# The default value is: NO.
+
+GENERATE_MAN           = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it. A directory man3 will be created inside the directory specified by
+# MAN_OUTPUT.
+# The default directory is: man.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to the generated
+# man pages. In case the manual section does not start with a number, the number
+# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
+# optional.
+# The default value is: .3.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_EXTENSION          = .3
+
+# The MAN_SUBDIR tag determines the name of the directory created within
+# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
+# MAN_EXTENSION with the initial . removed.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_SUBDIR             =
+
+# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
+# will generate one additional man file for each entity documented in the real
+# man page(s). These additional files only source the real man page, but without
+# them the man command would be unable to find the correct page.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
+# captures the structure of the code including all documentation.
+# The default value is: NO.
+
+GENERATE_XML           = YES
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: xml.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_OUTPUT             = xml
+
+# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program
+# listings (including syntax highlighting and cross-referencing information) to
+# the XML output. Note that enabling this will significantly increase the size
+# of the XML output.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_PROGRAMLISTING     = YES
+
+# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include
+# namespace members in file scope as well, matching the HTML output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_NS_MEMB_FILE_SCOPE = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files
+# that can be used to generate PDF.
+# The default value is: NO.
+
+GENERATE_DOCBOOK       = NO
+
+# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
+# front of it.
+# The default directory is: docbook.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_OUTPUT         = docbook
+
+# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the
+# program listings (including syntax highlighting and cross-referencing
+# information) to the DOCBOOK output. Note that enabling this will significantly
+# increase the size of the DOCBOOK output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_PROGRAMLISTING = NO
+
+#---------------------------------------------------------------------------
+# Configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
+# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures
+# the structure of the code including all documentation. Note that this feature
+# is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module
+# file that captures the structure of the code including all documentation.
+#
+# Note that this feature is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary
+# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
+# output from the Perl module output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely
+# formatted so it can be parsed by a human reader. This is useful if you want to
+# understand what is going on. On the other hand, if this tag is set to NO, the
+# size of the Perl module output will be much smaller and Perl will parse it
+# just the same.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file are
+# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
+# so different doxyrules.make files included by the same Makefile don't
+# overwrite each other's variables.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
+# C-preprocessor directives found in the sources and include files.
+# The default value is: YES.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
+# in the source code. If set to NO, only conditional compilation will be
+# performed. Macro expansion can be done in a controlled way by setting
+# EXPAND_ONLY_PREDEF to YES.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
+# the macro expansion is limited to the macros specified with the PREDEFINED and
+# EXPAND_AS_DEFINED tags.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES, the include files in the
+# INCLUDE_PATH will be searched if a #include is found.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by the
+# preprocessor.
+# This tag requires that the tag SEARCH_INCLUDES is set to YES.
+
+INCLUDE_PATH           =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will be
+# used.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+INCLUDE_FILE_PATTERNS  =
+
+# The PREDEFINED tag can be used to specify one or more macro names that are
+# defined before the preprocessor is started (similar to the -D option of e.g.
+# gcc). The argument of the tag is a list of macros of the form: name or
+# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
+# is assumed. To prevent a macro definition from being undefined via #undef or
+# recursively expanded use the := operator instead of the = operator.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+PREDEFINED             =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
+# tag can be used to specify a list of macro names that should be expanded. The
+# macro definition that is found in the sources will be used. Use the PREDEFINED
+# tag if you want to use a different macro definition that overrules the
+# definition found in the source code.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_AS_DEFINED      =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
+# remove all references to function-like macros that are alone on a line, have
+# an all uppercase name, and do not end with a semicolon. Such function macros
+# are typically used for boiler-plate code, and will confuse the parser if not
+# removed.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES tag can be used to specify one or more tag files. For each tag
+# file the location of the external documentation should be added. The format of
+# a tag file without this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where loc1 and loc2 can be relative or absolute paths or URLs. See the
+# section "Linking to external documentation" for more information about the use
+# of tag files.
+# Note: Each tag file must have a unique name (where the name does NOT include
+# the path). If a tag file is not located in the directory in which doxygen is
+# run, you must also specify the path to the tagfile here.
+
+TAGFILES               =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
+# tag file that is based on the input files it reads. See section "Linking to
+# external documentation" for more information about the usage of tag files.
+
+GENERATE_TAGFILE       =
+
+# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
+# the class index. If set to NO, only the inherited external classes will be
+# listed.
+# The default value is: NO.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will be
+# listed.
+# The default value is: YES.
+
+EXTERNAL_GROUPS        = YES
+
+# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in
+# the related pages index. If set to NO, only the current project's pages will
+# be listed.
+# The default value is: YES.
+
+EXTERNAL_PAGES         = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of 'which perl').
+# The default file (with absolute path) is: /usr/bin/perl.
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram
+# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
+# NO turns the diagrams off. Note that this option also works with HAVE_DOT
+# disabled, but it is recommended to install and use dot, since it yields more
+# powerful graphs.
+# The default value is: YES.
+
+CLASS_DIAGRAMS         = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see:
+# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH            =
+
+# You can include diagrams made with dia in doxygen documentation. Doxygen will
+# then run dia to produce the diagram and insert it in the documentation. The
+# DIA_PATH tag allows you to specify the directory where the dia binary resides.
+# If left empty dia is assumed to be found in the default search path.
+
+DIA_PATH               =
+
+# If set to YES the inheritance and collaboration graphs will hide inheritance
+# and usage relations if the target is undocumented or is not a class.
+# The default value is: YES.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz (see:
+# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
+# Bell Labs. The other options in this section have no effect if this option is
+# set to NO
+# The default value is: NO.
+
+HAVE_DOT               = NO
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
+# to run in parallel. When set to 0 doxygen will base this on the number of
+# processors available in the system. You can set it explicitly to a value
+# larger than 0 to get control over the balance between CPU load and processing
+# speed.
+# Minimum value: 0, maximum value: 32, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_NUM_THREADS        = 0
+
+# When you want a differently looking font in the dot files that doxygen
+# generates you can specify the font name using DOT_FONTNAME. You need to make
+# sure dot is able to find the font, which can be done by putting it in a
+# standard location or by setting the DOTFONTPATH environment variable or by
+# setting DOT_FONTPATH to the directory containing the font.
+# The default value is: Helvetica.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTNAME           = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
+# dot graphs.
+# Minimum value: 4, maximum value: 24, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTSIZE           = 10
+
+# By default doxygen will tell dot to use the default font as specified with
+# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
+# the path where dot can find it using this tag.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTPATH           =
+
+# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
+# each documented class showing the direct and indirect inheritance relations.
+# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
+# graph for each documented class showing the direct and indirect implementation
+# dependencies (inheritance, containment, and class references variables) of the
+# class with other documented classes.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
+# groups, showing the direct groups dependencies.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LOOK               = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
+# class node. If there are many fields or methods and many nodes the graph may
+# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
+# number of items for each type to make the size more manageable. Set this to 0
+# for no limit. Note that the threshold may be exceeded by 50% before the limit
+# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
+# but if the number exceeds 15, the total amount of fields shown is limited to
+# 10.
+# Minimum value: 0, maximum value: 100, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LIMIT_NUM_FIELDS   = 10
+
+# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
+# collaboration graphs will show the relations between templates and their
+# instances.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
+# YES then doxygen will generate a graph for each documented file showing the
+# direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDE_GRAPH          = YES
+
+# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
+# set to YES then doxygen will generate a graph for each documented file showing
+# the direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command. Disabling a call graph can be
+# accomplished by means of the command \hidecallgraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command. Disabling a caller graph can be
+# accomplished by means of the command \hidecallergraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
+# hierarchy of all classes instead of a textual one.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
+# dependencies a directory has on other directories in a graphical way. The
+# dependency relations are determined by the #include relations between the
+# files in the directories.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. For an explanation of the image formats see the section
+# output formats in the documentation of the dot tool (Graphviz (see:
+# http://www.graphviz.org/)).
+# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
+# to make the SVG files visible in IE 9+ (other browsers do not have this
+# requirement).
+# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo,
+# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
+# png:gdiplus:gdiplus.
+# The default value is: png.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_IMAGE_FORMAT       = png
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+#
+# Note that this requires a modern browser other than Internet Explorer. Tested
+# and working are Firefox, Chrome, Safari, and Opera.
+# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
+# the SVG files visible. Older versions of IE do not have SVG support.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INTERACTIVE_SVG        = NO
+
+# The DOT_PATH tag can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_PATH               =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the \dotfile
+# command).
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOTFILE_DIRS           =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the \mscfile
+# command).
+
+MSCFILE_DIRS           =
+
+# The DIAFILE_DIRS tag can be used to specify one or more directories that
+# contain dia files that are included in the documentation (see the \diafile
+# command).
+
+DIAFILE_DIRS           =
+
+# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
+# path where java can find the plantuml.jar file. If left blank, it is assumed
+# PlantUML is not used or called during a preprocessing step. Doxygen will
+# generate a warning when it encounters a \startuml command in this case and
+# will not generate output for the diagram.
+
+PLANTUML_JAR_PATH      =
+
+# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a
+# configuration file for plantuml.
+
+PLANTUML_CFG_FILE      =
+
+# When using plantuml, the specified paths are searched for files specified by
+# the !include statement in a plantuml block.
+
+PLANTUML_INCLUDE_PATH  =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
+# that will be shown in the graph. If the number of nodes in a graph becomes
+# larger than this value, doxygen will truncate the graph, which is visualized
+# by representing a node as a red box. Note that doxygen if the number of direct
+# children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
+# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# Minimum value: 0, maximum value: 10000, default value: 50.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
+# generated by dot. A depth value of 3 means that only nodes reachable from the
+# root by following a path via at most 3 edges will be shown. Nodes that lay
+# further from the root node will be omitted. Note that setting this option to 1
+# or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+# Minimum value: 0, maximum value: 1000, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not seem
+# to support this out of the box.
+#
+# Warning: Depending on the platform used, enabling this option may lead to
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
+# read).
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10) support
+# this, this feature is disabled by default.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
+# explaining the meaning of the various boxes and arrows in the dot generated
+# graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot
+# files that are used to generate the various graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_CLEANUP            = YES

+ 2566 - 0
3rdparty/rapidyaml/ext/c4core/cmake/Doxyfile.in

@@ -0,0 +1,2566 @@
+# Doxyfile 1.8.15
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the configuration
+# file that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# https://www.gnu.org/software/libiconv/ for the list of possible encodings.
+# The default value is: UTF-8.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
+
+PROJECT_NAME           = @_PROJ@
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
+
+PROJECT_NUMBER         = @_VERSION@
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF          = @_PROJ_BRIEF@
+
+# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
+# in the documentation. The maximum height of the logo should not exceed 55
+# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
+# the logo to the output directory.
+
+PROJECT_LOGO           =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
+# into which the generated documentation will be written. If a relative path is
+# entered, it will be relative to the location where doxygen was started. If
+# left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = @_OUTPUT_DIR@
+
+# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
+# directories (in 2 levels) under the output directory of each output format and
+# will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system.
+# The default value is: NO.
+
+CREATE_SUBDIRS         = NO
+
+# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
+# characters to appear in the names of generated files. If set to NO, non-ASCII
+# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
+# U+3044.
+# The default value is: NO.
+
+ALLOW_UNICODE_NAMES    = YES
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
+# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
+# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
+# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
+# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
+# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
+# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
+# Ukrainian and Vietnamese.
+# The default value is: English.
+
+OUTPUT_LANGUAGE        = English
+
+# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all generated output in the proper direction.
+# Possible values are: None, LTR, RTL and Context.
+# The default value is: None.
+
+OUTPUT_TEXT_DIRECTION  = None
+
+# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
+# descriptions after the members that are listed in the file and class
+# documentation (similar to Javadoc). Set to NO to disable this.
+# The default value is: YES.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief
+# description of a member or function before the detailed description
+#
+# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+# The default value is: YES.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator that is
+# used to form the text in various listings. Each string in this list, if found
+# as the leading text of the brief description, will be stripped from the text
+# and the result, after processing the whole list, is used as the annotated
+# text. Otherwise, the brief description is used as-is. If left blank, the
+# following values are used ($name is automatically replaced with the name of
+# the entity):The $name class, The $name widget, The $name file, is, provides,
+# specifies, contains, represents, a, an and the.
+
+ABBREVIATE_BRIEF       = "The $name class" \
+                         "The $name widget" \
+                         "The $name file" \
+                         is \
+                         provides \
+                         specifies \
+                         contains \
+                         represents \
+                         a \
+                         an \
+                         the
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# doxygen will generate a detailed section even if there is only a brief
+# description.
+# The default value is: NO.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+# The default value is: NO.
+
+INLINE_INHERITED_MEMB  = YES
+
+# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
+# before files name in the file list and in the header files. If set to NO the
+# shortest path that makes the file name unique will be used
+# The default value is: YES.
+
+FULL_PATH_NAMES        = YES
+
+# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
+# Stripping is only done if one of the specified strings matches the left-hand
+# part of the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the path to
+# strip.
+#
+# Note that you can specify absolute paths here, but also relative paths, which
+# will be relative from the directory where doxygen is started.
+# This tag requires that the tag FULL_PATH_NAMES is set to YES.
+
+STRIP_FROM_PATH        = @_STRIP_FROM_PATH@
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
+# path mentioned in the documentation of a class, which tells the reader which
+# header file to include in order to use a class. If left blank only the name of
+# the header file containing the class definition is used. Otherwise one should
+# specify the list of include paths that are normally passed to the compiler
+# using the -I flag.
+
+STRIP_FROM_INC_PATH    = @_STRIP_FROM_INC_PATH@
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
+# less readable) file names. This can be useful is your file systems doesn't
+# support long names like on DOS, Mac, or CD-ROM.
+# The default value is: NO.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
+# first line (until the first dot) of a Javadoc-style comment as the brief
+# description. If set to NO, the Javadoc-style will behave just like regular Qt-
+# style comments (thus requiring an explicit @brief command for a brief
+# description.)
+# The default value is: NO.
+
+JAVADOC_AUTOBRIEF      = YES
+
+# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
+# line (until the first dot) of a Qt-style comment as the brief description. If
+# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
+# requiring an explicit \brief command for a brief description.)
+# The default value is: NO.
+
+QT_AUTOBRIEF           = YES
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
+# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
+# a brief description. This used to be the default behavior. The new default is
+# to treat a multi-line C++ comment block as a detailed description. Set this
+# tag to YES if you prefer the old behavior instead.
+#
+# Note that setting this tag to YES also means that rational rose comments are
+# not recognized any more.
+# The default value is: NO.
+
+MULTILINE_CPP_IS_BRIEF = YES
+
+# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
+# documentation from any documented member that it re-implements.
+# The default value is: YES.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new
+# page for each member. If set to NO, the documentation of a member will be part
+# of the file/class/namespace that contains it.
+# The default value is: NO.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
+# uses this value to replace tabs by spaces in code fragments.
+# Minimum value: 1, maximum value: 16, default value: 4.
+
+TAB_SIZE               = 4
+
+# This tag can be used to specify a number of aliases that act as commands in
+# the documentation. An alias has the form:
+# name=value
+# For example adding
+# "sideeffect=@par Side Effects:\n"
+# will allow you to put the command \sideeffect (or @sideeffect) in the
+# documentation, which will result in a user-defined paragraph with heading
+# "Side Effects:". You can put \n's in the value part of an alias to insert
+# newlines (in the resulting output). You can put ^^ in the value part of an
+# alias to insert a newline as if a physical newline was in the original file.
+# When you need a literal { or } or , in the value part of an alias you have to
+# escape them by means of a backslash (\), this can lead to conflicts with the
+# commands \{ and \} for these it is advised to use the version @{ and @} or use
+# a double escape (\\{ and \\})
+
+ALIASES                =
+
+# This tag can be used to specify a number of word-keyword mappings (TCL only).
+# A mapping has the form "name=value". For example adding "class=itcl::class"
+# will allow you to use the command class in the itcl::class meaning.
+
+TCL_SUBST              =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C. For
+# instance, some of the names that are used will be different. The list of all
+# members will be omitted, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
+# Python sources only. Doxygen will then generate output that is more tailored
+# for that language. For instance, namespaces will be presented as packages,
+# qualified scopes will look different, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources. Doxygen will then generate output that is tailored for Fortran.
+# The default value is: NO.
+
+OPTIMIZE_FOR_FORTRAN   = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for VHDL.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_VHDL   = NO
+
+# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice
+# sources only. Doxygen will then generate output that is more tailored for that
+# language. For instance, namespaces will be presented as modules, types will be
+# separated into more groups, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_SLICE  = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given
+# extension. Doxygen has a built-in mapping, but you can override or extend it
+# using this tag. The format is ext=language, where ext is a file extension, and
+# language is one of the parsers supported by doxygen: IDL, Java, Javascript,
+# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice,
+# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
+# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
+# tries to guess whether the code is fixed or free formatted code, this is the
+# default for Fortran type files), VHDL, tcl. For instance to make doxygen treat
+# .inc files as Fortran files (default is PHP), and .f files as C (default is
+# Fortran), use: inc=Fortran f=C.
+#
+# Note: For files without extension you can use no_extension as a placeholder.
+#
+# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
+# the files are not read by doxygen.
+
+EXTENSION_MAPPING      =
+
+# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
+# according to the Markdown format, which allows for more readable
+# documentation. See https://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you can
+# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
+# case of backward compatibilities issues.
+# The default value is: YES.
+
+MARKDOWN_SUPPORT       = YES
+
+# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up
+# to that level are automatically included in the table of contents, even if
+# they do not have an id attribute.
+# Note: This feature currently applies only to Markdown headings.
+# Minimum value: 0, maximum value: 99, default value: 0.
+# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
+
+TOC_INCLUDE_HEADINGS   = 4
+
+# When enabled doxygen tries to link words that correspond to documented
+# classes, or namespaces to their corresponding documentation. Such a link can
+# be prevented in individual cases by putting a % sign in front of the word or
+# globally by setting AUTOLINK_SUPPORT to NO.
+# The default value is: YES.
+
+AUTOLINK_SUPPORT       = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should set this
+# tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string);
+# versus func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+# The default value is: NO.
+
+BUILTIN_STL_SUPPORT    = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+# The default value is: NO.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
+# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen
+# will parse them like normal C++ but will assume all classes use public instead
+# of private inheritance when no explicit protection keyword is present.
+# The default value is: NO.
+
+SIP_SUPPORT            = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate
+# getter and setter methods for a property. Setting this option to YES will make
+# doxygen to replace the get and set methods by a property in the documentation.
+# This will only work if the methods are indeed getting or setting a simple
+# type. If this is not the case, or you want to show the methods anyway, you
+# should set this option to NO.
+# The default value is: YES.
+
+IDL_PROPERTY_SUPPORT   = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+# The default value is: NO.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# If one adds a struct or class to a group and this option is enabled, then also
+# any nested class or struct is added to the same group. By default this option
+# is disabled and one has to add nested compounds explicitly via \ingroup.
+# The default value is: NO.
+
+GROUP_NESTED_COMPOUNDS = YES
+
+# Set the SUBGROUPING tag to YES to allow class member groups of the same type
+# (for instance a group of public functions) to be put as a subgroup of that
+# type (e.g. under the Public Functions section). Set it to NO to prevent
+# subgrouping. Alternatively, this can be done per class using the
+# \nosubgrouping command.
+# The default value is: YES.
+
+SUBGROUPING            = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
+# are shown inside the group in which they are included (e.g. using \ingroup)
+# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
+# and RTF).
+#
+# Note that this feature does not work in combination with
+# SEPARATE_MEMBER_PAGES.
+# The default value is: NO.
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
+# with only public data fields or simple typedef fields will be shown inline in
+# the documentation of the scope in which they are defined (i.e. file,
+# namespace, or group documentation), provided this scope is documented. If set
+# to NO, structs, classes, and unions are shown on a separate page (for HTML and
+# Man pages) or section (for LaTeX and RTF).
+# The default value is: NO.
+
+INLINE_SIMPLE_STRUCTS  = YES
+
+# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
+# enum is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically be
+# useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+# The default value is: NO.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
+# cache is used to resolve symbols given their name and scope. Since this can be
+# an expensive process and often the same symbol appears multiple times in the
+# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
+# doxygen will become slower. If the cache is too large, memory is wasted. The
+# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
+# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
+# symbols. At the end of a run doxygen will report the cache usage and suggest
+# the optimal cache size from a speed point of view.
+# Minimum value: 0, maximum value: 9, default value: 0.
+
+LOOKUP_CACHE_SIZE      = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
+# documentation are documented, even if no documentation was available. Private
+# class members and static file members will be hidden unless the
+# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
+# Note: This will also disable the warnings about undocumented members that are
+# normally produced when WARNINGS is set to YES.
+# The default value is: NO.
+
+EXTRACT_ALL            = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
+# be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
+# scope will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PACKAGE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
+# included in the documentation.
+# The default value is: NO.
+
+EXTRACT_STATIC         = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
+# locally in source files will be included in the documentation. If set to NO,
+# only classes defined in header files are included. Does not have any effect
+# for Java sources.
+# The default value is: YES.
+
+EXTRACT_LOCAL_CLASSES  = NO
+
+# This flag is only useful for Objective-C code. If set to YES, local methods,
+# which are defined in the implementation section but not in the interface are
+# included in the documentation. If set to NO, only methods in the interface are
+# included.
+# The default value is: NO.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base name of
+# the file that contains the anonymous namespace. By default anonymous namespace
+# are hidden.
+# The default value is: NO.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
+# undocumented members inside documented classes or files. If set to NO these
+# members will be included in the various overviews, but no documentation
+# section is generated. This option has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy. If set
+# to NO, these classes will be included in the various overviews. This option
+# has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
+# (class|struct|union) declarations. If set to NO, these declarations will be
+# included in the documentation.
+# The default value is: NO.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
+# documentation blocks found inside the body of a function. If set to NO, these
+# blocks will be appended to the function's detailed documentation block.
+# The default value is: NO.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation that is typed after a
+# \internal command is included. If the tag is set to NO then the documentation
+# will be excluded. Set it to YES to include the internal documentation.
+# The default value is: NO.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
+# names in lower-case letters. If set to YES, upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+# The default value is: system dependent.
+
+CASE_SENSE_NAMES       = NO
+
+# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
+# their full class and namespace scopes in the documentation. If set to YES, the
+# scope will be hidden.
+# The default value is: NO.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
+# append additional text to a page's title, such as Class Reference. If set to
+# YES the compound reference will be hidden.
+# The default value is: NO.
+
+HIDE_COMPOUND_REFERENCE= NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
+# the files that are included by a file in the documentation of that file.
+# The default value is: YES.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
+# grouped member an include statement to the documentation, telling the reader
+# which file to include in order to use the member.
+# The default value is: NO.
+
+SHOW_GROUPED_MEMB_INC  = YES
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
+# files with double quotes in the documentation rather than with sharp brackets.
+# The default value is: NO.
+
+FORCE_LOCAL_INCLUDES   = NO
+
+# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
+# documentation for inline members.
+# The default value is: YES.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
+# (detailed) documentation of file and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order.
+# The default value is: YES.
+
+SORT_MEMBER_DOCS       = NO
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
+# descriptions of file, namespace and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order. Note that
+# this will also influence the order of the classes in the class list.
+# The default value is: NO.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
+# (brief and detailed) documentation of class members so that constructors and
+# destructors are listed first. If set to NO the constructors will appear in the
+# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
+# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
+# member documentation.
+# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
+# detailed member documentation.
+# The default value is: NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
+# of group names into alphabetical order. If set to NO the group names will
+# appear in their defined order.
+# The default value is: NO.
+
+SORT_GROUP_NAMES       = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
+# fully-qualified names, including namespaces. If set to NO, the class list will
+# be sorted only by class name, not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the alphabetical
+# list.
+# The default value is: NO.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
+# type resolution of all parameters of a function it will reject a match between
+# the prototype and the implementation of a member function even if there is
+# only one candidate or it is obvious which candidate to choose by doing a
+# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
+# accept a match between prototype and implementation in such cases.
+# The default value is: NO.
+
+STRICT_PROTO_MATCHING  = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo
+# list. This list is created by putting \todo commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test
+# list. This list is created by putting \test commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug
+# list. This list is created by putting \bug commands in the documentation.
+# The default value is: YES.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)
+# the deprecated list. This list is created by putting \deprecated commands in
+# the documentation.
+# The default value is: YES.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional documentation
+# sections, marked by \if <section_label> ... \endif and \cond <section_label>
+# ... \endcond blocks.
+
+ENABLED_SECTIONS       =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
+# initial value of a variable or macro / define can have for it to appear in the
+# documentation. If the initializer consists of more lines than specified here
+# it will be hidden. Use a value of 0 to hide initializers completely. The
+# appearance of the value of individual variables and macros / defines can be
+# controlled using \showinitializer or \hideinitializer command in the
+# documentation regardless of this setting.
+# Minimum value: 0, maximum value: 10000, default value: 30.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
+# the bottom of the documentation of classes and structs. If set to YES, the
+# list will mention the files that were used to generate the documentation.
+# The default value is: YES.
+
+SHOW_USED_FILES        = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
+# will remove the Files entry from the Quick Index and from the Folder Tree View
+# (if specified).
+# The default value is: YES.
+
+SHOW_FILES             = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
+# page. This will remove the Namespaces entry from the Quick Index and from the
+# Folder Tree View (if specified).
+# The default value is: YES.
+
+SHOW_NAMESPACES        = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command command input-file, where command is the value of the
+# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
+# by doxygen. Whatever the program writes to standard output is used as the file
+# version. For an example see the documentation.
+
+FILE_VERSION_FILTER    =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option. You can
+# optionally specify a file name after the option, if omitted DoxygenLayout.xml
+# will be used as the name of the layout file.
+#
+# Note that if you run doxygen from a directory containing a file called
+# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
+# tag is left empty.
+
+LAYOUT_FILE            =
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
+# the reference definitions. This must be a list of .bib files. The .bib
+# extension is automatically appended if omitted. This requires the bibtex tool
+# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info.
+# For LaTeX the style of the bibliography can be controlled using
+# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
+# search path. See also \cite for info how to create references.
+
+CITE_BIB_FILES         =
+
+#---------------------------------------------------------------------------
+# Configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated to
+# standard output by doxygen. If QUIET is set to YES this implies that the
+# messages are off.
+# The default value is: NO.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
+# this implies that the warnings are on.
+#
+# Tip: Turn warnings on while writing the documentation.
+# The default value is: YES.
+
+WARNINGS               = YES
+
+# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
+# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: YES.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some parameters
+# in a documented function, or documenting parameters that don't exist or using
+# markup commands wrongly.
+# The default value is: YES.
+
+WARN_IF_DOC_ERROR      = YES
+
+# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
+# are documented, but have no documentation for their parameters or return
+# value. If set to NO, doxygen will only warn about wrong or incomplete
+# parameter documentation, but not about the absence of documentation. If
+# EXTRACT_ALL is set to YES then this flag will automatically be disabled.
+# The default value is: NO.
+
+WARN_NO_PARAMDOC       = NO
+
+# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
+# a warning is encountered.
+# The default value is: NO.
+
+WARN_AS_ERROR          = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that doxygen
+# can produce. The string should contain the $file, $line, and $text tags, which
+# will be replaced by the file and line number from which the warning originated
+# and the warning text. Optionally the format may contain $version, which will
+# be replaced by the version of the file (if it could be obtained via
+# FILE_VERSION_FILTER)
+# The default value is: $file:$line: $text.
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning and error
+# messages should be written. If left blank the output is written to standard
+# error (stderr).
+
+WARN_LOGFILE           =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag is used to specify the files and/or directories that contain
+# documented source files. You may enter file names like myfile.cpp or
+# directories like /usr/src/myproject. Separate the files or directories with
+# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
+# Note: If this tag is empty the current directory is searched.
+
+INPUT                  = @_INPUT@
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
+# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
+# documentation (see: https://www.gnu.org/software/libiconv/) for the list of
+# possible encodings.
+# The default value is: UTF-8.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
+# *.h) to filter out the source-files in the directories.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# read by doxygen.
+#
+# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
+# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
+# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
+# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,
+# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice.
+
+FILE_PATTERNS          = *.c \
+                         *.cc \
+                         *.cxx \
+                         *.cpp \
+                         *.c++ \
+                         *.java \
+                         *.ii \
+                         *.ixx \
+                         *.ipp \
+                         *.i++ \
+                         *.inl \
+                         *.idl \
+                         *.ddl \
+                         *.odl \
+                         *.h \
+                         *.hh \
+                         *.hxx \
+                         *.hpp \
+                         *.h++ \
+                         *.cs \
+                         *.d \
+                         *.php \
+                         *.php4 \
+                         *.php5 \
+                         *.phtml \
+                         *.inc \
+                         *.m \
+                         *.markdown \
+                         *.md \
+                         *.mm \
+                         *.dox \
+                         *.py \
+                         *.pyw \
+                         *.f90 \
+                         *.f95 \
+                         *.f03 \
+                         *.f08 \
+                         *.f \
+                         *.for \
+                         *.tcl \
+                         *.vhd \
+                         *.vhdl \
+                         *.ucf \
+                         *.qsf \
+                         *.ice \
+                         @_FILE_PATTERNS@
+
+# The RECURSIVE tag can be used to specify whether or not subdirectories should
+# be searched for input files as well.
+# The default value is: NO.
+
+RECURSIVE              = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+#
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE                = @_EXCLUDE@
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+# The default value is: NO.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       = @_EXCLUDE_PATTERNS@
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories use the pattern */test/*
+
+EXCLUDE_SYMBOLS        = @_EXCLUDE_SYMBOLS@
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or directories
+# that contain example code fragments that are included (see the \include
+# command).
+
+EXAMPLE_PATH           = @_EXAMPLE_PATH@
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank all
+# files are included.
+
+EXAMPLE_PATTERNS       = *
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude commands
+# irrespective of the value of the RECURSIVE tag.
+# The default value is: NO.
+
+EXAMPLE_RECURSIVE      = YES
+
+# The IMAGE_PATH tag can be used to specify one or more files or directories
+# that contain images that are to be included in the documentation (see the
+# \image command).
+
+IMAGE_PATH             =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command:
+#
+# <filter> <input-file>
+#
+# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
+# name of an input file. Doxygen will then use the output that the filter
+# program writes to standard output. If FILTER_PATTERNS is specified, this tag
+# will be ignored.
+#
+# Note that the filter must not add or remove lines; it is applied before the
+# code is scanned, but not when the output code is generated. If lines are added
+# or removed, the anchors will not be placed correctly.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+INPUT_FILTER           =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form: pattern=filter
+# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
+# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
+# patterns match the file name, INPUT_FILTER is applied.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+FILTER_PATTERNS        =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will also be used to filter the input files that are used for
+# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# The default value is: NO.
+
+FILTER_SOURCE_FILES    = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
+# it is also possible to disable source filtering for a specific pattern using
+# *.ext= (so without naming a filter).
+# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
+
+FILTER_SOURCE_PATTERNS =
+
+# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
+# is part of the input, its contents will be placed on the main page
+# (index.html). This can be useful if you have a project on for instance GitHub
+# and want to reuse the introduction page also for the doxygen output.
+
+USE_MDFILE_AS_MAINPAGE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
+# generated. Documented entities will be cross-referenced with these sources.
+#
+# Note: To get rid of all source code in the generated output, make sure that
+# also VERBATIM_HEADERS is set to NO.
+# The default value is: NO.
+
+SOURCE_BROWSER         = YES
+
+# Setting the INLINE_SOURCES tag to YES will include the body of functions,
+# classes and enums directly into the documentation.
+# The default value is: NO.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
+# special comment blocks from generated source code fragments. Normal C, C++ and
+# Fortran comments will always remain visible.
+# The default value is: YES.
+
+STRIP_CODE_COMMENTS    = NO
+
+# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
+# entity all documented functions referencing it will be listed.
+# The default value is: NO.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES then for each documented function
+# all documented entities called/used by that function will be listed.
+# The default value is: NO.
+
+REFERENCES_RELATION    = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
+# to YES then the hyperlinks from functions in REFERENCES_RELATION and
+# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
+# link to the documentation.
+# The default value is: YES.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
+# source code will show a tooltip with additional information such as prototype,
+# brief description and links to the definition and documentation. Since this
+# will make the HTML file larger and loading of large files a bit slower, you
+# can opt to disable this feature.
+# The default value is: YES.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+SOURCE_TOOLTIPS        = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code will
+# point to the HTML generated by the htags(1) tool instead of doxygen built-in
+# source browser. The htags tool is part of GNU's global source tagging system
+# (see https://www.gnu.org/software/global/global.html). You will need version
+# 4.8.6 or higher.
+#
+# To use it do the following:
+# - Install the latest version of global
+# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file
+# - Make sure the INPUT points to the root of the source tree
+# - Run doxygen as normal
+#
+# Doxygen will invoke htags (and that will in turn invoke gtags), so these
+# tools must be available from the command line (i.e. in the search path).
+#
+# The result: instead of the source browser generated by doxygen, the links to
+# source code will now point to the output of htags.
+# The default value is: NO.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
+# verbatim copy of the header file for each class for which an include is
+# specified. Set to NO to disable this.
+# See also: Section \class.
+# The default value is: YES.
+
+VERBATIM_HEADERS       = YES
+
+# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the
+# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the
+# cost of reduced performance. This can be particularly helpful with template
+# rich C++ code for which doxygen's built-in parser lacks the necessary type
+# information.
+# Note: The availability of this option depends on whether or not doxygen was
+# generated with the -Duse_libclang=ON option for CMake.
+# The default value is: NO.
+
+CLANG_ASSISTED_PARSING = YES
+
+# If clang assisted parsing is enabled you can provide the compiler with command
+# line options that you would normally use when invoking the compiler. Note that
+# the include paths will already be set by doxygen for the files and directories
+# specified with INPUT and INCLUDE_PATH.
+# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
+
+CLANG_OPTIONS          =
+
+# If clang assisted parsing is enabled you can provide the clang parser with the
+# path to the compilation database (see:
+# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) used when the files
+# were built. This is equivalent to specifying the "-p" option to a clang tool,
+# such as clang-check. These options will then be passed to the parser.
+# Note: The availability of this option depends on whether or not doxygen was
+# generated with the -Duse_libclang=ON option for CMake.
+
+CLANG_DATABASE_PATH    = @_CLANG_DATABASE_PATH@
+
+#---------------------------------------------------------------------------
+# Configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
+# compounds will be generated. Enable this if the project contains a lot of
+# classes, structs, unions or interfaces.
+# The default value is: YES.
+
+ALPHABETICAL_INDEX     = YES
+
+# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in
+# which the alphabetical index list will be split.
+# Minimum value: 1, maximum value: 20, default value: 5.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all classes will
+# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
+# can be used to specify a prefix (or a list of prefixes) that should be ignored
+# while generating the index headers.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+IGNORE_PREFIX          =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
+# The default value is: YES.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
+# generated HTML page (for example: .htm, .php, .asp).
+# The default value is: .html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
+# each generated HTML page. If the tag is left blank doxygen will generate a
+# standard header.
+#
+# To get valid HTML the header file that includes any scripts and style sheets
+# that doxygen needs, which is dependent on the configuration options used (e.g.
+# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
+# default header using
+# doxygen -w html new_header.html new_footer.html new_stylesheet.css
+# YourConfigFile
+# and then modify the file new_header.html. See also section "Doxygen usage"
+# for information on how to generate the default header that doxygen normally
+# uses.
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. For a description
+# of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_HEADER            =
+
+# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
+# generated HTML page. If the tag is left blank doxygen will generate a standard
+# footer. See HTML_HEADER for more information on how to generate a default
+# footer and what special commands can be used inside the footer. See also
+# section "Doxygen usage" for information on how to generate the default footer
+# that doxygen normally uses.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FOOTER            =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
+# sheet that is used by each HTML page. It can be used to fine-tune the look of
+# the HTML output. If left blank doxygen will generate a default style sheet.
+# See also section "Doxygen usage" for information on how to generate the style
+# sheet that doxygen normally uses.
+# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
+# it is more robust and this tag (HTML_STYLESHEET) will in the future become
+# obsolete.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_STYLESHEET        =
+
+# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# cascading style sheets that are included after the standard style sheets
+# created by doxygen. Using this option one can overrule certain style aspects.
+# This is preferred over using HTML_STYLESHEET since it does not replace the
+# standard style sheet and is therefore more robust against future updates.
+# Doxygen will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list). For an example see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_STYLESHEET  =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
+# files will be copied as-is; there are no commands or markers available.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_FILES       =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
+# will adjust the colors in the style sheet and background images according to
+# this color. Hue is specified as an angle on a colorwheel, see
+# https://en.wikipedia.org/wiki/Hue for more information. For instance the value
+# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
+# purple, and 360 is red again.
+# Minimum value: 0, maximum value: 359, default value: 220.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_HUE    = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
+# in the HTML output. For a value of 0 the output will use grayscales only. A
+# value of 255 will produce the most vivid colors.
+# Minimum value: 0, maximum value: 255, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_SAT    = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
+# luminance component of the colors in the HTML output. Values below 100
+# gradually make the output lighter, whereas values above 100 make the output
+# darker. The value divided by 100 is the actual gamma applied, so 80 represents
+# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
+# change the gamma.
+# Minimum value: 40, maximum value: 240, default value: 80.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_GAMMA  = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting this
+# to YES can help to show when doxygen was last run and thus if the
+# documentation is up to date.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_TIMESTAMP         = NO
+
+# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
+# documentation will contain a main index with vertical navigation menus that
+# are dynamically created via Javascript. If disabled, the navigation index will
+# consists of multiple levels of tabs that are statically embedded in every HTML
+# page. Disable this option to support browsers that do not have Javascript,
+# like the Qt help browser.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_MENUS     = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
+# shown in the various tree structured indices initially; the user can expand
+# and collapse entries dynamically later on. Doxygen will expand the tree to
+# such a level that at most the specified number of entries are visible (unless
+# a fully collapsed tree already exceeds this amount). So setting the number of
+# entries 1 will produce a full collapsed tree by default. 0 is a special value
+# representing an infinite number of entries and will result in a full expanded
+# tree by default.
+# Minimum value: 0, maximum value: 9999, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files will be
+# generated that can be used as input for Apple's Xcode 3 integrated development
+# environment (see: https://developer.apple.com/xcode/), introduced with OSX
+# 10.5 (Leopard). To create a documentation set, doxygen will generate a
+# Makefile in the HTML output directory. Running make will produce the docset in
+# that directory and running make install will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
+# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy
+# genXcode/_index.html for more information.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_DOCSET        = NO
+
+# This tag determines the name of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# The default value is: Doxygen generated docs.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+
+# This tag specifies a string that should uniquely identify the documentation
+# set bundle. This should be a reverse domain-name style string, e.g.
+# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+
+# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+# The default value is: org.doxygen.Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_ID    = org.doxygen.Publisher
+
+# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
+# The default value is: Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_NAME  = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
+# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
+# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
+# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on
+# Windows.
+#
+# The HTML Help Workshop contains a compiler that can convert all HTML output
+# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
+# files are now used as the Windows 98 help format, and will replace the old
+# Windows help format (.hlp) on all Windows platforms in the future. Compressed
+# HTML files also contain an index, a table of contents, and you can search for
+# words in the documentation. The HTML workshop also contains a viewer for
+# compressed HTML files.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_HTMLHELP      = NO
+
+# The CHM_FILE tag can be used to specify the file name of the resulting .chm
+# file. You can add a path in front of the file if the result should not be
+# written to the html output directory.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_FILE               =
+
+# The HHC_LOCATION tag can be used to specify the location (absolute path
+# including file name) of the HTML help compiler (hhc.exe). If non-empty,
+# doxygen will try to run the HTML help compiler on the generated index.hhp.
+# The file has to be specified with full path.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+HHC_LOCATION           =
+
+# The GENERATE_CHI flag controls if a separate .chi index file is generated
+# (YES) or that it should be included in the master .chm file (NO).
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+GENERATE_CHI           = NO
+
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
+# and project file content.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_INDEX_ENCODING     =
+
+# The BINARY_TOC flag controls whether a binary table of contents is generated
+# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it
+# enables the Previous and Next buttons.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members to
+# the table of contents of the HTML help documentation and to the tree view.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+TOC_EXPAND             = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
+# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
+# (.qch) of the generated HTML documentation.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_QHP           = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
+# the file name of the resulting .qch file. The path specified is relative to
+# the HTML output folder.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QCH_FILE               =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
+# Project output. For more information please see Qt Help Project / Namespace
+# (see: http://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_NAMESPACE          = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
+# Help Project output. For more information please see Qt Help Project / Virtual
+# Folders (see: http://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-
+# folders).
+# The default value is: doc.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_VIRTUAL_FOLDER     = doc
+
+# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
+# filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_NAME   =
+
+# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_ATTRS  =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's filter section matches. Qt Help Project / Filter Attributes (see:
+# http://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_SECT_FILTER_ATTRS  =
+
+# The QHG_LOCATION tag can be used to specify the location of Qt's
+# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
+# generated .qhp file.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHG_LOCATION           =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
+# generated, together with the HTML files, they form an Eclipse help plugin. To
+# install this plugin and make it available under the help contents menu in
+# Eclipse, the contents of the directory containing the HTML and XML files needs
+# to be copied into the plugins directory of eclipse. The name of the directory
+# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
+# After copying Eclipse needs to be restarted before the help appears.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_ECLIPSEHELP   = NO
+
+# A unique identifier for the Eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have this
+# name. Each documentation set should have its own identifier.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
+
+ECLIPSE_DOC_ID         = org.doxygen.Project
+
+# If you want full control over the layout of the generated HTML pages it might
+# be necessary to disable the index and replace it with your own. The
+# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
+# of each HTML page. A value of NO enables the index and the value YES disables
+# it. Since the tabs in the index contain the same information as the navigation
+# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+DISABLE_INDEX          = NO
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information. If the tag
+# value is set to YES, a side panel will be generated containing a tree-like
+# index structure (just like the one that is generated for HTML Help). For this
+# to work a browser that supports JavaScript, DHTML, CSS and frames is required
+# (i.e. any modern browser). Windows users are probably better off using the
+# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
+# further fine-tune the look of the index. As an example, the default style
+# sheet generated by doxygen has an example that shows how to put an image at
+# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
+# the same information as the tab index, you could consider setting
+# DISABLE_INDEX to YES when enabling this option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_TREEVIEW      = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
+# doxygen will group on one line in the generated HTML documentation.
+#
+# Note that a value of 0 will completely suppress the enum values from appearing
+# in the overview section.
+# Minimum value: 0, maximum value: 20, default value: 4.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
+# to set the initial width (in pixels) of the frame in which the tree is shown.
+# Minimum value: 0, maximum value: 1500, default value: 250.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+TREEVIEW_WIDTH         = 250
+
+# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
+# external symbols imported via tag files in a separate window.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+EXT_LINKS_IN_WINDOW    = NO
+
+# Use this tag to change the font size of LaTeX formulas included as images in
+# the HTML documentation. When you change the font size after a successful
+# doxygen run you need to manually remove any form_*.png images from the HTML
+# output directory to force them to be regenerated.
+# Minimum value: 8, maximum value: 50, default value: 10.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_FONTSIZE       = 10
+
+# Use the FORMULA_TRANSPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are not
+# supported properly for IE 6.0, but are supported on all modern browsers.
+#
+# Note that when changing this option you need to delete any form_*.png files in
+# the HTML output directory before the changes have effect.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_TRANSPARENT    = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
+# https://www.mathjax.org) which uses client side Javascript for the rendering
+# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
+# installed or if you want to formulas look prettier in the HTML output. When
+# enabled you may also need to install MathJax separately and configure the path
+# to it using the MATHJAX_RELPATH option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+USE_MATHJAX            = NO
+
+# When MathJax is enabled you can set the default output format to be used for
+# the MathJax output. See the MathJax site (see:
+# http://docs.mathjax.org/en/latest/output.html) for more details.
+# Possible values are: HTML-CSS (which is slower, but has the best
+# compatibility), NativeMML (i.e. MathML) and SVG.
+# The default value is: HTML-CSS.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_FORMAT         = HTML-CSS
+
+# When MathJax is enabled you need to specify the location relative to the HTML
+# output directory using the MATHJAX_RELPATH option. The destination directory
+# should contain the MathJax.js script. For instance, if the mathjax directory
+# is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
+# Content Delivery Network so you can quickly see the result without installing
+# MathJax. However, it is strongly recommended to install a local copy of
+# MathJax from https://www.mathjax.org before deployment.
+# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_RELPATH        = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
+# extension names that should be enabled during MathJax rendering. For example
+# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_EXTENSIONS     =
+
+# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
+# of code that will be used on startup of the MathJax code. See the MathJax site
+# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
+# example see the documentation.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_CODEFILE       =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
+# the HTML output. The underlying search engine uses javascript and DHTML and
+# should work on any modern browser. Note that when using HTML help
+# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
+# there is already a search function so this one should typically be disabled.
+# For large projects the javascript based search engine can be slow, then
+# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
+# search using the keyboard; to jump to the search box use <access key> + S
+# (what the <access key> is depends on the OS and browser, but it is typically
+# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
+# key> to jump into the search results window, the results can be navigated
+# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
+# the search. The filter options can be selected when the cursor is inside the
+# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
+# to select a filter and <Enter> or <escape> to activate or cancel the filter
+# option.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+SEARCHENGINE           = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a web server instead of a web client using Javascript. There
+# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
+# setting. When disabled, doxygen will generate a PHP script for searching and
+# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
+# and searching needs to be provided by external tools. See the section
+# "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SERVER_BASED_SEARCH    = NO
+
+# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
+# script for searching. Instead the search results are written to an XML file
+# which needs to be processed by an external indexer. Doxygen will invoke an
+# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
+# search results.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: https://xapian.org/).
+#
+# See the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH        = NO
+
+# The SEARCHENGINE_URL should point to a search engine hosted by a web server
+# which will return the search results when EXTERNAL_SEARCH is enabled.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: https://xapian.org/). See the section "External Indexing and
+# Searching" for details.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHENGINE_URL       =
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
+# search data is written to a file for indexing by an external tool. With the
+# SEARCHDATA_FILE tag the name of this file can be specified.
+# The default file is: searchdata.xml.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHDATA_FILE        = searchdata.xml
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
+# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
+# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
+# projects and redirect the results back to the right project.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH_ID     =
+
+# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
+# projects other than the one defined by this configuration file, but that are
+# all added to the same external search index. Each project needs to have a
+# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
+# to a relative location where the documentation can be found. The format is:
+# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTRA_SEARCH_MAPPINGS  =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
+# The default value is: YES.
+
+GENERATE_LATEX         = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked.
+#
+# Note that when not enabling USE_PDFLATEX the default is latex when enabling
+# USE_PDFLATEX the default is pdflatex and when in the later case latex is
+# chosen this is overwritten by pdflatex. For specific output languages the
+# default can have been set differently, this depends on the implementation of
+# the output language.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_CMD_NAME         =
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
+# index for LaTeX.
+# Note: This tag is used in the Makefile / make.bat.
+# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file
+# (.tex).
+# The default file is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to
+# generate index for LaTeX.
+# Note: This tag is used in the generated output file (.tex).
+# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat.
+# The default value is: \makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_MAKEINDEX_CMD    = \makeindex
+
+# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used by the
+# printer.
+# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
+# 14 inches) and executive (7.25 x 10.5 inches).
+# The default value is: a4.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PAPER_TYPE             = a4
+
+# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
+# that should be included in the LaTeX output. The package can be specified just
+# by its name or with the correct syntax as to be used with the LaTeX
+# \usepackage command. To get the times font for instance you can specify :
+# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}
+# To use the option intlimits with the amsmath package you can specify:
+# EXTRA_PACKAGES=[intlimits]{amsmath}
+# If left blank no extra packages will be included.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+EXTRA_PACKAGES         =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
+# generated LaTeX document. The header should contain everything until the first
+# chapter. If it is left blank doxygen will generate a standard header. See
+# section "Doxygen usage" for information on how to let doxygen write the
+# default header to a separate file.
+#
+# Note: Only use a user-defined header if you know what you are doing! The
+# following commands have a special meaning inside the header: $title,
+# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
+# $projectbrief, $projectlogo. Doxygen will replace $title with the empty
+# string, for the replacement values of the other commands the user is referred
+# to HTML_HEADER.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HEADER           =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
+# generated LaTeX document. The footer should contain everything after the last
+# chapter. If it is left blank doxygen will generate a standard footer. See
+# LATEX_HEADER for more information on how to generate a default footer and what
+# special commands can be used inside the footer.
+#
+# Note: Only use a user-defined footer if you know what you are doing!
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_FOOTER           =
+
+# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# LaTeX style sheets that are included after the standard style sheets created
+# by doxygen. Using this option one can overrule certain style aspects. Doxygen
+# will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list).
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_STYLESHEET =
+
+# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the LATEX_OUTPUT output
+# directory. Note that the files will be copied as-is; there are no commands or
+# markers available.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_FILES      =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
+# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
+# contain links (just like the HTML output) instead of page references. This
+# makes the output suitable for online browsing using a PDF viewer.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PDF_HYPERLINKS         = YES
+
+# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
+# the PDF file directly from the LaTeX files. Set this option to YES, to get a
+# higher quality PDF documentation.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+USE_PDFLATEX           = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
+# command to the generated LaTeX files. This will instruct LaTeX to keep running
+# if errors occur, instead of asking the user for help. This option is also used
+# when generating formulas in HTML.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BATCHMODE        = NO
+
+# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
+# index chapters (such as File Index, Compound Index, etc.) in the output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HIDE_INDICES     = NO
+
+# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
+# code with syntax highlighting in the LaTeX output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_SOURCE_CODE      = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. See
+# https://en.wikipedia.org/wiki/BibTeX and \cite for more info.
+# The default value is: plain.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BIB_STYLE        = plain
+
+# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
+# page will contain the date and time when the page was generated. Setting this
+# to NO can help when comparing the output of multiple runs.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_TIMESTAMP        = NO
+
+# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)
+# path from which the emoji images will be read. If a relative path is entered,
+# it will be relative to the LATEX_OUTPUT directory. If left blank the
+# LATEX_OUTPUT directory will be used.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EMOJI_DIRECTORY  =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The
+# RTF output is optimized for Word 97 and may not look too pretty with other RTF
+# readers/editors.
+# The default value is: NO.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: rtf.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
+# contain hyperlink fields. The RTF file will contain links (just like the HTML
+# output) instead of page references. This makes the output suitable for online
+# browsing using Word or some other Word compatible readers that support those
+# fields.
+#
+# Note: WordPad (write) and others do not support links.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# configuration file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+#
+# See also section "Doxygen usage" for information on how to generate the
+# default style sheet that doxygen normally uses.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_STYLESHEET_FILE    =
+
+# Set optional variables used in the generation of an RTF document. Syntax is
+# similar to doxygen's configuration file. A template extensions file can be
+# generated using doxygen -e rtf extensionFile.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_EXTENSIONS_FILE    =
+
+# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code
+# with syntax highlighting in the RTF output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_SOURCE_CODE        = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for
+# classes and files.
+# The default value is: NO.
+
+GENERATE_MAN           = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it. A directory man3 will be created inside the directory specified by
+# MAN_OUTPUT.
+# The default directory is: man.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to the generated
+# man pages. In case the manual section does not start with a number, the number
+# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
+# optional.
+# The default value is: .3.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_EXTENSION          = .3
+
+# The MAN_SUBDIR tag determines the name of the directory created within
+# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
+# MAN_EXTENSION with the initial . removed.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_SUBDIR             =
+
+# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
+# will generate one additional man file for each entity documented in the real
+# man page(s). These additional files only source the real man page, but without
+# them the man command would be unable to find the correct page.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
+# captures the structure of the code including all documentation.
+# The default value is: NO.
+
+GENERATE_XML           = YES
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: xml.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_OUTPUT             = xml
+
+# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program
+# listings (including syntax highlighting and cross-referencing information) to
+# the XML output. Note that enabling this will significantly increase the size
+# of the XML output.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_PROGRAMLISTING     = YES
+
+# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include
+# namespace members in file scope as well, matching the HTML output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_NS_MEMB_FILE_SCOPE = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files
+# that can be used to generate PDF.
+# The default value is: NO.
+
+GENERATE_DOCBOOK       = NO
+
+# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
+# front of it.
+# The default directory is: docbook.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_OUTPUT         = docbook
+
+# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the
+# program listings (including syntax highlighting and cross-referencing
+# information) to the DOCBOOK output. Note that enabling this will significantly
+# increase the size of the DOCBOOK output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_PROGRAMLISTING = NO
+
+#---------------------------------------------------------------------------
+# Configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
+# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures
+# the structure of the code including all documentation. Note that this feature
+# is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module
+# file that captures the structure of the code including all documentation.
+#
+# Note that this feature is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary
+# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
+# output from the Perl module output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely
+# formatted so it can be parsed by a human reader. This is useful if you want to
+# understand what is going on. On the other hand, if this tag is set to NO, the
+# size of the Perl module output will be much smaller and Perl will parse it
+# just the same.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file are
+# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
+# so different doxyrules.make files included by the same Makefile don't
+# overwrite each other's variables.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
+# C-preprocessor directives found in the sources and include files.
+# The default value is: YES.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
+# in the source code. If set to NO, only conditional compilation will be
+# performed. Macro expansion can be done in a controlled way by setting
+# EXPAND_ONLY_PREDEF to YES.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
+# the macro expansion is limited to the macros specified with the PREDEFINED and
+# EXPAND_AS_DEFINED tags.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES, the include files in the
+# INCLUDE_PATH will be searched if a #include is found.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by the
+# preprocessor.
+# This tag requires that the tag SEARCH_INCLUDES is set to YES.
+
+INCLUDE_PATH           =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will be
+# used.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+INCLUDE_FILE_PATTERNS  =
+
+# The PREDEFINED tag can be used to specify one or more macro names that are
+# defined before the preprocessor is started (similar to the -D option of e.g.
+# gcc). The argument of the tag is a list of macros of the form: name or
+# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
+# is assumed. To prevent a macro definition from being undefined via #undef or
+# recursively expanded use the := operator instead of the = operator.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+PREDEFINED             =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
+# tag can be used to specify a list of macro names that should be expanded. The
+# macro definition that is found in the sources will be used. Use the PREDEFINED
+# tag if you want to use a different macro definition that overrules the
+# definition found in the source code.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_AS_DEFINED      =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
+# remove all references to function-like macros that are alone on a line, have
+# an all uppercase name, and do not end with a semicolon. Such function macros
+# are typically used for boiler-plate code, and will confuse the parser if not
+# removed.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES tag can be used to specify one or more tag files. For each tag
+# file the location of the external documentation should be added. The format of
+# a tag file without this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where loc1 and loc2 can be relative or absolute paths or URLs. See the
+# section "Linking to external documentation" for more information about the use
+# of tag files.
+# Note: Each tag file must have a unique name (where the name does NOT include
+# the path). If a tag file is not located in the directory in which doxygen is
+# run, you must also specify the path to the tagfile here.
+
+TAGFILES               =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
+# tag file that is based on the input files it reads. See section "Linking to
+# external documentation" for more information about the usage of tag files.
+
+GENERATE_TAGFILE       =
+
+# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
+# the class index. If set to NO, only the inherited external classes will be
+# listed.
+# The default value is: NO.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will be
+# listed.
+# The default value is: YES.
+
+EXTERNAL_GROUPS        = YES
+
+# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in
+# the related pages index. If set to NO, only the current project's pages will
+# be listed.
+# The default value is: YES.
+
+EXTERNAL_PAGES         = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of 'which perl').
+# The default file (with absolute path) is: /usr/bin/perl.
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram
+# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
+# NO turns the diagrams off. Note that this option also works with HAVE_DOT
+# disabled, but it is recommended to install and use dot, since it yields more
+# powerful graphs.
+# The default value is: YES.
+
+CLASS_DIAGRAMS         = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see:
+# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH            =
+
+# You can include diagrams made with dia in doxygen documentation. Doxygen will
+# then run dia to produce the diagram and insert it in the documentation. The
+# DIA_PATH tag allows you to specify the directory where the dia binary resides.
+# If left empty dia is assumed to be found in the default search path.
+
+DIA_PATH               =
+
+# If set to YES the inheritance and collaboration graphs will hide inheritance
+# and usage relations if the target is undocumented or is not a class.
+# The default value is: YES.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz (see:
+# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
+# Bell Labs. The other options in this section have no effect if this option is
+# set to NO
+# The default value is: NO.
+
+HAVE_DOT               = NO
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
+# to run in parallel. When set to 0 doxygen will base this on the number of
+# processors available in the system. You can set it explicitly to a value
+# larger than 0 to get control over the balance between CPU load and processing
+# speed.
+# Minimum value: 0, maximum value: 32, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_NUM_THREADS        = 0
+
+# When you want a differently looking font in the dot files that doxygen
+# generates you can specify the font name using DOT_FONTNAME. You need to make
+# sure dot is able to find the font, which can be done by putting it in a
+# standard location or by setting the DOTFONTPATH environment variable or by
+# setting DOT_FONTPATH to the directory containing the font.
+# The default value is: Helvetica.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTNAME           = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
+# dot graphs.
+# Minimum value: 4, maximum value: 24, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTSIZE           = 10
+
+# By default doxygen will tell dot to use the default font as specified with
+# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
+# the path where dot can find it using this tag.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTPATH           =
+
+# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
+# each documented class showing the direct and indirect inheritance relations.
+# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
+# graph for each documented class showing the direct and indirect implementation
+# dependencies (inheritance, containment, and class references variables) of the
+# class with other documented classes.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
+# groups, showing the direct groups dependencies.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LOOK               = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
+# class node. If there are many fields or methods and many nodes the graph may
+# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
+# number of items for each type to make the size more manageable. Set this to 0
+# for no limit. Note that the threshold may be exceeded by 50% before the limit
+# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
+# but if the number exceeds 15, the total amount of fields shown is limited to
+# 10.
+# Minimum value: 0, maximum value: 100, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LIMIT_NUM_FIELDS   = 10
+
+# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
+# collaboration graphs will show the relations between templates and their
+# instances.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
+# YES then doxygen will generate a graph for each documented file showing the
+# direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDE_GRAPH          = YES
+
+# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
+# set to YES then doxygen will generate a graph for each documented file showing
+# the direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command. Disabling a call graph can be
+# accomplished by means of the command \hidecallgraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command. Disabling a caller graph can be
+# accomplished by means of the command \hidecallergraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
+# hierarchy of all classes instead of a textual one.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
+# dependencies a directory has on other directories in a graphical way. The
+# dependency relations are determined by the #include relations between the
+# files in the directories.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. For an explanation of the image formats see the section
+# output formats in the documentation of the dot tool (Graphviz (see:
+# http://www.graphviz.org/)).
+# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
+# to make the SVG files visible in IE 9+ (other browsers do not have this
+# requirement).
+# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo,
+# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
+# png:gdiplus:gdiplus.
+# The default value is: png.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_IMAGE_FORMAT       = png
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+#
+# Note that this requires a modern browser other than Internet Explorer. Tested
+# and working are Firefox, Chrome, Safari, and Opera.
+# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
+# the SVG files visible. Older versions of IE do not have SVG support.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INTERACTIVE_SVG        = NO
+
+# The DOT_PATH tag can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_PATH               =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the \dotfile
+# command).
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOTFILE_DIRS           =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the \mscfile
+# command).
+
+MSCFILE_DIRS           =
+
+# The DIAFILE_DIRS tag can be used to specify one or more directories that
+# contain dia files that are included in the documentation (see the \diafile
+# command).
+
+DIAFILE_DIRS           =
+
+# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
+# path where java can find the plantuml.jar file. If left blank, it is assumed
+# PlantUML is not used or called during a preprocessing step. Doxygen will
+# generate a warning when it encounters a \startuml command in this case and
+# will not generate output for the diagram.
+
+PLANTUML_JAR_PATH      =
+
+# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a
+# configuration file for plantuml.
+
+PLANTUML_CFG_FILE      =
+
+# When using plantuml, the specified paths are searched for files specified by
+# the !include statement in a plantuml block.
+
+PLANTUML_INCLUDE_PATH  =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
+# that will be shown in the graph. If the number of nodes in a graph becomes
+# larger than this value, doxygen will truncate the graph, which is visualized
+# by representing a node as a red box. Note that doxygen if the number of direct
+# children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
+# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# Minimum value: 0, maximum value: 10000, default value: 50.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
+# generated by dot. A depth value of 3 means that only nodes reachable from the
+# root by following a path via at most 3 edges will be shown. Nodes that lay
+# further from the root node will be omitted. Note that setting this option to 1
+# or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+# Minimum value: 0, maximum value: 1000, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not seem
+# to support this out of the box.
+#
+# Warning: Depending on the platform used, enabling this option may lead to
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
+# read).
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10) support
+# this, this feature is disabled by default.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
+# explaining the meaning of the various boxes and arrows in the dot generated
+# graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot
+# files that are used to generate the various graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_CLEANUP            = YES

+ 215 - 0
3rdparty/rapidyaml/ext/c4core/cmake/ExternalProjectUtils.cmake

@@ -0,0 +1,215 @@
+# (C) 2017 Joao Paulo Magalhaes <dev@jpmag.me>
+
+include(CMakeParseArguments)
+
+#------------------------------------------------------------------------------
+# Usage:
+#
+#   ExternalProject_GetFwdArgs(output_var
+#       [NO_DEFAULTS]
+#       [VARS var1 var2 ...]
+#       [EXCLUDE xvar1 xvar2 ...]
+#       [QUIET]
+#   )
+#
+# Get the current cmake environment in a sequence of -DVAR=${VAR}
+# tokens so that the environment can be forwarded to an external
+# cmake project through CMAKE_ARGS.
+#
+# Example:
+#   ExternalProject_GetFwdArgs(FWD_ARGS)
+#   ExternalProject_Add(foo SOURCE_DIR ../foo
+#       CMAKE_ARGS ${FWD_ARGS}
+#       ... etc)
+#
+# Use this function to enable forwarding the current cmake environment
+# to an external project. It outputs all the needed variables in the
+# form of a sequence of -DVAR=value, suitable for use in the CMAKE_ARGS
+# clause of ExternalProject_Add().
+#
+# This function uses ExternalProject_GetFwdVarNames() to find out the
+# list of variables to export. If this behaviour does not fit your
+# needs you can:
+#
+#     * append more of your own variables (using the VARS
+#       argument). The vars specified in this option will each be
+#       added to the output in the form of -Dvar=${var}
+#
+#     * you can also avoid any defaults obtained through usage of
+#       ExternalProject_GetFwdNames() by specifying NO_DEFAULTS.
+#
+# Example with custom variable names (adding more):
+#   ExternalProject_GetFwdVarNames(FWD_ARGS VARS USER_VAR1 USER_VAR2)
+#   ExternalProjectAdd(foo SOURCE_DIR ../foo CMAKE_ARGS ${FWD_ARGS})
+#
+# Example with custom variable names (just your own):
+#   ExternalProject_GetFwdVarNames(FWD_ARGS NO_DEFAULTS VARS USER_VAR1 USER_VAR2)
+#   ExternalProjectAdd(foo SOURCE_DIR ../foo CMAKE_ARGS ${FWD_ARGS})
+#
+function(ExternalProject_GetFwdArgs output_var)
+    set(options0arg
+        NO_DEFAULTS
+        QUIET
+        )
+    set(options1arg
+        )
+    set(optionsnarg
+        VARS
+        EXCLUDE
+        )
+    cmake_parse_arguments(_epgfa "${options0arg}" "${options1arg}" "${optionsnarg}" ${ARGN})
+    if(NOT _epfga_NO_DEFAULTS)
+        ExternalProject_GetFwdVarNames(_fwd_names)
+    endif()
+    if(_epgfa_VARS)
+        list(APPEND _fwd_names ${_epgfa_VARS})
+    endif()
+    if(_epgfa_EXCLUDE)
+        list(REMOVE_ITEM _fwd_names ${_epgfa_EXCLUDE})
+    endif()
+    set(_epgfa_args)
+    foreach(_f ${_fwd_names})
+        if(${_f})
+            list(APPEND _epgfa_args -D${_f}=${${_f}})
+            if(NOT _epfga_QUIET)
+                message(STATUS "ExternalProject_GetFwdArgs: ${_f}=${${_f}}")
+            endif()
+        endif()
+    endforeach()
+    set(${output_var} "${_epgfa_args}" PARENT_SCOPE)
+endfunction(ExternalProject_GetFwdArgs)
+
+
+#------------------------------------------------------------------------------
+# Gets a default list with the names of variables to forward to an
+# external project. This function creates a list of common cmake
+# variable names which have an impact in the output binaries or their
+# placement.
+function(ExternalProject_GetFwdVarNames output_var)
+    # these common names are irrespective of build type
+    set(names
+        CMAKE_GENERATOR
+        CMAKE_INSTALL_PREFIX
+        CMAKE_ARCHIVE_OUTPUT_DIRECTORY
+        CMAKE_LIBRARY_OUTPUT_DIRECTORY
+        CMAKE_RUNTIME_OUTPUT_DIRECTORY
+        CMAKE_AR
+        CMAKE_BUILD_TYPE
+        CMAKE_INCLUDE_PATH
+        CMAKE_LIBRARY_PATH
+        #CMAKE_MODULE_PATH # this is dangerous as it can override the external project's build files.
+        CMAKE_PREFIX_PATH
+        BUILD_SHARED_LIBS
+        CMAKE_CXX_COMPILER
+        CMAKE_C_COMPILER
+        CMAKE_LINKER
+        CMAKE_MAKE_PROGRAM
+        CMAKE_NM
+        CMAKE_OBJCOPY
+        CMAKE_RANLIB
+        CMAKE_STRIP
+        CMAKE_TOOLCHAIN_FILE
+        #CMAKE_CONFIGURATION_TYPES # not this. external projects will have their own build configurations
+        )
+    # these names have per-build type values;
+    # use CMAKE_CONFIGURATION_TYPES to construct the list
+    foreach(v
+            CMAKE_CXX_FLAGS
+            CMAKE_C_FLAGS
+            CMAKE_EXE_LINKER_FLAGS
+            CMAKE_MODULE_LINKER_FLAGS
+            CMAKE_SHARED_LINKER_FLAGS)
+        list(APPEND names ${v})
+        foreach(t ${CMAKE_CONFIGURATION_TYPES})
+            string(TOUPPER ${t} u)
+            list(APPEND names ${v}_${u})
+        endforeach()
+    endforeach()
+    set(${output_var} "${names}" PARENT_SCOPE)
+endfunction(ExternalProject_GetFwdVarNames)
+
+
+#------------------------------------------------------------------------------
+macro(ExternalProject_Import name)
+    set(options0arg
+        )
+    set(options1arg
+        PREFIX        # look only here when findind
+        )
+    set(optionsnarg
+        INCLUDE_PATHS  # use these dirs for searching includes
+        LIBRARY_PATHS  # use these dirs for searching libraries
+        INCLUDES       # find these includes and append them to ${name}_INCLUDE_DIRS
+        INCLUDE_DIR_SUFFIXES
+        LIBRARIES      # find these libs and append them to ${name}_LIBRARIES
+        LIBRARY_DIR_SUFFIXES
+        )
+    cmake_parse_arguments(_eep "${options0arg}" "${options1arg}" "${optionsnarg}" ${ARGN})
+
+    if(NOT _eep_PREFIX)
+        message(FATAL_ERROR "no prefix was given")
+    endif()
+
+    include(FindPackageHandleStandardArgs)
+
+    #----------------------------------------------------------------
+    # includes
+
+    # the list of paths to search for includes
+    set(_eep_ipaths ${_eep_PREFIX})
+    foreach(_eep_i ${_eep_INCLUDE_DIRS})
+        list(APPEND _eep_ipaths ${__eep_PREFIX}/${_eep_i})
+    endforeach()
+
+    # find the includes that were asked for, and add
+    # their paths to the includes list
+    set(_eep_idirs)
+    foreach(_eep_i ${_eep_INCLUDES})
+        find_path(_eep_path_${_eep_i} ${_eep_i}
+            PATHS ${_eep_ipaths}
+            PATH_SUFFIXES include ${_eep_INCLUDE_DIR_SUFFIXES}
+            NO_DEFAULT_PATH
+            )
+        if(NOT _eep_path_${_eep_i})
+            message(FATAL_ERROR "could not find include: ${_eep_i}")
+        endif()
+        #message(STATUS "include: ${_eep_i} ---> ${_eep_path_${_eep_i}}")
+        list(APPEND _eep_idirs ${_eep_path_${_eep_i}})
+        find_package_handle_standard_args(${_eep_i}_INCLUDE_DIR DEFAULT_MSG _eep_path_${_eep_i})
+    endforeach()
+    if(_eep_idirs)
+        list(REMOVE_DUPLICATES _eep_idirs)
+    endif()
+
+    # save the include list
+    set(${name}_INCLUDE_DIRS "${_eep_idirs}" CACHE STRING "" FORCE)
+
+    #----------------------------------------------------------------
+    # libraries
+
+    # the list of paths to search for libraries
+    set(_eep_lpaths ${_eep_PREFIX})
+    foreach(_eep_i ${_eep_LIBRARIES})
+        list(APPEND _eep_lpaths ${__eep_PREFIX}/${_eep_i})
+    endforeach()
+
+    # find any libraries that were asked for
+    set(_eep_libs)
+    foreach(_eep_i ${_eep_LIBRARIES})
+        find_library(_eep_lib_${_eep_i} ${_eep_i}
+            PATHS ${_eep_lpaths}
+            PATH_SUFFIXES lib ${_eep_LIBRARY_DIR_SUFFIXES}
+            NO_DEFAULT_PATH
+            )
+        if(NOT _eep_lib_${_eep_i})
+            message(FATAL_ERROR "could not find library: ${_eep_i}")
+        endif()
+        #message(STATUS "lib: ${_eep_i} ---> ${_eep_lib_${_eep_i}}")
+        list(APPEND _eep_libs ${_eep_lib_${_eep_i}})
+        find_package_handle_standard_args(${_eep_i}_LIBRARY DEFAULT_MSG _eep_lib_${_eep_i})
+    endforeach()
+
+    # save the include list
+    set(${name}_LIBRARIES ${_eep_libs} CACHE STRING "")
+
+endmacro(ExternalProject_Import)

+ 75 - 0
3rdparty/rapidyaml/ext/c4core/cmake/FindD3D12.cmake

@@ -0,0 +1,75 @@
+# Find the win10 SDK path.
+if ("$ENV{WIN10_SDK_PATH}$ENV{WIN10_SDK_VERSION}" STREQUAL "" )
+  get_filename_component(WIN10_SDK_PATH "[HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\Microsoft\\Microsoft SDKs\\Windows\\v10.0;InstallationFolder]" ABSOLUTE CACHE)
+  get_filename_component(TEMP_WIN10_SDK_VERSION "[HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\Microsoft\\Microsoft SDKs\\Windows\\v10.0;ProductVersion]" ABSOLUTE CACHE)
+  get_filename_component(WIN10_SDK_VERSION ${TEMP_WIN10_SDK_VERSION} NAME)
+elseif(TRUE)
+  set (WIN10_SDK_PATH $ENV{WIN10_SDK_PATH})
+  set (WIN10_SDK_VERSION $ENV{WIN10_SDK_VERSION})
+endif ("$ENV{WIN10_SDK_PATH}$ENV{WIN10_SDK_VERSION}" STREQUAL "" )
+
+# WIN10_SDK_PATH will be something like C:\Program Files (x86)\Windows Kits\10
+# WIN10_SDK_VERSION will be something like 10.0.14393 or 10.0.14393.0; we need the
+# one that matches the directory name.
+
+if (IS_DIRECTORY "${WIN10_SDK_PATH}/Include/${WIN10_SDK_VERSION}.0")
+  set(WIN10_SDK_VERSION "${WIN10_SDK_VERSION}.0")
+endif (IS_DIRECTORY "${WIN10_SDK_PATH}/Include/${WIN10_SDK_VERSION}.0")
+
+
+# Find the d3d12 and dxgi include path, it will typically look something like this.
+# C:\Program Files (x86)\Windows Kits\10\Include\10.0.10586.0\um\d3d12.h
+# C:\Program Files (x86)\Windows Kits\10\Include\10.0.10586.0\shared\dxgi1_4.h
+find_path(D3D12_INCLUDE_DIR    # Set variable D3D12_INCLUDE_DIR
+          d3d12.h                # Find a path with d3d12.h
+          HINTS "${WIN10_SDK_PATH}/Include/${WIN10_SDK_VERSION}/um"
+          DOC "path to WIN10 SDK header files"
+          HINTS
+          )
+
+find_path(DXGI_INCLUDE_DIR    # Set variable DXGI_INCLUDE_DIR
+          dxgi1_4.h           # Find a path with dxgi1_4.h
+          HINTS "${WIN10_SDK_PATH}/Include/${WIN10_SDK_VERSION}/shared"
+          DOC "path to WIN10 SDK header files"
+          HINTS
+          )
+
+if ("${DXC_BUILD_ARCH}" STREQUAL "x64" )
+  find_library(D3D12_LIBRARY NAMES d3d12.lib
+               HINTS ${WIN10_SDK_PATH}/Lib/${WIN10_SDK_VERSION}/um/x64 )
+elseif (CMAKE_GENERATOR MATCHES "Visual Studio.*ARM" OR "${DXC_BUILD_ARCH}" STREQUAL "ARM")
+  find_library(D3D12_LIBRARY NAMES d3d12.lib
+               HINTS ${WIN10_SDK_PATH}/Lib/${WIN10_SDK_VERSION}/um/arm )
+elseif (CMAKE_GENERATOR MATCHES "Visual Studio.*ARM64" OR "${DXC_BUILD_ARCH}" STREQUAL "ARM64")
+  find_library(D3D12_LIBRARY NAMES d3d12.lib
+               HINTS ${WIN10_SDK_PATH}/Lib/${WIN10_SDK_VERSION}/um/arm64 )
+elseif ("${DXC_BUILD_ARCH}" STREQUAL "Win32" )
+  find_library(D3D12_LIBRARY NAMES d3d12.lib
+               HINTS ${WIN10_SDK_PATH}/Lib/${WIN10_SDK_VERSION}/um/x86 )
+endif ("${DXC_BUILD_ARCH}" STREQUAL "x64" )
+
+if ("${DXC_BUILD_ARCH}" STREQUAL "x64" )
+  find_library(DXGI_LIBRARY NAMES dxgi.lib
+               HINTS ${WIN10_SDK_PATH}/Lib/${WIN10_SDK_VERSION}/um/x64 )
+elseif (CMAKE_GENERATOR MATCHES "Visual Studio.*ARM" OR "${DXC_BUILD_ARCH}" STREQUAL "ARM")
+  find_library(DXGI_LIBRARY NAMES dxgi.lib
+               HINTS ${WIN10_SDK_PATH}/Lib/${WIN10_SDK_VERSION}/um/arm )
+elseif (CMAKE_GENERATOR MATCHES "Visual Studio.*ARM64" OR "${DXC_BUILD_ARCH}" STREQUAL "ARM64")
+  find_library(DXGI_LIBRARY NAMES dxgi.lib
+               HINTS ${WIN10_SDK_PATH}/Lib/${WIN10_SDK_VERSION}/um/arm64 )
+elseif ("${DXC_BUILD_ARCH}" STREQUAL "Win32" )
+  find_library(DXGI_LIBRARY NAMES dxgi.lib
+               HINTS ${WIN10_SDK_PATH}/Lib/${WIN10_SDK_VERSION}/um/x86 )
+endif ("${DXC_BUILD_ARCH}" STREQUAL "x64" )
+
+set(D3D12_LIBRARIES ${D3D12_LIBRARY} ${DXGI_LIBRARY})
+set(D3D12_INCLUDE_DIRS ${D3D12_INCLUDE_DIR} ${DXGI_INCLUDE_DIR})
+
+
+include(FindPackageHandleStandardArgs)
+# handle the QUIETLY and REQUIRED arguments and set D3D12_FOUND to TRUE
+# if all listed variables are TRUE
+find_package_handle_standard_args(D3D12  DEFAULT_MSG
+                                  D3D12_INCLUDE_DIRS D3D12_LIBRARIES)
+
+mark_as_advanced(D3D12_INCLUDE_DIRS D3D12_LIBRARIES)

+ 76 - 0
3rdparty/rapidyaml/ext/c4core/cmake/FindDX12.cmake

@@ -0,0 +1,76 @@
+# Attempt to find the D3D12 libraries
+# Defines:
+#
+#  DX12_FOUND        - system has DX12
+#  DX12_INCLUDE_PATH - path to the DX12 headers
+#  DX12_LIBRARIES    - path to the DX12 libraries
+#  DX12_LIB          - d3d12.lib
+
+set(DX12_FOUND "NO")
+
+if(WIN32)
+    set(WIN10_SDK_DIR "C:/Program Files (x86)/Windows Kits/10")
+    #set(WIN10_SDK_VERSION "10.0.10069.0")
+    file(GLOB WIN10_SDK_VERSIONS
+        LIST_DIRECTORIES TRUE
+        RELATIVE "${WIN10_SDK_DIR}/Lib"
+        "${WIN10_SDK_DIR}/Lib/*")
+    list(SORT WIN10_SDK_VERSIONS)
+    list(GET WIN10_SDK_VERSIONS -1 WIN10_SDK_VERSION)
+
+    if(CMAKE_CL_64)
+        set(w10ARCH x64)
+    elseif(CMAKE_GENERATOR MATCHES "Visual Studio.*ARM" OR "${DXC_BUILD_ARCH}" STREQUAL "ARM")
+        set(w10ARCH arm)
+    elseif(CMAKE_GENERATOR MATCHES "Visual Studio.*ARM64" OR "${DXC_BUILD_ARCH}" STREQUAL "ARM64")
+        set(w10ARCH arm64)
+    else()
+        set(w10ARCH x86)
+    endif()
+
+    # Look for the windows 8 sdk
+    find_path(DX12_INC_DIR
+        NAMES d3d12.h
+        PATHS "${WIN10_SDK_DIR}/Include/${WIN10_SDK_VERSION}/um"
+        DOC "Path to the d3d12.h file"
+    )
+    find_path(DXGI_INC_DIR
+        NAMES dxgi1_4.h
+        PATHS "${WIN10_SDK_DIR}/Include/${WIN10_SDK_VERSION}/shared"
+        DOC "Path to the dxgi header file"
+    )
+
+    if(DX12_INC_DIR AND DXGI_INC_DIR)
+        find_library(DX12_LIB
+            NAMES d3d12
+            PATHS "${WIN10_SDK_DIR}/Lib/${WIN10_SDK_VERSION}/um/${w10ARCH}"
+            NO_DEFAULT_PATH
+            DOC "Path to the d3d12.lib file"
+        )
+        find_library(DXGI_LIB
+            NAMES dxgi
+            PATHS "${WIN10_SDK_DIR}/Lib/${WIN10_SDK_VERSION}/um/${w10ARCH}"
+            NO_DEFAULT_PATH
+            DOC "Path to the dxgi.lib file"
+        )
+        if(DX12_LIB AND DXGI_LIB)
+            set(DX12_FOUND "YES")
+            set(DX12_LIBRARIES ${DX12_LIB} ${DXGI_LIB})
+            mark_as_advanced(DX12_INC_DIR DX12_LIB)
+            mark_as_advanced(DXGI_INC_DIR DXGI_LIB)
+        endif()
+    endif()
+endif(WIN32)
+
+if(DX12_FOUND)
+    if(NOT DX12_FIND_QUIETLY)
+        message(STATUS "DX12 headers found at ${DX12_INC_DIR}")
+    endif()
+else()
+    if(DX12_FIND_REQUIRED)
+        message(FATAL_ERROR "Could NOT find Direct3D12")
+    endif()
+    if(NOT DX12_FIND_QUIETLY)
+        message(STATUS "Could NOT find Direct3D12")
+    endif()
+endif()

+ 53 - 0
3rdparty/rapidyaml/ext/c4core/cmake/GetFlags.cmake

@@ -0,0 +1,53 @@
+
+function(_c4_intersperse_with_flag outvar flag)
+    if(MSVC AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") # it may be clang as well
+        set(f "/${flag}")
+    else()
+        set(f "-${flag}")
+    endif()
+    set(out)
+    foreach(i ${ARGN})
+        if(NOT "${i}" STREQUAL "")
+            set(out "${out} ${f} '${i}'")
+
+            # ... Following this are several unsuccessful attempts to make
+            # sure that an empty generator expression passed as part of the
+            # arguments won't be expanded to nothing between successive
+            # flags.  For example, -I /some/include -I -I /other/include,
+            # which is wrong as it misses an empty quote.  This causes
+            # clang-tidy in particular to fail.  Maybe this is happening
+            # because the result is passed to separate_arguments() which
+            # prevents the lists from being evaluated correctly. Also, note
+            # that add_custom_target() has the following options which may
+            # help: COMMAND_EXPAND_LISTS and VERBATIM.
+
+            # Anyway -- for now it is working, but maybe the generator
+            # expression approach turns out to work while being much cleaner
+            # than the current approach.
+
+            #set(c $<GENEX_EVAL,$<BOOL:${i}>>)
+            #set(c $<BOOL:${i}>)  # i may be a generator expression the evaluates to empty
+            #set(s "${f} ${i}")
+            #set(e "${f} aaaaaaWTF")
+            #list(APPEND out $<IF:${c},${s},${e}>)
+            #list(APPEND out $<${c},${s}>)
+            #list(APPEND out $<GENEX_EVAL:${c},${s}>)
+            #list(APPEND out $<TARGET_GENEX_EVAL:${tgt},${c},${s}>)
+        endif()
+    endforeach()
+    ## https://cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html#string-valued-generator-expressions
+    #if(ARGN)
+    #    set(out "${f}$<JOIN:${ARGN},;${f}>")
+    #endif()
+    set(${outvar} ${out} PARENT_SCOPE)
+endfunction()
+
+function(c4_get_define_flags outvar)
+    _c4_intersperse_with_flag(out D ${ARGN})
+    set(${outvar} ${out} PARENT_SCOPE)
+endfunction()
+
+function(c4_get_include_flags outvar)
+    _c4_intersperse_with_flag(out I ${ARGN})
+    set(${outvar} ${out} PARENT_SCOPE)
+endfunction()

+ 51 - 0
3rdparty/rapidyaml/ext/c4core/cmake/GetNames.cmake

@@ -0,0 +1,51 @@
+function(get_lib_names lib_names base)
+    set(${lib_names})
+    foreach(__glnname ${ARGN})
+        if(WIN32)
+            set(__glnn ${__glnname}.lib)
+        else()
+            set(__glnn lib${__glnname}.a)
+        endif()
+        list(APPEND ${lib_names} "${base}${__glnn}")
+    endforeach()
+    set(lib_names ${lib_names} PARENT_SCOPE)
+endfunction()
+
+function(get_dll_names dll_names base)
+    set(${dll_names})
+    foreach(__glnname ${ARGN})
+        if(WIN32)
+            set(__glnn ${__glnname}.dll)
+        else()
+            set(__glnn lib${__glnname}.so)
+        endif()
+        list(APPEND ${dll_names} "${base}${__glnn}")
+    endforeach()
+    set(dll_names ${dll_names} PARENT_SCOPE)
+endfunction()
+
+function(get_script_names script_names base)
+    set(${script_names})
+    foreach(__glnname ${ARGN})
+        if(WIN32)
+            set(__glnn ${__glnname}.bat)
+        else()
+            set(__glnn ${__glnname}.sh)
+        endif()
+        list(APPEND ${script_names} "${base}${__glnn}")
+    endforeach()
+    set(script_names ${script_names} PARENT_SCOPE)
+endfunction()
+
+function(get_exe_names exe_names base)
+    set(${exe_names})
+    foreach(__glnname ${ARGN})
+        if(WIN32)
+            set(__glnn ${__glnname}.exe)
+        else()
+            set(__glnn ${__glnname})
+        endif()
+        list(APPEND ${exe_names} "${base}${__glnn}")
+    endforeach()
+    set(exe_names ${exe_names} PARENT_SCOPE)
+endfunction()

+ 20 - 0
3rdparty/rapidyaml/ext/c4core/cmake/LICENSE.txt

@@ -0,0 +1,20 @@
+Copyright (c) 2018, Joao Paulo Magalhaes <dev@jpmag.me>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+

+ 275 - 0
3rdparty/rapidyaml/ext/c4core/cmake/PVS-Studio.cmake

@@ -0,0 +1,275 @@
+# 2006-2008 (c) Viva64.com Team
+# 2008-2016 (c) OOO "Program Verification Systems"
+#
+# Version 2
+
+function (pvs_studio_relative_path VAR ROOT FILEPATH)
+    set("${VAR}" "${FILEPATH}" PARENT_SCOPE)
+    if ("${FILEPATH}" MATCHES "^/.*$")
+        file(RELATIVE_PATH RPATH "${ROOT}" "${FILEPATH}")
+        if (NOT "${RPATH}" MATCHES "^\\.\\..*$")
+            set("${VAR}" "${RPATH}" PARENT_SCOPE)
+        endif ()
+    endif ()
+endfunction ()
+
+function (pvs_studio_join_path VAR DIR1 DIR2)
+    if ("${DIR2}" MATCHES "^(/|~).*$" OR "${DIR1}" STREQUAL "")
+        set("${VAR}" "${DIR2}" PARENT_SCOPE)
+    else ()
+        set("${VAR}" "${DIR1}/${DIR2}" PARENT_SCOPE)
+    endif ()
+endfunction ()
+
+macro (pvs_studio_append_flags_from_property CXX C DIR PREFIX)
+    if (NOT "${PROPERTY}" STREQUAL "NOTFOUND" AND NOT "${PROPERTY}" STREQUAL "PROPERTY-NOTFOUND")
+        foreach (PROP ${PROPERTY})
+            pvs_studio_join_path(PROP "${DIR}" "${PROP}")
+            list(APPEND "${CXX}" "${PREFIX}${PROP}")
+            list(APPEND "${C}" "${PREFIX}${PROP}")
+        endforeach ()
+    endif ()
+endmacro ()
+
+macro (pvs_studio_append_standard_flag FLAGS STANDARD)
+    if ("${STANDARD}" MATCHES "^(99|11|14|17)$")
+        if ("${PVS_STUDIO_PREPROCESSOR}" MATCHES "gcc|clang")
+            list(APPEND "${FLAGS}" "-std=c++${STANDARD}")
+        endif ()
+    endif ()
+endmacro ()
+
+function (pvs_studio_set_directory_flags DIRECTORY CXX C)
+    set(CXX_FLAGS "${${CXX}}")
+    set(C_FLAGS "${${C}}")
+
+    get_directory_property(PROPERTY DIRECTORY "${DIRECTORY}" INCLUDE_DIRECTORIES)
+    pvs_studio_append_flags_from_property(CXX_FLAGS C_FLAGS "${DIRECTORY}" "-I")
+
+    get_directory_property(PROPERTY DIRECTORY "${DIRECTORY}" COMPILE_DEFINITIONS)
+    pvs_studio_append_flags_from_property(CXX_FLAGS C_FLAGS "" "-D")
+
+    set("${CXX}" "${CXX_FLAGS}" PARENT_SCOPE)
+    set("${C}" "${C_FLAGS}" PARENT_SCOPE)
+endfunction ()
+
+function (pvs_studio_set_target_flags TARGET CXX C)
+    set(CXX_FLAGS "${${CXX}}")
+    set(C_FLAGS "${${C}}")
+
+    get_target_property(PROPERTY "${TARGET}" INCLUDE_DIRECTORIES)
+    pvs_studio_append_flags_from_property(CXX_FLAGS C_FLAGS "${DIRECTORY}" "-I")
+
+    get_target_property(PROPERTY "${TARGET}" COMPILE_DEFINITIONS)
+    pvs_studio_append_flags_from_property(CXX_FLAGS C_FLAGS "" "-D")
+
+    get_target_property(PROPERTY "${TARGET}" CXX_STANDARD)
+    pvs_studio_append_standard_flag(CXX_FLAGS "${PROPERTY}")
+
+    set("${CXX}" "${CXX_FLAGS}" PARENT_SCOPE)
+    set("${C}" "${C_FLAGS}" PARENT_SCOPE)
+endfunction ()
+
+function (pvs_studio_set_source_file_flags SOURCE)
+    set(LANGUAGE "")
+
+    string(TOLOWER "${SOURCE}" SOURCE_LOWER)
+    if ("${LANGUAGE}" STREQUAL "" AND "${SOURCE_LOWER}" MATCHES "^.*\\.(c|cpp|cc|cx|cxx|cp|c\\+\\+)$")
+        if ("${SOURCE}" MATCHES "^.*\\.c$")
+            set(LANGUAGE C)
+        else ()
+            set(LANGUAGE CXX)
+        endif ()
+    endif ()
+
+    if ("${LANGUAGE}" STREQUAL "C")
+        set(CL_PARAMS ${PVS_STUDIO_C_FLAGS} ${PVS_STUDIO_TARGET_C_FLAGS} -DPVS_STUDIO)
+    elseif ("${LANGUAGE}" STREQUAL "CXX")
+        set(CL_PARAMS ${PVS_STUDIO_CXX_FLAGS} ${PVS_STUDIO_TARGET_CXX_FLAGS} -DPVS_STUDIO)
+    endif ()
+
+    set(PVS_STUDIO_LANGUAGE "${LANGUAGE}" PARENT_SCOPE)
+    set(PVS_STUDIO_CL_PARAMS "${CL_PARAMS}" PARENT_SCOPE)
+endfunction ()
+
+function (pvs_studio_analyze_file SOURCE SOURCE_DIR BINARY_DIR)
+    set(PLOGS ${PVS_STUDIO_PLOGS})
+    pvs_studio_set_source_file_flags("${SOURCE}")
+
+    get_filename_component(SOURCE "${SOURCE}" REALPATH)
+    pvs_studio_relative_path(SOURCE_RELATIVE "${SOURCE_DIR}" "${SOURCE}")
+    pvs_studio_join_path(SOURCE "${SOURCE_DIR}" "${SOURCE}")
+
+    set(LOG "${BINARY_DIR}/PVS-Studio/${SOURCE_RELATIVE}.plog")
+    get_filename_component(LOG "${LOG}" REALPATH)
+    get_filename_component(PARENT_DIR "${LOG}" DIRECTORY)
+
+    if (EXISTS "${SOURCE}" AND NOT TARGET "${LOG}" AND NOT "${PVS_STUDIO_LANGUAGE}" STREQUAL "")
+        add_custom_command(OUTPUT "${LOG}"
+                           COMMAND mkdir -p "${PARENT_DIR}"
+                           COMMAND rm -f "${LOG}"
+                           COMMAND "${PVS_STUDIO_BIN}" analyze
+                                                       --output-file "${LOG}"
+                                                       --source-file "${SOURCE}"
+                                                       ${PVS_STUDIO_ARGS}
+                                                       --cl-params ${PVS_STUDIO_CL_PARAMS} "${SOURCE}"
+                           WORKING_DIRECTORY "${BINARY_DIR}"
+                           DEPENDS "${SOURCE}" "${PVS_STUDIO_CONFIG}"
+                           VERBATIM
+                           COMMENT "Analyzing ${PVS_STUDIO_LANGUAGE} file ${SOURCE_RELATIVE}")
+        list(APPEND PLOGS "${LOG}")
+    endif ()
+    set(PVS_STUDIO_PLOGS "${PLOGS}" PARENT_SCOPE)
+endfunction ()
+
+function (pvs_studio_analyze_target TARGET DIR)
+    set(PVS_STUDIO_PLOGS "${PVS_STUDIO_PLOGS}")
+    set(PVS_STUDIO_TARGET_CXX_FLAGS "")
+    set(PVS_STUDIO_TARGET_C_FLAGS "")
+
+    get_target_property(PROPERTY "${TARGET}" SOURCES)
+    pvs_studio_relative_path(BINARY_DIR "${CMAKE_SOURCE_DIR}" "${DIR}")
+    if ("${BINARY_DIR}" MATCHES "^/.*$")
+        pvs_studio_join_path(BINARY_DIR "${CMAKE_BINARY_DIR}" "PVS-Studio/__${BINARY_DIR}")
+    else ()
+        pvs_studio_join_path(BINARY_DIR "${CMAKE_BINARY_DIR}" "${BINARY_DIR}")
+    endif ()
+
+    file(MAKE_DIRECTORY "${BINARY_DIR}")
+
+    pvs_studio_set_directory_flags("${DIR}" PVS_STUDIO_TARGET_CXX_FLAGS PVS_STUDIO_TARGET_C_FLAGS)
+    pvs_studio_set_target_flags("${TARGET}" PVS_STUDIO_TARGET_CXX_FLAGS PVS_STUDIO_TARGET_C_FLAGS)
+
+    if (NOT "${PROPERTY}" STREQUAL "NOTFOUND" AND NOT "${PROPERTY}" STREQUAL "PROPERTY-NOTFOUND")
+        foreach (SOURCE ${PROPERTY})
+            pvs_studio_join_path(SOURCE "${DIR}" "${SOURCE}")
+            pvs_studio_analyze_file("${SOURCE}" "${DIR}" "${BINARY_DIR}")
+        endforeach ()
+    endif ()
+
+    set(PVS_STUDIO_PLOGS "${PVS_STUDIO_PLOGS}" PARENT_SCOPE)
+endfunction ()
+
+function (pvs_studio_add_target)
+    macro (default VAR VALUE)
+        if ("${${VAR}}" STREQUAL "")
+            set("${VAR}" "${VALUE}")
+        endif ()
+    endmacro ()
+
+    set(PVS_STUDIO_SUPPORTED_PREPROCESSORS "gcc|clang")
+    if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
+        set(DEFAULT_PREPROCESSOR "clang")
+    else ()
+        set(DEFAULT_PREPROCESSOR "gcc")
+    endif ()
+
+    set(OPTIONAL OUTPUT ALL)
+    set(SINGLE LICENSE CONFIG TARGET LOG FORMAT BIN CONVERTER PLATFORM PREPROCESSOR CFG_TEXT)
+    set(MULTI SOURCES C_FLAGS CXX_FLAGS ARGS DEPENDS ANALYZE)
+    cmake_parse_arguments(PVS_STUDIO "${OPTIONAL}" "${SINGLE}" "${MULTI}" ${ARGN})
+
+    if ("${PVS_STUDIO_CFG}" STREQUAL "" OR NOT "${PVS_STUDIO_CFG_TEXT}" STREQUAL "")
+        set(PVS_STUDIO_EMPTY_CONFIG ON)
+    else ()
+        set(PVS_STUDIO_EMPTY_CONFIG OFF)
+    endif ()
+
+    default(PVS_STUDIO_CFG_TEXT "analysis-mode=4")
+    default(PVS_STUDIO_CONFIG "${CMAKE_BINARY_DIR}/PVS-Studio.cfg")
+    default(PVS_STUDIO_C_FLAGS "")
+    default(PVS_STUDIO_CXX_FLAGS "")
+    default(PVS_STUDIO_TARGET "pvs")
+    default(PVS_STUDIO_LOG "PVS-Studio.log")
+    default(PVS_STUDIO_BIN "pvs-studio-analyzer")
+    default(PVS_STUDIO_CONVERTER "plog-converter")
+    default(PVS_STUDIO_PREPROCESSOR "${DEFAULT_PREPROCESSOR}")
+    default(PVS_STUDIO_PLATFORM "linux64")
+
+    if (PVS_STUDIO_EMPTY_CONFIG)
+        set(PVS_STUDIO_CONFIG_COMMAND echo "${PVS_STUDIO_CFG_TEXT}" > "${PVS_STUDIO_CONFIG}")
+    else ()
+        set(PVS_STUDIO_CONFIG_COMMAND touch "${PVS_STUDIO_CONFIG}")
+    endif ()
+
+    add_custom_command(OUTPUT "${PVS_STUDIO_CONFIG}"
+                       COMMAND ${PVS_STUDIO_CONFIG_COMMAND}
+                       WORKING_DIRECTORY "${BINARY_DIR}"
+                       COMMENT "Generating PVS-Studio.cfg")
+
+
+    if (NOT "${PVS_STUDIO_PREPROCESSOR}" MATCHES "^${PVS_STUDIO_SUPPORTED_PREPROCESSORS}$")
+        message(FATAL_ERROR "Preprocessor ${PVS_STUDIO_PREPROCESSOR} isn't supported. Available options: ${PVS_STUDIO_SUPPORTED_PREPROCESSORS}.")
+    endif ()
+
+    pvs_studio_append_standard_flag(PVS_STUDIO_CXX_FLAGS "${CMAKE_CXX_STANDARD}")
+    pvs_studio_set_directory_flags("${CMAKE_CURRENT_SOURCE_DIR}" PVS_STUDIO_CXX_FLAGS PVS_STUDIO_C_FLAGS)
+
+    if (NOT "${PVS_STUDIO_LICENSE}" STREQUAL "")
+        pvs_studio_join_path(PVS_STUDIO_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}" "${PVS_STUDIO_LICENSE}")
+        list(APPEND PVS_STUDIO_ARGS --lic-file "${PVS_STUDIO_LICENSE}")
+    endif ()
+    list(APPEND PVS_STUDIO_ARGS --cfg "${PVS_STUDIO_CONFIG}"
+                                --platform "${PVS_STUDIO_PLATFORM}"
+                                --preprocessor "${PVS_STUDIO_PREPROCESSOR}")
+
+    set(PVS_STUDIO_PLOGS "")
+
+    foreach (TARGET ${PVS_STUDIO_ANALYZE})
+        set(DIR "${CMAKE_CURRENT_SOURCE_DIR}")
+        string(FIND "${TARGET}" ":" DELIM)
+        if ("${DELIM}" GREATER "-1")
+            math(EXPR DELIMI "${DELIM}+1")
+            string(SUBSTRING "${TARGET}" "${DELIMI}" "-1" DIR)
+            string(SUBSTRING "${TARGET}" "0" "${DELIM}" TARGET)
+            pvs_studio_join_path(DIR "${CMAKE_CURRENT_SOURCE_DIR}" "${DIR}")
+        endif ()
+        pvs_studio_analyze_target("${TARGET}" "${DIR}")
+        list(APPEND PVS_STUDIO_DEPENDS "${TARGET}")
+    endforeach ()
+
+    set(PVS_STUDIO_TARGET_CXX_FLAGS "")
+    set(PVS_STUDIO_TARGET_C_FLAGS "")
+    foreach (SOURCE ${PVS_STUDIO_SOURCES})
+        pvs_studio_analyze_file("${SOURCE}" "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}")
+    endforeach ()
+
+    pvs_studio_relative_path(LOG_RELATIVE "${CMAKE_BINARY_DIR}" "${PVS_STUDIO_LOG}")
+    if (PVS_STUDIO_PLOGS)
+        set(COMMANDS COMMAND cat ${PVS_STUDIO_PLOGS} > "${PVS_STUDIO_LOG}")
+        set(COMMENT "Generating ${LOG_RELATIVE}")
+        if (NOT "${PVS_STUDIO_FORMAT}" STREQUAL "" OR PVS_STUDIO_OUTPUT)
+            if ("${PVS_STUDIO_FORMAT}" STREQUAL "")
+                set(PVS_STUDIO_FORMAT "errorfile")
+            endif ()
+            list(APPEND COMMANDS
+                 COMMAND mv "${PVS_STUDIO_LOG}" "${PVS_STUDIO_LOG}.pvs.raw"
+                 COMMAND "${PVS_STUDIO_CONVERTER}" -t "${PVS_STUDIO_FORMAT}" "${PVS_STUDIO_LOG}.pvs.raw" -o "${PVS_STUDIO_LOG}"
+                 COMMAND rm -f "${PVS_STUDIO_LOG}.pvs.raw")
+        endif ()
+    else ()
+        set(COMMANDS COMMAND touch "${PVS_STUDIO_LOG}")
+        set(COMMENT "Generating ${LOG_RELATIVE}: no sources found")
+    endif ()
+
+    add_custom_command(OUTPUT "${PVS_STUDIO_LOG}"
+                       ${COMMANDS}
+                       COMMENT "${COMMENT}"
+                       DEPENDS ${PVS_STUDIO_PLOGS}
+                       WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")
+
+    if (PVS_STUDIO_ALL)
+        set(ALL "ALL")
+    else ()
+        set(ALL "")
+    endif ()
+
+    if (PVS_STUDIO_OUTPUT)
+        set(COMMANDS COMMAND cat "${PVS_STUDIO_LOG}" 1>&2)
+    else ()
+        set(COMMANDS "")
+    endif ()
+
+    add_custom_target("${PVS_STUDIO_TARGET}" ${ALL} ${COMMANDS} WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" DEPENDS ${PVS_STUDIO_DEPENDS} "${PVS_STUDIO_LOG}")
+endfunction ()
+

+ 25 - 0
3rdparty/rapidyaml/ext/c4core/cmake/PatchUtils.cmake

@@ -0,0 +1,25 @@
+# create a script that applies a patch (it's different in windows)
+
+# to generate a patch:
+# subversion: svn diff --patch-compatible > path/to/the/patch.diff
+
+
+function(apply_patch patch where mark)
+    if(NOT EXISTS "${mark}")
+        if(NOT Patch_EXECUTABLE)
+          find_package(Patch REQUIRED)
+        endif()
+        file(TO_NATIVE_PATH ${patch} patch_native)
+        get_filename_component(patch_name "${patch}" NAME)
+        message(STATUS "Applying patch: ${patch_name}")
+        execute_process(
+          COMMAND "${Patch_EXECUTABLE}" "-p0" "--input=${patch_native}"
+          WORKING_DIRECTORY "${where}"
+          RESULT_VARIABLE status)
+        if(NOT status STREQUAL "0")
+            message(FATAL_ERROR "could not apply patch: ${patch} ---> ${where}")
+        else()
+          file(TOUCH "${mark}")
+        endif()
+    endif()
+endfunction()

+ 27 - 0
3rdparty/rapidyaml/ext/c4core/cmake/PrintVar.cmake

@@ -0,0 +1,27 @@
+function(status)
+    message(STATUS "${ARGV}")
+endfunction()
+
+function(print_var var)
+    message(STATUS "${var}=${${var}} ${ARGN}")
+endfunction()
+
+function(print_vars)
+    foreach(a ${ARGN})
+        message(STATUS "${a}=${${a}}")
+    endforeach(a)
+endfunction()
+
+function(debug_var debug var)
+    if(${debug})
+        message(STATUS "${var}=${${var}} ${ARGN}")
+    endif()
+endfunction()
+
+function(debug_vars debug)
+    if(${debug})
+        foreach(a ${ARGN})
+            message(STATUS "${a}=${${a}}")
+        endforeach(a)
+    endif()
+endfunction()

+ 25 - 0
3rdparty/rapidyaml/ext/c4core/cmake/README.md

@@ -0,0 +1,25 @@
+# cmake project utilities
+
+Useful cmake scripts, at [c4Project.cmake](c4Project.cmake).
+
+## Project utilities
+
+## Adding targets
+
+### Target types
+
+## Downloading and configuring third-party projects at configure time
+
+## Setting up tests
+
+### Coverage
+### Static analysis
+### Valgrind
+
+## Setting up benchmarks
+
+## License
+
+MIT License
+
+

+ 176 - 0
3rdparty/rapidyaml/ext/c4core/cmake/TargetArchitecture.cmake

@@ -0,0 +1,176 @@
+
+
+function(c4_get_architecture_defines output_var)
+    c4_get_target_cpu_architecture(arch)
+    if("${arch}" STREQUAL "x86_64")
+        set(defines __x86_64__)
+    elseif("${arch}" STREQUAL "i386")
+        set(defines __i386__)
+    elseif("${arch}" STREQUAL "armv8_64")
+        set(defines __arm__ __aarch64__)
+    elseif("${arch}" STREQUAL "armv8")
+        set(defines __arm__ __ARM_ARCH_8__)
+    elseif("${arch}" STREQUAL "armv7")
+        set(defines __arm__ __ARM_ARCH_7__)
+    elseif("${arch}" STREQUAL "armv6")
+        set(defines __arm__ __ARM_ARCH_6__)
+    elseif("${arch}" STREQUAL "armv5")
+        set(defines __arm__ __ARM_ARCH_5__)
+    elseif("${arch}" STREQUAL "armv4")
+        set(defines __arm__ __ARM_ARCH_4T__)
+    elseif("${arch}" STREQUAL "ia64")
+        set(defines __ia64__)
+    elseif("${arch}" STREQUAL "ppc64")
+        set(defines __ppc64__)
+    elseif("${arch}" STREQUAL "ia64")
+        set(defines __ia64__)
+    elseif("${arch}" STREQUAL "riscv64")
+        set(defines __riscv64__)
+    elseif("${arch}" STREQUAL "riscv32")
+        set(defines __riscv32__)
+    else()
+        message(FATAL_ERROR "unknown target architecture: ${arch}")
+    endif()
+    set(${output_var} ${defines} PARENT_SCOPE)
+endfunction()
+
+
+# adapted from https://github.com/axr/solar-cmake/blob/master/TargetArch.cmake
+# Set ppc_support to TRUE before including this file or ppc and ppc64
+# will be treated as invalid architectures since they are no longer supported by Apple
+function(c4_get_target_cpu_architecture output_var)
+    # this should be more or less in line with c4core/cpu.hpp
+    set(archdetect_c_code "
+#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(_M_X64)
+   #error cmake_ARCH x86_64
+#elif defined(__i386) || defined(__i386__) || defined(_M_IX86)
+   #error cmake_ARCH i386
+#elif defined(__arm__) || defined(_M_ARM) \
+    || defined(__TARGET_ARCH_ARM) || defined(__aarch64__) || defined(_M_ARM64)
+   #if defined(__aarch64__) || defined(_M_ARM64)
+       #error cmake_ARCH armv8_64
+   #else
+       #if defined(__ARM_ARCH_8__) || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM >= 8)
+           #error cmake_ARCH armv8
+       #elif defined(__ARM_ARCH_7__) || defined(_ARM_ARCH_7)    \
+        || defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7R__) \
+        || defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7S__) \
+        || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM >= 7) \
+        || (defined(_M_ARM) && _M_ARM >= 7)
+           #error cmake_ARCH armv7
+       #elif defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) \
+        || defined(__ARM_ARCH_6T2__) || defined(__ARM_ARCH_6Z__) \
+        || defined(__ARM_ARCH_6K__)  || defined(__ARM_ARCH_6ZK__) \
+        || defined(__ARM_ARCH_6M__) \
+        || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM >= 6)
+           #error cmake_ARCH armv6
+       #elif defined(__ARM_ARCH_5TEJ__) \
+        || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM >= 5)
+           #error cmake_ARCH armv5
+       #elif defined(__ARM_ARCH_4T__) \
+        || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM >= 4)
+           #error cmake_ARCH armv4
+       #else
+           #error cmake_ARCH arm
+       #endif
+   #endif
+#elif defined(__ia64) || defined(__ia64__) || defined(_M_IA64)
+    #error cmake_ARCH ia64
+#elif defined(__ppc__) || defined(__ppc) || defined(__powerpc__)       \
+    || defined(_ARCH_COM) || defined(_ARCH_PWR) || defined(_ARCH_PPC)  \
+    || defined(_M_MPPC) || defined(_M_PPC)
+   #if defined(__ppc64__) || defined(__powerpc64__) || defined(__64BIT__)
+       #error cmake_ARCH ppc64
+   #else
+       #error cmake_ARCH ppc32
+   #endif
+#elif defined(__riscv)
+   #if __riscv_xlen == 64
+       #error cmake_ARCH riscv64
+   #else
+       #error cmake_ARCH riscv32
+   #endif
+#endif
+#error cmake_ARCH unknown
+")
+    if(APPLE AND CMAKE_OSX_ARCHITECTURES)
+        # On OS X we use CMAKE_OSX_ARCHITECTURES *if* it was set
+        # First let's normalize the order of the values
+
+        # Note that it's not possible to compile PowerPC applications if you are using
+        # the OS X SDK version 10.6 or later - you'll need 10.4/10.5 for that, so we
+        # disable it by default
+        # See this page for more information:
+        # http://stackoverflow.com/questions/5333490/how-can-we-restore-ppc-ppc64-as-well-as-full-10-4-10-5-sdk-support-to-xcode-4
+
+        # Architecture defaults to i386 or ppc on OS X 10.5 and earlier, depending on the CPU type detected at runtime.
+        # On OS X 10.6+ the default is x86_64 if the CPU supports it, i386 otherwise.
+
+        foreach(osx_arch ${CMAKE_OSX_ARCHITECTURES})
+            if("${osx_arch}" STREQUAL "ppc" AND ppc_support)
+                set(osx_arch_ppc TRUE)
+            elseif("${osx_arch}" STREQUAL "i386")
+                set(osx_arch_i386 TRUE)
+            elseif("${osx_arch}" STREQUAL "x86_64")
+                set(osx_arch_x86_64 TRUE)
+            elseif("${osx_arch}" STREQUAL "ppc64" AND ppc_support)
+                set(osx_arch_ppc64 TRUE)
+            else()
+                message(FATAL_ERROR "Invalid OS X arch name: ${osx_arch}")
+            endif()
+        endforeach()
+
+        # Now add all the architectures in our normalized order
+        if(osx_arch_ppc)
+            list(APPEND ARCH ppc)
+        endif()
+
+        if(osx_arch_i386)
+            list(APPEND ARCH i386)
+        endif()
+
+        if(osx_arch_x86_64)
+            list(APPEND ARCH x86_64)
+        endif()
+
+        if(osx_arch_ppc64)
+            list(APPEND ARCH ppc64)
+        endif()
+    else()
+        file(WRITE "${CMAKE_BINARY_DIR}/detect_cpu_arch.c" "${archdetect_c_code}")
+
+        enable_language(C)
+
+        # Detect the architecture in a rather creative way...
+        # This compiles a small C program which is a series of ifdefs that selects a
+        # particular #error preprocessor directive whose message string contains the
+        # target architecture. The program will always fail to compile (both because
+        # file is not a valid C program, and obviously because of the presence of the
+        # #error preprocessor directives... but by exploiting the preprocessor in this
+        # way, we can detect the correct target architecture even when cross-compiling,
+        # since the program itself never needs to be run (only the compiler/preprocessor)
+        try_run(
+            run_result_unused
+            compile_result_unused
+            "${CMAKE_BINARY_DIR}"
+            "${CMAKE_BINARY_DIR}/detect_cpu_arch.c"
+            COMPILE_OUTPUT_VARIABLE ARCH
+            CMAKE_FLAGS CMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES}
+        )
+
+        # Parse the architecture name from the compiler output
+        string(REGEX MATCH "cmake_ARCH ([a-zA-Z0-9_]+)" ARCH "${ARCH}")
+
+        # Get rid of the value marker leaving just the architecture name
+        string(REPLACE "cmake_ARCH " "" ARCH "${ARCH}")
+
+        # If we are compiling with an unknown architecture this variable should
+        # already be set to "unknown" but in the case that it's empty (i.e. due
+        # to a typo in the code), then set it to unknown
+        if (NOT ARCH)
+            set(ARCH unknown)
+        endif()
+    endif()
+
+    set(${output_var} "${ARCH}" PARENT_SCOPE)
+endfunction()

+ 29 - 0
3rdparty/rapidyaml/ext/c4core/cmake/Toolchain-Arm-ubuntu.cmake

@@ -0,0 +1,29 @@
+SET(CMAKE_SYSTEM_NAME Linux)
+SET(CMAKE_SYSTEM_PROCESSOR arm)
+SET(CMAKE_SYSTEM_VERSION 1)
+set(CMAKE_CROSSCOMPILING TRUE)
+
+find_program(CC_GCC arm-linux-gnueabihf-gcc REQUIRED)
+
+set(CMAKE_FIND_ROOT_PATH /usr/arm-gnueabihf)
+
+# Cross compiler
+SET(CMAKE_C_COMPILER   arm-linux-gnueabihf-gcc)
+SET(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++)
+set(CMAKE_LIBRARY_ARCHITECTURE arm-linux-gnueabihf)
+
+# Search for programs in the build host directories
+SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+
+# Libraries and headers in the target directories
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
+
+set(THREADS_PTHREAD_ARG "0" CACHE STRING "Result from TRY_RUN" FORCE)
+
+get_filename_component(TOOLCHAIN_DIR "${CC_GCC}" DIRECTORY)
+get_filename_component(TOOLCHAIN_DIR "${TOOLCHAIN_DIR}" DIRECTORY)
+set(TOOLCHAIN_SO_DIR "${TOOLCHAIN_DIR}/arm-linux-gnueabihf/")
+#/home/jpmag/local/arm/gcc-arm-9.2-2019.12-x86_64-arm-none-linux-gnueabihf
+set(CMAKE_CROSSCOMPILING_EMULATOR qemu-arm -L ${TOOLCHAIN_SO_DIR})

+ 84 - 0
3rdparty/rapidyaml/ext/c4core/cmake/Toolchain-Armv7.cmake

@@ -0,0 +1,84 @@
+# taken from https://stackoverflow.com/a/49086560
+
+# tested with the toolchain from ARM:
+# gcc-arm-9.2-2019.12-mingw-w64-i686-arm-none-linux-gnueabihf.tar.xz
+# found at
+# https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-a/downloads
+
+# see also:
+# https://stackoverflow.com/questions/42371788/how-to-run-helloworld-on-arm
+# https://dev.to/younup/cmake-on-stm32-the-beginning-3766
+
+SET(CMAKE_SYSTEM_NAME Linux)
+SET(CMAKE_SYSTEM_PROCESSOR arm)
+SET(CMAKE_SYSTEM_VERSION 1)
+set(CMAKE_CROSSCOMPILING TRUE)
+
+find_program(CC_GCC arm-none-linux-gnueabihf-gcc REQUIRED)
+
+set(CMAKE_FIND_ROOT_PATH /usr/arm-linux-gnueabihf)
+
+# Cross compiler
+SET(CMAKE_C_COMPILER   arm-none-linux-gnueabihf-gcc)
+SET(CMAKE_CXX_COMPILER arm-none-linux-gnueabihf-g++)
+set(CMAKE_LIBRARY_ARCHITECTURE arm-none-linux-gnueabihf)
+
+# Search for programs in the build host directories
+SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+
+# Libraries and headers in the target directories
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
+
+set(THREADS_PTHREAD_ARG "0" CACHE STRING "Result from TRY_RUN" FORCE)
+
+get_filename_component(TOOLCHAIN_DIR "${CC_GCC}" DIRECTORY)
+get_filename_component(TOOLCHAIN_DIR "${TOOLCHAIN_DIR}" DIRECTORY)
+set(TOOLCHAIN_SO_DIR "${TOOLCHAIN_DIR}/arm-none-linux-gnueabihf/libc/")
+
+#/home/jpmag/local/arm/gcc-arm-9.2-2019.12-x86_64-arm-none-linux-gnueabihf
+set(CMAKE_CROSSCOMPILING_EMULATOR qemu-arm -L ${TOOLCHAIN_SO_DIR})
+
+return()
+
+
+set(CMAKE_SYSTEM_NAME Generic)
+set(CMAKE_SYSTEM_PROCESSOR arm)
+set(CMAKE_SYSTEM_VERSION 1)
+set(CMAKE_CROSSCOMPILING 1)
+
+set(CMAKE_C_COMPILER "arm-none-eabi-gcc")
+set(CMAKE_CXX_COMPILER "arm-none-eabi-g++")
+
+
+set(CMAKE_FIND_ROOT_PATH /usr/arm-none-eabi)
+set(CMAKE_EXE_LINKER_FLAGS "--specs=nosys.specs" CACHE INTERNAL "")
+
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
+
+set(COMPILER_FLAGS "-marm -mfpu=neon  -mfloat-abi=hard -mcpu=cortex-a9 -D_GNU_SOURCE")
+
+message(STATUS)
+message(STATUS)
+message(STATUS)
+
+if(NOT DEFINED CMAKE_C_FLAGS)
+    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COMPILER_FLAGS}" CACHE STRING "")
+else()
+    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COMPILER_FLAGS}")
+endif()
+
+message(STATUS)
+message(STATUS)
+message(STATUS)
+message(STATUS)
+
+if(NOT DEFINED CMAKE_CXX_FLAGS)
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COMPILER_FLAGS}" CACHE STRING "")
+else()
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COMPILER_FLAGS}")
+endif()

+ 73 - 0
3rdparty/rapidyaml/ext/c4core/cmake/Toolchain-PS4.cmake

@@ -0,0 +1,73 @@
+# Copyright 2017 Autodesk Inc. http://www.autodesk.com
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you
+# may not use this file except in compliance with the License. You may
+# obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# permissions and limitations under the License.
+
+# This module is shared; use include blocker.
+if( _PS4_TOOLCHAIN_ )
+	return()
+endif()
+set(_PS4_TOOLCHAIN_ 1)
+
+# PS4 SCE version requirement
+set(REQUIRED_PS4_VERSION "4.000")
+
+# Get PS4 SCE environment
+if( EXISTS "$ENV{SCE_ROOT_DIR}" AND IS_DIRECTORY "$ENV{SCE_ROOT_DIR}" )
+	string(REGEX REPLACE "\\\\" "/" PS4_ROOT $ENV{SCE_ROOT_DIR})
+	string(REGEX REPLACE "//" "/" PS4_ROOT ${PS4_ROOT})
+	if( EXISTS "$ENV{SCE_ORBIS_SDK_DIR}" AND IS_DIRECTORY "$ENV{SCE_ORBIS_SDK_DIR}" )
+		string(REGEX REPLACE "\\\\" "/" PS4_SDK $ENV{SCE_ORBIS_SDK_DIR})
+		string(REGEX REPLACE "//" "/" PS4_SDK ${PS4_SDK})
+		get_filename_component(SCE_VERSION "${PS4_SDK}" NAME)
+	endif()
+endif()
+
+# Report and check version if it exist
+if( NOT "${SCE_VERSION}" STREQUAL "" )
+	message(STATUS "PS4 SCE version found: ${SCE_VERSION}")
+	if( NOT "${SCE_VERSION}" MATCHES "${REQUIRED_PS4_VERSION}+" )
+		message(WARNING "Expected PS4 SCE version: ${REQUIRED_PS4_VERSION}")
+		if( PLATFORM_TOOLCHAIN_ENVIRONMENT_ONLY )
+			set(PS4_ROOT)
+			set(PS4_SDK)
+		endif()
+	endif()
+endif()
+
+# If we only want the environment values, exit now
+if( PLATFORM_TOOLCHAIN_ENVIRONMENT_ONLY )
+	return()
+endif()
+
+# We are building PS4 platform, fail if PS4 SCE not found
+if( NOT PS4_ROOT OR NOT PS4_SDK )
+	message(FATAL_ERROR "Engine requires PS4 SCE SDK to be installed in order to build PS4 platform.")
+endif()
+
+# Tell CMake we are cross-compiling to PS4 (Orbis)
+set(CMAKE_SYSTEM_NAME Orbis)
+set(PS4 True)
+
+# Set CMake system root search path
+set(CMAKE_SYSROOT "${PS4_ROOT}")
+
+# Set compilers to the ones found in PS4 SCE SDK directory
+set(CMAKE_C_COMPILER "${PS4_SDK}/host_tools/bin/orbis-clang.exe")
+set(CMAKE_CXX_COMPILER "${PS4_SDK}/host_tools/bin/orbis-clang++.exe")
+set(CMAKE_ASM_COMPILER "${PS4_SDK}/host_tools/bin/orbis-as.exe")
+
+# Only search the PS4 SCE SDK, not the remainder of the host file system
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)

+ 93 - 0
3rdparty/rapidyaml/ext/c4core/cmake/Toolchain-XBoxOne.cmake

@@ -0,0 +1,93 @@
+# Copyright 2017 Autodesk Inc. http://www.autodesk.com
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you
+# may not use this file except in compliance with the License. You may
+# obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# permissions and limitations under the License.
+
+# This module is shared; use include blocker.
+if( _XB1_TOOLCHAIN_ )
+	return()
+endif()
+set(_XB1_TOOLCHAIN_ 1)
+
+# XB1 XDK version requirement
+set(REQUIRED_XB1_TOOLCHAIN_VERSION "160305")
+
+# Get XDK environment
+if( EXISTS "$ENV{DurangoXDK}" AND IS_DIRECTORY "$ENV{DurangoXDK}" )
+	string(REGEX REPLACE "\\\\" "/" XDK_ROOT $ENV{DurangoXDK})
+	string(REGEX REPLACE "//" "/" XDK_ROOT ${XDK_ROOT})
+endif()
+
+# Fail if XDK not found
+if( NOT XDK_ROOT )
+	if( PLATFORM_TOOLCHAIN_ENVIRONMENT_ONLY )
+		return()
+	endif()
+	message(FATAL_ERROR "Engine requires XB1 XDK to be installed in order to build XB1 platform.")
+endif()
+
+# Get toolchain version
+get_filename_component(XDK_TOOLCHAIN_VERSION "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\Microsoft\\Durango XDK\\${REQUIRED_XB1_TOOLCHAIN_VERSION};EditionVersion]" NAME)
+
+if( XDK_TOOLCHAIN_VERSION STREQUAL REQUIRED_XB1_TOOLCHAIN_VERSION )
+	message(STATUS "Found required XDK toolchain version (${XDK_TOOLCHAIN_VERSION})")
+else()
+	get_filename_component(XDK_TOOLCHAIN_VERSION "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\Microsoft\\Durango XDK;Latest]" NAME)
+	message(WARNING "Could not find required XDK toolchain version (${REQUIRED_XB1_TOOLCHAIN_VERSION}), using latest version instead (${XDK_TOOLCHAIN_VERSION})")
+endif()
+
+# If we only want the environment values, exit now
+if( PLATFORM_TOOLCHAIN_ENVIRONMENT_ONLY )
+	return()
+endif()
+
+# Find XDK compiler directory
+if( CMAKE_GENERATOR STREQUAL "Visual Studio 11 2012" )
+	set(XDK_COMPILER_DIR "${XDK_ROOT}/${XDK_TOOLCHAIN_VERSION}/Compilers/dev11.1")
+elseif( CMAKE_GENERATOR STREQUAL "Visual Studio 14 2015" )
+	get_filename_component(XDK_COMPILER_DIR "[HKEY_CURRENT_USER\\Software\\Microsoft\\VisualStudio\\14.0_Config\\Setup\\VC;ProductDir]" DIRECTORY)
+	if( DEFINED XDK_COMPILER_DIR )
+		string(REGEX REPLACE "\\\\" "/" XDK_COMPILER_DIR ${XDK_COMPILER_DIR})
+		string(REGEX REPLACE "//" "/" XDK_COMPILER_DIR ${XDK_COMPILER_DIR})
+	endif()
+	if( NOT XDK_COMPILER_DIR )
+		message(FATAL_ERROR "Can't find Visual Studio 2015 installation path.")
+	endif()
+else()
+	message(FATAL_ERROR "Unsupported Visual Studio version!")
+endif()
+
+# Tell CMake we are cross-compiling to XBoxOne (Durango)
+set(CMAKE_SYSTEM_NAME Durango)
+set(XBOXONE True)
+
+# Set CMake system root search path
+set(CMAKE_SYSROOT "${XDK_COMPILER_DIR}")
+
+# Set the compilers to the ones found in XboxOne XDK directory
+set(CMAKE_C_COMPILER "${XDK_COMPILER_DIR}/vc/bin/amd64/cl.exe")
+set(CMAKE_CXX_COMPILER "${XDK_COMPILER_DIR}/vc/bin/amd64/cl.exe")
+set(CMAKE_ASM_COMPILER "${XDK_COMPILER_DIR}/vc/bin/amd64/ml64.exe")
+
+# Force compilers to skip detecting compiler ABI info and compile features
+set(CMAKE_C_COMPILER_FORCED True)
+set(CMAKE_CXX_COMPILER_FORCED True)
+set(CMAKE_ASM_COMPILER_FORCED True)
+
+# Only search the XBoxOne XDK, not the remainder of the host file system
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
+
+# Global variables
+set(XBOXONE_SDK_REFERENCES "Xbox Services API, Version=8.0;Xbox GameChat API, Version=8.0")

+ 216 - 0
3rdparty/rapidyaml/ext/c4core/cmake/amalgamate_utils.py

@@ -0,0 +1,216 @@
+import re
+import os
+
+
+class cmtfile:
+    """commented file"""
+    def __init__(self, filename):
+        self.filename = filename
+    def __str__(self):
+        return self.filename
+
+
+class cmttext:
+    """commented text"""
+    def __init__(self, text):
+        self.text = text
+    def __str__(self):
+        return self.text
+
+
+class ignfile:
+    """ignore file"""
+    def __init__(self, filename):
+        self.filename = filename
+    def __str__(self):
+        return self.filename
+
+
+class hdrfile:
+    """header file, with custom include guard"""
+    def __init__(self, filename, incpattern, include_guard=None):
+        self.filename = filename
+        self.incpattern = incpattern
+        self.include_guard = include_guard
+    def __str__(self):
+        return self.filename
+
+
+class injfile:
+    """header file, to be injected at the first include point"""
+    def __init__(self, filename, incpattern):
+        self.filename = filename
+        self.incpattern = incpattern
+    def __str__(self):
+        return self.filename
+
+
+class injcode:
+    """direct code to inject"""
+    def __init__(self, code):
+        self.code = code
+    def __str__(self):
+        return self.code
+
+
+class onlyif:
+    def __init__(self, condition, obj):
+        self.condition = condition
+        self.obj = obj
+
+
+def catfiles(filenames, rootdir,
+             include_regexes,
+             definition_macro,
+             repo,
+             result_incguard):
+    sepb = "//" + ("**" * 40)
+    sepf = "//" + ("--" * 40)
+    to_inject = {}
+    custom_include_guards = {}
+    def banner(s):
+        return f"\n\n\n{sepb}\n{sepf}\n// {s}\n// {repo}/{s}\n{sepf}\n{sepb}\n\n"
+    def footer(s):
+        return f"\n\n// (end {repo}/{s})\n"
+    def incguard(filename):
+        return custom_include_guards.get(filename,
+                                         f"{filename.replace('.','_').replace('/','_').upper()}_")
+    def replace_include(rx, match, line, guard):
+        line = line.rstrip()
+        incl = match.group(1)
+        if to_inject.get(incl) is None:
+            if guard is None:
+                guard = incguard(incl)
+            return f"""// amalgamate: removed include of
+// {repo}/src/{incl}
+//{line}
+#if !defined({guard}) && !defined(_{guard})
+#error "amalgamate: file {incl} must have been included at this point"
+#endif /* {guard} */\n
+"""
+        else:
+            entry = to_inject[incl]
+            del to_inject[incl]
+            return append_file(entry.filename)
+    def append_file(filename, guard=None):
+        s = ""
+        with open(filename) as f:
+            for line in f.readlines():
+                for rx in include_regexes:
+                    match = rx.match(line)
+                    if match:
+                        line = replace_include(rx, match, line, guard)
+                s += line
+        return s
+    def append_cpp(filename):
+        return f"""#ifdef {definition_macro}
+{append_file(filename)}
+#endif /* {definition_macro} */
+"""
+    def is_src(filename):
+        return filename.endswith(".cpp") or filename.endswith(".c")
+    def cmtline(line, more=""):
+        if len(line.strip()) > 0:
+            return f"// {line}{more}"
+        else:
+            return "//\n"
+    out = ""
+    for entry in filenames:
+        if isinstance(entry, onlyif):
+            if entry.condition:
+                entry = entry.obj
+            else:
+                continue
+        if isinstance(entry, ignfile):
+            pass
+        elif isinstance(entry, cmttext):
+            for line in entry.text.split("\n"):
+                out += cmtline(line, "\n")
+        elif isinstance(entry, cmtfile):
+            filename = f"{rootdir}/{entry.filename}"
+            out += banner(entry.filename)
+            with open(filename) as file:
+                for line in file.readlines():
+                    out += cmtline(line)
+        elif isinstance(entry, injcode):
+            out += f"\n{entry.code}\n"
+        elif isinstance(entry, injfile):
+            entry.filename = f"{rootdir}/{entry.filename}"
+            to_inject[entry.incpattern] = entry
+        else:
+            filename = f"{rootdir}/{entry}"
+            out += banner(entry)
+            if isinstance(entry, hdrfile):
+                if entry.include_guard is not None:
+                    custom_include_guards[entry.incpattern] = entry.include_guard
+                out += append_file(filename, entry.include_guard)
+            else:
+                assert isinstance(entry, str)
+                if is_src(filename):
+                    out += append_cpp(filename)
+                else:
+                    out += append_file(filename)
+            out += footer(entry)
+    return f"""#ifndef {result_incguard}
+{out}
+#endif /* {result_incguard} */
+"""
+
+def include_only_first(file_contents: str):
+    rx = [
+        re.compile(r'^\s*#\s*include "(.*?)".*'),
+        re.compile(r'^\s*#\s*include <(.*?)>.*'),
+    ]
+    already_included = {}
+    out = ""
+    for line in file_contents.split("\n"):
+        for expr in rx:
+            match = expr.match(line)
+            if match:
+                incl = match.group(1)
+                if already_included.get(incl) is None:
+                    already_included[incl] = line
+                    if incl.endswith(".h"):
+                        cpp_version = f"c{incl[:-2]}"
+                        already_included[cpp_version] = line
+                    elif incl.startswith("c") and not (incl.endswith(".h") or incl.endswith(".hpp")):
+                        c_version = f"{incl[1:]}.h"
+                        already_included[c_version] = line
+                else:
+                    line = f"//included above:\n//{line}"
+                break
+        out += line
+        out += "\n"
+    return out
+
+
+def mkparser(**bool_args):
+    import argparse
+    parser = argparse.ArgumentParser()
+    parser.add_argument("output", default=None, nargs='?', help="output file. defaults to stdout")
+    for k, (default, help) in bool_args.items():
+        # https://stackoverflow.com/questions/15008758/parsing-boolean-values-with-argparse
+        feature = parser.add_mutually_exclusive_group(required=False)
+        yes = '--' + k
+        no = '--no-' + k
+        if default:
+            yes_default = "this is the default"
+            no_default = f"the default is {yes}"
+        else:
+            yes_default = f"the default is {no}"
+            no_default = "this is the default"
+        feature.add_argument(yes, dest=k, action='store_true', help=f"{help}. {yes_default}.")
+        feature.add_argument(no, dest=k, action='store_false', help=f"{help}. {no_default}.")
+        parser.set_defaults(**{k: default})
+    return parser
+
+
+def file_put_contents(filename: str, contents: str):
+    if filename is None:
+        print(contents)
+    else:
+        dirname = os.path.dirname(filename)
+        if dirname:
+            os.makedirs(dirname, exist_ok=True)
+        with open(filename, "w") as output:
+            output.write(contents)

+ 1 - 0
3rdparty/rapidyaml/ext/c4core/cmake/bm-xp/.gitignore

@@ -0,0 +1 @@
+static/*

+ 7 - 0
3rdparty/rapidyaml/ext/c4core/cmake/bm-xp/README.md

@@ -0,0 +1,7 @@
+# Benchmark explorer
+
+You need to start a http server on this folder:
+
+```shellsession
+$ python bm.py serve .
+```

+ 475 - 0
3rdparty/rapidyaml/ext/c4core/cmake/bm-xp/bm.js

@@ -0,0 +1,475 @@
+
+/* https://stackoverflow.com/questions/9050345/selecting-last-element-in-javascript-array */
+function last(arr)
+{
+    return arr[arr.length - 1];
+};
+
+function dbg()
+{
+  /* pass ?dbg=1 to enable debug logs */
+  /*if(!getParam('dbg', 0)){
+    return;
+  }*/
+  elm = $("#dbg");
+  var s = "";
+  for (var i = 0; i < arguments.length; i++) {
+    if(i > 0) s += ' ';
+    s += arguments[i].toString();
+  }
+  console.log(s);
+  s+= "\n";
+  elm.append(document.createTextNode(s));
+}
+
+
+function iterArr(arr, fn) {
+  for (var key in arr) {
+     if (arr.hasOwnProperty(key)) {
+       fn(key, arr[key]);
+     }
+  }
+}
+
+
+function fileContents(file, onComplete)
+{
+  dbg(`${file}: requesting...`);
+  var data;
+  $.get(file, function(d) {
+    dbg(`${file}: got response! ${d.length}B...`);
+    if(onComplete) {
+      onComplete(d);
+    }
+  }, "text");
+}
+
+
+
+/* https://stackoverflow.com/questions/7394748/whats-the-right-way-to-decode-a-string-that-has-special-html-entities-in-it/7394787 */
+function decodeHtmlEntities(str)
+{
+  return str
+    .replace("&amp;", "&")
+    .replace("&lt;", "<")
+    .replace("&gt;", ">")
+    .replace("&quot;", "\"")
+    .replace(/&#(\d+);/g, function(match, dec) {
+      return String.fromCharCode(dec);
+    });
+}
+/* https://stackoverflow.com/questions/6234773/can-i-escape-html-special-chars-in-javascript */
+function escapeHtml(unsafe)
+{
+    return unsafe
+         .replace(/&/g, "&amp;")
+         .replace(/</g, "&lt;")
+         .replace(/>/g, "&gt;")
+         .replace(/"/g, "&quot;")
+         .replace(/'/g, "&#039;");
+}
+
+
+/* URL params ----------------------------------------------------------------- */
+
+var _curr_url_params = null;
+function parseUrlParams()
+{
+  var keyvals = [];
+  var keys = document.location.search.substring(1).split('&');
+  dbg("keys=", keys)
+  for(var i = 0; i < keys.length; i++) {
+    var key = keys[i].split('=');
+    dbg("i=", i, "  key=", key);
+    keyvals.push(key[0]);
+    keyvals[key[0]] = key[1];
+  }
+  _curr_url_params = keyvals;
+}
+
+function dbgParams() {
+  iterArr(_curr_url_params, function(key, val){ dbg("url params:", key, "=", val); })
+
+}
+function getParam(name, fallback)
+{
+  if(_curr_url_params === null) { parseUrlParams(); }
+  if(name in _curr_url_params) {
+    return _curr_url_params[name];
+  }
+  return fallback;
+}
+
+function setParam(name, value) {
+  if(_curr_url_params === null) { parseUrlParams(); }
+  _curr_url_params[name] = value;
+  // https://stackoverflow.com/questions/486896/adding-a-parameter-to-the-url-with-javascript
+  document.location.search = joinParams();
+}
+
+function joinParams() {
+  if(_curr_url_params === null) { parseUrlParams(); }
+  var s = "";
+  iterArr(_curr_url_params, function(key, val){
+    if(s != ""){ s += '&'; }
+    s += `${key}=${val}`;
+  });
+  return s;
+}
+
+
+/* ----------------------------------------------------------------------------- */
+
+function colMax(data, col)
+{
+  var max = -1.e30;
+  data.forEach(function(item, index){
+    max = item[col] > max ? item[col] : max;
+  });
+  return max;
+}
+
+function colMin(data, col)
+{
+  var min = 1.e30;
+  data.forEach(function(item, index){
+    min = item[col] < min ? item[col] : min;
+  });
+  return min;
+}
+
+/* https://stackoverflow.com/questions/2283566/how-can-i-round-a-number-in-javascript-tofixed-returns-a-string */
+function toFixedNumber(num, digits, base)
+{
+  var pow = Math.pow(base||10, digits);
+  return Math.round(num*pow) / pow;
+}
+
+function humanReadable(sz, base=1024, precision=3)
+{
+  var i = -1;
+  var units;
+  if(base == 1000)
+  {
+    units = ['k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
+  }
+  else if(base == 1024)
+  {
+    units = ['ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'];
+  }
+  do
+  {
+    sz /= base;
+    i++;
+  } while (sz > base);
+  return sz.toFixed(precision) + units[i];
+};
+
+
+/* ----------------------------------------------------------------------------- */
+
+class BmResults
+{
+  constructor(dict={})
+  {
+    Object.assign(this, dict);
+    for(var i = 0; i < this.benchmarks.length; ++i) {
+      var bm = this.benchmarks[i];
+      bm.name = decodeHtmlEntities(bm.name);
+      bm.run_name = decodeHtmlEntities(bm.run_name);
+    }
+  }
+}
+
+var bmSpecs;
+function iterBms(fn)
+{
+  iterArr(bmSpecs.bm, fn);
+}
+
+function loadSpecs(specs)
+{
+  dbg("loading specs ....");
+  iterArr(specs, function(k, v){dbg("k=", k, 'v=', v); });
+  $("#heading-title").html(`Benchmarks: <a href="${specs.url}">${specs.projname}</a>`);
+  bmSpecs = specs;
+  var toc = $("#toc");
+  /*toc.append(`<li><a href="#" onclick="setParam('bm', 'all');">Load all</a></li>`);*/
+  iterBms(function(key, bm) {
+    toc.append(`<li><a href="#${key}" onclick="setParam('bm', '${key}');">${key}</a>: ${bm.specs.desc}</li>`)
+    bm.name = key;
+  });
+  // load if required
+  currBm = getParam("bm", "");
+  dbg("params=", _curr_url_params, currBm);
+  if(currBm != "") {
+    dbg("loading BM from URL:", currBm)
+    loadBm(currBm);
+  }
+}
+
+function normalizeBy(results, column_name, best_fn)
+{
+  var best = best_fn(results.benchmarks, column_name);
+  results.benchmarks.forEach(function(item, index){
+    item[`${column_name}_normalized`] = item[column_name] / best;
+  });
+}
+
+
+function loadAll()
+{
+  var id = "#bm-results";
+  $(id).empty();
+  var i = 0;
+  iterBms(function(key, bm){
+    if(i++ > 0) $(id).append("<div class='bm-sep'><hr/></div>");
+    appendBm(key);
+  });
+}
+
+
+function loadBm(key)
+{
+  dbg("loading-.....", key);
+  /*if(key == "all") {
+    loadAll();
+  }*/
+  $("#bm-results").empty();
+  var bm = bmSpecs.bm[key];
+  if(bm.src != "") {
+    fileContents(bm.src, function(data){
+      dbg(`${key}: got src data!`)
+      bm.src_data = data;
+    });
+  }
+  var latestRun = last(bm.entries);
+  var bmfile = `${latestRun}/${key}.json`;
+  dbg("bmfile=", bmfile);
+  fileContents("bm/"+bmfile, function(data){
+    dbg(`${key}: got bm data!`)
+    bm.results_data = new BmResults(JSON.parse(data));
+    bm.results_data.benchmarks.forEach(function(item, index){
+      item.id = index;
+    });
+    normalizeBy(bm.results_data, 'iterations', colMin);
+    normalizeBy(bm.results_data, 'real_time', colMin, );
+    normalizeBy(bm.results_data, 'cpu_time', colMin);
+    normalizeBy(bm.results_data, 'bytes_per_second', colMin);
+    normalizeBy(bm.results_data, 'items_per_second', colMin);
+    appendBm(latestRun, key, bm);
+  });
+}
+
+
+function appendBm(run_id, id, bm)
+{
+  if($(document).find(`bm-results-${id}`).length == 0)
+  {
+    $("#bm-results").append(`
+<div id="bm-results-${id}">
+  <h2 id="bm-title-${id}">${id}</h2>
+
+  <h3 id="heading-details-table-${id}">Run details</h3><table id="table-details-${id}" class="datatable" width="800px"></table>
+
+  <h3 id="heading-table-${id}">Result tables</h3>
+  <h4 id="heading-table-${id}_pretty">Results</h4><table id="table-${id}_pretty" class="datatable" width="800px"></table>
+  <h4 id="heading-table-${id}_normalized">Normalized by column min</h4><table id="table-${id}_normalized" class="datatable" width="800px"></table>
+
+  <h3 id="heading-chart-${id}">Chart</h2>
+  <div id="chart-container-${id}"></div>
+
+  <h3 id="heading-code-${id}">Code</h2>
+  <pre><code id="code-${id}" class="lang-c++"></code></pre>
+</div>
+`);
+  }
+  var results = bm.results_data;
+  var code = bm.src_data;
+  loadDetailsTable(run_id, id, bm, results);
+  loadTable(id, bm, results);
+  loadChart(id, bm, results);
+  loadCode(id, bm, code);
+}
+
+
+function loadCode(elmId, bm, code)
+{
+  var elm = $(`#code-${elmId}`);
+  elm.text(code);
+  /*  hljs.highlightBlock(elm); // this doesn't work */
+  /*  ... and this is very inefficient: */
+  document.querySelectorAll('pre code').forEach((block) => {
+    hljs.highlightBlock(block);
+  });
+}
+
+function parseRunId(run_id)
+{
+  // example:
+  //        commit id          /  cpu id       -  system id   -    build id
+  // git20201204_202919-b3f7fa7/x86_64_b9db3176-linux_4e9326b4-64bit_Debug_gcc10.2.0_10c5d03c
+  // git20201203_193348-2974fb0/x86_64_16ac0500-win32_59f3579c-64bit_MinSizeRel_msvc19.28.29304.1_32f6fc66
+  // to tune the regex: https://regex101.com/r/rdkPi8/1
+  //          commit             / cpu               - system            - build
+  var rx = /^(.+?)-([0-9a-f]{7})\/(.+?)_([0-9a-f]{8})-(.+?)_([0-9a-f]{8})-(.+?)_([0-9a-f]{8})$/gim;
+  var tag = rx.exec(run_id);
+  dbg("fdx: run_id=", run_id);
+  dbg("fdx: tag=", tag);
+  dbg("fdx: len=", tag.length);
+  return {
+    commit_id: `${tag[2]}: ${tag[1]}`,
+    cpu_id: `${tag[4]}: ${tag[3]} `,
+    system_id: `${tag[6]}: ${tag[5]}`,
+    build_id: `${tag[8]}: ${tag[7]}`,
+  };
+}
+
+function getBuildId(run_id)
+{
+  return parseRunId(run_id).build_id;
+}
+
+function loadDetailsTable(run_id, id, bm, results)
+{
+  var url = bmSpecs.url;
+  var run = bmSpecs.runs[run_id];
+  var commit = bmSpecs.commit[run.commit].specs;
+  var cpu = bmSpecs.cpu[run.cpu].specs;
+  var system = bmSpecs.system[run.system].specs;
+
+  let other_commit_entries = bmSpecs.commit[run.commit].entries.filter(
+    entry_run => entry_run != run_id
+  ).map(entry_run => getBuildId(entry_run)).join('<br>');
+
+  /*  https://datatables.net/ */
+  $(`#table-details-${id}`).DataTable({
+    data: results.benchmarks,
+    info: false,
+    paging: false,
+    searching: false,
+    retrieve: false,
+    order: [],
+    columns: [
+      {title: "", data: "desc"},
+      {title: "", data: "contents"},
+    ],
+    data: [
+      {desc: "benchmark id"  , contents: id},
+      {desc: "commit"        , contents: ahref(`${url}/commit/${commit.sha1}`, commit.sha1)},
+      {desc: "commit date"   , contents: ahref(`${url}/commit/${commit.sha1}`, commit.committed_datetime)},
+      {desc: "commit summary", contents: ahref(`${url}/commit/${commit.sha1}`, commit.summary)},
+      {desc: "source tree"   , contents: ahref(`${url}/tree/${commit.sha1}`, `tree @ ${commit.sha1}`)},
+      {desc: "benchmark"     , contents: ahref(`${url}/tree/${commit.sha1}/${bm.specs.src}`, `source @ ${commit.sha1}`)},
+      {desc: "cpu used"      , contents: `${cpu.arch} ${cpu.brand_raw}`},
+      {desc: "system used"   , contents: `${system.uname.system} ${system.uname.release}`},
+      {desc: "this build"    , contents: `<pre>${getBuildId(run_id)}</pre>`},
+      {desc: "commit builds" , contents: `<pre>${other_commit_entries}</pre>`},
+    ]
+  });
+  function ahref(url, txt) { return `<a href="${url}" target="_blank">${txt}</a>`; }
+}
+
+
+function loadTable(id, bm, results)
+{
+  function render_int(data, type, row, meta) { return toFixedNumber(data, 0); }
+  function render_megas(data, type, row, meta) { return toFixedNumber(data / 1.e6, 3); }
+  function render_fixed(data, type, row, meta) { return toFixedNumber(data, 3); }
+  function render_human(data, type, row, meta) { return humanReadable(data, 1000, 3); }
+
+  addTable("_pretty"    , ""           , {ns: render_int, iters: render_megas, rates: render_megas});
+  addTable("_normalized", "_normalized", {ns: render_fixed, iters: render_fixed, rates: render_fixed});
+
+  function addTable(suffix, data_suffix, renderers) {
+    /*  https://datatables.net/ */
+    var searching = (results.benchmarks.count > 20);
+    var ratePrefix = renderers.rates == render_megas ? "M" : "";
+    var iterPrefix = renderers.iters == render_megas ? "M" : "";
+    var clockSuffix = data_suffix == "_normalized" ? "" : "(ns)";
+    $(`#table-${id}${suffix}`).DataTable( {
+      data: results.benchmarks,
+      info: false,
+      paging: false,
+      searching: searching,
+      retrieve: searching,
+      /*  https://datatables.net/reference/option/columns.type */
+      columns: [
+        {title: "ID", data: "id", type: "num"},
+        {title: "Name", data: "name", render: function(data, type, row, meta) { return escapeHtml(data); }},
+        {title: `${ratePrefix}B/s`       , data: `bytes_per_second${data_suffix}`, type: "num", className: "text-right", render: renderers.rates},
+        {title: `${ratePrefix}items/s`   , data: `items_per_second${data_suffix}`, type: "num", className: "text-right", render: renderers.rates},
+        {title: `Clock${clockSuffix}`    , data: `real_time${data_suffix}`       , type: "num", className: "text-right", render: renderers.ns},
+        {title: `CPU${clockSuffix}`      , data: `cpu_time${data_suffix}`        , type: "num", className: "text-right", render: renderers.ns},
+        {title: `${ratePrefix}Iterations`, data: `iterations${data_suffix}`      , type: "num", className: "text-right", render: renderers.iters},
+      ]});
+  }
+}
+
+function loadChart(id, bm, results)
+{
+
+  addChartFromColumn('bytes_per_second_normalized', "B/s", "(more is better)");
+  addChartFromColumn('items_per_second_normalized', "items/s", "(more is better)");
+  addChartFromColumn('iterations_normalized', "Iterations", "(more is better)");
+  addChartFromColumn('real_time_normalized', "Clock time", "(less is better)");
+  addChartFromColumn('cpu_time_normalized', "CPU time", "(less is better)");
+
+  function addChartFromColumn(column, column_name, obs) {
+    var elmId = `chart-${id}-${column}`;
+    var canvas = `${elmId}-canvas`;
+
+    $(`#chart-container-${id}`).append(`
+<div id="${elmId}" class="chart">
+  <canvas id="${canvas}"></canvas>
+</div>
+`);
+
+    var chart = new CanvasJS.Chart(elmId, {
+      animationEnabled: false,
+      title:{
+        fontSize: 24,
+        /* text: `${id}: ${column_name}\n${obs}` */
+        text: `${column_name}\n${obs}`
+      },
+      axisX: {
+          labelFontSize: 12,
+      },
+      data: [{
+        type: "bar",
+        axisYType: "secondary",
+        color: "#eb7434",/*"#014D65",*/
+        dataPoints: results.benchmarks.map(function(item){
+          return {
+            indexLabelFormatter: function(e) { return e.dataPoint.indexLabel; },
+            indexLabelFontSize: 16,
+            indexLabel: item.name,
+            /* label: item.name, */
+            y: item[column],
+            /* save the result here: the tooltip will show the full thing */
+            benchmark_results: item
+          };
+        }),
+      }],
+      toolTip: {
+        /*content: "{indexLabel}: {y}",*/
+        contentFormatter: function(e){
+          function hr(val) { return humanReadable(val, 1000, 3); }
+          function fx(val) { return toFixedNumber(val, 3); }
+          function fxi(val) { return toFixedNumber(val, 0); }
+          function getRow(name, abs, rel) { return `<tr><td>${name}</td><td>${abs}</td><td>${rel}x min</td></tr>`; }
+          var r = e.entries[0].dataPoint.benchmark_results;
+          var hdrRow = `<tr><th></th><th>Absolute</th><th>Normalized</th></tr>`;
+          var bpsRow = getRow("B/s", hr(r.bytes_per_second), fx(r.bytes_per_second_normalized));
+          var ipsRow = getRow("items/s", hr(r.items_per_second), fx(r.items_per_second_normalized));
+          var cpuRow = getRow("CPU", fxi(r.cpu_time) + "ns", fx(r.cpu_time_normalized));
+          var clockRow = getRow("Clock", fxi(r.real_time) + "ns", fx(r.real_time_normalized));
+          var itersRow = getRow("Iterations", hr(r.iterations), fx(r.iterations_normalized));
+          var table = `<table>${hdrRow}${bpsRow}${ipsRow}${cpuRow}${clockRow}${itersRow}</table>`;
+          return `<h4>${escapeHtml(r.name)}</h4>${table}`;
+        }
+      }
+    });
+    chart.render();
+  }
+}

+ 568 - 0
3rdparty/rapidyaml/ext/c4core/cmake/bm-xp/bm.py

@@ -0,0 +1,568 @@
+import os
+import sys
+import argparse
+import requests
+import flask
+import json
+import re
+import yaml
+import shutil
+import mmh3
+
+from munch import Munch, munchify
+from flask import render_template, redirect, url_for, send_from_directory
+from markupsafe import escape
+
+
+def log(*args, **kwargs):
+    print(*args, **kwargs, flush=True)
+
+
+def myhash_combine(curr, value):
+    return curr ^ (value + 0x9e3779b9 + (curr<<6) + (curr>>2))
+
+
+def optionals(obj, *attrs):
+    ret = []
+    for attr in attrs:
+        if not hasattr(obj, attr):
+            log("attr not present:", attr)
+            continue
+        ret.append(getattr(obj, attr))
+    return ret
+
+
+def myhash(*args):
+    h = 137597
+    for a in args:
+        if isinstance(a, str):
+            if a == "":
+                continue
+            b = bytes(a, "utf8")
+        else:
+            b = bytes(a)
+        hb = mmh3.hash(b, signed=False)
+        h = myhash_combine(h, hb)
+    s = hex(h)
+    return s[2:min(10, len(s))]
+
+
+def copy_file_to_dir(file, dir):
+    dir = os.path.abspath(dir)
+    src = os.path.abspath(file)
+    dst = f"{dir}/{os.path.basename(src)}"
+    if not os.path.exists(dir):
+        os.makedirs(dir)
+    if os.path.exists(dst):
+        os.remove(dst)
+    log("copy:", src, "-->", dst)
+    shutil.copy(src, dst)
+    return dst
+
+
+def chk(f):
+    log(f"looking for file:", f)
+    assert os.path.exists(f), f
+    return f
+
+
+def load_yml_file(filename):
+    if not os.path.exists(filename):
+        raise Exception(f"not found: {filename}")
+    with open(filename) as f:
+        return load_yml(f.read())
+
+
+def dump_yml(data, filename):
+    with open(filename, "w") as f:
+        yaml.safe_dump(data, f)
+
+
+def load_yml(yml):
+    return munchify(yaml.safe_load(yml))
+
+
+def dump_json(data, filename):
+    with open(filename, "w") as f:
+        f.write(json.dumps(data, indent=2, sort_keys=True))
+
+
+def main():
+    #
+    parser = argparse.ArgumentParser(description="Browse benchmark results", prog="bm")
+    parser.add_argument("--debug", action="store_true", help="enable debug mode")
+    subparsers = parser.add_subparsers()
+    #
+    sp = subparsers.add_parser("create", help="create benchmark collection")
+    sp.set_defaults(func=BenchmarkCollection.create_new)
+    sp.add_argument("--debug", action="store_true", help="enable debug mode")
+    sp.add_argument("filename", type=str, help="the YAML file with the benchmark specs")
+    sp.add_argument("target", type=str, help="the directory to store the results")
+    #
+    sp = subparsers.add_parser("meta", help="get the required meta-information: cpu info, commit data")
+    sp.set_defaults(func=add_meta)
+    sp.add_argument("--debug", action="store_true", help="enable debug mode")
+    sp.add_argument("results", type=str, help="the directory with the results")
+    sp.add_argument("cmakecache", type=str, help="the path to the CMakeCache.txt file used to build the benchmark binaries")
+    sp.add_argument("build_type", type=str, help="the build type, eg Release Debug MinSizeRel RelWithDebInfo")
+    #
+    sp = subparsers.add_parser("add", help="add benchmark results")
+    sp.set_defaults(func=add_results)
+    sp.add_argument("--debug", action="store_true", help="enable debug mode")
+    sp.add_argument("results", type=str, help="the directory with the results")
+    sp.add_argument("target", type=str, help="the directory to store the results")
+    #
+    sp = subparsers.add_parser("serve", help="serve benchmark results")
+    sp.set_defaults(func=serve)
+    sp.add_argument("--debug", action="store_true", help="enable debug mode")
+    sp.add_argument("bmdir", type=os.path.abspath, default=os.getcwd(), help="the directory with the results. default=.")
+    sp.add_argument("-H", "--host", type=str, default="localhost", help="host. default=%(default)s")
+    sp.add_argument("-p", "--port", type=int, default=8000, help="port. default=%(default)s")
+    #
+    sp = subparsers.add_parser("export", help="export static html")
+    sp.set_defaults(func=freeze)
+    sp.add_argument("--debug", action="store_true", help="enable debug mode")
+    sp.add_argument("bmdir", type=os.path.abspath, default=os.getcwd(), help="the directory with the results. default=.")
+    #
+    sp = subparsers.add_parser("deps", help="install server dependencies")
+    sp.set_defaults(func=lambda _: download_deps())
+    sp.add_argument("--debug", action="store_true", help="enable debug mode")
+    #
+    args = parser.parse_args(sys.argv[1:] if len(sys.argv) > 1 else ["serve"])
+    if args.debug:
+        log(args)
+    args.func(args)
+
+
+def get_manifest(args):
+    bmdir = os.path.abspath(args.bmdir)
+    manif_yml = os.path.join(bmdir, "manifest.yml")
+    manif_json = os.path.join(bmdir, "manifest.json")
+    manif = load_yml_file(manif_yml)
+    dump_json(manif, manif_json)
+    return manif
+
+
+# ------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
+
+app = flask.Flask(__name__,
+                  template_folder='template')
+
+
+def _setup_app(args):
+    def _s(prop, val):
+        assert not hasattr(app, prop), prop
+        setattr(app, prop, val)
+    _s('args', args)
+    _s('manifest', get_manifest(args))
+    if args.debug:
+        app.config["DEBUG"] = True
+
+
+def freeze(args):
+    "https://pythonhosted.org/Frozen-Flask/"
+    from flask_frozen import Freezer
+    _setup_app(args)
+    freezer = Freezer(app)
+    freezer.freeze(debug=args.debug)
+
+
+def serve(args):
+    _setup_app(args)
+    app.run(host=args.host, port=args.port, debug=args.debug)
+
+
+@app.route("/")
+def home():
+    log("requested home")
+    return render_template("index.html")
+
+
+@app.route("/<path>")
+def other_(path):
+    path = escape(path)
+    d = app.args.bmdir
+    log("requested other path:", path, "---", os.path.join(d, path))
+    return send_from_directory(d, path)
+
+
+@app.route("/static/<path>")
+def static_(path):
+    path = escape(path)
+    d = os.path.join(app.args.bmdir, "static")
+    log("requested static path:", path, "---", os.path.join(d, path))
+    return send_from_directory(d, path, cache_timeout=1)  # timeout in seconds
+
+
+@app.route("/bm/<commit>/<run>/<resultjson>")
+def bm_(commit, run, resultjson):
+    commit = escape(commit)
+    run = escape(run)
+    resultjson = escape(resultjson)
+    d = os.path.join(app.args.bmdir, "runs", commit, run)
+    log("requested result:", os.path.join(d, resultjson))
+    return send_from_directory(d, resultjson, cache_timeout=1)  # timeout in seconds
+
+
+# ------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
+
+def download_deps():
+    deps = [
+        "https://code.jquery.com/jquery-3.3.1.js",
+        "https://code.jquery.com/jquery-3.3.1.js",
+        "https://code.jquery.com/ui/1.12.1/jquery-ui.js",
+        "https://cdn.datatables.net/1.10.20/js/jquery.dataTables.js",
+        "https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js",
+        "https://cdn.datatables.net/1.10.20/css/jquery.dataTables.css",
+        "https://cdn.datatables.net/1.10.20/css/jquery.dataTables.min.css",
+        "https://www.chartjs.org/dist/2.9.1/Chart.min.js",
+        #("https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.3.2/styles/github.css", "highlight.github.css"),
+        ("https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.3.2/styles/github.min.css", "highlight.github.min.css"),
+        #"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.3.2/highlight.js",
+        "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.3.2/highlight.min.js",
+    ]
+    for src in deps:
+        if type(src) == str:
+            base = os.path.basename(src)
+        else:
+            src, base = src
+        dst = f"{os.getcwd()}/static/{base}"
+        download_url(src, dst)
+
+
+def download_url(url, dst):
+    log("download url:", url, "--->", dst)
+    req = requests.get(url, stream=True)
+    if req.status_code == 200:
+        sz = 0
+        with open(dst, 'wb') as f:
+            for chunk in req:
+                f.write(chunk)
+                sz += len(chunk)
+        log(f"........ finished: {sz}B")
+    else:
+        log(f"         error:", req.status_code, url)
+
+
+# ------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
+
+class BenchmarkCollection:
+
+    @staticmethod
+    def create_new(args):
+        dir = args.target
+        filename = os.path.join(dir, "bm.yml")
+        manifest = os.path.join(dir, "manifest.yml")
+        if not os.path.exists(dir):
+            os.makedirs(dir)
+        shutil.copyfile(args.filename, filename)
+        dump_yml(load_yml("""{runs: {}, bm: {}}"""), manifest)
+        return __class__(dir)
+
+    def __init__(self, dir):
+        if not os.path.exists(dir):
+            raise Exception(f"not found: {dir}")
+        self.dir = os.path.abspath(dir)
+        self.runs_dir = os.path.join(self.dir, "runs")
+        self.manifest = os.path.join(self.dir, "manifest.yml")
+        self.filename = os.path.join(self.dir, "bm.yml")
+        self.specs = munchify(load_yml_file(self.filename))
+        self.manif = munchify(load_yml_file(self.manifest))
+
+    def add(self, results_dir):
+        results_dir = os.path.abspath(results_dir)
+        dst_dir, meta = self._read_run(results_dir)
+        self._add_run(results_dir, dst_dir, meta)
+        dump_yml(self.manif, self.manifest)
+
+    def _read_run(self, results_dir):
+        log("adding run...")
+        id = f"{len(self.manif.runs.keys()):05d}"
+        log(f"adding run: id={id}")
+        meta = ResultMeta.load(results_dir)
+        dst_dir = os.path.join(self.runs_dir, meta.name)
+        return dst_dir, meta
+
+    def _add_run(self, results_dir, dst_dir, meta):
+        cats = self._add_meta_categories(meta)
+        for filename in ("meta.yml",
+                         "CMakeCCompiler.cmake",
+                         "CMakeCXXCompiler.cmake",
+                         "CMakeSystem.cmake",
+                         "compile_commands.json"):
+            filename = os.path.join(results_dir, filename)
+            if os.path.exists(filename):
+                copy_file_to_dir(filename, dst_dir)
+            else:
+                if not filename.endswith("compile_commands.json"):
+                    raise Exception(f"wtf???? {filename}")
+        for name, specs in self.specs.bm.items():
+            if not hasattr(specs, 'variants'):
+                filename = chk(f"{results_dir}/{name}.json")
+                dst = copy_file_to_dir(filename, dst_dir)
+                self._add_bm_run(name, specs, meta)
+            else:
+                for t in specs.variants:
+                    tname = f"{name}-{t}"
+                    filename = chk(f"{results_dir}/{tname}.json")
+                    dst = copy_file_to_dir(filename, dst_dir)
+                    self._add_bm_run(tname, specs, meta)
+
+    def _add_bm_run(self, name, specs, meta):
+        if name not in self.manif.bm.keys():
+            self.manif.bm[name] = Munch(specs=specs, entries=[])
+        entry = self.manif.bm[name]
+        entry.specs = specs
+        if meta.name not in entry.entries:
+            entry.entries.append(meta.name)
+
+    def _add_meta_categories(self, meta):
+        run = Munch()
+        for catname in ('commit', 'cpu', 'system', 'build'):
+            meta_item = getattr(meta, catname)
+            self._add_item_to_category(meta.name, catname, meta_item)
+            run[catname] = meta_item.storage_id
+        # build specs are too verbose; remove them
+        self.manif.build[meta.build.storage_id].specs = Munch()
+        self.manif.runs[meta.name] = run
+
+    def _add_item_to_category(self, run, category_name, item):
+        if not hasattr(self.manif, category_name):
+            setattr(self.manif, category_name, Munch())
+        category = getattr(self.manif, category_name)
+        if item.storage_id not in category.keys():
+            category[item.storage_id] = Munch(specs=item, entries=[])
+        entry = category[item.storage_id]
+        entry.specs = item
+        if run not in entry.entries:
+            entry.entries.append(run)
+
+
+# -----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
+
+class ResultMeta(Munch):
+
+    def __init__(self, results_dir, cmakecache, build_type):
+        super().__init__(self)
+        self.date = __class__.get_date()
+        self.commit = __class__.get_commit(results_dir)
+        self.cpu = __class__.get_cpu_info()
+        self.system = __class__.get_sys_info()
+        self.build = __class__.get_build_info(cmakecache, build_type)
+        self.name = self._get_name()
+
+    @staticmethod
+    def load(results_dir):
+        results_dir = os.path.join(os.path.abspath(results_dir), "meta.yml")
+        data = load_yml_file(results_dir)
+        return munchify(data)
+
+    def save(self, results_dir):
+        out = os.path.join(results_dir, "meta.yml")
+        log("saving meta:", out)
+        dump_yml(self, out)
+        self.build.save(results_dir)
+
+    @staticmethod
+    def get_date():
+        import datetime
+        now = datetime.datetime.now()
+        return now.strftime("%Y%m%d-%H%M%S")
+
+    def _get_name(self):
+        commit = self.commit.storage_name
+        cpu = self.cpu.storage_name
+        sys = self.system.storage_name
+        build = self.build.storage_name
+        name = f"{commit}/{cpu}-{sys}-{build}"
+        return name
+
+    @staticmethod
+    def get_commit(results_dir):
+        import git
+        repo = git.Repo(results_dir, search_parent_directories=True)
+        commit = repo.head.commit
+        commit = {p: str(getattr(commit, p))
+                  for p in ('message', 'summary', 'name_rev',
+                            'author',
+                            'authored_datetime',
+                            'committer',
+                            'committed_datetime',)}
+        commit = Munch(commit)
+        commit.message = commit.message.strip()
+        commit.sha1 = commit.name_rev[:7]
+        spl = commit.authored_datetime.split(" ")
+        date = re.sub(r'-', '', spl[0])
+        time = re.sub(r'(\d+):(\d+):(\d+).*', r'\1\2\3', spl[1])
+        commit.storage_id = commit.sha1
+        commit.storage_name = f"git{date}_{time}-{commit.sha1}"
+        return commit
+
+    @staticmethod
+    def get_cpu_info():
+        import cpuinfo
+        nfo = cpuinfo.get_cpu_info()
+        nfo = Munch(nfo)
+        for a in ('cpu_version', 'cpu_version_string', 'python_version'):
+            if hasattr(nfo, a):
+                delattr(nfo, a)
+        for a in ('arch_string_raw', 'brand_raw', 'hardware_raw', 'vendor_id_raw'):
+            if not hasattr(nfo, a):
+                setattr(nfo, a, '')
+        nfo.storage_id = myhash(
+            nfo.arch_string_raw, nfo.brand_raw, nfo.hardware_raw, nfo.vendor_id_raw,
+            nfo.arch, nfo.bits, nfo.count, nfo.family, nfo.model, nfo.stepping,
+            ",".join(nfo.flags), nfo.hz_advertised_friendly,
+            nfo.l2_cache_associativity,
+            nfo.l2_cache_line_size,
+            nfo.l2_cache_size,
+            nfo.l3_cache_size,
+            *optionals('l1_data_cache_size', 'l1_instruction_cache_size')
+        )
+        nfo.storage_name = f"{nfo.arch.lower()}_{nfo.storage_id}"
+        return nfo
+
+    @staticmethod
+    def get_sys_info():
+        import platform
+        uname = platform.uname()
+        nfo = Munch(
+            sys_platform=sys.platform,
+            sys=platform.system(),
+            uname=Munch(
+                machine=uname.machine,
+                node=uname.node,
+                release=uname.release,
+                system=uname.system,
+                version=uname.version,
+            )
+        )
+        nfo.storage_id = myhash(
+            nfo.sys_platform,
+            nfo.uname.machine,
+        )
+        nfo.storage_name = f"{nfo.sys_platform}_{nfo.storage_id}"
+        return nfo
+
+    @staticmethod
+    def get_build_info(cmakecache_txt, buildtype):
+        nfo = CMakeCache(cmakecache_txt)
+        def _btflags(name):
+            return (getattr(nfo, name), getattr(nfo, f"{name}_{buildtype.upper()}"))
+        nfo.storage_id = myhash(
+            buildtype,
+            nfo.CMAKE_CXX_COMPILER_ID,
+            nfo.CMAKE_CXX_COMPILER_VERSION,
+            nfo.CMAKE_CXX_COMPILER_VERSION_INTERNAL,
+            nfo.CMAKE_CXX_COMPILER_ABI,
+            nfo.CMAKE_CXX_SIZEOF_DATA_PTR,
+            nfo.CMAKE_C_COMPILER_ID,
+            nfo.CMAKE_C_COMPILER_VERSION,
+            nfo.CMAKE_C_COMPILER_VERSION_INTERNAL,
+            nfo.CMAKE_C_COMPILER_ABI,
+            nfo.CMAKE_C_SIZEOF_DATA_PTR,
+            *_btflags("CMAKE_CXX_FLAGS"),
+            *_btflags("CMAKE_C_FLAGS"),
+            *_btflags("CMAKE_STATIC_LINKER_FLAGS"),
+            *_btflags("CMAKE_SHARED_LINKER_FLAGS"),
+        )
+        #
+        ccname = nfo.CMAKE_CXX_COMPILER_ID.lower()
+        if ccname == "gnu":
+            ccname = "gcc"
+        ccname += nfo.CMAKE_CXX_COMPILER_VERSION.lower()
+        #
+        if nfo.CMAKE_C_SIZEOF_DATA_PTR == "4":
+            bits = "32bit"
+        elif nfo.CMAKE_C_SIZEOF_DATA_PTR == "8":
+            bits = "64bit"
+        else:
+            raise Exception("unknown architecture")
+        #
+        nfo.storage_name = f"{bits}_{buildtype}_{ccname}_{nfo.storage_id}"
+        return nfo
+
+
+# -----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
+
+class CMakeCache(Munch):
+
+    def __init__(self, cmakecache_txt):
+        import glob
+        for line in iter_cmake_lines(cmakecache_txt):
+            spl = line.split("=")
+            if len(spl) < 2:
+                continue
+            k, ty = spl[0].split(":")
+            v = "=".join(spl[1:]).strip()
+            setattr(self, k, v)
+        bdir = os.path.dirname(os.path.abspath(cmakecache_txt))
+        self._c_compiler_file = sorted(glob.glob(f"{bdir}/CMakeFiles/*/CMakeCCompiler.cmake"))[-1]  # get the last
+        self._cxx_compiler_file = sorted(glob.glob(f"{bdir}/CMakeFiles/*/CMakeCXXCompiler.cmake"))[-1]  # get the last
+        self._system_file = sorted(glob.glob(f"{bdir}/CMakeFiles/*/CMakeSystem.cmake"))[-1]  # get the last
+        self._load_cmake_file(self._c_compiler_file)
+        self._load_cmake_file(self._cxx_compiler_file)
+        ccomfile = f"{bdir}/compile_commands.json"
+        self._compile_commands_file = ccomfile if os.path.exists(ccomfile) else None
+
+    def _load_cmake_file(self, filename):
+        for line in iter_cmake_lines(filename):
+            if not line.startswith("set("):
+                continue
+            k = re.sub(r"set\((.*)\ +(.*)\)", r"\1", line)
+            v = re.sub(r"set\((.*)\ +(.*)\)", r"\2", line)
+            v = v.strip('"').strip("'").strip()
+            setattr(self, k, v)
+
+    def save(self, results_dir):
+        copy_file_to_dir(self._c_compiler_file, results_dir)
+        copy_file_to_dir(self._cxx_compiler_file, results_dir)
+        copy_file_to_dir(self._system_file, results_dir)
+        if self._compile_commands_file is not None:
+            copy_file_to_dir(self._compile_commands_file, results_dir)
+
+
+def iter_cmake_lines(filename):
+    with open(filename) as f:
+        for line in f.readlines():
+            line = line.strip()
+            if line.startswith("#") or line.startswith("//") or len(line) == 0:
+                continue
+            yield line
+
+
+# ------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
+
+def add_results(args):
+    log("adding results:", args.results)
+    col = BenchmarkCollection(args.target)
+    col.add(args.results)
+
+
+def add_meta(args):
+    log("adding bm run metadata to results dir:", args.results)
+    meta = ResultMeta(results_dir=args.results,
+                      cmakecache=args.cmakecache,
+                      build_type=args.build_type)
+    meta.save(args.results)
+    log("adding bm run metadata to results dir: success!")
+
+
+# ------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
+
+if __name__ == '__main__':
+    main()

+ 10 - 0
3rdparty/rapidyaml/ext/c4core/cmake/bm-xp/requirements.txt

@@ -0,0 +1,10 @@
+munch
+pyyaml
+py-cpuinfo
+psutil
+gitpython
+flask
+markupsafe
+Frozen-Flask
+requests
+mmh3

+ 45 - 0
3rdparty/rapidyaml/ext/c4core/cmake/bm-xp/template/index.html

@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+
+  <head>
+    <meta charset="UTF-8">
+    <link rel="shortcut icon" href="#">
+    <link rel="stylesheet" type="text/css" href="/static/jquery-ui.min.css"/>
+    <link rel="stylesheet" type="text/css" href="/static/jquery.dataTables.min.css"/>
+    <link rel="stylesheet" type="text/css" href="/static/highlight.github.min.css"/>
+    <style>
+      body {
+        font-family: "Trebuchet MS", sans-serif;
+        margin: 50px;
+      }
+      .chart {
+        height: 700px; max-width: 920px; margin: 0px auto;
+      }
+    </style>
+  </head>
+
+  <body>
+    <h1 id="heading-title">Title</h1>
+    <div>
+      Available benchmarks:
+      <ul id="toc"></ul>
+    </div>
+    <div id="bm-results"></div>
+    <div><pre id="dbg"></pre></div>
+    <!-- scripts -->
+    <script type="text/javascript" src="/static/jquery-3.3.1.min.js"></script>
+    <script type="text/javascript" src="/static/jquery-ui.js"></script>
+    <script type="text/javascript" src="/static/jquery.canvasjs.min.js"></script>
+    <script type="text/javascript" src="/static/Chart.min.js"></script>
+    <script type="text/javascript" src="/static/jquery.dataTables.min.js"></script>
+    <script type="text/javascript" src="/static/highlight.min.js"></script>
+    <script type="text/javascript" src="/bm.js"></script>
+    <script type="text/javascript">
+      $(document).ready(function() {
+        $.getJSON('/manifest.json', function(specs){
+          loadSpecs(specs);
+        })
+      });
+    </script>
+  </body>
+</html>

+ 105 - 0
3rdparty/rapidyaml/ext/c4core/cmake/c4CatSources.cmake

@@ -0,0 +1,105 @@
+if(NOT _c4CatSourcesIncluded)
+set(_c4CatSourcesIncluded ON)
+
+
+#------------------------------------------------------------------------------
+# concatenate the source files to an output file, adding preprocessor adjustment
+# for correct file/line reporting
+function(c4_cat_sources files output umbrella_target)
+    _c4_cat_sources_create_cat(cat)
+    c4_to_full_path("${files}" full_files) # we must work with full paths
+    c4_separate_list("${full_files}" sepfiles) # and use a string instead of a list
+    c4_dbg("${_c4_prefix}: catting sources to ${output}")
+    if(NOT EXISTS "${output}")
+        # the cat command is executed at build time, but we need the output
+        # file to exist to be able to create the target. so to bootstrap, just
+        # run the command now
+        c4_dbg("${_c4_prefix}: creating ${output} for the first time")
+        execute_process(
+            COMMAND ${cat} "${sepfiles}" "${output}"
+            WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
+            )
+    else()
+        c4_dbg("output exists: ${output}")
+    endif()
+    # add a custom command invoking our cat script for the input files
+    add_custom_command(OUTPUT ${output}
+        COMMAND ${cat} "${sepfiles}" "${output}"
+        DEPENDS ${files}
+        WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
+        COMMENT "concatenating sources to ${output}")
+    if(NOT TARGET ${umbrella_target})
+        add_custom_target(${umbrella_target} DEPENDS ${output} ${files})
+    endif()
+endfunction(c4_cat_sources)
+
+
+#------------------------------------------------------------------------------
+# get a cat script
+function(_c4_cat_sources_create_cat catfile)
+    # create a script to concatenate the sources
+    if(WIN32)
+        set(cat ${CMAKE_BINARY_DIR}/_c4catfiles.bat)
+        set(cattmp ${CMAKE_BINARY_DIR}/${CMAKE_FILES_DIRECTORY}/_c4catfiles.bat)
+    else()
+        set(cat ${CMAKE_BINARY_DIR}/_c4catfiles.sh)
+        set(cattmp ${CMAKE_BINARY_DIR}/${CMAKE_FILES_DIRECTORY}/_c4catfiles.sh)
+    endif()
+    set(${catfile} ${cat} PARENT_SCOPE)
+    if(NOT EXISTS ${cat})
+        if(WIN32)
+            file(WRITE ${cattmp} "
+setlocal EnableDelayedExpansion
+set \"src_files=%1\"
+set \"out_file=%2\"
+echo.>\"out_file%\"
+for %%f in (%src_files%) do (
+    echo.>>\"%out_file%\"
+    echo.>>\"%out_file%\"
+    echo \"/*BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB*/\".>>\"%out_file%\"
+    echo \"/*BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB*/\".>>\"%out_file%\"
+    echo \"/*BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB*/\".>>\"%out_file%\"
+    echo \"#line 1 \\\"%%f\\\" // reset __LINE__ and __FILE__ to the correct value\".>>\"%out_file%\"
+    type %%f>>\"%out_file%\"
+)
+")
+        else()
+            file(WRITE ${cattmp} "#!/bin/sh
+
+src_files=$1
+out_file=$2
+#echo \"src_files $src_files\"
+#echo \"out_file $out_file\"
+
+cat > $out_file << EOF
+// DO NOT EDIT.
+// this is an auto-generated file, and will be overwritten
+EOF
+for f in $src_files ; do
+    cat >> $out_file <<EOF
+
+
+/*BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB*/
+/*BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB*/
+/*BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB*/
+#line 1 \"$f\"
+EOF
+    cat $f >> $out_file
+done
+
+echo \"Wrote output to $out_file\"
+")
+        endif()
+        # add execute permissions
+        get_filename_component(catdir ${cat} DIRECTORY)
+        file(COPY ${cattmp} DESTINATION ${catdir}
+            FILE_PERMISSIONS
+            OWNER_READ OWNER_WRITE OWNER_EXECUTE
+            GROUP_READ GROUP_EXECUTE
+            WORLD_READ WORLD_EXECUTE
+            )
+    endif()
+endfunction()
+
+
+endif(NOT _c4CatSourcesIncluded)

+ 121 - 0
3rdparty/rapidyaml/ext/c4core/cmake/c4Doxygen.cmake

@@ -0,0 +1,121 @@
+# (C) 2019 Joao Paulo Magalhaes <dev@jpmag.me>
+if(NOT _c4_doxygen_included)
+set(_c4_doxygen_included ON)
+
+
+#------------------------------------------------------------------------------
+# TODO use customizations from https://cmake.org/cmake/help/v3.9/module/FindDoxygen.html
+function(c4_setup_doxygen umbrella_option)
+    cmake_dependent_option(${_c4_uprefix}BUILD_DOCS "Enable targets to build documentation for ${prefix}" ON "${umbrella_option}" OFF)
+    if(${_c4_uprefix}BUILD_DOCS)
+        find_package(Doxygen QUIET)
+        if(DOXYGEN_FOUND)
+            c4_log("enabling documentation targets")
+        else()
+            c4_dbg("doxygen not found")
+        endif()
+    endif()
+endfunction()
+
+#------------------------------------------------------------------------------
+function(c4_add_doxygen doc_name)
+    if(NOT ${_c4_uprefix}BUILD_DOCS)
+        return()
+    endif()
+    #
+    set(opt0
+    )
+    set(opt1
+        DOXYFILE DOXYFILE_IN
+        PROJ
+        PROJ_BRIEF
+        VERSION
+        OUTPUT_DIR
+        CLANG_DATABASE_PATH
+    )
+    set(optN
+        INPUT
+        FILE_PATTERNS
+        EXCLUDE
+        EXCLUDE_PATTERNS
+        EXCLUDE_SYMBOLS
+        STRIP_FROM_PATH
+        STRIP_FROM_INC_PATH
+        EXAMPLE_PATH
+    )
+    cmake_parse_arguments("" "${opt0}" "${opt1}" "${optN}" ${ARGN})
+    #
+    if(NOT _PROJ)
+        set(_PROJ ${_c4_ucprefix})
+    endif()
+    if(NOT _DOXYFILE AND NOT _DOXYFILE_IN)
+        set(_DOXYFILE_IN ${CMAKE_CURRENT_LIST_DIR}/Doxyfile.in)
+    endif()
+    if(NOT _OUTPUT_DIR)
+        if("${doc_name}" MATCHES "^[Dd]oc")
+            set(_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/${doc_name})
+        else()
+            set(_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/doc/${doc_name})
+        endif()
+    endif()
+    #
+    _c4_doxy_fwd_to_cmd(_PROJ OFF)
+    _c4_doxy_fwd_to_cmd(_PROJ_BRIEF OFF)
+    _c4_doxy_fwd_to_cmd(_VERSION OFF)
+    _c4_doxy_fwd_to_cmd(_OUTPUT_DIR OFF)
+    _c4_doxy_fwd_to_cmd(_CLANG_DATABASE_PATH OFF)
+    _c4_doxy_fwd_to_cmd(_INPUT ON)
+    _c4_doxy_fwd_to_cmd(_FILE_PATTERNS ON)
+    _c4_doxy_fwd_to_cmd(_EXCLUDE ON)
+    _c4_doxy_fwd_to_cmd(_EXCLUDE_PATTERNS ON)
+    _c4_doxy_fwd_to_cmd(_EXCLUDE_SYMBOLS ON)
+    _c4_doxy_fwd_to_cmd(_STRIP_FROM_PATH ON)
+    _c4_doxy_fwd_to_cmd(_STRIP_FROM_INC_PATH ON)
+    _c4_doxy_fwd_to_cmd(_EXAMPLE_PATH ON)
+    #
+    if("${doc_name}" MATCHES "^[Dd]oc")
+        set(tgt ${_c4_lcprefix}-${doc_name})
+    else()
+        set(tgt ${_c4_lcprefix}-doc-${doc_name})
+    endif()
+    #
+    if(_DOXYFILE)
+        set(doxyfile_out ${_DOXYFILE})
+    elseif(_DOXYFILE_IN)
+        set(doxyfile_out ${_OUTPUT_DIR}/Doxyfile)
+        set(config_script ${_c4_project_dir}/c4DoxygenConfig.cmake)
+        add_custom_command(OUTPUT ${doxyfile_out}
+            COMMAND ${CMAKE_COMMAND} -E remove -f ${doxyfile_out}
+            COMMAND ${CMAKE_COMMAND} -DDOXYFILE_IN=${_DOXYFILE_IN}  -DDOXYFILE_OUT=${doxyfile_out} ${defs} '-DALLVARS=${allvars}' '-DLISTVARS=${listvars}' -P ${config_script}
+            DEPENDS ${_DOXYFILE_IN} ${config_script}
+            COMMENT "${tgt}: generating ${doxyfile_out}"
+            WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+    endif()
+    #
+    add_custom_target(${tgt}
+        COMMAND ${DOXYGEN_EXECUTABLE} ${doxyfile_out}
+        DEPENDS ${doxyfile_out}
+        WORKING_DIRECTORY ${_OUTPUT_DIR}
+        COMMENT "${tgt}: docs will be placed in ${_OUTPUT_DIR}"
+        VERBATIM)
+    _c4_set_target_folder(${tgt} doc)
+endfunction()
+
+
+macro(_c4_doxy_fwd_to_cmd varname is_list)
+    if(NOT ("${${varname}}" STREQUAL ""))
+        if("${defs}" STREQUAL "")
+            set(li "-D${varname}=${${varname}}")
+        else()
+            set(li ${defs})
+            list(APPEND li "-D${varname}='${${varname}}'")
+        endif()
+        set(defs ${li})
+    endif()
+    set(allvars "${allvars};${varname}")
+    if(${is_list})
+        set(listvars "${listvars};${varname}")
+    endif()
+endmacro()
+
+endif(NOT _c4_doxygen_included)

+ 24 - 0
3rdparty/rapidyaml/ext/c4core/cmake/c4DoxygenConfig.cmake

@@ -0,0 +1,24 @@
+function(_c4_doxy_list_to_str var)
+    set(il)
+    foreach(i ${${var}})
+        if("${il}" STREQUAL "")
+            set(il "${i}")
+        else()
+            set(il "${il} ${i}")
+        endif()
+    endforeach()
+    set(${var} "${il}" PARENT_SCOPE)
+endfunction()
+
+string(REPLACE " " ";" ALLVARS ${ALLVARS})
+string(REPLACE " " ";" LISTVARS ${LISTVARS})
+
+foreach(var ${LISTVARS})
+    _c4_doxy_list_to_str(${var})
+endforeach()
+
+foreach(var ${ALLVARS})
+    message(STATUS "${var}='${${var}}'")
+endforeach()
+
+configure_file(${DOXYFILE_IN} ${DOXYFILE_OUT} @ONLY)

+ 186 - 0
3rdparty/rapidyaml/ext/c4core/cmake/c4GetTargetPropertyRecursive.cmake

@@ -0,0 +1,186 @@
+if(NOT _c4_GTPR_included)
+set(_c4_GTPR_included ON)
+
+function(c4_get_target_property_recursive outputvar target property)
+    #
+    # helps for debugging
+    if(_stack)
+        set(_stack "${_stack}/${target}")
+    else()
+        set(_stack "${property}:${target}")
+    endif()
+    #
+    # what type of target is this?
+    get_target_property(_rec_target_type ${target} TYPE)
+    c4_dbg("${_stack} [type=${_rec_target_type}]: get property ${property}")
+    #
+    # adjust the property names for interface targets
+    set(_ept_prop_ll LINK_LIBRARIES)
+    if(_rec_target_type STREQUAL "INTERFACE_LIBRARY")
+        set(_ept_prop_ll INTERFACE_LINK_LIBRARIES)
+        if(property STREQUAL "INCLUDE_DIRECTORIES")
+            c4_dbg("${_stack} [type=${_rec_target_type}]: property ${property} ---> INTERFACE_INCLUDE_DIRECTORIES")
+            set(property INTERFACE_INCLUDE_DIRECTORIES)
+        elseif(property STREQUAL "LINK_LIBRARIES")
+            c4_dbg("${_stack} [type=${_rec_target_type}]: property ${property} ---> INTERFACE_LINK_LIBRARIES")
+            set(property INTERFACE_LINK_LIBRARIES)
+        endif()
+    endif()
+    #
+    get_target_property(_ept_li ${target} ${property})
+    c4_dbg("${_stack} [type=${_rec_target_type}]: property ${property}=${_ept_li}")
+    if(NOT _ept_li)  # the property may not be set (ie foo-NOTFOUND)
+        set(_ept_li) # so clear it in that case
+    endif()
+    #
+    # now descend and append the property for each of the linked libraries
+    get_target_property(_ept_deps ${target} ${_ept_prop_ll})
+    if(_ept_deps)
+        foreach(_ept_ll ${_ept_deps})
+            if(TARGET ${_ept_ll})
+                c4_get_target_property_recursive(_ept_out ${_ept_ll} ${property})
+                list(APPEND _ept_li ${_ept_out})
+            endif()
+        endforeach()
+    endif()
+    #
+    foreach(le_ ${_ept_li})
+        string(STRIP "${le_}" le)
+        if(NOT le)
+        elseif("${le}" STREQUAL "")
+        else()
+            list(APPEND _ept_li_f ${le})
+        endif()
+    endforeach()
+    c4_dbg("${_stack} [type=${_rec_target_type}]: final=${_ept_li_f}")
+    set(${outputvar} ${_ept_li_f} PARENT_SCOPE)
+endfunction()
+
+
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+
+
+function(c4_set_transitive_property target prop_name prop_value)
+    set_target_properties(${target} PROPERTIES "${prop_name}" "${prop_value}")
+endfunction()
+
+
+function(c4_append_transitive_property target prop_name prop_value)
+    get_target_property(curr_value ${target} "${prop_name}")
+    if(curr_value)
+        list(APPEND curr_value "${prop_value}")
+    else()
+        set(curr_value "${prop_value}")
+    endif()
+    c4_set_transitive_property(${target} "${prop_name}" "${curr_value}")
+endfunction()
+
+
+# TODO: maybe we can use c4_get_target_property_recursive()?
+function(c4_get_transitive_property target prop_name out)
+    if(NOT TARGET ${target})
+        return()
+    endif()
+    # these will be the names of the variables we'll use to cache the result
+    set(_trval _C4_TRANSITIVE_${prop_name})
+    set(_trmark _C4_TRANSITIVE_${prop_name}_DONE)
+    #
+    get_target_property(cached ${target} ${_trmark})  # is it cached already
+    if(cached)
+        get_target_property(p ${target} _C4_TRANSITIVE_${prop_name})
+        set(${out} ${p} PARENT_SCOPE)
+        #c4_dbg("${target}: c4_get_transitive_property ${target} ${prop_name}: cached='${p}'")
+    else()
+        #c4_dbg("${target}: gathering transitive property: ${prop_name}...")
+        set(interleaved)
+        get_target_property(lv ${target} ${prop_name})
+        if(lv)
+            list(APPEND interleaved ${lv})
+        endif()
+        c4_get_transitive_libraries(${target} LINK_LIBRARIES libs)
+        c4_get_transitive_libraries(${target} INTERFACE_LINK_LIBRARIES ilibs)
+        list(APPEND libs ${ilibs})
+        foreach(lib ${libs})
+            #c4_dbg("${target}: considering ${lib}...")
+            if(NOT lib)
+                #c4_dbg("${target}: considering ${lib}: not found, skipping...")
+                continue()
+            endif()
+            if(NOT TARGET ${lib})
+                #c4_dbg("${target}: considering ${lib}: not a target, skipping...")
+                continue()
+            endif()
+            get_target_property(lv ${lib} ${prop_name})
+            if(lv)
+                list(APPEND interleaved ${lv})
+            endif()
+            c4_get_transitive_property(${lib} ${prop_name} v)
+            if(v)
+                list(APPEND interleaved ${v})
+            endif()
+            #c4_dbg("${target}: considering ${lib}---${interleaved}")
+        endforeach()
+        #c4_dbg("${target}: gathering transitive property: ${prop_name}: ${interleaved}")
+        set(${out} ${interleaved} PARENT_SCOPE)
+        get_target_property(aliased_target ${target} ALIASED_TARGET)
+        if(NOT aliased_target)
+            set_target_properties(${target} PROPERTIES
+                ${_trmark} ON
+                ${_trval} "${interleaved}")
+        endif()
+    endif()
+endfunction()
+
+
+function(c4_get_transitive_libraries target prop_name out)
+    if(NOT TARGET ${target})
+        return()
+    endif()
+    # these will be the names of the variables we'll use to cache the result
+    set(_trval _C4_TRANSITIVE_${prop_name})
+    set(_trmark _C4_TRANSITIVE_${prop_name}_DONE)
+    #
+    get_target_property(cached ${target} ${_trmark})
+    if(cached)
+        get_target_property(p ${target} ${_trval})
+        set(${out} ${p} PARENT_SCOPE)
+        #c4_dbg("${target}: c4_get_transitive_libraries ${target} ${prop_name}: cached='${p}'")
+    else()
+        #c4_dbg("${target}: gathering transitive libraries: ${prop_name}...")
+        get_target_property(target_type ${target} TYPE)
+        set(interleaved)
+        if(NOT ("${target_type}" STREQUAL "INTERFACE_LIBRARY")
+                AND ("${prop_name}" STREQUAL LINK_LIBRARIES))
+            get_target_property(l ${target} ${prop_name})
+            foreach(ll ${l})
+                #c4_dbg("${target}: considering ${ll}...")
+                if(NOT ll)
+                    #c4_dbg("${target}: considering ${ll}: not found, skipping...")
+                    continue()
+                endif()
+                if(NOT ll)
+                    #c4_dbg("${target}: considering ${ll}: not a target, skipping...")
+                    continue()
+                endif()
+                list(APPEND interleaved ${ll})
+                c4_get_transitive_libraries(${ll} ${prop_name} v)
+                if(v)
+                    list(APPEND interleaved ${v})
+                endif()
+                #c4_dbg("${target}: considering ${ll}---${interleaved}")
+            endforeach()
+        endif()
+        #c4_dbg("${target}: gathering transitive libraries: ${prop_name}: result='${interleaved}'")
+        set(${out} ${interleaved} PARENT_SCOPE)
+        get_target_property(aliased_target ${target} ALIASED_TARGET)
+        if(NOT aliased_target)
+            set_target_properties(${target} PROPERTIES
+                ${_trmark} ON
+                ${_trval} "${interleaved}")
+        endif()
+    endif()
+endfunction()
+
+endif(NOT _c4_GTPR_included)

+ 3666 - 0
3rdparty/rapidyaml/ext/c4core/cmake/c4Project.cmake

@@ -0,0 +1,3666 @@
+if(NOT _c4_project_included)
+set(_c4_project_included ON)
+set(_c4_project_file ${CMAKE_CURRENT_LIST_FILE})
+set(_c4_project_dir  ${CMAKE_CURRENT_LIST_DIR})
+
+
+# "I didn't have time to write a short letter, so I wrote a long one
+# instead." --  Mark Twain
+#
+# ... Eg, hopefully this code will be cleaned up. There's a lot of
+# code here that can be streamlined into a more intuitive arrangement.
+
+
+cmake_minimum_required(VERSION 3.12 FATAL_ERROR)
+
+list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR})
+set_property(GLOBAL PROPERTY USE_FOLDERS ON)
+
+include(ConfigurationTypes)
+include(CreateSourceGroup)
+include(c4SanitizeTarget)
+include(c4StaticAnalysis)
+include(PrintVar)
+include(c4CatSources)
+include(c4Doxygen)
+include(PatchUtils)
+
+
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+# define c4 project settings
+
+set(C4_EXTERN_DIR "$ENV{C4_EXTERN_DIR}" CACHE PATH "the directory where imported projects should be looked for (or cloned in when not found)")
+set(C4_DBG_ENABLED OFF CACHE BOOL "enable detailed cmake logs in c4Project code")
+set(C4_LIBRARY_TYPE "" CACHE STRING "default library type: either \"\"(defer to BUILD_SHARED_LIBS),INTERFACE,STATIC,SHARED,MODULE")
+set(C4_SOURCE_TRANSFORM NONE CACHE STRING "global source transform method")
+set(C4_HDR_EXTS "h;hpp;hh;h++;hxx" CACHE STRING "list of header extensions for determining which files are headers")
+set(C4_SRC_EXTS "c;cpp;cc;c++;cxx;cu;" CACHE STRING "list of compilation unit extensions for determining which files are sources")
+set(C4_ADD_EXTS "natvis" CACHE STRING "list of additional file extensions that might be added as sources to targets")
+set(C4_GEN_SRC_EXT "cpp" CACHE STRING "the extension of the output source files resulting from concatenation")
+set(C4_GEN_HDR_EXT "hpp" CACHE STRING "the extension of the output header files resulting from concatenation")
+set(C4_CXX_STANDARDS "20;17;14;11" CACHE STRING "list of CXX standards")
+set(C4_CXX_STANDARD_DEFAULT "11" CACHE STRING "the default CXX standard for projects not specifying one")
+
+
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+
+macro(c4_log)
+    message(STATUS "${_c4_prefix}: ${ARGN}")
+endmacro()
+
+
+macro(c4_err)
+    message(FATAL_ERROR "${_c4_prefix}: ${ARGN}")
+endmacro()
+
+
+macro(c4_dbg)
+    if(C4_DBG_ENABLED)
+        message(STATUS "${_c4_prefix}: ${ARGN}")
+    endif()
+endmacro()
+
+
+macro(c4_log_var varname)
+    c4_log("${varname}=${${varname}} ${ARGN}")
+endmacro()
+macro(c4_log_vars)
+    set(____s____)
+    foreach(varname ${ARGN})
+        set(____s____ "${____s____}${varname}=${${varname}} ")
+    endforeach()
+    c4_log("${____s____}")
+endmacro()
+macro(c4_dbg_var varname)
+    c4_dbg("${varname}=${${varname}} ${ARGN}")
+endmacro()
+macro(c4_log_var_if varname)
+    if(${varname})
+        c4_log("${varname}=${${varname}} ${ARGN}")
+    endif()
+endmacro()
+macro(c4_dbg_var_if varname)
+    if(${varname})
+        c4_dbg("${varname}=${${varname}} ${ARGN}")
+    endif()
+endmacro()
+
+
+macro(_c4_show_pfx_vars)
+    if(NOT ("${ARGN}" STREQUAL ""))
+        c4_log("prefix vars: ${ARGN}")
+    endif()
+    print_var(_c4_prefix)
+    print_var(_c4_ocprefix)
+    print_var(_c4_ucprefix)
+    print_var(_c4_lcprefix)
+    print_var(_c4_oprefix)
+    print_var(_c4_uprefix)
+    print_var(_c4_lprefix)
+endmacro()
+
+
+function(c4_zero_pad padded size str)
+    string(LENGTH "${str}" len)
+    math(EXPR numchars "${size} - ${len}")
+    if(numchars EQUAL 0)
+        set(${padded} "${str}" PARENT_SCOPE)
+    else()
+        set(out "${str}")
+        math(EXPR ncm1 "${numchars} - 1")
+        foreach(z RANGE ${ncm1})
+            set(out "0${out}")
+        endforeach()
+        set(${padded} "${out}" PARENT_SCOPE)
+    endif()
+endfunction()
+
+
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+
+# handy macro for dealing with arguments in one single statement.
+# look for example usage cases below.
+macro(_c4_handle_args)
+    set(opt0arg
+    )
+    set(opt1arg
+        _PREFIX
+    )
+    set(optNarg
+        _ARGS0
+        _ARGS1
+        _ARGSN
+        _ARGS
+        _DEPRECATE
+    )
+    # parse the arguments to this macro to find out the required arguments
+    cmake_parse_arguments("__c4ha" "${opt0arg}" "${opt1arg}" "${optNarg}" ${ARGN})
+    # now parse the required arguments
+    cmake_parse_arguments("${__c4ha__PREFIX}" "${__c4ha__ARGS0}" "${__c4ha__ARGS1}" "${__c4ha__ARGSN}" ${__c4ha__ARGS})
+    # raise an error on deprecated arguments
+    foreach(a ${__c4ha__DEPRECATE})
+        list(FIND __c4ha__ARGS ${a} contains)
+        if(NOT (${contains} EQUAL -1))
+            c4err("${a} is deprecated")
+        endif()
+    endforeach()
+endmacro()
+
+# fallback to provided default(s) if argument is not set
+macro(_c4_handle_arg argname)
+     if("${_${argname}}" STREQUAL "")
+         set(_${argname} "${ARGN}")
+     else()
+         set(_${argname} "${_${argname}}")
+     endif()
+endmacro()
+macro(_c4_handle_arg_no_pfx argname)
+     if("${${argname}}" STREQUAL "")
+         set(${argname} "${ARGN}")
+     else()
+         set(${argname} "${${argname}}")
+     endif()
+endmacro()
+
+
+# if ${_${argname}} is non empty, return it
+# otherwise, fallback to ${_c4_uprefix}${argname}
+# otherwise, fallback to C4_${argname}
+# otherwise, fallback to provided default through ${ARGN}
+macro(_c4_handle_arg_or_fallback argname)
+    if(NOT ("${_${argname}}" STREQUAL ""))
+        c4_dbg("handle arg ${argname}: picking explicit value _${argname}=${_${argname}}")
+    else()
+        foreach(_c4haf_varname "${_c4_uprefix}${argname}" "C4_${argname}" "${argname}" "CMAKE_${argname}")
+            set(v ${${_c4haf_varname}})
+            if("${v}" STREQUAL "")
+                c4_dbg("handle arg ${argname}: ${_c4haf_varname}: empty, continuing")
+            else()
+                c4_dbg("handle arg ${argname}: ${_c4haf_varname}=${v} not empty!")
+                c4_setg(_${argname} "${v}")
+                break()
+            endif()
+        endforeach()
+        if("${_${argname}}" STREQUAL "")
+            c4_dbg("handle arg ${argname}: picking default: ${ARGN}")
+            c4_setg(_${argname} "${ARGN}")
+        endif()
+    endif()
+endmacro()
+
+
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+
+function(c4_get_config var name)
+    c4_dbg("get_config: ${var} ${name}")
+    c4_get_from_first_of(config ${ARGN} VARS ${_c4_uprefix}${name} C4_${name} ${name})
+    c4_dbg("get_config: ${var} ${name}=${config}")
+    set(${var} ${config} PARENT_SCOPE)
+endfunction()
+
+
+function(c4_get_from_first_of var)
+    _c4_handle_args(_ARGS ${ARGN}
+        _ARGS0
+            REQUIRED  # raise an error if no set variable was found
+            ENV  # if none of the provided vars is given,
+                 # then search next on environment variables
+                 # of the same name, using the same sequence
+        _ARGS1
+            DEFAULT
+        _ARGSN
+            VARS
+    )
+    c4_dbg("get_from_first_of(): searching ${var}")
+    foreach(_var ${_VARS})
+        set(val ${${_var}})
+        c4_dbg("${var}: searching ${_var}=${val}")
+        if(NOT ("${val}" STREQUAL ""))
+            set(${var} "${val}" PARENT_SCOPE)
+            return()
+        endif()
+    endforeach()
+    if(_ENV)
+        foreach(_envvar ${_VARS})
+            set(val $ENV{${_envvar}})
+            c4_dbg("${var}: searching environment variable ${_envvar}=${val}")
+            if(NOT ("${val}" STREQUAL ""))
+                c4_dbg("${var}: picking ${val} from ${_envvar}")
+                set(${var} "${val}" PARENT_SCOPE)
+                return()
+            endif()
+        endforeach()
+    endif()
+    if(_REQUIRED)
+        c4_err("could not find a value for the variable ${var}")
+    endif()
+    set(${var} ${_DEFAULT} PARENT_SCOPE)
+endfunction()
+
+
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+
+# assumes a prior call to project()
+function(c4_project)
+    _c4_handle_args(_ARGS ${ARGN}
+      _ARGS0  # zero-value macro arguments
+        STANDALONE # Declare that targets from this project MAY be
+                   # compiled in standalone mode. In this mode, any
+                   # designated libraries on which a target depends
+                   # will be incorporated into the target instead of
+                   # being linked with it. The effect is to "flatten"
+                   # those libraries into the requesting library, with
+                   # their sources now becoming part of the requesting
+                   # library; their dependencies are transitively handled.
+                   # Note that requesting targets must explicitly
+                   # opt-in to this behavior via the INCORPORATE
+                   # argument to c4_add_library() or
+                   # c4_add_executable(). Note also that this behavior
+                   # is only enabled if this project's option
+                   # ${prefix}_STANDALONE or C4_STANDALONE is set to ON.
+      _ARGS1  # one-value macro arguments
+        AUTHOR        # specify author(s); used in cpack
+        VERSION       # cmake does not accept semantic versioning so we provide
+                      # that here (see https://gitlab.kitware.com/cmake/cmake/-/issues/16716)
+        CXX_STANDARD  # one of latest;${C4_VALID_CXX_STANDARDS}
+                      # if this is not provided, falls back on
+                      # ${uprefix}CXX_STANDARD, then C4_CXX_STANDARD,
+                      # then CXX_STANDARD. if none are provided,
+                      # defaults to 11
+      _ARGSN  # multi-value macro arguments
+    )
+    # get the prefix from the call to project()
+    set(prefix ${PROJECT_NAME})
+    string(TOUPPER "${prefix}" ucprefix) # ucprefix := upper case prefix
+    string(TOLOWER "${prefix}" lcprefix) # lcprefix := lower case prefix
+    if(NOT _c4_prefix)
+        c4_setg(_c4_is_root_proj ON)
+        c4_setg(_c4_root_proj ${prefix})
+        c4_setg(_c4_root_uproj ${ucprefix})
+        c4_setg(_c4_root_lproj ${lcprefix})
+        c4_setg(_c4_curr_path "")
+    else()
+        c4_setg(_c4_is_root_proj OFF)
+        if(_c4_curr_path)
+            c4_setg(_c4_curr_path "${_c4_curr_path}/${prefix}")
+        else()
+            c4_setg(_c4_curr_path "${prefix}")
+        endif()
+    endif()
+    c4_setg(_c4_curr_subproject ${prefix})
+    # get the several prefix flavors
+    c4_setg(_c4_ucprefix ${ucprefix})
+    c4_setg(_c4_lcprefix ${lcprefix})
+    c4_setg(_c4_ocprefix ${prefix})              # ocprefix := original case prefix
+    c4_setg(_c4_prefix   ${prefix})              # prefix := original prefix
+    c4_setg(_c4_oprefix  ${prefix})              # oprefix := original prefix
+    c4_setg(_c4_uprefix  ${_c4_ucprefix})        # upper prefix: for variables
+    c4_setg(_c4_lprefix  ${_c4_lcprefix})        # lower prefix: for targets
+    if(_c4_oprefix)
+        c4_setg(_c4_oprefix "${_c4_oprefix}_")
+    endif()
+    if(_c4_uprefix)
+        c4_setg(_c4_uprefix "${_c4_uprefix}_")
+    endif()
+    if(_c4_lprefix)
+        c4_setg(_c4_lprefix "${_c4_lprefix}-")
+    endif()
+    #
+    if(_STANDALONE)
+        option(${_c4_uprefix}STANDALONE
+            "Enable compilation of opting-in targets from ${_c4_lcprefix} in standalone mode (ie, incorporate subprojects as specified in the INCORPORATE clause to c4_add_library/c4_add_target)"
+            ${_c4_is_root_proj})
+        c4_setg(_c4_root_proj_standalone ${_c4_uprefix}STANDALONE)
+    endif()
+    _c4_handle_arg_or_fallback(CXX_STANDARD ${C4_CXX_STANDARD_DEFAULT})
+    _c4_handle_arg(VERSION 0.0.0-pre0)
+    _c4_handle_arg(AUTHOR "")
+    _c4_handle_semantic_version(${_VERSION})
+    #
+    # make sure project-wide settings are defined -- see cmake's
+    # documentation for project(), which defines these and other
+    # variables
+    if("${PROJECT_DESCRIPTION}" STREQUAL "")
+        c4_setg(PROJECT_DESCRIPTION "${prefix}")
+        c4_setg(${prefix}_DESCRIPTION "${prefix}")
+    endif()
+    if("${PROJECT_HOMEPAGE_URL}" STREQUAL "")
+        c4_setg(PROJECT_HOMEPAGE_URL "")
+        c4_setg(${prefix}_HOMEPAGE_URL "")
+    endif()
+    # other specific c4_project properties
+    c4_setg(PROJECT_AUTHOR "${_AUTHOR}")
+    c4_setg(${prefix}_AUTHOR "${_AUTHOR}")
+
+    # CXX standard
+    if("${_CXX_STANDARD}" STREQUAL "latest")
+        _c4_find_latest_supported_cxx_standard(_CXX_STANDARD)
+    endif()
+    c4_log("using C++ standard: C++${_CXX_STANDARD}")
+    c4_set_proj_prop(CXX_STANDARD "${_CXX_STANDARD}")
+    c4_setg(${_c4_uprefix}CXX_STANDARD "${_CXX_STANDARD}")
+    if(${_CXX_STANDARD})
+        c4_set_cxx(${_CXX_STANDARD})
+    endif()
+
+    # we are opinionated with respect to directory structure
+    c4_setg(${_c4_uprefix}SRC_DIR ${CMAKE_CURRENT_LIST_DIR}/src)
+    c4_setg(${_c4_uprefix}EXT_DIR ${CMAKE_CURRENT_LIST_DIR}/ext)
+    c4_setg(${_c4_uprefix}API_DIR ${CMAKE_CURRENT_LIST_DIR}/api)
+    # opionionated also for directory test
+    # opionionated also for directory bm (benchmarks)
+
+    if("${C4_DEV}" STREQUAL "")
+        option(C4_DEV "enable development targets for all c4 projects" OFF)
+    endif()
+    option(${_c4_uprefix}DEV "enable development targets: tests, benchmarks, sanitize, static analysis, coverage" ${C4_DEV})
+
+    if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/test")
+        cmake_dependent_option(${_c4_uprefix}BUILD_TESTS "build unit tests" ON ${_c4_uprefix}DEV OFF)
+    else()
+        c4_dbg("no tests: directory does not exist: ${CMAKE_CURRENT_LIST_DIR}/test")
+    endif()
+    if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/bm")
+        cmake_dependent_option(${_c4_uprefix}BUILD_BENCHMARKS "build benchmarks" ON ${_c4_uprefix}DEV OFF)
+    else()
+        c4_dbg("no benchmarks: directory does not exist: ${CMAKE_CURRENT_LIST_DIR}/bm")
+    endif()
+    if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/api")
+        cmake_dependent_option(${_c4_uprefix}BUILD_API "build API" OFF ${_c4_uprefix}DEV OFF)
+    else()
+        c4_dbg("no API generation: directory does not exist: ${CMAKE_CURRENT_LIST_DIR}/api")
+    endif()
+    c4_setup_coverage()
+    c4_setup_valgrind(${_c4_uprefix}DEV)
+    c4_setup_sanitize(${_c4_uprefix}DEV)
+    c4_setup_static_analysis(${_c4_uprefix}DEV)
+    c4_setup_doxygen(${_c4_uprefix}DEV)
+
+    # option to use libc++
+    option(${_c4_uprefix}USE_LIBCXX "use libc++ instead of the default standard library" OFF)
+    if(${_c4_uprefix}USE_LIBCXX)
+        if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang")
+            c4_log("using libc++")
+            list(APPEND CMAKE_CXX_FLAGS           -stdlib=libc++)
+            list(APPEND CMAKE_EXE_LINKER_FLAGS    -lc++)
+            list(APPEND CMAKE_MODULE_LINKER_FLAGS -lc++)
+            list(APPEND CMAKE_SHARED_LINKER_FLAGS -lc++)
+            list(APPEND CMAKE_STATIC_LINKER_FLAGS -lc++)
+        else()
+            c4_err("libc++ can only be used with clang")
+        endif()
+    endif()
+
+    # default compilation flags
+    set(${_c4_uprefix}CXX_FLAGS "${${_c4_uprefix}CXX_FLAGS_FWD}" CACHE STRING "compilation flags for ${_c4_prefix} targets")
+    set(${_c4_uprefix}CXX_LINKER_FLAGS "${${_c4_uprefix}CXX_LINKER_FLAGS_FWD}" CACHE STRING "linker flags for ${_c4_prefix} targets")
+    c4_dbg_var_if(${_c4_uprefix}CXX_LINKER_FLAGS_FWD)
+    c4_dbg_var_if(${_c4_uprefix}CXX_FLAGS_FWD)
+    c4_dbg_var_if(${_c4_uprefix}CXX_LINKER_FLAGS)
+    c4_dbg_var_if(${_c4_uprefix}CXX_FLAGS)
+
+    # Dev compilation flags, appended to the project's flags. They
+    # are enabled when in dev mode, but provided as a (default-disabled)
+    # option when not in dev mode
+    c4_dbg_var_if(${_c4_uprefix}CXX_FLAGS_OPT_FWD)
+    c4_setg(${_c4_uprefix}CXX_FLAGS_OPT "${${_c4_uprefix}CXX_FLAGS_OPT_FWD}")
+    c4_optional_compile_flags_dev(WERROR "Compile with warnings as errors"
+        GCC_CLANG -Werror -pedantic-errors
+        MSVC /WX
+        )
+    c4_optional_compile_flags_dev(STRICT_ALIASING "Enable strict aliasing"
+        GCC_CLANG -fstrict-aliasing
+        MSVC # does it have this?
+        )
+    c4_optional_compile_flags_dev(PEDANTIC "Compile in pedantic mode"
+        GCC ${_C4_PEDANTIC_FLAGS_GCC}
+        CLANG ${_C4_PEDANTIC_FLAGS_CLANG}
+        MSVC ${_C4_PEDANTIC_FLAGS_MSVC}
+        )
+    c4_dbg_var_if(${_c4_uprefix}CXX_FLAGS_OPT)
+endfunction(c4_project)
+
+
+# cmake: VERSION argument in project() does not accept semantic versioning
+# see: https://gitlab.kitware.com/cmake/cmake/-/issues/16716
+macro(_c4_handle_semantic_version version)
+    # https://stackoverflow.com/questions/18658233/split-string-to-3-variables-in-cmake
+    string(REPLACE "." ";" version_list ${version})
+    list(GET version_list 0 _major)
+    list(GET version_list 1 _minor)
+    list(GET version_list 2 _patch)
+    if("${_patch}" STREQUAL "")
+        set(_patch 1)
+        set(_tweak)
+    else()
+        string(REGEX REPLACE "([0-9]+)[-_.]?(.*)" "\\2" _tweak ${_patch}) # do this first
+        string(REGEX REPLACE "([0-9]+)[-_.]?(.*)" "\\1" _patch ${_patch}) # ... because this replaces _patch
+    endif()
+    # because cmake handles only numeric tweak fields, make sure to skip our
+    # semantic tweak field if it is not numeric
+    if(${_tweak} MATCHES "^[0-9]+$")
+        set(_safe_tweak ${_tweak})
+        set(_safe_version ${_major}.${_minor}.${_patch}.${_tweak})
+    else()
+        set(_safe_tweak)
+        set(_safe_version ${_major}.${_minor}.${_patch})
+    endif()
+    c4_setg(PROJECT_VERSION_FULL ${version})
+    c4_setg(PROJECT_VERSION ${_safe_version})
+    c4_setg(PROJECT_VERSION_MAJOR ${_major})
+    c4_setg(PROJECT_VERSION_MINOR ${_minor})
+    c4_setg(PROJECT_VERSION_PATCH ${_patch})
+    c4_setg(PROJECT_VERSION_TWEAK "${_safe_tweak}")
+    c4_setg(PROJECT_VERSION_TWEAK_FULL "${_tweak}")
+    c4_setg(${prefix}_VERSION_FULL ${version})
+    c4_setg(${prefix}_VERSION ${_safe_version})
+    c4_setg(${prefix}_VERSION_MAJOR ${_major})
+    c4_setg(${prefix}_VERSION_MINOR ${_minor})
+    c4_setg(${prefix}_VERSION_PATCH ${_patch})
+    c4_setg(${prefix}_VERSION_TWEAK "${_safe_tweak}")
+    c4_setg(${prefix}_VERSION_TWEAK_FULL "${_tweak}")
+endmacro()
+
+
+# Add targets for testing (dir=./test), benchmark (dir=./bm) and API (dir=./api).
+# Call this macro towards the end of the project's main CMakeLists.txt.
+# Experimental feature: docs.
+function(c4_add_dev_targets)
+    if(NOT CMAKE_CURRENT_LIST_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
+        c4_err("this macro needs to be called on the project's main CMakeLists.txt file")
+    endif()
+    #
+    if(${_c4_uprefix}BUILD_TESTS)
+        if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/test")
+            c4_dbg("adding tests: ${CMAKE_CURRENT_LIST_DIR}/test")
+            enable_testing() # this must be done here (and not inside the
+                             # test dir) so that the cmake-generated test
+                             # targets are available at the top level
+            add_subdirectory(test)
+        endif()
+    endif()
+    #
+    if(${_c4_uprefix}BUILD_BENCHMARKS)
+        if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/bm")
+            c4_dbg("adding benchmarks: ${CMAKE_CURRENT_LIST_DIR}/bm")
+            add_subdirectory(bm)
+        endif()
+    endif()
+    #
+    if(${_c4_uprefix}BUILD_API)
+        if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/api")
+            c4_dbg("adding API: ${d}")
+            add_subdirectory(api)
+        endif()
+    endif()
+    #
+    # FIXME
+    c4_add_doxygen(doc DOXYFILE_IN ${_c4_project_dir}/Doxyfile.in
+        PROJ c4core
+        INPUT ${${_c4_uprefix}SRC_DIR}
+        EXCLUDE ${${_c4_uprefix}EXT_DIR} ${${_c4_uprefix}SRC_DIR}/c4/ext
+        STRIP_FROM_PATH ${${_c4_uprefix}SRC_DIR}
+        STRIP_FROM_INC_PATH ${${_c4_uprefix}SRC_DIR}
+        CLANG_DATABASE_PATH ${CMAKE_BINARY_DIR}
+        )
+    c4_add_doxygen(doc-full DOXYFILE_IN ${_c4_project_dir}/Doxyfile.full.in
+        PROJ c4core
+        INPUT ${${_c4_uprefix}SRC_DIR}
+        EXCLUDE ${${_c4_uprefix}EXT_DIR} ${${_c4_uprefix}SRC_DIR}/c4/ext
+        STRIP_FROM_PATH ${${_c4_uprefix}SRC_DIR}
+        STRIP_FROM_INC_PATH ${${_c4_uprefix}SRC_DIR}
+        CLANG_DATABASE_PATH ${CMAKE_BINARY_DIR}
+        )
+endfunction()
+
+
+function(_c4_get_san_targets target result)
+    _c4_get_tgt_prop(san_targets ${target} C4_SAN_TARGETS)
+    if(NOT san_targets)
+        #c4_err("${target} must have at least itself in its sanitized target list")
+        set(${result} ${target} PARENT_SCOPE)
+    else()
+        set(${result} ${san_targets} PARENT_SCOPE)
+    endif()
+endfunction()
+
+
+# -----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
+
+# utilities for compilation flags and defines
+
+# flags enabled only on dev mode
+macro(c4_optional_compile_flags_dev tag desc)
+    _c4_handle_args(_ARGS ${ARGN}
+        _ARGS0
+        _ARGS1
+        _ARGSN
+            MSVC         # flags for Visual Studio compilers
+            GCC          # flags for gcc compilers
+            CLANG        # flags for clang compilers
+            GCC_CLANG    # flags common to gcc and clang
+        _DEPRECATE
+    )
+    cmake_dependent_option(${_c4_uprefix}${tag} "${desc}" ON ${_c4_uprefix}DEV OFF)
+    set(optname ${_c4_uprefix}${tag})
+    if(${optname})
+        c4_dbg("${optname} is enabled. Adding flags...")
+        if(MSVC)
+            set(flags ${_MSVC})
+        elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang")
+            set(flags ${_GCC_CLANG};${_CLANG})
+        elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+            set(flags ${_GCC_CLANG};${_GCC})
+        elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Intel")
+            set(flags ${_ALL};${_GCC_CLANG};${_GCC})  # FIXME
+        elseif(CMAKE_CXX_COMPILER_ID STREQUAL "IntelLLVM")
+            set(flags ${_ALL};${_GCC_CLANG};${_CLANG})  # FIXME
+        else()
+            c4_err("unknown compiler")
+        endif()
+    else()
+        c4_dbg("${optname} is disabled.")
+    endif()
+    if(flags)
+        c4_log("${tag} flags [${desc}]: ${flags}")
+        c4_setg(${_c4_uprefix}CXX_FLAGS_OPT "${${_c4_uprefix}CXX_FLAGS_OPT};${flags}")
+    endif()
+endmacro()
+
+
+function(c4_target_compile_flags target)
+    _c4_handle_args(_ARGS ${ARGN}
+        _ARGS0
+            PUBLIC
+            PRIVATE
+            INTERFACE
+            AFTER        # this is the default
+            BEFORE
+        _ARGS1
+        _ARGSN
+            ALL          # flags for all compilers
+            MSVC         # flags for Visual Studio compilers
+            GCC          # flags for gcc compilers
+            CLANG        # flags for clang compilers
+            GCC_CLANG    # flags common to gcc and clang
+        _DEPRECATE
+    )
+    if(MSVC)
+        set(flags ${_ALL};${_MSVC})
+    elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang")
+        set(flags ${_ALL};${_GCC_CLANG};${_CLANG})
+    elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+        set(flags ${_ALL};${_GCC_CLANG};${_GCC})
+    elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Intel")
+        set(flags ${_ALL};${_GCC_CLANG};${_GCC})  # FIXME
+    elseif(CMAKE_CXX_COMPILER_ID STREQUAL "IntelLLVM")
+        set(flags ${_ALL};${_GCC_CLANG};${_CLANG})  # FIXME
+    else()
+        c4_err("unknown compiler")
+    endif()
+    if(NOT flags)
+        c4_dbg("no compile flags to be set")
+        return()
+    endif()
+    if(_AFTER OR (NOT _BEFORE))
+        set(mode)
+        c4_log("${target}: adding compile flags AFTER: ${flags}")
+    elseif(_BEFORE)
+        set(mode BEFORE)
+        c4_log("${target}: adding compile flags BEFORE: ${flags}")
+    endif()
+    _c4_get_san_targets(${target} san_targets)
+    foreach(st ${san_targets})
+        if(_PUBLIC)
+            target_compile_options(${st} ${mode} PUBLIC ${flags})
+        elseif(_PRIVATE)
+            target_compile_options(${st} ${mode} PRIVATE ${flags})
+        elseif(_INTERFACE)
+            target_compile_options(${st} ${mode} INTERFACE ${flags})
+        else()
+            c4_err("${target}: must have one of PUBLIC, PRIVATE or INTERFACE")
+        endif()
+    endforeach()
+endfunction()
+
+
+function(c4_target_definitions target)
+    _c4_handle_args(_ARGS ${ARGN}
+        _ARGS0
+            PUBLIC
+            PRIVATE
+            INTERFACE
+        _ARGS1
+        _ARGSN
+            ALL          # defines for all compilers
+            MSVC         # defines for Visual Studio compilers
+            GCC          # defines for gcc compilers
+            CLANG        # defines for clang compilers
+            GCC_CLANG    # defines common to gcc and clang
+        _DEPRECATE
+    )
+    if(MSVC)
+        set(flags ${_ALL};${_MSVC})
+    elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang")
+        set(flags ${_ALL};${_GCC_CLANG};${_CLANG})
+    elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+        set(flags ${_ALL};${_GCC_CLANG};${_GCC})
+    else()
+        c4_err("unknown compiler")
+    endif()
+    if(NOT flags)
+        c4_dbg("no compile flags to be set")
+        return()
+    endif()
+    if(_AFTER OR (NOT _BEFORE))
+        set(mode)
+        c4_log("${target}: adding definitions AFTER: ${flags}")
+    elseif(_BEFORE)
+        set(mode BEFORE)
+        c4_log("${target}: adding definitions BEFORE: ${flags}")
+    endif()
+    _c4_get_san_targets(${target} san_targets)
+    foreach(st ${san_targets})
+        if(_PUBLIC)
+            target_compile_definitions(${st} ${mode} PUBLIC ${flags})
+        elseif(_PRIVATE)
+            target_compile_definitions(${st} ${mode} PRIVATE ${flags})
+        elseif(_INTERFACE)
+            target_compile_definitions(${st} ${mode} INTERFACE ${flags})
+        else()
+            c4_err("${target}: must have one of PUBLIC, PRIVATE or INTERFACE")
+        endif()
+    endforeach()
+endfunction()
+
+
+function(c4_target_remove_compile_flags target)
+    _c4_handle_args(_ARGS ${ARGN}
+        _ARGS0
+            PUBLIC       # remove only from public compile options
+            INTERFACE    # remove only from interface compile options
+        _ARGS1
+        _ARGSN
+            MSVC         # flags for Visual Studio compilers
+            GCC          # flags for gcc compilers
+            CLANG        # flags for clang compilers
+            GCC_CLANG    # flags common to gcc and clang
+        _DEPRECATE
+    )
+    if(MSVC)
+        set(flags ${_MSVC})
+    elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang")
+        set(flags ${_GCC_CLANG};${_CLANG})
+    elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+        set(flags ${_GCC_CLANG};${_GCC})
+    else()
+        c4_err("unknown compiler")
+    endif()
+    if(NOT flags)
+        return()
+    endif()
+    _c4_get_san_targets(${target} san_targets)
+    foreach(st ${san_targets})
+        if(_PUBLIC OR (NOT _INTERFACE))
+            get_target_property(co ${st} COMPILE_OPTIONS)
+            if(co)
+                _c4_remove_entries_from_list("${flags}" co)
+                set_target_properties(${st} PROPERTIES COMPILE_OPTIONS "${co}")
+            endif()
+        endif()
+        if(_INTERFACE OR (NOT _PUBLIC))
+            get_target_property(ico ${st} INTERFACE_COMPILE_OPTIONS)
+            if(ico)
+                _c4_remove_entries_from_list("${flags}" ico)
+                set_target_properties(${st} PROPERTIES INTERFACE_COMPILE_OPTIONS "${ico}")
+            endif()
+        endif()
+    endforeach()
+endfunction()
+
+
+function(_c4_remove_entries_from_list entries_to_remove list)
+    set(str ${${list}})
+    string(REPLACE ";" "==?==" str "${str}")
+    foreach(entry ${entries_to_remove})
+        string(REPLACE "${entry}" "" str "${str}")
+    endforeach()
+    string(REPLACE "==?==" ";" str "${str}")
+    string(REPLACE ";;" ";" str "${str}")
+    set(${list} "${str}" PARENT_SCOPE)
+endfunction()
+
+
+
+# pedantic flags...
+# default pedantic flags taken from:
+# https://github.com/lefticus/cpp_starter_project/blob/master/cmake/CompilerWarnings.cmake
+set(_C4_PEDANTIC_FLAGS_MSVC
+    /W4 # Baseline reasonable warnings
+    /w14242 # 'identifier': conversion from 'type1' to 'type1', possible loss of data
+    /w14254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data
+    /w14263 # 'function': member function does not override any base class virtual member function
+    /w14265 # 'classname': class has virtual functions, but destructor is not virtual instances of this class may not
+            # be destructed correctly
+    /w14287 # 'operator': unsigned/negative constant mismatch
+    /we4289 # nonstandard extension used: 'variable': loop control variable declared in the for-loop is used outside
+            # the for-loop scope
+    /w14296 # 'operator': expression is always 'boolean_value'
+    /w14311 # 'variable': pointer truncation from 'type1' to 'type2'
+    /w14545 # expression before comma evaluates to a function which is missing an argument list
+    /w14546 # function call before comma missing argument list
+    /w14547 # 'operator': operator before comma has no effect; expected operator with side-effect
+    /w14549 # 'operator': operator before comma has no effect; did you intend 'operator'?
+    /w14555 # expression has no effect; expected expression with side- effect
+    /w14619 # pragma warning: there is no warning number 'number'
+    /w14640 # Enable warning on thread un-safe static member initialization
+    /w14826 # Conversion from 'type1' to 'type_2' is sign-extended. This may cause unexpected runtime behavior.
+    /w14905 # wide string literal cast to 'LPSTR'
+    /w14906 # string literal cast to 'LPWSTR'
+    /w14928 # illegal copy-initialization; more than one user-defined conversion has been implicitly applied
+    $<$<VERSION_GREATER:${MSVC_VERSION},1900>:/permissive-> # standards conformance mode for MSVC compiler (only vs2017+)
+    )
+
+set(_C4_PEDANTIC_FLAGS_CLANG
+    -Wall
+    -Wextra
+    -pedantic
+    -Wshadow # warn the user if a variable declaration shadows one from a parent context
+    -Wnon-virtual-dtor # warn the user if a class with virtual functions has a non-virtual destructor. This helps
+                       # catch hard to track down memory errors
+    #-Wold-style-cast # warn for c-style casts
+    -Wcast-align # warn for potential performance problem casts
+    -Wunused # warn on anything being unused
+    -Woverloaded-virtual # warn if you overload (not override) a virtual function
+    -Wpedantic # warn if non-standard C++ is used
+    -Wconversion # warn on type conversions that may lose data
+    -Wsign-conversion # warn on sign conversions
+    -Wdouble-promotion # warn if float is implicit promoted to double
+    -Wfloat-equal # warn if comparing floats
+    -Wformat=2 # warn on security issues around functions that format output (ie printf)
+    )
+
+set(_C4_PEDANTIC_FLAGS_GCC ${_C4_PEDANTIC_FLAGS_CLANG}
+    -Wlogical-op # where logical operations are used where bitwise were probably wanted
+    -Wuseless-cast # where you perform a cast to the same type
+    )
+
+if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") AND (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 6.0))
+    list(APPEND _C4_PEDANTIC_FLAGS_GCC
+        -Wnull-dereference # warn if a null dereference is detected
+        -Wmisleading-indentation # where indentation implies blocks where blocks do not exist
+        -Wduplicated-cond # where if-else chain has duplicated conditions
+        )
+endif()
+
+if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") AND (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 7.0))
+    list(APPEND _C4_PEDANTIC_FLAGS_GCC
+        -Wduplicated-branches # where if-else branches have duplicated code
+        )
+endif()
+
+
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+
+function(c4_pack_project)
+    # if this is the top-level project... pack it.
+    if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
+        c4_log("packing the project: ${ARGN}")
+        c4_set_default_pack_properties(${ARGN})
+        include(CPack)
+    endif()
+endfunction()
+
+
+# [WIP] set convenient defaults for the properties used by CPack
+function(c4_set_default_pack_properties)
+    _c4_handle_args(_ARGS ${ARGN}
+      _ARGS0  # zero-value macro arguments
+      _ARGS1  # one-value macro arguments
+        TYPE     # one of LIBRARY, EXECUTABLE
+      _ARGSN  # multi-value macro arguments
+    )
+    set(pd "${PROJECT_SOURCE_DIR}")
+    _c4_handle_arg(TYPE EXECUTABLE)  # default to EXECUTABLE
+    #
+    _c4_get_platform_tag(platform_tag)
+    if("${_TYPE}" STREQUAL "LIBRARY")
+        if(BUILD_SHARED_LIBS)
+            set(build_tag "-shared")
+        else()
+            set(build_tag "-static")
+        endif()
+        get_property(multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+        if(multi_config)
+            # doesn't work because generators are not evaluated: set(build_tag "${build_tag}-$<CONFIG>")
+            # doesn't work because generators are not evaluated: set(build_tag "${build_tag}$<$<CONFIG:Debug>:-Debug>$<$<CONFIG:MinSizeRel>:-MinSizeRel>$<$<CONFIG:Release>:-Release>$<$<CONFIG:RelWithDebInfo>:-RelWithDebInfo>")
+            # see also https://stackoverflow.com/questions/44153730/how-to-change-cpack-package-file-name-based-on-configuration
+            if(CMAKE_BUILD_TYPE)  # in the off-chance it was explicitly set
+                set(build_tag "${build_tag}-${CMAKE_BUILD_TYPE}")
+            endif()
+        else()
+            set(build_tag "${build_tag}-${CMAKE_BUILD_TYPE}")
+        endif()
+    elseif("${_TYPE}" STREQUAL "EXECUTABLE")
+        set(build_tag)
+    elseif()
+        c4_err("unknown TYPE: ${_TYPE}")
+    endif()
+    #
+    c4_setg(CPACK_VERBATIM_VARIABLES true)
+    c4_setg(CPACK_PACKAGE_VENDOR "${${_c4_prefix}_HOMEPAGE_URL}")
+    c4_setg(CPACK_PACKAGE_CONTACT "${${_c4_prefix}_AUTHOR}")
+    c4_setg(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${${_c4_prefix}_DESCRIPTION}")
+    if(EXISTS "${pd}/README.md")
+        c4_setg(CPACK_PACKAGE_DESCRIPTION_FILE "${pd}/README.md")
+        c4_setg(CPACK_PACKAGE_DESCRIPTION_README "${pd}/README.md")
+        c4_setg(CPACK_PACKAGE_DESCRIPTION_WELCOME "${pd}/README.md")
+    elseif(EXISTS "${pd}/README.txt")
+        c4_setg(CPACK_PACKAGE_DESCRIPTION_FILE "${pd}/README.txt")
+        c4_setg(CPACK_PACKAGE_DESCRIPTION_README "${pd}/README.txt")
+        c4_setg(CPACK_PACKAGE_DESCRIPTION_WELCOME "${pd}/README.txt")
+    endif()
+    if(EXISTS "${pd}/LICENSE.md")
+        c4_setg(CPACK_RESOURCE_FILE_LICENSE "${pd}/LICENSE.md")
+    elseif(EXISTS "${pd}/LICENSE.txt")
+        c4_setg(CPACK_RESOURCE_FILE_LICENSE "${pd}/LICENSE.txt")
+    endif()
+    c4_proj_get_version("${pd}" version_tag full major minor patch tweak)
+    c4_setg(CPACK_PACKAGE_VERSION "${full}")
+    c4_setg(CPACK_PACKAGE_VERSION_MAJOR "${major}")
+    c4_setg(CPACK_PACKAGE_VERSION_MINOR "${minor}")
+    c4_setg(CPACK_PACKAGE_VERSION_PATCH "${patch}")
+    c4_setg(CPACK_PACKAGE_VERSION_TWEAK "${tweak}")
+    c4_setg(CPACK_PACKAGE_INSTALL_DIRECTORY "${_c4_prefix}-${version_tag}")
+    c4_setg(CPACK_PACKAGE_FILE_NAME "${_c4_prefix}-${version_tag}-${platform_tag}${build_tag}")
+    if(WIN32 AND NOT UNIX)
+        # There is a bug in NSI that does not handle full UNIX paths properly.
+        # Make sure there is at least one set of four backlashes.
+        #c4_setg(CPACK_PACKAGE_ICON "${CMake_SOURCE_DIR}/Utilities/Release\\\\InstallIcon.bmp")
+        #c4_setg(CPACK_NSIS_INSTALLED_ICON_NAME "bin\\\\MyExecutable.exe")
+        c4_setg(CPACK_NSIS_DISPLAY_NAME "${_c4_prefix} ${version_tag}")
+        c4_setg(CPACK_NSIS_HELP_LINK "${${_c4_prefix}_HOMEPAGE_URL}")
+        c4_setg(CPACK_NSIS_URL_INFO_ABOUT "${${_c4_prefix}_HOMEPAGE_URL}")
+        c4_setg(CPACK_NSIS_CONTACT "${${_c4_prefix}_AUTHOR}")
+        c4_setg(CPACK_NSIS_MODIFY_PATH ON)
+    else()
+        #c4_setg(CPACK_STRIP_FILES "bin/MyExecutable")
+        #c4_setg(CPACK_SOURCE_STRIP_FILES "")
+        c4_setg(CPACK_DEBIAN_PACKAGE_MAINTAINER "${${_c4_prefix}_AUTHOR}")
+    endif()
+    #c4_setg(CPACK_PACKAGE_EXECUTABLES "MyExecutable" "My Executable")
+endfunction()
+
+
+function(_c4_get_platform_tag tag_)
+    if(WIN32 AND NOT UNIX)
+        set(tag win)
+    elseif(APPLE)
+        set(tag apple)
+    elseif(UNIX)
+        set(tag unix)
+    else()
+        set(tag ${CMAKE_SYSTEM_NAME})
+    endif()
+    if(CMAKE_SIZEOF_VOID_P EQUAL 8)  # 64 bits
+        set(tag ${tag}64)
+    elseif(CMAKE_SIZEOF_VOID_P EQUAL 4)  # 32 bits
+        set(tag ${tag}32)
+    else()
+        c4_err("not implemented")
+    endif()
+    set(${tag_} ${tag} PARENT_SCOPE)
+endfunction()
+
+
+function(_c4_extract_version_tag tag_)
+    # git describe --tags  <commit-id> for unannotated tags
+    # git describe --contains <commit>
+endfunction()
+
+
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+
+# set project-wide property
+function(c4_set_proj_prop prop value)
+    c4_dbg("set ${prop}=${value}")
+    set(C4PROJ_${_c4_prefix}_${prop} ${value})
+endfunction()
+
+# set project-wide property
+function(c4_get_proj_prop prop var)
+    c4_dbg("get ${prop}=${C4PROJ_${_c4_prefix}_${prop}}")
+    set(${var} ${C4PROJ_${_c4_prefix}_${prop}} PARENT_SCOPE)
+endfunction()
+
+
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+
+# set target-wide c4 property
+function(c4_set_target_prop target prop value)
+    _c4_set_tgt_prop(${target} C4_TGT_${prop} "${value}")
+endfunction()
+function(c4_append_target_prop target prop value)
+    _c4_append_tgt_prop(${target} C4_TGT_${prop} "${value}")
+endfunction()
+
+# get target-wide c4 property
+function(c4_get_target_prop target prop var)
+    _c4_get_tgt_prop(val ${target} C4_TGT_${prop})
+    set(${var} ${val} PARENT_SCOPE)
+endfunction()
+
+
+# get target-wide property
+function(_c4_get_tgt_prop out tgt prop)
+    get_target_property(target_type ${target} TYPE)
+    if(target_type STREQUAL "INTERFACE_LIBRARY")
+        get_property(val GLOBAL PROPERTY C4_TGT_${tgt}_${prop})
+    else()
+        get_target_property(val ${tgt} ${prop})
+    endif()
+    c4_dbg("target ${tgt}: get ${prop}=${val}")
+    set(${out} "${val}" PARENT_SCOPE)
+endfunction()
+
+# set target-wide property
+function(_c4_set_tgt_prop tgt prop propval)
+    c4_dbg("target ${tgt}: set ${prop}=${propval}")
+    get_target_property(target_type ${target} TYPE)
+    if(target_type STREQUAL "INTERFACE_LIBRARY")
+        set_property(GLOBAL PROPERTY C4_TGT_${tgt}_${prop} "${propval}")
+    else()
+        set_target_properties(${tgt} PROPERTIES ${prop} "${propval}")
+    endif()
+endfunction()
+function(_c4_append_tgt_prop tgt prop propval)
+    c4_dbg("target ${tgt}: appending ${prop}=${propval}")
+    _c4_get_tgt_prop(curr ${tgt} ${prop})
+    if(curr)
+        list(APPEND curr "${propval}")
+    else()
+        set(curr "${propval}")
+    endif()
+    _c4_set_tgt_prop(${tgt} ${prop} "${curr}")
+endfunction()
+
+
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+
+function(c4_set_var_tmp var value)
+    c4_dbg("tmp-setting ${var} to ${value} (was ${${value}})")
+    set(_c4_old_val_${var} ${${var}})
+    set(${var} ${value} PARENT_SCOPE)
+endfunction()
+
+function(c4_clean_var_tmp var)
+    c4_dbg("cleaning ${var} to ${_c4_old_val_${var}} (tmp was ${${var}})")
+    set(${var} ${_c4_old_val_${var}} PARENT_SCOPE)
+endfunction()
+
+macro(c4_override opt val)
+    set(${opt} ${val} CACHE BOOL "" FORCE)
+endmacro()
+
+
+macro(c4_setg var val)
+    set(${var} ${val})
+    set(${var} ${val} PARENT_SCOPE)
+endmacro()
+
+
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+
+function(c4_proj_get_version dir tag_o full_o major_o minor_o patch_o tweak_o)
+    if("${dir}" STREQUAL "")
+        set(dir ${CMAKE_CURRENT_LIST_DIR})
+    endif()
+    find_program(GIT git REQUIRED)
+    function(_c4pgv_get_cmd outputvar)
+        execute_process(COMMAND ${ARGN}
+            WORKING_DIRECTORY ${dir}
+            ERROR_VARIABLE error
+            ERROR_STRIP_TRAILING_WHITESPACE
+            OUTPUT_VARIABLE output
+            OUTPUT_STRIP_TRAILING_WHITESPACE)
+        c4_dbg("output of ${ARGN}: ${outputvar}=${output} [@${dir}]")
+        set(${outputvar} ${output} PARENT_SCOPE)
+    endfunction()
+    # do we have any tags yet?
+    _c4pgv_get_cmd(head_desc ${GIT} describe HEAD)
+    _c4pgv_get_cmd(branch ${GIT} rev-parse --abbrev-ref HEAD)
+    if(NOT head_desc)
+        c4_dbg("the repo does not have any tags yet")
+        _c4pgv_get_cmd(commit_hash ${GIT} rev-parse --short HEAD)
+        set(otag "${commit_hash}-${branch}")
+    else()
+        c4_dbg("there are tags!")
+        # is the current commit tagged?
+        _c4pgv_get_cmd(commit_hash_full ${GIT} rev-parse HEAD)
+        _c4pgv_get_cmd(commit_desc ${GIT} describe --exact-match ${commit_hash_full})
+        if(commit_desc)
+            c4_dbg("current commit is tagged")
+            # is the tag a version tag?
+            _c4_parse_version_tag(${commit_desc} is_version major minor patch tweak more)
+            if(is_version)
+                c4_dbg("current commit's tag is a version tag")
+                # is the tag the current version tag?
+                if("${is_version}" VERSION_EQUAL "${${_c4_prefix}_VERSION_FULL}")
+                    c4_dbg("this is the official version commit")
+                else()
+                    c4_dbg("this is a different version")
+                endif()
+                set(otag "${commit_desc}")
+            else()
+                c4_dbg("this is a non-version tag")
+                set(otag "${commit_desc}-${branch}")
+            endif()
+        else(commit_desc)
+            # is the latest tag in the head_desc a version tag?
+            string(REGEX REPLACE "(.*)-[0-9]+-[0-9a-f]+" "\\1" latest_tag "${head_desc}")
+            c4_dbg("current commit is NOT tagged. latest tag=${latest_tag}")
+            _c4_parse_version_tag(${latest_tag} latest_tag_is_a_version major minor patch tweak more)
+            if(latest_tag_is_a_version)
+                c4_dbg("latest tag is a version. stick to the head description")
+                set(otag "${head_desc}-${branch}")
+                set(full "${latest_tag_is_a_version}")
+            else()
+                c4_dbg("latest tag is NOT a version. Use the current project version from cmake + the output of git describe")
+                set(otag "v${full}-${head_desc}-${branch}")
+                set(full "${${_c4_prefix}_VERSION_FULL}")
+                set(major "${${_c4_prefix}_VERSION_MAJOR}")
+                set(minor "${${_c4_prefix}_VERSION_MINOR}")
+                set(patch "${${_c4_prefix}_VERSION_PATCH}")
+                set(tweak "${${_c4_prefix}_VERSION_TWEAK}")
+            endif()
+        endif(commit_desc)
+    endif(NOT head_desc)
+    c4_log("cpack tag: ${otag}")
+    set(${tag_o}   "${otag}"  PARENT_SCOPE)
+    set(${full_o}  "${full}"  PARENT_SCOPE)
+    set(${major_o} "${major}" PARENT_SCOPE)
+    set(${minor_o} "${minor}" PARENT_SCOPE)
+    set(${patch_o} "${patch}" PARENT_SCOPE)
+    set(${tweak_o} "${tweak}" PARENT_SCOPE)
+    # also: dirty index?
+    #   https://stackoverflow.com/questions/2657935/checking-for-a-dirty-index-or-untracked-files-with-git
+endfunction()
+
+
+function(_c4_parse_version_tag tag is_version major minor patch tweak more)
+    # does the tag match a four-part version?
+    string(REGEX MATCH "v?([0-9]+)([\._][0-9]+)([\._][0-9]+)([\._][0-9]+)(.*)" match "${tag}")
+    function(_triml arg out) # trim the leading [\._] from the left
+        if("${arg}" STREQUAL "")
+            set(${out} "" PARENT_SCOPE)
+        else()
+            string(REGEX REPLACE "[\._](.*)" "\\1" ret "${arg}")
+            set("${out}" "${ret}" PARENT_SCOPE)
+        endif()
+    endfunction()
+    if(match)
+        set(${is_version} ${tag} PARENT_SCOPE)
+        _triml("${CMAKE_MATCH_1}" major_v)
+        _triml("${CMAKE_MATCH_2}" minor_v)
+        _triml("${CMAKE_MATCH_3}" patch_v)
+        _triml("${CMAKE_MATCH_4}" tweak_v)
+        _triml("${CMAKE_MATCH_5}" more_v)
+    else()
+        # does the tag match a three-part version?
+        string(REGEX MATCH "v?([0-9]+)([\._][0-9]+)([\._][0-9]+)(.*)" match "${tag}")
+        if(match)
+            set(${is_version} ${tag} PARENT_SCOPE)
+            _triml("${CMAKE_MATCH_1}" major_v)
+            _triml("${CMAKE_MATCH_2}" minor_v)
+            _triml("${CMAKE_MATCH_3}" patch_v)
+            _triml("${CMAKE_MATCH_4}" more_v)
+        else()
+            # does the tag match a two-part version?
+            string(REGEX MATCH "v?([0-9]+)([\._][0-9]+)(.*)" match "${tag}")
+            if(match)
+                set(${is_version} ${tag} PARENT_SCOPE)
+                _triml("${CMAKE_MATCH_1}" major_v)
+                _triml("${CMAKE_MATCH_2}" minor_v)
+                _triml("${CMAKE_MATCH_3}" more_v)
+            else()
+                # not a version!
+                set(${is_version} FALSE PARENT_SCOPE)
+            endif()
+        endif()
+    endif()
+    set(${major} "${major_v}" PARENT_SCOPE)
+    set(${minor} "${minor_v}" PARENT_SCOPE)
+    set(${patch} "${patch_v}" PARENT_SCOPE)
+    set(${tweak} "${tweak_v}" PARENT_SCOPE)
+    set(${more} "${more_v}" PARENT_SCOPE)
+endfunction()
+
+
+#function(testvtag)
+#    set(err FALSE)
+#    function(cmp value expected)
+#        if(NOT ("${${value}}" STREQUAL "${expected}"))
+#            c4_log("${tag}: error: expected ${value}=='${expected}': '${${value}}'=='${expected}'")
+#            set(err TRUE PARENT_SCOPE)
+#        else()
+#            c4_log("${tag}: ok: expected ${value}=='${expected}': '${${value}}'=='${expected}'")
+#        endif()
+#    endfunction()
+#    function(verify tag is_version_e major_e minor_e patch_e tweak_e more_e)
+#        _c4_parse_version_tag(${tag} is_version major minor patch tweak more)
+#        cmp(is_version ${is_version_e})
+#        cmp(major "${major_e}")
+#        cmp(minor "${minor_e}")
+#        cmp(patch "${patch_e}")
+#        cmp(tweak "${tweak_e}")
+#        cmp(more "${more_e}")
+#        set(err ${err} PARENT_SCOPE)
+#    endfunction()
+#    verify(v12.34.567.89-rcfoo TRUE 12 34 567 89 -rcfoo)
+#    verify(v12_34_567_89-rcfoo TRUE 12 34 567 89 -rcfoo)
+#    verify(v12.34.567.89       TRUE 12 34 567 89 "")
+#    verify(v12_34_567_89       TRUE 12 34 567 89 "")
+#    verify(v12.34.567-rcfoo    TRUE 12 34 567 "" -rcfoo)
+#    verify(v12_34_567-rcfoo    TRUE 12 34 567 "" -rcfoo)
+#    verify(v12.34.567          TRUE 12 34 567 "" "")
+#    verify(v12_34_567          TRUE 12 34 567 "" "")
+#    verify(v12_34              TRUE 12 34 ""  "" "")
+#    verify(v12.34              TRUE 12 34 ""  "" "")
+#    if(err)
+#        c4_err("test failed")
+#    endif()
+#endfunction()
+#testvtag()
+
+
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+
+
+macro(_c4_handle_cxx_standard_args)
+    # EXTENSIONS:
+    # enable compiler extensions eg, prefer gnu++11 to c++11
+    if(EXTENSIONS IN_LIST ARGN)
+        set(_EXTENSIONS ON)
+    else()
+        c4_get_from_first_of(_EXTENSIONS
+            ENV
+            DEFAULT OFF
+            VARS ${_c4_uprefix}CXX_EXTENSIONS C4_CXX_EXTENSIONS CMAKE_CXX_EXTENSIONS)
+    endif()
+    #
+    # OPTIONAL
+    if(OPTIONAL IN_LIST ARGN)
+        set(_REQUIRED OFF)
+    else()
+        c4_get_from_first_of(_REQUIRED
+            ENV
+            DEFAULT ON
+            VARS ${_c4_uprefix}CXX_STANDARD_REQUIRED C4_CXX_STANDARD_REQUIRED CMAKE_CXX_STANDARD_REQUIRED)
+    endif()
+endmacro()
+
+
+# set the global cxx standard for the project.
+#
+# examples:
+# c4_set_cxx(latest) # find the latest standard supported by the compiler, and use that
+# c4_set_cxx(11) # required, no extensions (eg c++11)
+# c4_set_cxx(14) # required, no extensions (eg c++14)
+# c4_set_cxx(11 EXTENSIONS) # opt-in to extensions (eg, gnu++11)
+# c4_set_cxx(14 EXTENSIONS) # opt-in to extensions (eg, gnu++14)
+# c4_set_cxx(11 OPTIONAL) # not REQUIRED. no extensions
+# c4_set_cxx(11 OPTIONAL EXTENSIONS) # not REQUIRED. with extensions.
+macro(c4_set_cxx standard)
+    _c4_handle_cxx_standard_args(${ARGN})
+    if(NOT DEFINED CMAKE_CXX_STANDARD)
+        c4_log("setting C++ standard: ${standard}")
+        c4_setg(CMAKE_CXX_STANDARD ${standard})
+    endif()
+    if(NOT DEFINED CMAKE_CXX_STANDARD_REQUIRED)
+        c4_log("setting C++ standard required: ${_REQUIRED}")
+        c4_setg(CMAKE_CXX_STANDARD_REQUIRED ${_REQUIRED})
+    endif()
+    if(NOT DEFINED CMAKE_CXX_STANDARD_REQUIRED)
+        c4_log("setting C++ standard extensions: ${_EXTENSIONS}")
+        c4_setg(CMAKE_CXX_EXTENSIONS ${_EXTENSIONS})
+    endif()
+endmacro()
+
+
+# set the cxx standard for a target.
+#
+# examples:
+# c4_target_set_cxx(target latest) # find the latest standard supported by the compiler, and use that
+# c4_target_set_cxx(target 11) # required, no extensions (eg c++11)
+# c4_target_set_cxx(target 14) # required, no extensions (eg c++14)
+# c4_target_set_cxx(target 11 EXTENSIONS) # opt-in to extensions (eg, gnu++11)
+# c4_target_set_cxx(target 14 EXTENSIONS) # opt-in to extensions (eg, gnu++14)
+# c4_target_set_cxx(target 11 OPTIONAL) # not REQUIRED. no extensions
+# c4_target_set_cxx(target 11 OPTIONAL EXTENSIONS)
+function(c4_target_set_cxx target standard)
+    c4_dbg("setting C++ standard for target ${target}: ${standard}")
+    _c4_handle_cxx_standard_args(${ARGN})
+    set_target_properties(${target} PROPERTIES
+        CXX_STANDARD ${standard}
+        CXX_STANDARD_REQUIRED ${_REQUIRED}
+        CXX_EXTENSIONS ${_EXTENSIONS})
+    target_compile_features(${target} PUBLIC cxx_std_${standard})
+endfunction()
+
+
+# set the cxx standard for a target based on the global project settings
+function(c4_target_inherit_cxx_standard target)
+    c4_dbg("inheriting C++ standard for target ${target}: ${CMAKE_CXX_STANDARD}")
+    set_target_properties(${target} PROPERTIES
+        CXX_STANDARD "${CMAKE_CXX_STANDARD}"
+        CXX_STANDARD_REQUIRED "${CMAKE_CXX_STANDARD_REQUIRED}"
+        CXX_EXTENSIONS "${CMAKE_CXX_EXTENSIONS}")
+    target_compile_features(${target} PUBLIC cxx_std_${CMAKE_CXX_STANDARD})
+endfunction()
+
+
+function(_c4_find_latest_supported_cxx_standard out)
+    if(NOT c4_latest_supported_cxx_standard)
+        include(CheckCXXCompilerFlag)
+        # make sure CMAKE_CXX_FLAGS is clean here
+        # see https://cmake.org/cmake/help/v3.16/module/CheckCXXCompilerFlag.html
+        # Note: since this is a function, we don't need to reset CMAKE_CXX_FLAGS
+        # back to its previous value
+        set(CMAKE_CXX_FLAGS)
+        set(standard 11)  # default to C++11 if everything fails
+        foreach(s ${C4_CXX_STANDARDS})
+            if(MSVC)
+                set(flag /std:c++${s})
+            else()
+                # assume GNU-style compiler
+                set(flag -std=c++${s})
+            endif()
+            c4_log("checking CXX standard: C++${s} flag=${flag}")
+            check_cxx_compiler_flag(${flag} has${s})
+            if(has${s})
+                c4_log("checking CXX standard: C++${s} is supported! flag=${flag}")
+                set(standard ${s})
+                break()
+            else()
+                c4_log("checking CXX standard: C++${s}: no support for flag=${flag} no")
+            endif()
+        endforeach()
+        set(c4_latest_supported_cxx_standard ${standard} CACHE INTERNAL "")
+    endif()
+    set(${out} ${c4_latest_supported_cxx_standard} PARENT_SCOPE)
+endfunction()
+
+
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+# examples:
+#
+# # require subproject c4core, as a subdirectory. c4core will be used
+# # as a separate library
+# c4_require_subproject(c4core SUBDIRECTORY ${C4OPT_EXT_DIR}/c4core)
+#
+# # require subproject c4core, as a remote proj
+# c4_require_subproject(c4core REMOTE
+#     GIT_REPOSITORY https://github.com/biojppm/c4core
+#     GIT_TAG master
+#     )
+function(c4_require_subproject subproj)
+    _c4_handle_args(_ARGS ${ARGN}
+        _ARGS0
+            INCORPORATE
+            EXCLUDE_FROM_ALL
+        _ARGS1
+            SUBDIRECTORY   # the subproject is located in the given directory name and
+                           # will be added via add_subdirectory()
+        _ARGSN
+            REMOTE         # the subproject is located in a remote repo/url
+                           # and will be added via c4_import_remote_proj(),
+                           # forwarding all the arguments in here.
+            OVERRIDE       # a list of variable name+value pairs
+                           # these variables will be set with c4_override()
+                           # before calling add_subdirectory()
+            SET_FOLDER_TARGETS   # Set the folder of the given targets using
+                                 # c4_set_folder_remote_project_targets().
+                                 # The first expected argument is the folder,
+                                 # and the remaining arguments are the targets
+                                 # which we want to set the folder.
+        _DEPRECATE
+            INTERFACE
+    )
+    list(APPEND _${_c4_uprefix}_deps ${subproj})
+    c4_setg(_${_c4_uprefix}_deps ${_${_c4_uprefix}_deps})
+    c4_dbg("-----------------------------------------------")
+    c4_dbg("requires subproject ${subproj}!")
+    if(_INCORPORATE)
+        c4_dbg("requires subproject ${subproj} in INCORPORATE mode!")
+        c4_dbg_var(${_c4_root_uproj}_STANDALONE)
+        if(${_c4_root_uproj}_STANDALONE)
+            c4_dbg("${_c4_root_uproj} is STANDALONE: honoring INCORPORATE mode...")
+        else()
+            c4_dbg("${_c4_root_uproj} is not STANDALONE: ignoring INCORPORATE mode...")
+            set(_INCORPORATE OFF)
+        endif()
+    endif()
+    #
+    _c4_get_subproject_property(${subproj} AVAILABLE _available)
+    if(_available)
+        c4_dbg("required subproject ${subproj} was already imported:")
+        c4_dbg_subproject(${subproj})
+        # TODO check version compatibility
+    else() #elseif(NOT _${subproj}_available)
+        c4_dbg("required subproject ${subproj} is unknown. Importing...")
+        if(_EXCLUDE_FROM_ALL)
+            set(excl EXCLUDE_FROM_ALL)
+        endif()
+        # forward c4 compile flags
+        string(TOUPPER ${subproj} usubproj)
+        c4_setg(${usubproj}_CXX_FLAGS_FWD "${${_c4_uprefix}CXX_FLAGS}")
+        c4_setg(${usubproj}_CXX_FLAGS_OPT_FWD "${${_c4_uprefix}CXX_FLAGS_OPT}")
+        c4_setg(${usubproj}_CXX_LINKER_FLAGS_FWD "${${_c4_uprefix}CXX_LINKER_FLAGS}")
+        # root dir
+        set(_r ${CMAKE_CURRENT_BINARY_DIR}/subprojects/${subproj})
+        if(_REMOTE)
+            c4_log("importing subproject ${subproj} (REMOTE)... ${_REMOTE}")
+            _c4_mark_subproject_imported(${subproj} ${_r}/src ${_r}/build ${_INCORPORATE})
+            c4_import_remote_proj(${subproj} ${_r} REMOTE ${_REMOTE} OVERRIDE ${_OVERRIDE} ${excl})
+            _c4_get_subproject_property(${subproj} SRC_DIR _srcdir)
+            c4_dbg("finished importing subproject ${subproj} (REMOTE, SRC_DIR=${_srcdir}).")
+        elseif(_SUBDIRECTORY)
+            c4_log("importing subproject ${subproj} (SUBDIRECTORY)... ${_SUBDIRECTORY}")
+            _c4_mark_subproject_imported(${subproj} ${_SUBDIRECTORY} ${_r}/build ${_INCORPORATE})
+            c4_add_subproj(${subproj} ${_SUBDIRECTORY} ${_r}/build OVERRIDE ${_OVERRIDE} ${excl})
+            set(_srcdir ${_SUBDIRECTORY})
+            c4_dbg("finished importing subproject ${subproj} (SUBDIRECTORY=${_SUBDIRECTORY}).")
+        else()
+            c4_err("subproject type must be either REMOTE or SUBDIRECTORY")
+        endif()
+    endif()
+    #
+    if(_SET_FOLDER_TARGETS)
+        c4_set_folder_remote_project_targets(${_SET_FOLDER_TARGETS})
+    endif()
+endfunction(c4_require_subproject)
+
+
+function(c4_add_subproj proj dir bindir)
+    _c4_handle_args(_ARGS ${ARGN}
+        _ARGS0
+            EXCLUDE_FROM_ALL # forward to add_subdirectory()
+        _ARGS1
+        _ARGSN
+            OVERRIDE   # a list of variable name+value pairs
+                       # these variables will be set with c4_override()
+                       # before calling add_subdirectory()
+    )
+    # push the subproj into the current path
+    set(prev_subproject ${_c4_curr_subproject})
+    set(prev_path ${_c4_curr_path})
+    set(_c4_curr_subproject ${proj})
+    string(REGEX MATCH ".*/${proj}\$" pos "${_c4_curr_path}")
+    if(pos EQUAL -1)
+        string(REGEX MATCH "^${proj}\$" pos "${_c4_curr_path}")
+        if(pos EQUAL -1)
+            set(_c4_curr_path ${_c4_curr_path}/${proj})
+        endif()
+    endif()
+    #
+    while(_OVERRIDE)
+        list(POP_FRONT _OVERRIDE varname)
+        list(POP_FRONT _OVERRIDE varvalue)
+        c4_override(${varname} ${varvalue})
+    endwhile()
+    #
+    if(_EXCLUDE_FROM_ALL)
+        set(excl EXCLUDE_FROM_ALL)
+    endif()
+    #
+    c4_dbg("adding subproj: ${prev_subproject}->${_c4_curr_subproject}. path=${_c4_curr_path}")
+    add_subdirectory(${dir} ${bindir} ${excl})
+    # pop the subproj from the current path
+    set(_c4_curr_subproject ${prev_subproject})
+    set(_c4_curr_path ${prev_path})
+endfunction()
+
+
+function(_c4_mark_subproject_imported subproject_name subproject_src_dir subproject_bin_dir incorporate)
+    c4_dbg("marking subproject imported: ${subproject_name} (imported by ${_c4_prefix}). src=${subproject_src_dir}")
+    _c4_append_subproject_property(${_c4_prefix} DEPENDENCIES ${subproject_name})
+    _c4_get_folder(folder ${_c4_prefix} ${subproject_name})
+    _c4_set_subproject_property(${subproject_name} AVAILABLE ON)
+    _c4_set_subproject_property(${subproject_name} IMPORTER "${_c4_prefix}")
+    _c4_set_subproject_property(${subproject_name} SRC_DIR "${subproject_src_dir}")
+    _c4_set_subproject_property(${subproject_name} BIN_DIR "${subproject_bin_dir}")
+    _c4_set_subproject_property(${subproject_name} FOLDER "${folder}")
+    _c4_set_subproject_property(${subproject_name} INCORPORATE "${incorporate}")
+endfunction()
+
+
+function(_c4_get_subproject_property subproject property var)
+    get_property(v GLOBAL PROPERTY _c4_subproject-${subproject}-${property})
+    set(${var} "${v}" PARENT_SCOPE)
+endfunction()
+
+
+function(_c4_set_subproject_property subproject property value)
+    c4_dbg("setting subproj prop: ${subproject}: ${property}=${value}")
+    set_property(GLOBAL PROPERTY _c4_subproject-${subproject}-${property} "${value}")
+endfunction()
+function(_c4_append_subproject_property subproject property value)
+    _c4_get_subproject_property(${subproject} ${property} cval)
+    if(cval)
+        list(APPEND cval ${value})
+    else()
+        set(cval ${value})
+    endif()
+    _c4_set_subproject_property(${subproject} ${property} ${cval})
+endfunction()
+
+
+function(_c4_is_incorporated subproj out)
+    if("${subproj}" STREQUAL "${_c4_root_proj}")
+        c4_dbg("${subproj} is incorporated? root proj, no")
+        set(${out} OFF PARENT_SCOPE)
+    else()
+        _c4_get_subproject_property(${subproj} INCORPORATE inc)
+        c4_dbg("${subproj} is incorporated? not root proj, incorporate=${inc}")
+        set(${out} ${inc} PARENT_SCOPE)
+    endif()
+endfunction()
+
+
+function(c4_dbg_subproject subproject)
+    set(props AVAILABLE IMPORTER SRC_DIR BIN_DIR DEPENDENCIES FOLDER INCORPORATE)
+    foreach(p ${props})
+        _c4_get_subproject_property(${subproject} ${p} pv)
+        c4_dbg("${subproject}: ${p}=${pv}")
+    endforeach()
+endfunction()
+
+
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+
+#
+#
+function(c4_import_remote_proj name dir)
+    _c4_handle_args(_ARGS ${ARGN}
+        _ARGS0
+            EXCLUDE_FROM_ALL
+        _ARGS1
+        _ARGSN
+            OVERRIDE   # a list of variable name+value pairs
+                       # these variables will be set with c4_override()
+                       # before calling add_subdirectory()
+            REMOTE     # to specify url, repo, tag, or branch,
+                       # pass the needed arguments after dir.
+                       # These arguments will be forwarded to ExternalProject_Add()
+            SET_FOLDER_TARGETS   # Set the folder of the given targets using
+                                 # c4_set_folder_remote_project_targets().
+                                 # The first expected argument is the folder,
+                                 # and the remaining arguments are the targets
+                                 # which we want to set the folder.
+    )
+    set(srcdir_in_out "${dir}")
+    c4_download_remote_proj(${name} srcdir_in_out ${_REMOTE})
+    _c4_set_subproject_property(${name} SRC_DIR "${srcdir_in_out}")
+    if(_EXCLUDE_FROM_ALL)
+        set(excl EXCLUDE_FROM_ALL)
+    endif()
+    c4_add_subproj(${name} "${srcdir_in_out}" "${dir}/build" OVERRIDE ${_OVERRIDE} ${excl})
+    #
+    if(_SET_FOLDER_TARGETS)
+        c4_set_folder_remote_project_targets(${_SET_FOLDER_TARGETS})
+    endif()
+endfunction()
+
+
+# download remote projects while running cmake
+# to specify url, repo, tag, or branch,
+# pass the needed arguments after dir.
+# These arguments will be forwarded to ExternalProject_Add()
+function(c4_download_remote_proj name candidate_dir)
+    # https://crascit.com/2015/07/25/cmake-gtest/
+    # (via https://stackoverflow.com/questions/15175318/cmake-how-to-build-external-projects-and-include-their-targets)
+    set(dir ${${candidate_dir}})
+    if("${dir}" STREQUAL "")
+        set(dir "${CMAKE_BINARY_DIR}/extern/${name}")
+    endif()
+    set(cvar _${_c4_uprefix}_DOWNLOAD_${name}_LOCATION)
+    set(cval ${${cvar}})
+    #
+    # was it already downloaded in this project?
+    if(NOT ("${cval}" STREQUAL ""))
+        if(EXISTS "${cval}")
+            c4_log("${name} was previously imported into this project - found at \"${cval}\"!")
+            set(${candidate_dir} "${cval}" PARENT_SCOPE)
+            return()
+        else()
+            c4_log("${name} was previously imported into this project - but was NOT found at \"${cval}\"!")
+        endif()
+    endif()
+    #
+    # try to find an existing version (downloaded by some other project)
+    set(out "${dir}")
+    _c4_find_cached_proj(${name} out)
+    if(NOT ("${out}" STREQUAL "${dir}"))
+        c4_log("using ${name} from \"${out}\"...")
+        set(${cvar} "${out}" CACHE INTERNAL "")
+        set(${candidate_dir} "${out}" PARENT_SCOPE)
+        return()
+    endif()
+    #
+    # no version was found; need to download.
+    c4_log("downloading ${name}: not in cache...")
+    # check for a global place to download into
+    set(srcdir)
+    _c4_get_cached_srcdir_global_extern(${name} srcdir)
+    if("${srcdir}" STREQUAL "")
+        # none found; default to the given dir
+        set(srcdir "${dir}/src")
+    endif()
+    #
+    # do it
+    #if((EXISTS ${dir}/dl) AND (EXISTS ${dir}/dl/CMakeLists.txt))
+    #    return()
+    #endif()
+    c4_log("downloading remote project: ${name} -> \"${srcdir}\" (dir=${dir})...")
+    #
+    file(WRITE ${dir}/dl/CMakeLists.txt "
+cmake_minimum_required(VERSION 2.8.2)
+project(${_c4_lcprefix}-download-${name} NONE)
+
+# this project only downloads ${name}
+# (ie, no configure, build or install step)
+include(ExternalProject)
+
+ExternalProject_Add(${name}-dl
+    ${ARGN}
+    SOURCE_DIR \"${srcdir}\"
+    BINARY_DIR \"${dir}/build\"
+    CONFIGURE_COMMAND \"\"
+    BUILD_COMMAND \"\"
+    INSTALL_COMMAND \"\"
+    TEST_COMMAND \"\"
+)
+")
+    execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" .
+        WORKING_DIRECTORY ${dir}/dl)
+    execute_process(COMMAND ${CMAKE_COMMAND} --build .
+        WORKING_DIRECTORY ${dir}/dl)
+    #
+    set(${candidate_dir} "${srcdir}" PARENT_SCOPE)
+    set(_${_c4_uprefix}_DOWNLOAD_${name}_LOCATION "${srcdir}" CACHE INTERNAL "")
+endfunction()
+
+
+# checks if the project was already downloaded. If it was, then dir_in_out is
+# changed to the directory where the project was found at.
+function(_c4_find_cached_proj name dir_in_out)
+    c4_log("downloading ${name}: searching cached project...")
+    #
+    # 1. search in the per-import variable, eg RYML_CACHE_DOWNLOAD_GTEST
+    string(TOUPPER ${name} uname)
+    set(var ${_c4_uprefix}CACHE_DOWNLOAD_${uname})
+    set(val "${${var}}")
+    if(NOT ("${val}" STREQUAL ""))
+        c4_log("downloading ${name}: searching in ${var}=${val}")
+        if(EXISTS "${val}")
+            c4_log("downloading ${name}: picked ${sav} instead of ${${dir_in_out}}")
+            set(${dir_in_out} ${sav} PARENT_SCOPE)
+        endif()
+    endif()
+    #
+    # 2. search in the global directory (if there is one)
+    _c4_get_cached_srcdir_global_extern(${name} sav)
+    if(NOT ("${sav}" STREQUAL ""))
+        c4_log("downloading ${name}: searching in C4_EXTERN_DIR: ${sav}")
+        if(EXISTS "${sav}")
+            c4_log("downloading ${name}: picked ${sav} instead of ${${dir_in_out}}")
+            set(${dir_in_out} ${sav} PARENT_SCOPE)
+        endif()
+    endif()
+endfunction()
+
+
+function(_c4_get_cached_srcdir_global_extern name out)
+    set(${out} "" PARENT_SCOPE)
+    if("${C4_EXTERN_DIR}" STREQUAL "")
+        set(C4_EXTERN_DIR "$ENV{C4_EXTERN_DIR}")
+    endif()
+    if(NOT ("${C4_EXTERN_DIR}" STREQUAL ""))
+        set(${out} "${C4_EXTERN_DIR}/${name}" PARENT_SCOPE)
+    endif()
+endfunction()
+
+
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+
+function(_c4_get_folder output importer_subproject subproject_name)
+    _c4_get_subproject_property(${importer_subproject} FOLDER importer_folder)
+    if("${importer_folder}" STREQUAL "")
+        set(folder ${importer_subproject})
+    else()
+        set(folder "${importer_folder}/deps/${subproject_name}")
+    endif()
+    set(${output} ${folder} PARENT_SCOPE)
+endfunction()
+
+
+function(_c4_set_target_folder target subfolder)
+    string(FIND "${subfolder}" "/" pos)
+    if(pos EQUAL 0)
+        if("${_c4_curr_path}" STREQUAL "")
+            string(SUBSTRING "${subfolder}" 1 -1 sf)
+            set_target_properties(${target} PROPERTIES
+                FOLDER "${sf}")
+        else()
+            set_target_properties(${target} PROPERTIES
+                FOLDER "${subfolder}")
+        endif()
+    elseif("${subfolder}" STREQUAL "")
+        set_target_properties(${target} PROPERTIES
+            FOLDER "${_c4_curr_path}")
+    else()
+        if("${_c4_curr_path}" STREQUAL "")
+            set_target_properties(${target} PROPERTIES
+                FOLDER "${subfolder}")
+        else()
+            set_target_properties(${target} PROPERTIES
+                FOLDER "${_c4_curr_path}/${subfolder}")
+        endif()
+    endif()
+endfunction()
+
+
+function(c4_set_folder_remote_project_targets subfolder)
+    foreach(target ${ARGN})
+        if(TARGET ${target})
+            _c4_set_target_folder(${target} "${subfolder}")
+        endif()
+    endforeach()
+endfunction()
+
+
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+
+# a convenience alias to c4_add_target()
+function(c4_add_executable target)
+    c4_add_target(${target} EXECUTABLE ${ARGN})
+endfunction(c4_add_executable)
+
+
+# a convenience alias to c4_add_target()
+function(c4_add_library target)
+    c4_add_target(${target} LIBRARY ${ARGN})
+endfunction(c4_add_library)
+
+
+# example: c4_add_target(ryml LIBRARY SOURCES ${SRC})
+function(c4_add_target target)
+    c4_dbg("adding target: ${target}: ${ARGN}")
+    set(opt0arg
+        LIBRARY     # the target is a library
+        EXECUTABLE  # the target is an executable
+        WIN32       # the executable is WIN32
+        SANITIZE    # deprecated
+    )
+    set(opt1arg
+        LIBRARY_TYPE    # override global setting for C4_LIBRARY_TYPE
+        SHARED_MACRO    # the name of the macro to turn on export/import symbols
+                        # for compiling the library as a windows DLL.
+                        # defaults to ${_c4_uprefix}SHARED.
+        SHARED_EXPORTS  # the name of the macro to turn on export of symbols
+                        # for compiling the library as a windows DLL.
+                        # defaults to ${_c4_uprefix}EXPORTS.
+        SOURCE_ROOT     # the directory where relative source paths
+                        # should be resolved. when empty,
+                        # use CMAKE_CURRENT_SOURCE_DIR
+        FOLDER          # IDE folder to group the target in
+        SANITIZERS      # outputs the list of sanitize targets in this var
+        SOURCE_TRANSFORM  # WIP
+    )
+    set(optnarg
+        INCORPORATE  # incorporate these libraries into this target,
+                     # subject to ${_c4_uprefix}STANDALONE and C4_STANDALONE
+        SOURCES  PUBLIC_SOURCES  INTERFACE_SOURCES  PRIVATE_SOURCES
+        HEADERS  PUBLIC_HEADERS  INTERFACE_HEADERS  PRIVATE_HEADERS
+        INC_DIRS PUBLIC_INC_DIRS INTERFACE_INC_DIRS PRIVATE_INC_DIRS
+        LIBS     PUBLIC_LIBS     INTERFACE_LIBS     PRIVATE_LIBS
+        DEFS     PUBLIC_DEFS     INTERFACE_DEFS     PRIVATE_DEFS    # defines
+        CFLAGS   PUBLIC_CFLAGS   INTERFACE_CFLAGS   PRIVATE_CFLAGS  # compiler flags. TODO: linker flags
+        DLLS           # DLLs required by this target
+        MORE_ARGS
+    )
+    cmake_parse_arguments("" "${opt0arg}" "${opt1arg}" "${optnarg}" ${ARGN})
+    #
+    if(_SANITIZE)
+        c4_err("SANITIZE is deprecated")
+    endif()
+
+    if(${_LIBRARY})
+        set(_what LIBRARY)
+    elseif(${_EXECUTABLE})
+        set(_what EXECUTABLE)
+    else()
+        c4_err("must be either LIBRARY or EXECUTABLE")
+    endif()
+
+    _c4_handle_arg(SHARED_MACRO ${_c4_uprefix}MACRO)
+    _c4_handle_arg(SHARED_EXPORTS ${_c4_uprefix}EXPORTS)
+    _c4_handle_arg_or_fallback(SOURCE_ROOT "${CMAKE_CURRENT_SOURCE_DIR}")
+    function(_c4_transform_to_full_path list all)
+        set(l)
+        foreach(f ${${list}})
+            if(NOT IS_ABSOLUTE "${f}")
+                set(f "${_SOURCE_ROOT}/${f}")
+            endif()
+            list(APPEND l "${f}")
+        endforeach()
+        set(${list} "${l}" PARENT_SCOPE)
+        set(cp ${${all}})
+        list(APPEND cp ${l})
+        set(${all} ${cp} PARENT_SCOPE)
+    endfunction()
+    _c4_transform_to_full_path(          _SOURCES allsrc)
+    _c4_transform_to_full_path(          _HEADERS allsrc)
+    _c4_transform_to_full_path(   _PUBLIC_SOURCES allsrc)
+    _c4_transform_to_full_path(_INTERFACE_SOURCES allsrc)
+    _c4_transform_to_full_path(  _PRIVATE_SOURCES allsrc)
+    _c4_transform_to_full_path(   _PUBLIC_HEADERS allsrc)
+    _c4_transform_to_full_path(_INTERFACE_HEADERS allsrc)
+    _c4_transform_to_full_path(  _PRIVATE_HEADERS allsrc)
+    create_source_group("" "${_SOURCE_ROOT}" "${allsrc}")
+    # is the target name prefixed with the project prefix?
+    string(REGEX MATCH "${_c4_prefix}::.*" target_is_prefixed "${target}")
+    if(NOT ${_c4_uprefix}SANITIZE_ONLY)
+        if(${_EXECUTABLE})
+            c4_dbg("adding executable: ${target}")
+            if(WIN32)
+                if(${_WIN32})
+                    list(APPEND _MORE_ARGS WIN32)
+                endif()
+            endif()
+	    add_executable(${target} ${_MORE_ARGS})
+            if(NOT target_is_prefixed)
+                add_executable(${_c4_prefix}::${target} ALIAS ${target})
+            endif()
+	    set(src_mode PRIVATE)
+            set(tgt_type PUBLIC)
+            set(compiled_target ON)
+        elseif(${_LIBRARY})
+            c4_dbg("adding library: ${target}")
+            set(_blt ${C4_LIBRARY_TYPE})  # build library type
+            if(NOT "${_LIBRARY_TYPE}" STREQUAL "")
+                set(_blt ${_LIBRARY_TYPE})
+            endif()
+            if("${_blt}" STREQUAL "")
+            endif()
+            #
+            if("${_blt}" STREQUAL "INTERFACE")
+                c4_dbg("adding interface library ${target}")
+                add_library(${target} INTERFACE)
+                set(src_mode INTERFACE)
+                set(tgt_type INTERFACE)
+                set(compiled_target OFF)
+            else()
+                if("${_blt}" STREQUAL "")
+                    # obey BUILD_SHARED_LIBS (ie, either static or shared library)
+                    c4_dbg("adding library ${target} (defer to BUILD_SHARED_LIBS=${BUILD_SHARED_LIBS}) --- ${_MORE_ARGS}")
+                    add_library(${target} ${_MORE_ARGS})
+                    if(BUILD_SHARED_LIBS)
+                        set(_blt SHARED)
+                    else()
+                        set(_blt STATIC)
+                    endif()
+                else()
+                    c4_dbg("adding library ${target} with type ${_blt}")
+                    add_library(${target} ${_blt} ${_MORE_ARGS})
+                endif()
+                # libraries
+                set(src_mode PRIVATE)
+                set(tgt_type PUBLIC)
+                set(compiled_target ON)
+                # exports for shared libraries
+                if(WIN32)
+                    if("${_blt}" STREQUAL SHARED)
+                        set_target_properties(${target} PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON)
+                        target_compile_definitions(${target} PUBLIC ${_SHARED_MACRO})
+                        target_compile_definitions(${target} PRIVATE $<BUILD_INTERFACE:${_SHARED_EXPORTS}>)
+                        # save the name of the macro for later use when(if) incorporating this library
+                        c4_set_target_prop(${target} SHARED_EXPORTS ${_SHARED_EXPORTS})
+                    endif()  # shared lib
+                endif() # win32
+            endif() # interface or lib
+            if(NOT target_is_prefixed)
+                add_library(${_c4_prefix}::${target} ALIAS ${target})
+            endif()
+            set_target_properties(${target} PROPERTIES SO_VERSION ${${_c4_prefix}_VERSION})
+        endif(${_EXECUTABLE})
+        set_target_properties(${target} PROPERTIES VERSION ${${_c4_prefix}_VERSION})
+
+        if(src_mode STREQUAL "PUBLIC")
+            c4_add_target_sources(${target}
+                PUBLIC    "${_SOURCES};${_HEADERS};${_PUBLIC_SOURCES};${_PUBLIC_HEADERS}"
+                INTERFACE "${_INTERFACE_SOURCES};${_INTERFACE_HEADERS}"
+                PRIVATE   "${_PRIVATE_SOURCES};${_PRIVATE_HEADERS}")
+        elseif(src_mode STREQUAL "INTERFACE")
+            c4_add_target_sources(${target}
+                PUBLIC    "${_PUBLIC_SOURCES};${_PUBLIC_HEADERS}"
+                INTERFACE "${_SOURCES};${_HEADERS};${_INTERFACE_SOURCES};${_INTERFACE_HEADERS}"
+                PRIVATE   "${_PRIVATE_SOURCES};${_PRIVATE_HEADERS}")
+        elseif(src_mode STREQUAL "PRIVATE")
+            c4_add_target_sources(${target}
+                PUBLIC    "${_PUBLIC_SOURCES};${_PUBLIC_HEADERS}"
+                INTERFACE "${_INTERFACE_SOURCES};${_INTERFACE_HEADERS}"
+                PRIVATE   "${_SOURCES};${_HEADERS};${_PRIVATE_SOURCES};${_PRIVATE_HEADERS}")
+        elseif()
+            c4_err("${target}: adding sources: invalid source mode")
+        endif()
+        _c4_set_tgt_prop(${target} C4_SOURCE_ROOT "${_SOURCE_ROOT}")
+
+        if(_INC_DIRS)
+            c4_dbg("${target}: adding include dirs ${_INC_DIRS} [from target: ${tgt_type}]")
+            target_include_directories(${target} "${tgt_type}" ${_INC_DIRS})
+        endif()
+        if(_PUBLIC_INC_DIRS)
+            c4_dbg("${target}: adding PUBLIC include dirs ${_PUBLIC_INC_DIRS}")
+            target_include_directories(${target} PUBLIC ${_PUBLIC_INC_DIRS})
+        endif()
+        if(_INTERFACE_INC_DIRS)
+            c4_dbg("${target}: adding INTERFACE include dirs ${_INTERFACE_INC_DIRS}")
+            target_include_directories(${target} INTERFACE ${_INTERFACE_INC_DIRS})
+        endif()
+        if(_PRIVATE_INC_DIRS)
+            c4_dbg("${target}: adding PRIVATE include dirs ${_PRIVATE_INC_DIRS}")
+            target_include_directories(${target} PRIVATE ${_PRIVATE_INC_DIRS})
+        endif()
+
+        if(_LIBS)
+            _c4_link_with_libs(${target} "${tgt_type}" "${_LIBS}" "${_INCORPORATE}")
+        endif()
+        if(_PUBLIC_LIBS)
+            _c4_link_with_libs(${target} PUBLIC "${_PUBLIC_LIBS}" "${_INCORPORATE}")
+        endif()
+        if(_INTERFACE_LIBS)
+            _c4_link_with_libs(${target} INTERFACE "${_INTERFACE_LIBS}" "${_INCORPORATE}")
+        endif()
+        if(_PRIVATE_LIBS)
+            _c4_link_with_libs(${target} PRIVATE "${_PRIVATE_LIBS}" "${_INCORPORATE}")
+        endif()
+
+        if(compiled_target)
+            if(_FOLDER)
+                _c4_set_target_folder(${target} "${_FOLDER}")
+            else()
+                _c4_set_target_folder(${target} "")
+            endif()
+            # cxx standard
+            c4_target_inherit_cxx_standard(${target})
+            # compile flags
+            set(_more_flags
+                ${${_c4_uprefix}CXX_FLAGS}
+                ${${_c4_uprefix}C_FLAGS}
+                ${${_c4_uprefix}CXX_FLAGS_OPT})
+            if(_more_flags)
+                get_target_property(_flags ${target} COMPILE_OPTIONS)
+                if(_flags)
+                    set(_more_flags ${_flags};${_more_flags})
+                endif()
+                c4_dbg("${target}: COMPILE_FLAGS=${_more_flags}")
+                target_compile_options(${target} PRIVATE "${_more_flags}")
+            endif()
+            # linker flags
+            set(_link_flags ${${_c4_uprefix}CXX_LINKER_FLAGS})
+            if(_link_flags)
+                get_target_property(_flags ${target} LINK_OPTIONS)
+                if(_flags)
+                    set(_link_flags ${_flags};${_more_flags})
+                endif()
+                c4_dbg("${target}: LINKER_FLAGS=${_link_flags}")
+                target_link_options(${target} PUBLIC "${_link_flags}")
+            endif()
+            # static analysis
+            if(${_c4_uprefix}LINT)
+                c4_static_analysis_target(${target} "${_FOLDER}" lint_targets)
+            endif()
+        endif(compiled_target)
+
+        if(_DEFS)
+            target_compile_definitions(${target} "${tgt_type}" ${_DEFS})
+        endif()
+        if(_PUBLIC_DEFS)
+            target_compile_definitions(${target} PUBLIC ${_PUBLIC_DEFS})
+        endif()
+        if(_INTERFACE_DEFS)
+            target_compile_definitions(${target} INTERFACE ${_INTERFACE_DEFS})
+        endif()
+        if(_PRIVATE_DEFS)
+            target_compile_definitions(${target} PRIVATE ${_PRIVATE_DEFS})
+        endif()
+
+        if(_CFLAGS)
+            target_compile_options(${target} "${tgt_type}" ${_CFLAGS})
+        endif()
+        if(_PUBLIC_CFLAGS)
+            target_compile_options(${target} PUBLIC ${_PUBLIC_CFLAGS})
+        endif()
+        if(_INTERFACE_CFLAGS)
+            target_compile_options(${target} INTERFACE ${_INTERFACE_CFLAGS})
+        endif()
+        if(_PRIVATE_CFLAGS)
+            target_compile_options(${target} PRIVATE ${_PRIVATE_CFLAGS})
+        endif()
+
+        if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") AND
+          (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 4.8) AND
+          (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0))
+            c4_dbg("${target}: adding compat include path")
+            target_include_directories(${target} PUBLIC $<BUILD_INTERFACE:${_c4_project_dir}/compat>)
+        endif()
+
+    endif(NOT ${_c4_uprefix}SANITIZE_ONLY)
+
+    if(compiled_target)
+        if(${_c4_uprefix}SANITIZE)
+            c4_sanitize_target(${target}
+                ${_what}   # LIBRARY or EXECUTABLE
+                SOURCES ${allsrc}
+                INC_DIRS ${_INC_DIRS} ${_PUBLIC_INC_DIRS} ${_INTERFACE_INC_DIRS} ${_PRIVATE_INC_DIRS}
+                LIBS ${_LIBS} ${_PUBLIC_LIBS} ${_INTERFACE_LIBS} ${_PRIVATE_LIBS}
+                DEFS ${_DEFS} ${_PUBLIC_DEFS} ${_INTERFACE_DEFS} ${_PRIVATE_DEFS}
+                CFLAGS ${_CFLAGS} ${_PUBLIC_CFLAGS} ${_INTERFACE_CFLAGS} ${_PRIVATE_CFLAGS}
+                OUTPUT_TARGET_NAMES san_targets
+                FOLDER "${_FOLDER}"
+                )
+        endif()
+
+        if(NOT ${_c4_uprefix}SANITIZE_ONLY)
+            list(INSERT san_targets 0 ${target})
+        endif()
+
+        if(_SANITIZERS)
+            set(${_SANITIZERS} ${san_targets} PARENT_SCOPE)
+        endif()
+
+        _c4_set_tgt_prop(${target} C4_SAN_TARGETS "${san_targets}")
+    else()
+        _c4_set_tgt_prop(${target} C4_SAN_TARGETS "${target}")
+    endif()
+
+    # gather dlls so that they can be automatically copied to the target directory
+    if(_DLLS)
+        c4_append_transitive_property(${target} _C4_DLLS "${_DLLS}")
+    endif()
+
+    if(${_EXECUTABLE})
+        if(WIN32)
+            c4_get_transitive_property(${target} _C4_DLLS transitive_dlls)
+            list(REMOVE_DUPLICATES transitive_dlls)
+            foreach(_dll ${transitive_dlls})
+                if(_dll)
+                    c4_dbg("enable copy of dll to target file dir: ${_dll} ---> $<TARGET_FILE_DIR:${target}>")
+                    add_custom_command(TARGET ${target} POST_BUILD
+                        COMMAND ${CMAKE_COMMAND} -E copy_if_different "${_dll}" "$<TARGET_FILE_DIR:${target}>"
+                        )
+                else()
+                    message(WARNING "dll required by ${_c4_prefix}/${target} was not found, so cannot copy: ${_dll}")
+                endif()
+            endforeach()
+        endif()
+    endif()
+endfunction() # add_target
+
+
+function(_c4_link_with_libs target link_type libs incorporate)
+    foreach(lib ${libs})
+        # add targets that are DLLs
+        if(WIN32)
+            if(TARGET ${lib})
+                get_target_property(lib_type ${lib} TYPE)
+                if(lib_type STREQUAL SHARED_LIBRARY)
+                    c4_append_transitive_property(${target} _C4_DLLS "$<TARGET_FILE:${lib}>")
+                endif()
+            endif()
+        endif()
+        _c4_lib_is_incorporated(${lib} isinc)
+        if(isinc OR (incorporate AND ${_c4_uprefix}STANDALONE))
+            c4_log("-----> target ${target} ${link_type} incorporating lib ${lib}")
+            _c4_incorporate_lib(${target} ${link_type} ${lib})
+        else()
+            c4_dbg("${target} ${link_type} linking with lib ${lib}")
+            target_link_libraries(${target} ${link_type} ${lib})
+        endif()
+    endforeach()
+endfunction()
+
+
+function(_c4_lib_is_incorporated lib ret)
+    c4_dbg("${lib}: is incorporated?")
+    if(NOT TARGET ${lib})
+        c4_dbg("${lib}: no, not a target")
+        set(${ret} OFF PARENT_SCOPE)
+    else()
+        c4_get_target_prop(${lib} INCORPORATING_TARGETS inc)
+        if(inc)
+            c4_dbg("${lib}: is incorporated!")
+            set(${ret} ON PARENT_SCOPE)
+        else()
+            c4_dbg("${lib}: is not incorporated!")
+            set(${ret} OFF PARENT_SCOPE)
+        endif()
+    endif()
+endfunction()
+
+
+function(_c4_incorporate_lib target link_type lib)
+    c4_dbg("target ${target}: incorporating lib ${lib} [${link_type}]")
+    _c4_get_tgt_prop(srcroot ${lib} C4_SOURCE_ROOT)
+    #
+    c4_append_target_prop(${lib} INCORPORATING_TARGETS ${target})
+    c4_append_target_prop(${target} INCORPORATED_TARGETS ${lib})
+    #
+    _c4_get_tgt_prop(lib_src ${lib} SOURCES)
+    if(lib_src)
+        create_source_group("${lib}" "${srcroot}" "${lib_src}")
+        c4_add_target_sources(${target} INCORPORATED_FROM ${lib} PRIVATE ${lib_src})
+    endif()
+    #
+    _c4_get_tgt_prop(lib_isrc ${lib} INTERFACE_SOURCES)
+    if(lib_isrc)
+        create_source_group("${lib}" "${srcroot}" "${lib_isrc}")
+        c4_add_target_sources(${target} INCORPORATED_FROM ${lib} INTERFACE ${lib_isrc})
+    endif()
+    #
+    _c4_get_tgt_prop(lib_psrc ${lib} PRIVATE_SOURCES)
+    if(lib_psrc)
+        create_source_group("${lib}" "${srcroot}" "${lib_psrc}")
+        c4_add_target_sources(${target} INCORPORATED_FROM ${lib} INTERFACE ${lib_psrc})
+    endif()
+    #
+    #
+    _c4_get_tgt_prop(lib_incs ${lib} INCLUDE_DIRECTORIES)
+    if(lib_incs)
+        target_include_directories(${target} PUBLIC ${lib_incs})
+    endif()
+    #
+    _c4_get_tgt_prop(lib_iincs ${lib} INTERFACE_INCLUDE_DIRECTORIES)
+    if(lib_iincs)
+        target_include_directories(${target} INTERFACE ${lib_iincs})
+    endif()
+    #
+    #
+    _c4_get_tgt_prop(lib_lib ${lib} LINK_LIBRARIES)
+    if(lib_lib)
+        target_link_libraries(${target} PUBLIC ${lib_lib})
+    endif()
+    _c4_get_tgt_prop(lib_ilib ${lib} INTERFACE_LIBRARY)
+    if(lib_ilib)
+        target_link_libraries(${target} INTERFACE ${lib_ilib})
+    endif()
+    #
+    #
+    c4_get_target_prop(${lib} SHARED_EXPORTS lib_exports)
+    if(lib_exports)
+        target_compile_definitions(${target} PRIVATE $<BUILD_INTERFACE:${lib_exports}>)
+    endif()
+endfunction()
+
+
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+#
+#
+function(c4_add_target_sources target)
+    # https://steveire.wordpress.com/2016/08/09/opt-in-header-only-libraries-with-cmake/
+    _c4_handle_args(_ARGS ${ARGN}
+      _ARGS1  # one-value macro arguments
+        INCORPORATED_FROM
+        TRANSFORM # Transform types:
+                  #   * NONE - do not transform the sources
+                  #   * UNITY
+                  #   * UNITY_HDR
+                  #   * SINGLE_HDR
+                  #   * SINGLE_UNIT
+      _ARGSN  # multi-value macro arguments
+        PUBLIC
+        INTERFACE
+        PRIVATE
+    )
+    if(("${_TRANSFORM}" STREQUAL "GLOBAL") OR ("${_TRANSFORM}" STREQUAL ""))
+        set(_TRANSFORM ${C4_SOURCE_TRANSFORM})
+    endif()
+    if("${_TRANSFORM}" STREQUAL "")
+        set(_TRANSFORM NONE)
+    endif()
+    #
+    # is this target an interface?
+    set(_is_iface FALSE)
+    _c4_get_tgt_prop(target_type ${target} TYPE)
+    if("${target_type}" STREQUAL "INTERFACE_LIBRARY")
+        set(_is_iface TRUE)
+    elseif("${prop_name}" STREQUAL "LINK_LIBRARIES")
+        set(_is_iface FALSE)
+    endif()
+    #
+    set(out)
+    set(umbrella ${_c4_lprefix}transform-src)
+    #
+    if("${_TRANSFORM}" STREQUAL "NONE")
+        c4_dbg("target ${target}: source transform: NONE!")
+        #
+        # do not transform the sources
+        #
+        if(_PUBLIC)
+            c4_dbg("target=${target} PUBLIC sources: ${_PUBLIC}")
+            c4_append_target_prop(${target} PUBLIC_SRC "${_PUBLIC}")
+            if(_INCORPORATED_FROM)
+                c4_append_target_prop(${target} PUBLIC_SRC_${_INCORPORATED_FROM} "${_PUBLIC}")
+            else()
+                c4_append_target_prop(${target} PUBLIC_SRC_${target} "${_PUBLIC}")
+            endif()
+            target_sources(${target} PUBLIC "${_PUBLIC}")
+        endif()
+        if(_INTERFACE)
+            c4_dbg("target=${target} INTERFACE sources: ${_INTERFACE}")
+            c4_append_target_prop(${target} INTERFACE_SRC "${_INTERFACE}")
+            if(_INCORPORATED_FROM)
+                c4_append_target_prop(${target} INTERFACE_SRC_${_INCORPORATED_FROM} "${_INTERFACE}")
+            else()
+                c4_append_target_prop(${target} INTERFACE_SRC_${target} "${_INTERFACE}")
+            endif()
+            target_sources(${target} INTERFACE "${_INTERFACE}")
+        endif()
+        if(_PRIVATE)
+            c4_dbg("target=${target} PRIVATE sources: ${_PRIVATE}")
+            c4_append_target_prop(${target} PRIVATE_SRC "${_PRIVATE}")
+            if(_INCORPORATED_FROM)
+                c4_append_target_prop(${target} PRIVATE_SRC_${_INCORPORATED_FROM} "${_PRIVATE}")
+            else()
+                c4_append_target_prop(${target} PRIVATE_SRC_${target} "${_PRIVATE}")
+            endif()
+            target_sources(${target} PRIVATE "${_PRIVATE}")
+        endif()
+        #
+    elseif("${_TRANSFORM}" STREQUAL "UNITY")
+        c4_dbg("target ${target}: source transform: UNITY!")
+        c4_err("source transformation not implemented")
+        #
+        # concatenate all compilation unit files (excluding interface)
+        # into a single compilation unit
+        #
+        _c4cat_filter_srcs("${_PUBLIC}"    cpublic)
+        _c4cat_filter_hdrs("${_PUBLIC}"    hpublic)
+        _c4cat_filter_srcs("${_INTERFACE}" cinterface)
+        _c4cat_filter_hdrs("${_INTERFACE}" hinterface)
+        _c4cat_filter_srcs("${_PRIVATE}"   cprivate)
+        _c4cat_filter_hdrs("${_PRIVATE}"   hprivate)
+        if(cpublic OR cinterface OR cprivate)
+            _c4cat_get_outname(${target} "src" ${C4_GEN_SRC_EXT} out)
+            c4_dbg("${target}: output unit: ${out}")
+            c4_cat_sources("${cpublic};${cinterface};${cprivate}" "${out}" ${umbrella})
+            add_dependencies(${target} ${out})
+        endif()
+        if(_PUBLIC)
+            c4_append_target_prop(${target} PUBLIC_SRC
+                $<BUILD_INTERFACE:${hpublic};${out}>
+                $<INSTALL_INTERFACE:${hpublic};${out}>)
+            target_sources(${target} PUBLIC
+                $<BUILD_INTERFACE:${hpublic};${out}>
+                $<INSTALL_INTERFACE:${hpublic};${out}>)
+        endif()
+        if(_INTERFACE)
+            c4_append_target_prop(${target} INTERFACE_SRC
+                $<BUILD_INTERFACE:${hinterface}>
+                $<INSTALL_INTERFACE:${hinterface}>)
+            target_sources(${target} INTERFACE
+                $<BUILD_INTERFACE:${hinterface}>
+                $<INSTALL_INTERFACE:${hinterface}>)
+        endif()
+        if(_PRIVATE)
+            c4_append_target_prop(${target} PRIVATE_SRC
+                $<BUILD_INTERFACE:${hprivate}>
+                $<INSTALL_INTERFACE:${hprivate}>)
+            target_sources(${target} PRIVATE
+                $<BUILD_INTERFACE:${hprivate}>
+                $<INSTALL_INTERFACE:${hprivate}>)
+        endif()
+    elseif("${_TRANSFORM}" STREQUAL "UNITY_HDR")
+        c4_dbg("target ${target}: source transform: UNITY_HDR!")
+        c4_err("target ${target}: source transformation not implemented")
+        #
+        # like unity, but concatenate compilation units into
+        # a header file, leaving other header files untouched
+        #
+        _c4cat_filter_srcs("${_PUBLIC}"    cpublic)
+        _c4cat_filter_hdrs("${_PUBLIC}"    hpublic)
+        _c4cat_filter_srcs("${_INTERFACE}" cinterface)
+        _c4cat_filter_hdrs("${_INTERFACE}" hinterface)
+        _c4cat_filter_srcs("${_PRIVATE}"   cprivate)
+        _c4cat_filter_hdrs("${_PRIVATE}"   hprivate)
+        if(c)
+            _c4cat_get_outname(${target} "src" ${C4_GEN_HDR_EXT} out)
+            c4_dbg("${target}: output hdr: ${out}")
+            _c4cat_filter_srcs_hdrs("${_PUBLIC}" c_h)
+            c4_cat_sources("${c}" "${out}" ${umbrella})
+            add_dependencies(${target} ${out})
+            add_dependencies(${target} ${_c4_lprefix}cat)
+        endif()
+        set(${src} ${out} PARENT_SCOPE)
+        set(${hdr} ${h} PARENT_SCOPE)
+        #
+    elseif("${_TRANSFORM}" STREQUAL "SINGLE_HDR")
+        c4_dbg("target ${target}: source transform: SINGLE_HDR!")
+        c4_err("target ${target}: source transformation not implemented")
+        #
+        # concatenate everything into a single header file
+        #
+        _c4cat_get_outname(${target} "all" ${C4_GEN_HDR_EXT} out)
+        _c4cat_filter_srcs_hdrs("${_c4al_SOURCES}" ch)
+        c4_cat_sources("${ch}" "${out}" ${umbrella})
+        #
+    elseif("${_TRANSFORM}" STREQUAL "SINGLE_UNIT")
+        c4_dbg("target ${target}: source transform: SINGLE_UNIT!")
+        c4_err("target ${target}: source transformation not implemented")
+        #
+        # concatenate:
+        #  * all compilation units into a single compilation unit
+        #  * all headers into a single header
+        #
+        _c4cat_get_outname(${target} "src" ${C4_GEN_SRC_EXT} out)
+        _c4cat_get_outname(${target} "hdr" ${C4_GEN_SRC_EXT} out)
+        _c4cat_filter_srcs_hdrs("${_c4al_SOURCES}" ch)
+        c4_cat_sources("${ch}" "${out}" ${umbrella})
+    else()
+        c4_err("unknown transform type: ${transform_type}. Must be one of GLOBAL;NONE;UNITY;TO_HEADERS;SINGLE_HEADER")
+    endif()
+endfunction()
+
+
+# -----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
+# WIP, under construction (still incomplete)
+# see: https://github.com/pr0g/cmake-examples
+# see: https://cliutils.gitlab.io/modern-cmake/
+
+
+function(c4_install_target target)
+    _c4_handle_args(_ARGS ${ARGN}
+      _ARGS1  # one-value macro arguments
+        EXPORT # the name of the export target. default: see below.
+    )
+    _c4_handle_arg(EXPORT "${_c4_prefix}-export")
+    #
+    c4_dbg("installing target: ${target} ${ARGN}")
+    #_c4_is_incorporated(${_c4_prefix} inc)
+    #if(inc)
+    #    c4_dbg("this project is INCORPORATEd. skipping install of targets")
+    #    return()
+    #endif()
+    #
+    _c4_setup_install_vars()
+    install(TARGETS ${target}
+        EXPORT ${_EXPORT}
+        RUNTIME DESTINATION ${_RUNTIME_INSTALL_DIR}  #COMPONENT runtime
+        BUNDLE  DESTINATION ${_RUNTIME_INSTALL_DIR}  #COMPONENT runtime
+        LIBRARY DESTINATION ${_LIBRARY_INSTALL_DIR}  #COMPONENT runtime
+        ARCHIVE DESTINATION ${_ARCHIVE_INSTALL_DIR}  #COMPONENT development
+        OBJECTS DESTINATION ${_OBJECTS_INSTALL_DIR}  #COMPONENT development
+        INCLUDES DESTINATION ${_INCLUDE_INSTALL_DIR} #COMPONENT development
+        PUBLIC_HEADER DESTINATION ${_INCLUDE_INSTALL_DIR} #COMPONENT development
+        )
+    c4_install_sources(${target} include)
+    #
+    # on windows, install also required DLLs
+    if(WIN32)
+        get_target_property(target_type ${target} TYPE)
+        if("${target_type}" STREQUAL "EXECUTABLE")
+            c4_get_transitive_property(${target} _C4_DLLS transitive_dlls)
+            if(transitive_dlls)
+                c4_dbg("${target}: installing dlls: ${transitive_dlls} to ${_RUNTIME_INSTALL_DIR}")
+                list(REMOVE_DUPLICATES transitive_dlls)
+                install(FILES ${transitive_dlls}
+                    DESTINATION ${_RUNTIME_INSTALL_DIR}  # shouldn't it be _LIBRARY_INSTALL_DIR?
+                    #COMPONENT runtime
+                    )
+            endif()
+        endif()
+    endif()
+    #
+    set(l ${${_c4_prefix}_TARGETS})
+    list(APPEND l ${target})
+    set(${_c4_prefix}_TARGETS ${l} PARENT_SCOPE)
+    #
+#    # pkgconfig (WIP)
+#    set(pc ${CMAKE_CURRENT_BINARY_DIR}/pkgconfig/${target}.pc)
+#    file(WRITE ${pc} "# pkg-config: ${target}
+#
+#prefix=\"${CMAKE_INSTALL_PREFIX}\"
+#exec_prefix=\"\${_c4_prefix}\"
+#libdir=\"\${_c4_prefix}/${CMAKE_INSTALL_LIBDIR}\"
+#includedir=\"\${_c4_prefix}/include\"
+#
+#Name: ${target}
+#Description: A library for xyzzying frobnixes
+#URL: https://github.com/me/mylibrary
+#Version: 0.0.0
+#Requires: @PKGCONF_REQ_PUB@
+#Requires.private: @PKGCONF_REQ_PRIV@
+#Cflags: -I\"${includedir}\"
+#Libs: -L\"${libdir}\" -lmylibrary
+#Libs.private: -L\"${libdir}\" -lmylibrary @PKGCONF_LIBS_PRIV@
+#")
+#    _c4_setup_install_vars()
+#    install(FILES ${pc} DESTINATION "${_ARCHIVE_INSTALL_DIR}/pkgconfig/")
+endfunction()
+
+
+function(c4_install_sources target destination)
+    c4_dbg("target ${target}: installing sources to ${destination}")
+    # executables have no sources requiring install
+    _c4_get_tgt_prop(target_type ${target} TYPE)
+    if(target_type STREQUAL "EXECUTABLE")
+        c4_dbg("target ${target}: is executable, skipping source install")
+        return()
+    endif()
+    # install source from the target and incorporated targets
+    c4_get_target_prop(${target} INCORPORATED_TARGETS inctargets)
+    if(inctargets)
+        set(targets "${inctargets};${target}")
+    else()
+        set(targets "${target}")
+    endif()
+    foreach(t ${targets})
+        _c4_get_tgt_prop(srcroot ${t} C4_SOURCE_ROOT)
+        # get the sources from the target
+        #
+        c4_get_target_prop(${t} PUBLIC_SRC_${t} src)
+        if(src)
+            _c4cat_filter_hdrs("${src}" srcf)
+            _c4cat_filter_additional_exts("${src}" add)
+            c4_install_files("${srcf}" "${destination}" "${srcroot}")
+            c4_install_files("${add}" "${destination}" "${srcroot}")
+        endif()
+        #
+        c4_get_target_prop(${t} PRIVATE_SRC_${t} psrc)
+        if(psrc)
+            _c4cat_filter_hdrs("${psrc}" psrcf)
+            _c4cat_filter_additional_exts("${psrc}" add)
+            c4_install_files("${psrcf}" "${destination}" "${srcroot}")
+            c4_install_files("${add}" "${destination}" "${srcroot}")
+        endif()
+        #
+        c4_get_target_prop(${t} INTERFACE_SRC_${t} isrc)
+        if(isrc)
+            _c4cat_filter_srcs_hdrs("${isrc}" isrcf)
+            _c4cat_filter_additional_exts("${isrc}" add)
+            c4_install_files("${isrcf}" "${destination}" "${srcroot}")
+            c4_install_files("${add}" "${destination}" "${srcroot}")
+        endif()
+        #
+        c4_get_target_prop(${t} ADDFILES addfiles)
+        if(addfiles)
+            foreach(af ${addfiles})
+                string(REGEX REPLACE "(.*)!!(.*)!!(.*)" "\\1;\\2;\\3" li "${af}")
+                list(GET li 0 files)
+                list(GET li 1 dst)
+                list(GET li 2 relative_to)
+                string(REPLACE "%%%" ";" files "${files}")
+                c4_install_files("${files}" "${dst}" "${relative_to}")
+            endforeach()
+        endif()
+        #
+        c4_get_target_prop(${t} ADDDIRS adddirs)
+        if(adddirs)
+            foreach(af ${adddirs})
+                string(REGEX REPLACE "(.*)!!(.*)!!(.*)" "\\1;\\2;\\3" li "${af}")
+                list(GET li 0 dirs)
+                list(GET li 1 dst)
+                list(GET li 2 relative_to)
+                string(REPLACE "%%%" ";" dirs "${files}")
+                c4_install_dirs("${dirs}" "${dst}" "${relative_to}")
+            endforeach()
+        endif()
+    endforeach()
+endfunction()
+
+
+function(c4_install_target_add_files target files destination relative_to)
+    c4_dbg("installing additional files for target ${target}, destination=${destination}: ${files}")
+    string(REPLACE ";" "%%%" rfiles "${files}")
+    c4_append_target_prop(${target} ADDFILES "${rfiles}!!${destination}!!${relative_to}")
+    #
+    _c4_is_incorporated(${_c4_prefix} inc)
+    if(inc)
+        c4_dbg("this project is INCORPORATEd. skipping install of targets")
+        return()
+    endif()
+    c4_install_files("${files}" "${destination}" "${relative_to}")
+endfunction()
+
+
+function(c4_install_target_add_dirs target dirs destination relative_to)
+    c4_dbg("installing additional dirs for target ${target}, destination=${destination}: ${dirs}")
+    string(REPLACE ";" "%%%" rdirs "${dirs}")
+    c4_append_target_prop(${target} ADDDIRS "${rdirs}!!${destination}!!${relative_to}")
+    #
+    _c4_is_incorporated(${_c4_prefix} inc)
+    if(inc)
+        c4_dbg("this project is INCORPORATEd. skipping install of targets")
+        return()
+    endif()
+    c4_install_dirs("${dirs}" "${destination}" "${relative_to}")
+endfunction()
+
+
+function(c4_install_files files destination relative_to)
+    c4_dbg("adding files to install list, destination ${destination}: ${files}")
+    foreach(f ${files})
+        file(RELATIVE_PATH rf "${relative_to}" ${f})
+        get_filename_component(rd "${rf}" DIRECTORY)
+        install(FILES ${f} DESTINATION "${destination}/${rd}" ${ARGN})
+    endforeach()
+endfunction()
+
+
+function(c4_install_directories directories destination relative_to)
+    c4_dbg("adding directories to install list, destination ${destination}: ${directories}")
+    foreach(d ${directories})
+        file(RELATIVE_PATH rf "${relative_to}" ${d})
+        get_filename_component(rd "${rf}" DIRECTORY)
+        install(DIRECTORY ${d} DESTINATION "${destination}/${rd}" ${ARGN})
+    endforeach()
+endfunction()
+
+
+function(c4_install_exports)
+    _c4_handle_args(_ARGS ${ARGN}
+      _ARGS1  # one-value macro arguments
+        PREFIX     # override the c4 project-wide prefix. This will be used in the cmake
+        TARGET     # the name of the exports target
+        NAMESPACE  # the namespace for the targets
+      _ARGSN  # multi-value macro arguments
+        DEPENDENCIES
+    )
+    #
+    _c4_handle_arg(PREFIX    "${_c4_prefix}")
+    _c4_handle_arg(TARGET    "${_c4_prefix}-export")
+    _c4_handle_arg(NAMESPACE "${_c4_prefix}::")
+    #
+    c4_dbg("installing exports: ${ARGN}")
+    #_c4_is_incorporated(${_c4_prefix} inc)
+    #if(inc)
+    #    c4_dbg("this project is INCORPORATEd. skipping install of exports")
+    #    return()
+    #endif()
+    #
+    _c4_setup_install_vars()
+    #
+    list(GET ${_c4_prefix}_TARGETS 0 target)
+    set(exported_target "${_NAMESPACE}${target}")
+    set(targets_file "${_PREFIX}Targets.cmake")
+    #
+    set(deps)
+    if(_DEPENDENCIES)
+        set(deps "#-----------------------------
+include(CMakeFindDependencyMacro)
+")
+        foreach(d ${_DEPENDENCIES})
+            _c4_is_incorporated(${d} inc)
+            if(inc)
+                c4_dbg("install: dependency ${d} is INCORPORATEd, skipping check")
+                continue()
+            endif()
+            c4_dbg("install: adding dependency check for ${d}")
+            set(deps "${deps}find_dependency(${d} REQUIRED)
+")
+        endforeach()
+        set(deps "${deps}#-----------------------------")
+    endif()
+    #
+    # cfg_dst is the path relative to install root where the export
+    # should be installed; cfg_dst_rel is the path from there to
+    # the install root
+    macro(__c4_install_exports cfg_dst cfg_dst_rel)
+        # make sure that different exports are staged in different directories
+        set(case ${CMAKE_CURRENT_BINARY_DIR}/export_cases/${cfg_dst})
+        file(MAKE_DIRECTORY ${case})
+        #
+        install(EXPORT "${_TARGET}"
+            FILE "${targets_file}"
+            NAMESPACE "${_NAMESPACE}"
+            DESTINATION "${cfg_dst}")
+        export(EXPORT ${_TARGET}
+            FILE "${targets_file}"
+            NAMESPACE "${_NAMESPACE}")
+        #
+        # Config files
+        # the module below has nice docs in it; do read them
+        # to understand the macro calls below
+        include(CMakePackageConfigHelpers)
+        set(cfg ${case}/${_PREFIX}Config.cmake)
+        set(cfg_ver ${case}/${_PREFIX}ConfigVersion.cmake)
+        #
+        file(WRITE ${cfg}.in "${deps}
+set(${_c4_uprefix}VERSION ${${_c4_uprefix}VERSION})
+
+@PACKAGE_INIT@
+
+if(NOT TARGET ${exported_target})
+    include(\${PACKAGE_PREFIX_DIR}/${targets_file})
+endif()
+
+# HACK: PACKAGE_PREFIX_DIR is obtained from the PACKAGE_INIT macro above;
+# When used below in the calls to set_and_check(),
+# it points at the location of this file. So point it instead
+# to the CMAKE_INSTALL_PREFIX, in relative terms
+get_filename_component(PACKAGE_PREFIX_DIR
+    \"\${PACKAGE_PREFIX_DIR}/${cfg_dst_rel}\" ABSOLUTE)
+
+set_and_check(${_c4_uprefix}INCLUDE_DIR \"@PACKAGE__INCLUDE_INSTALL_DIR@\")
+set_and_check(${_c4_uprefix}LIB_DIR \"@PACKAGE__LIBRARY_INSTALL_DIR@\")
+#set_and_check(${_c4_uprefix}SYSCONFIG_DIR \"@PACKAGE__SYSCONFIG_INSTALL_DIR@\")
+
+check_required_components(${_c4_lcprefix})
+")
+        configure_package_config_file(${cfg}.in ${cfg}
+            INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}"  # defaults to CMAKE_INSTALL_PREFIX
+            INSTALL_DESTINATION "${CMAKE_INSTALL_PREFIX}"
+            PATH_VARS
+                _INCLUDE_INSTALL_DIR
+                _LIBRARY_INSTALL_DIR
+                _SYSCONFIG_INSTALL_DIR
+            #NO_SET_AND_CHECK_MACRO
+            #NO_CHECK_REQUIRED_COMPONENTS_MACRO
+        )
+        write_basic_package_version_file(
+            ${cfg_ver}
+            VERSION ${${_c4_uprefix}VERSION}
+            COMPATIBILITY AnyNewerVersion
+        )
+        install(FILES ${cfg} ${cfg_ver} DESTINATION ${cfg_dst})
+    endmacro(__c4_install_exports)
+    #
+    # To install the exports:
+    #
+    # Windows:
+    # <prefix>/
+    # <prefix>/(cmake|CMake)/
+    # <prefix>/<name>*/
+    # <prefix>/<name>*/(cmake|CMake)/
+    #
+    # Unix:
+    # <prefix>/(lib/<arch>|lib|share)/cmake/<name>*/
+    # <prefix>/(lib/<arch>|lib|share)/<name>*/
+    # <prefix>/(lib/<arch>|lib|share)/<name>*/(cmake|CMake)/
+    #
+    # Apple:
+    # <prefix>/<name>.framework/Resources/
+    # <prefix>/<name>.framework/Resources/CMake/
+    # <prefix>/<name>.framework/Versions/*/Resources/
+    # <prefix>/<name>.framework/Versions/*/Resources/CMake/
+    # <prefix>/<name>.app/Contents/Resources/
+    # <prefix>/<name>.app/Contents/Resources/CMake/
+    #
+    # (This was taken from the find_package() documentation)
+    if(WIN32)
+        __c4_install_exports(cmake/ "..")
+    elseif(APPLE)
+        __c4_install_exports(${_ARCHIVE_INSTALL_DIR}/cmake/${_c4_prefix} "../../..")
+        #__c4_install_exports(${_ARCHIVE_INSTALL_DIR}/${_c4_prefix}.framework/Resources/ "../../..")
+    elseif(UNIX OR (CMAKE_SYSTEM_NAME STREQUAL UNIX) OR (CMAKE_SYSTEM_NAME STREQUAL Linux) OR (CMAKE_SYSTEM_NAME STREQUAL Generic))
+        __c4_install_exports(${_ARCHIVE_INSTALL_DIR}/cmake/${_c4_prefix} "../../..")
+    else()
+        c4_err("unknown platform. CMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME} CMAKE_SYSTEM_PROCESSOR=${CMAKE_SYSTEM_PROCESSOR}")
+    endif()
+endfunction()
+
+
+macro(_c4_setup_install_vars)
+    set(_RUNTIME_INSTALL_DIR   bin/)
+    set(_ARCHIVE_INSTALL_DIR   lib/)
+    set(_LIBRARY_INSTALL_DIR   lib/) # TODO on Windows, ARCHIVE and LIBRARY dirs must be different to prevent name clashes
+    set(_INCLUDE_INSTALL_DIR   include/)
+    set(_OBJECTS_INSTALL_DIR   obj/)
+    set(_SYSCONFIG_INSTALL_DIR etc/${_c4_lcprefix}/)
+endmacro()
+
+
+function(c4_get_target_installed_headers target out)
+    c4_get_target_prop(${target} INCORPORATED_TARGETS inctargets)
+    if(inctargets)
+        set(targets "${inctargets};${target}")
+    else()
+        set(targets "${target}")
+    endif()
+    set(hdrs)
+    foreach(t ${targets})
+        _c4_get_tgt_prop(srcroot ${t} C4_SOURCE_ROOT)
+        #
+        c4_get_target_prop(${t} PUBLIC_SRC_${t} src)
+        if(src)
+            _c4cat_filter_hdrs("${src}" srcf)
+            if(thdrs)
+                set(thdrs "${thdrs};${srcf}")
+            else()
+                set(thdrs "${srcf}")
+            endif()
+        endif()
+        #
+        c4_get_target_prop(${t} PRIVATE_SRC_${t} psrc)
+        if(src)
+            _c4cat_filter_hdrs("${psrc}" psrcf)
+            if(thdrs)
+                set(thdrs "${thdrs};${psrcf}")
+            else()
+                set(thdrs "${psrcf}")
+            endif()
+        endif()
+        #
+        c4_get_target_prop(${t} INTERFACE_SRC_${t} isrc)
+        if(src)
+            _c4cat_filter_hdrs("${isrc}" isrcf)
+            if(thdrs)
+                set(thdrs "${thdrs};${isrcf}")
+            else()
+                set(thdrs "${isrcf}")
+            endif()
+        endif()
+        #
+        foreach(h ${thdrs})
+            file(RELATIVE_PATH rf "${srcroot}" "${h}")
+            list(APPEND hdrs "${rf}")
+        endforeach()
+    endforeach()
+    set(${out} ${hdrs} PARENT_SCOPE)
+endfunction()
+
+
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+function(c4_setup_testing)
+    _c4_handle_args(_ARGS ${ARGN}
+        _ARGS0
+            GTEST    # download and import googletest
+            DOCTEST  # download and import doctest
+        _ARGS1
+        _ARGSN
+    )
+    #include(GoogleTest) # this module requires at least cmake 3.9
+    c4_dbg("enabling tests")
+    # umbrella target for building test binaries
+    add_custom_target(${_c4_lprefix}test-build)
+    _c4_set_target_folder(${_c4_lprefix}test-build test)
+    # umbrella targets for running tests
+    if(NOT TARGET test-build)
+        add_custom_target(test-build)
+        add_custom_target(test-verbose)
+        _c4_set_target_folder(test-build "/test")
+        _c4_set_target_folder(test-verbose "/test")
+    endif()
+    if(NOT TARGET test)
+        # add a test target. To prevent a warning, we need to set up a policy,
+        # and also suppress the resulting warning from suppressing the warning.
+        set(_depr_old_val ${CMAKE_WARN_DEPRECATED})
+        set(CMAKE_WARN_DEPRECATED OFF CACHE BOOL "" FORCE)  # https://stackoverflow.com/questions/67432538/cannot-set-cmake-warn-deprecated-inside-the-cmakelists-txt
+        cmake_policy(PUSH)
+        cmake_policy(SET CMP0037 OLD)  # target name "test" is reserved for CTesting
+        add_custom_target(test)
+        _c4_set_target_folder(test "/test")
+        cmake_policy(POP)
+        set(CMAKE_WARN_DEPRECATED OFF CACHE BOOL "${_depr_old_val}" FORCE)
+        unset(_depr_old_val)
+    endif()
+    function(_def_runner runner)
+        set(echo "
+CWD=${CMAKE_CURRENT_BINARY_DIR}
+----------------------------------
+${ARGN}
+----------------------------------
+")
+        add_custom_target(${runner}
+            #${CMAKE_COMMAND} -E echo "${echo}"
+            COMMAND ${ARGN}
+            WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+            DEPENDS ${_c4_lprefix}test-build
+            )
+        _c4_set_target_folder(${runner} test)
+    endfunction()
+    _def_runner(${_c4_lprefix}test-run ${CMAKE_CTEST_COMMAND} --output-on-failure ${${_c4_uprefix}CTEST_OPTIONS} -C $<CONFIG>)
+    _def_runner(${_c4_lprefix}test-run-verbose ${CMAKE_CTEST_COMMAND} -VV ${${_c4_uprefix}CTEST_OPTIONS} -C $<CONFIG>)
+    add_dependencies(test ${_c4_lprefix}test-run)
+    add_dependencies(test-verbose ${_c4_lprefix}test-run-verbose)
+    add_dependencies(test-build ${_c4_lprefix}test-build)
+    #
+    # import required libraries
+    if(_GTEST)
+        c4_log("testing requires googletest")
+        if(NOT TARGET gtest)
+            c4_import_remote_proj(gtest ${CMAKE_CURRENT_BINARY_DIR}/ext/gtest
+                REMOTE
+                  GIT_REPOSITORY https://github.com/google/googletest.git
+                  GIT_TAG release-1.11.0
+                OVERRIDE
+                  BUILD_GTEST ON
+                  BUILD_GMOCK OFF
+                  gtest_force_shared_crt ON
+                  gtest_build_samples OFF
+                  gtest_build_tests OFF
+                SET_FOLDER_TARGETS ext gtest gtest_main
+                EXCLUDE_FROM_ALL
+                )
+            # old gcc-4.8 support
+            if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") AND
+              (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 4.8) AND
+              (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0))
+                _c4_get_subproject_property(gtest SRC_DIR _gtest_patch_src_dir)
+                apply_patch("${_c4_project_dir}/compat/gtest_gcc-4.8.patch"
+                  "${_gtest_patch_src_dir}"
+                  "${_gtest_patch_src_dir}/.gtest_gcc-4.8.patch")
+                unset(_gtest_patch_src_dir)
+                target_compile_options(gtest PUBLIC -include ${_c4_project_dir}/compat/c4/gcc-4.8.hpp)
+            endif()
+        endif()
+    endif()
+    if(_DOCTEST)
+        c4_log("testing requires doctest")
+        if(NOT TARGET doctest)
+            c4_import_remote_proj(doctest ${CMAKE_CURRENT_BINARY_DIR}/ext/doctest
+                REMOTE
+                  GIT_REPOSITORY https://github.com/onqtam/doctest.git
+                  GIT_TAG 2.4.6
+                OVERRIDE
+                  DOCTEST_WITH_TESTS OFF
+                  DOCTEST_WITH_MAIN_IN_STATIC_LIB ON
+                SET_FOLDER_TARGETS ext doctest_with_main
+                EXCLUDE_FROM_ALL
+                )
+        endif()
+    endif()
+endfunction(c4_setup_testing)
+
+
+function(c4_add_test target)
+    _c4_handle_args(_ARGS ${ARGN}
+      _ARGS0  # zero-value macro arguments
+      _ARGS1  # one-value macro arguments
+        WORKING_DIRECTORY
+      _ARGSN  # multi-value macro arguments
+        ARGS
+    )
+    #
+    if(_WORKING_DIRECTORY)
+        set(_WORKING_DIRECTORY WORKING_DIRECTORY ${_WORKING_DIRECTORY})
+    endif()
+    set(cmd_pfx)
+    if(CMAKE_CROSSCOMPILING)
+        set(cmd_pfx ${CMAKE_CROSSCOMPILING_EMULATOR})
+    endif()
+    if(NOT ${uprefix}SANITIZE_ONLY)
+        if(${CMAKE_VERSION} VERSION_LESS "3.16.0")
+            add_test(NAME ${target}
+                COMMAND ${cmd_pfx} "$<TARGET_FILE:${target}>" ${_ARGS}
+                ${_WORKING_DIRECTORY})
+        else()
+            add_test(NAME ${target}
+                COMMAND ${cmd_pfx} "$<TARGET_FILE:${target}>" ${_ARGS}
+                ${_WORKING_DIRECTORY}
+                COMMAND_EXPAND_LISTS)
+        endif()
+    endif()
+    #
+    if("${CMAKE_BUILD_TYPE}" STREQUAL "Coverage")
+        add_dependencies(${_c4_lprefix}test-build ${target})
+        return()
+    endif()
+    #
+    set(sanitized_targets)
+    foreach(s asan msan tsan ubsan)
+        set(t ${target}-${s})
+        if(TARGET ${t})
+            list(APPEND sanitized_targets ${s})
+        endif()
+    endforeach()
+    if(sanitized_targets)
+        add_custom_target(${target}-all)
+        add_dependencies(${target}-all ${target})
+        add_dependencies(${_c4_lprefix}test-build ${target}-all)
+        _c4_set_target_folder(${target}-all test/${target})
+    else()
+        add_dependencies(${_c4_lprefix}test-build ${target})
+    endif()
+    if(sanitized_targets)
+        foreach(s asan msan tsan ubsan)
+            set(t ${target}-${s})
+            if(TARGET ${t})
+                add_dependencies(${target}-all ${t})
+                c4_sanitize_get_target_command("${cmd_pfx};$<TARGET_FILE:${t}>" ${s} cmd)
+                #c4_log("adding test: ${t}")
+                add_test(NAME ${t}
+                    COMMAND ${cmd} ${_ARGS}
+                    ${_WORKING_DIRECTORY}
+                    COMMAND_EXPAND_LISTS)
+            endif()
+        endforeach()
+    endif()
+    if(NOT CMAKE_CROSSCOMPILING)
+        if(NOT ${_c4_uprefix}SANITIZE_ONLY)
+            c4_add_valgrind(${target} ${ARGN})
+        endif()
+    endif()
+    if(${_c4_uprefix}LINT)
+        c4_static_analysis_add_tests(${target})  # this will not actually run the executable
+    endif()
+endfunction(c4_add_test)
+
+
+# every excess argument is passed on to set_target_properties()
+function(c4_add_test_fail_build name srccontent_or_srcfilename)
+    #
+    set(sdir ${CMAKE_CURRENT_BINARY_DIR}/test_fail_build)
+    set(src ${srccontent_or_srcfilename})
+    if("${src}" STREQUAL "")
+        c4_err("must be given an existing source file name or a non-empty string")
+    endif()
+    #
+    if(EXISTS ${src})
+        set(fn ${src})
+    else()
+        if(NOT EXISTS ${sdir})
+            file(MAKE_DIRECTORY ${sdir})
+        endif()
+        set(fn ${sdir}/${name}.cpp)
+        file(WRITE ${fn} "${src}")
+    endif()
+    #
+    # https://stackoverflow.com/questions/30155619/expected-build-failure-tests-in-cmake
+    add_executable(${name} ${fn})
+    # don't build this target
+    set_target_properties(${name} PROPERTIES
+        EXCLUDE_FROM_ALL TRUE
+        EXCLUDE_FROM_DEFAULT_BUILD TRUE
+        # and pass on further properties given by the caller
+        ${ARGN})
+    add_test(NAME ${name}
+        COMMAND ${CMAKE_COMMAND} --build . --target ${name} --config $<CONFIGURATION>
+        WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+    set_tests_properties(${name} PROPERTIES WILL_FAIL TRUE)
+endfunction()
+
+
+# add a test ensuring that a target linking and using code from a library
+# successfully compiles and runs against the installed library
+function(c4_add_install_link_test library namespace exe_source_code)
+    if(CMAKE_CROSSCOMPILING)
+        c4_log("cross-compiling: skip install link test")
+        return()
+    endif()
+    if("${library}" STREQUAL "${_c4_prefix}")
+        set(testname ${_c4_lprefix}test-install-link)
+    else()
+        set(testname ${_c4_lprefix}test-install-link-${library})
+    endif()
+    _c4_add_library_client_test(${library} "${namespace}" "${testname}" "${exe_source_code}")
+endfunction()
+
+
+# add a test ensuring that a target consuming every header in a library
+# successfully compiles and runs against the installed library
+function(c4_add_install_include_test library namespace)
+    if(CMAKE_CROSSCOMPILING)
+        c4_log("cross-compiling: skip install include test")
+        return()
+    endif()
+    c4_get_target_installed_headers(${library} incfiles)
+    set(incblock)
+    foreach(i ${incfiles})
+        set(incblock "${incblock}
+#include <${i}>")
+    endforeach()
+    set(src "${incblock}
+
+int main()
+{
+    return 0;
+}
+")
+    if("${library}" STREQUAL "${_c4_prefix}")
+        set(testname ${_c4_lprefix}test-install-include)
+    else()
+        set(testname ${_c4_lprefix}test-install-include-${library})
+    endif()
+    _c4_add_library_client_test(${library} "${namespace}" "${testname}" "${src}")
+endfunction()
+
+
+function(_c4_add_library_client_test library namespace pname source_code)
+    if("${CMAKE_BUILD_TYPE}" STREQUAL Coverage)
+        add_test(NAME ${pname}
+            COMMAND ${CMAKE_COMMAND} -E echo "skipping this test in coverage builds"
+            )
+        return()
+    endif()
+    set(pdir "${CMAKE_CURRENT_BINARY_DIR}/${pname}")
+    set(bdir "${pdir}/build")
+    if(NOT EXISTS "${pdir}")
+        file(MAKE_DIRECTORY "${pdir}")
+    endif()
+    if(NOT EXISTS "${bdir}/build")
+        file(MAKE_DIRECTORY "${bdir}/build")
+    endif()
+    set(psrc "${pdir}/${pname}.cpp")
+    set(tsrc "${pdir}/${pname}-run.cmake")
+    set(tout "${pdir}/${pname}-run-out")
+    # generate the source file
+    file(WRITE "${psrc}" "${source_code}")
+    # generate the cmake project consuming this library
+    file(WRITE "${pdir}/CMakeLists.txt" "
+cmake_minimum_required(VERSION 3.12)
+project(${pname} LANGUAGES CXX)
+
+find_package(${library} REQUIRED)
+
+message(STATUS \"
+found ${library}:
+    ${_c4_uprefix}INCLUDE_DIR=\${${_c4_uprefix}INCLUDE_DIR}
+    ${_c4_uprefix}LIB_DIR=\${${_c4_uprefix}LIB_DIR}
+\")
+
+add_executable(${pname} ${pname}.cpp)
+# this must be the only required setup to link with ${library}
+target_link_libraries(${pname} PUBLIC ${namespace}${library})
+
+get_target_property(lib_type ${namespace}${library} TYPE)
+if(WIN32 AND (lib_type STREQUAL SHARED_LIBRARY))
+    # add the directory containing the DLL to the path
+    get_target_property(imported_configs ${namespace}${library} IMPORTED_CONFIGURATIONS)
+    message(STATUS \"${namespace}${library}: it's a shared library. imported configs: \${imported_configs}\")
+    foreach(cfg \${imported_configs})
+        get_target_property(implib ${namespace}${library} IMPORTED_IMPLIB_\${cfg})
+        get_target_property(location ${namespace}${library} IMPORTED_LOCATION_\${cfg})
+        message(STATUS \"${namespace}${library}: implib_\${cfg}=\${implib}\")
+        message(STATUS \"${namespace}${library}: location_\${cfg}=\${location}\")
+        break()
+    endforeach()
+    get_filename_component(dlldir \"\${location}\" DIRECTORY)
+    message(STATUS \"${namespace}${library}: dlldir=\${dlldir}\")
+    add_custom_target(${pname}-run
+        COMMAND \${CMAKE_COMMAND} -E echo \"cd \${dlldir} && \$<TARGET_FILE:${pname}>\"
+        COMMAND \$<TARGET_FILE:${pname}>
+        DEPENDS ${pname}
+        WORKING_DIRECTORY \${dlldir})
+else()
+    add_custom_target(${pname}-run
+        COMMAND \$<TARGET_FILE:${pname}>
+        DEPENDS ${pname})
+endif()
+")
+    # The test consists in running the script generated below.
+    # We force evaluation of the configuration generator expression
+    # by receiving its result via the command line.
+    add_test(NAME ${pname}
+        COMMAND ${CMAKE_COMMAND} -DCFG_IN=$<CONFIG> -P "${tsrc}"
+        )
+    # NOTE: in the cmake configure command, be sure to NOT use quotes
+    # in -DCMAKE_PREFIX_PATH=\"${CMAKE_INSTALL_PREFIX}\". Use
+    # -DCMAKE_PREFIX_PATH=${CMAKE_INSTALL_PREFIX} instead.
+    # So here we add a check to make sure the install path has no spaces
+    string(FIND "${CMAKE_INSTALL_PREFIX}" " " has_spaces)
+    if(NOT (has_spaces EQUAL -1))
+        c4_err("install tests will fail if the install path has spaces: '${CMAKE_INSTALL_PREFIX}' : ... ${has_spaces}")
+    endif()
+    # make sure the test project uses the same architecture
+    # CMAKE_VS_PLATFORM_NAME is available only since cmake 3.9
+    # see https://cmake.org/cmake/help/v3.9/variable/CMAKE_GENERATOR_PLATFORM.html
+    if(WIN32)
+        set(cfg_opt "--config \${cfg}")
+        if(CMAKE_GENERATOR_PLATFORM OR CMAKE_VS_PLATFORM_NAME)
+            set(arch "-DCMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM}" "-DCMAKE_VS_PLATFORM_NAME=${CMAKE_VS_PLATFORM_NAME}")
+        else()
+            if(CMAKE_SIZEOF_VOID_P EQUAL 8)
+                set(arch -A x64)
+            elseif(CMAKE_SIZEOF_VOID_P EQUAL 4)
+                set(arch -A Win32)
+            else()
+                c4_err("not implemented")
+            endif()
+        endif()
+    elseif(ANDROID OR IOS OR WINCE OR WINDOWS_PHONE)
+        c4_err("not implemented")
+    elseif(IOS)
+        c4_err("not implemented")
+    elseif(UNIX)
+        if(CMAKE_GENERATOR_PLATFORM OR CMAKE_VS_PLATFORM_NAME)
+            set(arch "-DCMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM}" "-DCMAKE_VS_PLATFORM_NAME=${CMAKE_VS_PLATFORM_NAME}")
+        else()
+            if(CMAKE_SYSTEM_PROCESSOR STREQUAL aarch64)
+            else()
+                if(CMAKE_SIZEOF_VOID_P EQUAL 8)
+                    set(arch "-DCMAKE_CXX_FLAGS=-m64")
+                elseif(CMAKE_SIZEOF_VOID_P EQUAL 4)
+                    set(arch "-DCMAKE_CXX_FLAGS=-m32")
+                else()
+                    c4_err("not implemented")
+                endif()
+            endif()
+        endif()
+    endif()
+    # generate the cmake script with the test content
+    file(WRITE "${tsrc}" "
+# run a command and check its return status
+function(runcmd id)
+    set(cmdout \"${tout}-\${id}.log\")
+    message(STATUS \"Running command: \${ARGN}\")
+    message(STATUS \"Running command: output goes to \${cmdout}\")
+    execute_process(
+        COMMAND \${ARGN}
+        RESULT_VARIABLE retval
+        OUTPUT_FILE \"\${cmdout}\"
+        ERROR_FILE \"\${cmdout}\"
+        # COMMAND_ECHO STDOUT  # only available from cmake-3.15
+    )
+    message(STATUS \"Running command: exit status was \${retval}\")
+    file(READ \"\${cmdout}\" output)
+    if(\"\${cmdout}\" STREQUAL \"\")
+        message(STATUS \"Running command: no output\")
+    else()
+        message(STATUS \"Running command: output:
+--------------------
+\${output}--------------------\")
+    endif()
+    if(NOT (\${retval} EQUAL 0))
+        message(FATAL_ERROR \"Command failed with exit status \${retval}: \${ARGN}\")
+    endif()
+endfunction()
+
+set(cmk \"${CMAKE_COMMAND}\")
+set(pfx \"${CMAKE_INSTALL_PREFIX}\")
+set(idir \"${CMAKE_BINARY_DIR}\")
+set(pdir \"${pdir}\")
+set(bdir \"${bdir}\")
+
+# force evaluation of the configuration generator expression
+# by receiving its result via the command line
+set(cfg \${CFG_IN})
+
+# remove any existing library install
+if(EXISTS \"\${pfx}\")
+    runcmd(0_rmdir \"\${cmk}\" -E remove_directory \"\${pfx}\")
+else()
+    message(STATUS \"does not exist: \${pfx}\")
+endif()
+
+# install the library
+#runcmd(1_install_lib \"\${cmk}\" --install \"\${idir}\" ${cfg_opt})  # requires cmake>3.13 (at least)
+runcmd(1_install_lib \"\${cmk}\" --build \"\${idir}\" ${cfg_opt} --target install)
+
+# configure the client project
+runcmd(2_config \"\${cmk}\" -S \"\${pdir}\" -B \"\${bdir}\" \"-DCMAKE_PREFIX_PATH=\${pfx}\" \"-DCMAKE_GENERATOR=${CMAKE_GENERATOR}\" ${arch} \"-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}\" \"-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}\")
+
+# build the client project
+runcmd(3_build \"\${cmk}\" --build \"\${bdir}\" ${cfg_opt})
+
+# run the client executable
+runcmd(4_install \"\${cmk}\" --build \"\${bdir}\" --target \"${pname}-run\" ${cfg_opt})
+")
+endfunction()
+
+
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+function(c4_setup_valgrind umbrella_option)
+    if(UNIX AND (NOT "${CMAKE_BUILD_TYPE}" STREQUAL "Coverage"))
+        if("${C4_VALGRIND}" STREQUAL "")
+            option(C4_VALGRIND "enable valgrind tests (all subprojects)" ON)
+        endif()
+        if("${C4_VALGRIND_SGCHECK}" STREQUAL "")
+            option(C4_VALGRIND_SGCHECK "enable valgrind tests with the exp-sgcheck tool (all subprojects)" OFF)
+        endif()
+        cmake_dependent_option(${_c4_uprefix}VALGRIND "enable valgrind tests" ${C4_VALGRIND} ${umbrella_option} OFF)
+        cmake_dependent_option(${_c4_uprefix}VALGRIND_SGCHECK "enable valgrind tests with the exp-sgcheck tool" ${C4_VALGRIND_SGCHECK} ${umbrella_option} OFF)
+        set(${_c4_uprefix}VALGRIND_OPTIONS "--gen-suppressions=all --error-exitcode=10101" CACHE STRING "options for valgrind tests")
+    endif()
+endfunction(c4_setup_valgrind)
+
+
+function(c4_add_valgrind target_name)
+    _c4_handle_args(_ARGS ${ARGN}
+      _ARGS0  # zero-value macro arguments
+      _ARGS1  # one-value macro arguments
+        WORKING_DIRECTORY
+      _ARGSN  # multi-value macro arguments
+        ARGS
+    )
+    #
+    if(_WORKING_DIRECTORY)
+        set(_WORKING_DIRECTORY WORKING_DIRECTORY ${_WORKING_DIRECTORY})
+    endif()
+    # @todo: consider doing this for valgrind:
+    # http://stackoverflow.com/questions/40325957/how-do-i-add-valgrind-tests-to-my-cmake-test-target
+    # for now we explicitly run it:
+    if(${_c4_uprefix}VALGRIND)
+        separate_arguments(_vg_opts UNIX_COMMAND "${${_c4_uprefix}VALGRIND_OPTIONS}")
+        add_test(NAME ${target_name}-valgrind
+            COMMAND valgrind ${_vg_opts} $<TARGET_FILE:${target_name}> ${_ARGS}
+            ${_WORKING_DIRECTORY}
+            COMMAND_EXPAND_LISTS)
+    endif()
+    if(${_c4_uprefix}VALGRIND_SGCHECK)
+        # stack and global array overrun detector
+        # http://valgrind.org/docs/manual/sg-manual.html
+        separate_arguments(_sg_opts UNIX_COMMAND "--tool=exp-sgcheck ${${_c4_uprefix}VALGRIND_OPTIONS}")
+        add_test(NAME ${target_name}-sgcheck
+            COMMAND valgrind ${_sg_opts} $<TARGET_FILE:${target_name}> ${_ARGS}
+            ${_WORKING_DIRECTORY}
+            COMMAND_EXPAND_LISTS)
+    endif()
+endfunction(c4_add_valgrind)
+
+
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+
+function(c4_setup_coverage)
+    if(NOT ("${CMAKE_BUILD_TYPE}" STREQUAL "Coverage"))
+        return()
+    endif()
+    #
+    _c4_handle_args(_ARGS ${ARGN}
+      _ARGS0  # zero-value macro arguments
+      _ARGS1  # one-value macro arguments
+      _ARGSN  # multi-value macro arguments
+        INCLUDE       # patterns to include in the coverage, relative to CMAKE_SOURCE_DIR
+        EXCLUDE       # patterns to exclude in the coverage, relative to CMAKE_SOURCE_DIR
+        EXCLUDE_ABS   # absolute paths to exclude in the coverage
+        GENHTML_ARGS  # options to pass to genhtml
+    )
+    # defaults for the macro arguments
+    _c4_handle_arg(INCLUDE src)
+    _c4_handle_arg(EXCLUDE test ext extern src/c4/ext build/ext build/extern)
+    _c4_handle_arg(EXCLUDE_ABS /usr "${CMAKE_BINARY_DIR}")
+    _c4_handle_arg(GENHTML_ARGS --title ${_c4_lcprefix} --demangle-cpp --sort --function-coverage --branch-coverage
+        --prefix "'${CMAKE_SOURCE_DIR}'"
+        --prefix "'${CMAKE_BINARY_DIR}'")
+    #
+    function(_c4cov_filters out incflag excflag)
+        set(f)
+        macro(_append flag item)
+            string(FIND "${item}" "*" star_pos)
+            string(FIND "${item}" " " space_pos)
+            if((star_pos EQUAL -1) AND (space_pos EQUAL -1))
+                list(APPEND f ${flag} ${item})
+            else()
+                list(APPEND f ${flag} "'${item}'")
+            endif()
+        endmacro()
+        foreach(inc ${_INCLUDE})
+            _append(${incflag} ${inc})
+        endforeach()
+        foreach(exc ${_EXCLUDE})
+            _append(${excflag} ${exc})
+        endforeach()
+        foreach(exc ${_EXCLUDE_ABS})
+            _append(${excflag} "${exc}/*")
+        endforeach()
+        set(${out} ${f} PARENT_SCOPE)
+    endfunction()
+    #
+    if("${CMAKE_CXX_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang")
+        if("${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS 3)
+	    c4_err("coverage: clang version must be 3.0.0 or greater")
+        endif()
+    elseif(NOT CMAKE_COMPILER_IS_GNUCXX)
+        c4_err("coverage: compiler is not GNUCXX")
+    endif()
+    #
+    find_program(GCOV gcov)
+    find_program(LCOV lcov)
+    find_program(GENHTML genhtml)
+    find_program(CTEST ctest)
+    set(covflags "-g -O0 --coverage") #set(covflags "-g -O0 -fprofile-arcs -ftest-coverage")
+    if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
+        set(covflags "${covflags} -fprofile-arcs -ftest-coverage -fno-inline -fno-inline-small-functions -fno-default-inline")
+    endif()
+    if(NOT (GCOV AND LCOV AND GENHTML AND CTEST))
+        c4_err("Coverage tools not available:
+    gcov: ${GCOV}
+    lcov: ${LCOV}
+    genhtml: ${GENHTML}
+    ctest: ${CTEST}
+    --coverage flag: ${covflags}")
+    endif()
+    #
+    add_configuration_type(Coverage
+        DEFAULT_FROM DEBUG
+        C_FLAGS ${covflags}
+        CXX_FLAGS ${covflags}
+        )
+    #
+    c4_dbg("adding coverage targets")
+    option(${_c4_uprefix}COVERAGE_CODECOV "enable target to submit coverage to codecov.io" OFF)
+    option(${_c4_uprefix}COVERAGE_COVERALLS "enable target to submit coverage to coveralls.io" OFF)
+    #
+    set(_filters)
+    foreach(exc ${_EXCLUDE})
+        list(APPEND _filters "'${CMAKE_SOURCE_DIR}/${exc}/*'")
+    endforeach()
+    foreach(exc ${_EXCLUDE_ABS})
+        list(APPEND _filters "'${exc}/*'")
+    endforeach()
+    #
+    set(result ${CMAKE_BINARY_DIR}/lcov/index.html)
+    add_custom_target(${_c4_lprefix}coverage
+        BYPRODUCTS ${result}
+        COMMAND ${LCOV} -q --zerocounters --directory .
+        COMMAND ${LCOV} -q --no-external --capture --base-directory "${CMAKE_SOURCE_DIR}" --directory . --output-file before.lcov --initial
+        COMMAND ${CMAKE_COMMAND} --build . --target ${_c4_lprefix}test-run || echo "Failed running the tests. Proceeding with coverage, but results may be affected or even empty."
+        COMMAND ${LCOV} -q --no-external --capture --base-directory "${CMAKE_SOURCE_DIR}" --directory . --output-file after.lcov
+        COMMAND ${LCOV} -q --add-tracefile before.lcov --add-tracefile after.lcov --output-file final.lcov
+        COMMAND ${LCOV} -q --remove final.lcov --output-file final.lcov ${_filters}
+        COMMAND ${GENHTML} final.lcov -o lcov ${_GENHTML_ARGS}
+        COMMAND echo "Coverage report: ${result}"
+        DEPENDS ${_c4_lprefix}test-build
+        WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+        COMMENT "${_c4_prefix} coverage: Running LCOV. Report at ${result}"
+        )
+    #
+    if(${_c4_uprefix}COVERAGE_CODECOV)
+        set(_subm ${_c4_lprefix}coverage-submit-codecov)
+        _c4cov_get_service_token(codecov _token)
+        if(NOT ("${_token}" STREQUAL ""))
+            set(_token -t "${_token}")
+        endif()
+        set(_silent)
+        if(${_c4_uprefix}COVERAGE_CODECOV_SILENT)
+            set(_silent >${CMAKE_BINARY_DIR}/codecov.log 2>&1)
+        endif()
+        #
+        c4_log("coverage: enabling submission of results to https://codecov.io: ${_subm}")
+        set(submitcc "${CMAKE_BINARY_DIR}/submit_codecov.sh")
+        c4_download_file("https://codecov.io/bash" "${submitcc}")
+        _c4cov_filters(_filters -G -g)
+        set(submit_cmd bash ${submitcc} -Z ${_token} -a '\\-lp' -X gcovout -p ${CMAKE_SOURCE_DIR} ${_filters} ${_silent})
+        add_custom_target(${_subm}
+            COMMAND echo "\"cd ${CMAKE_BINARY_DIR} && ${submit_cmd}\""
+            COMMAND ${submit_cmd}
+            DEPENDS ${_c4_lprefix}coverage
+            WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+            COMMENT "${_c4_lcprefix} coverage: submit to codecov"
+            )
+        c4_add_umbrella_target(coverage-submit-codecov coverage-submit)  # uses the current prefix
+    endif()
+    #
+    if(${_c4_uprefix}COVERAGE_COVERALLS)
+        set(_subm ${_c4_lprefix}coverage-submit-coveralls)
+        _c4cov_get_service_token(coveralls _token)
+        if(NOT ("${_token}" STREQUAL ""))
+            set(_token --repo-token "${_token}")
+        endif()
+        set(_silent)
+        if(${_c4_uprefix}COVERAGE_COVERALLS_SILENT)
+            set(_silent >${CMAKE_BINARY_DIR}/coveralls.log 2>&1)
+        endif()
+        #
+        c4_log("coverage: enabling submission of results to https://coveralls.io: ${_subm}")
+        _c4cov_filters(_filters --include --exclude)
+        set(submit_cmd coveralls ${_token} --gcov-options '\\-lp' --build-root ${CMAKE_BINARY_DIR} --root ${CMAKE_SOURCE_DIR} ${_filters} ${_silent})
+        add_custom_target(${_subm}
+            COMMAND echo "\"cd ${CMAKE_BINARY_DIR} && ${submit_cmd}\""
+            COMMAND ${submit_cmd}
+            DEPENDS ${_c4_lprefix}coverage
+            WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+            COMMENT "${_c4_lcprefix} coverage: submit to coveralls"
+            )
+        c4_add_umbrella_target(coverage-submit-coveralls coverage-submit)  # uses the current prefix
+    endif()
+endfunction(c4_setup_coverage)
+
+# 1. try cmake or environment variables
+# 2. try local file
+function(_c4cov_get_service_token service out)
+    # try cmake var
+    string(TOUPPER ${service} uservice)
+    c4_get_from_first_of(token COVERAGE_${uservice}_TOKEN ENV)
+    if(NOT ("${token}" STREQUAL ""))
+        c4_dbg("${service}: found token from variable: ${token}")
+    else()
+        # try local file
+        set(service_token_file ${CMAKE_SOURCE_DIR}/.ci/${service}.token)
+        if(EXISTS ${service_token_file})
+            file(READ ${service_token_file} token)
+            c4_dbg("found token file for ${service} coverage report: ${service_token_file}")
+        else()
+            c4_dbg("could not find token for ${service} coverage report")
+        endif()
+    endif()
+    set(${out} ${token} PARENT_SCOPE)
+endfunction()
+
+
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+
+function(c4_add_umbrella_target target umbrella_target)
+    _c4_handle_args(_ARGS ${ARGN}
+      # zero-value macro arguments
+      _ARGS0
+        ALWAYS  # Add the umbrella target even if this is the only one under it.
+                # The default behavior is to add the umbrella target only if
+                # there is more than one target under it.
+      # one-value macro arguments
+      _ARGS1
+        PREFIX  # The project prefix. Defaults to ${_c4_lprefix}
+      # multi-value macro arguments
+      _ARGSN
+        ARGS    # more args to add_custom_target()
+    )
+    if(NOT _PREFIX)
+        set(_PREFIX "${_c4_lprefix}")
+    endif()
+    set(t ${_PREFIX}${target})
+    set(ut ${_PREFIX}${umbrella_target})
+    # if the umbrella target already exists, just add the dependency
+    if(TARGET ${ut})
+        add_dependencies(${ut} ${t})
+    else()
+        if(_ALWAYS)
+            add_custom_target(${ut} ${_ARGS})
+            add_dependencies(${ut} ${t})
+        else()
+            # check if there is more than one under the same umbrella
+            c4_get_proj_prop(${ut}_subtargets sub)
+            if(sub)
+                add_custom_target(${ut} ${_ARGS})
+                add_dependencies(${ut} ${sub})
+                add_dependencies(${ut} ${t})
+            else()
+                c4_set_proj_prop(${ut}_subtargets ${t})
+            endif()
+        endif()
+    endif()
+endfunction()
+
+
+
+function(c4_download_file url dstpath)
+    c4_dbg("downloading file: ${url} ---> ${dstpath}")
+    get_filename_component(abspath ${dstpath} ABSOLUTE)
+    if(NOT EXISTS ${abspath})
+        c4_dbg("downloading file: does not exist: ${dstpath}")
+        file(DOWNLOAD ${url} ${abspath} LOG dl_log STATUS status ${ARGN})
+        if((NOT (status EQUAL 0)) OR (NOT EXISTS ${abspath}))
+            c4_err("error downloading file: ${url} -> ${abspath}:\n${dl_log}")
+        endif()
+    endif()
+endfunction()
+
+
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+function(c4_setup_benchmarking)
+    c4_log("enabling benchmarks: to build, ${_c4_lprefix}bm-build")
+    c4_log("enabling benchmarks: to run, ${_c4_lprefix}bm-run")
+    # umbrella target for building test binaries
+    add_custom_target(${_c4_lprefix}bm-build)
+    # umbrella target for running benchmarks
+    add_custom_target(${_c4_lprefix}bm-run
+        ${CMAKE_COMMAND} -E echo CWD=${CMAKE_CURRENT_BINARY_DIR}
+        DEPENDS ${_c4_lprefix}bm-build
+        )
+    if(NOT TARGET bm-run)
+        add_custom_target(bm-run)
+        add_custom_target(bm-build)
+    endif()
+    add_dependencies(bm-run ${_c4_lprefix}bm-run)
+    add_dependencies(bm-build ${_c4_lprefix}bm-build)
+    _c4_set_target_folder(${_c4_lprefix}bm-run bm)
+    _c4_set_target_folder(${_c4_lprefix}bm-build bm)
+    _c4_set_target_folder(bm-build "/bm")
+    _c4_set_target_folder(bm-run "/bm")
+    # download google benchmark
+    if(NOT TARGET benchmark)
+        c4_import_remote_proj(googlebenchmark ${CMAKE_CURRENT_BINARY_DIR}/ext/googlebenchmark
+          REMOTE
+            GIT_REPOSITORY https://github.com/google/benchmark.git
+          OVERRIDE
+            BENCHMARK_ENABLE_TESTING OFF
+            BENCHMARK_ENABLE_EXCEPTIONS OFF
+            BENCHMARK_ENABLE_LTO OFF
+          SET_FOLDER_TARGETS ext benchmark benchmark_main
+          EXCLUDE_FROM_ALL
+          )
+        #
+        if((CMAKE_CXX_COMPILER_ID STREQUAL GNU) OR (CMAKE_COMPILER_IS_GNUCC))
+            target_compile_options(benchmark PRIVATE -Wno-deprecated-declarations)
+        endif()
+        #
+        if(NOT WIN32)
+            option(${_c4_uprefix}BENCHMARK_CPUPOWER
+                "set the cpu mode to performance before / powersave after the benchmark" OFF)
+            if(${_c4_uprefix}BENCHMARK_CPUPOWER)
+                find_program(C4_SUDO sudo)
+                find_program(C4_CPUPOWER cpupower)
+            endif()
+        endif()
+    endif()
+endfunction()
+
+
+function(c4_add_benchmark_cmd casename)
+    add_custom_target(${casename}
+        COMMAND ${ARGN}
+        VERBATIM
+        COMMENT "${_c4_prefix}: running benchmark ${casename}: ${ARGN}")
+    add_dependencies(${_c4_lprefix}bm-build ${casename})
+    _c4_set_target_folder(${casename} bm)
+endfunction()
+
+
+# assumes this is a googlebenchmark target, and that multiple
+# benchmarks are defined from it
+function(c4_add_target_benchmark target casename)
+    set(opt0arg
+    )
+    set(opt1arg
+        WORKDIR # working directory
+        FILTER  # benchmark patterns to filter
+        UMBRELLA_TARGET
+    )
+    set(optnarg
+        ARGS
+    )
+    cmake_parse_arguments("" "${opt0arg}" "${opt1arg}" "${optnarg}" ${ARGN})
+    #
+    set(name "${target}-${casename}")
+    set(rdir "${CMAKE_CURRENT_BINARY_DIR}/bm-results")
+    set(rfile "${rdir}/${name}.json")
+    if(NOT EXISTS "${rdir}")
+        file(MAKE_DIRECTORY "${rdir}")
+    endif()
+    set(filter)
+    if(NOT ("${_FILTER}" STREQUAL ""))
+        set(filter "--benchmark_filter=${_FILTER}")
+    endif()
+    set(args_fwd ${filter} --benchmark_out_format=json --benchmark_out=${rfile} ${_ARGS})
+    c4_add_benchmark(${target}
+        "${name}"
+        "${_WORKDIR}"
+        "saving results in ${rfile}"
+        ${args_fwd})
+    if(_UMBRELLA_TARGET)
+        add_dependencies(${_UMBRELLA_TARGET} "${name}")
+    endif()
+endfunction()
+
+
+function(c4_add_benchmark target casename work_dir comment)
+    if(NOT TARGET ${target})
+        c4_err("target ${target} does not exist...")
+    endif()
+    if(NOT ("${work_dir}" STREQUAL ""))
+        if(NOT EXISTS "${work_dir}")
+            file(MAKE_DIRECTORY "${work_dir}")
+        endif()
+    endif()
+    set(exe $<TARGET_FILE:${target}>)
+    if(${_c4_uprefix}BENCHMARK_CPUPOWER)
+        if(C4_BM_SUDO AND C4_BM_CPUPOWER)
+            set(c ${C4_SUDO} ${C4_CPUPOWER} frequency-set --governor performance)
+            set(cpupow_before
+                COMMAND echo ${c}
+                COMMAND ${c})
+            set(c ${C4_SUDO} ${C4_CPUPOWER} frequency-set --governor powersave)
+            set(cpupow_after
+                COMMAND echo ${c}
+                COMMAND ${c})
+        endif()
+    endif()
+    add_custom_target(${casename}
+        ${cpupow_before}
+        # this is useful to show the target file (you cannot echo generator variables)
+        #COMMAND ${CMAKE_COMMAND} -E echo "target file = $<TARGET_FILE:${target}>"
+        COMMAND ${CMAKE_COMMAND} -E echo "${exe} ${ARGN}"
+        COMMAND "${exe}" ${ARGN}
+        ${cpupow_after}
+        VERBATIM
+        WORKING_DIRECTORY "${work_dir}"
+        DEPENDS ${target}
+        COMMENT "${_c4_lcprefix}: running benchmark ${target}, case ${casename}: ${comment}"
+        )
+    add_dependencies(${_c4_lprefix}bm-build ${target})
+    add_dependencies(${_c4_lprefix}bm-run ${casename})
+    _c4_set_target_folder(${casename} bm/run)
+endfunction()
+
+
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+
+function(_c4cat_get_outname target id ext out)
+    if("${_c4_lcprefix}" STREQUAL "${target}")
+        set(p "${target}")
+    else()
+        set(p "${_c4_lcprefix}.${target}")
+    endif()
+    set(${out} "${CMAKE_CURRENT_BINARY_DIR}/${p}.${id}.${ext}" PARENT_SCOPE)
+endfunction()
+
+function(_c4cat_filter_srcs in out)
+    _c4cat_filter_extensions("${in}" "${C4_SRC_EXTS}" l)
+    set(${out} ${l} PARENT_SCOPE)
+endfunction()
+
+function(_c4cat_filter_hdrs in out)
+    _c4cat_filter_extensions("${in}" "${C4_HDR_EXTS}" l)
+    set(${out} ${l} PARENT_SCOPE)
+endfunction()
+
+function(_c4cat_filter_srcs_hdrs in out)
+    _c4cat_filter_extensions("${in}" "${C4_HDR_EXTS};${C4_SRC_EXTS}" l)
+    set(${out} ${l} PARENT_SCOPE)
+endfunction()
+
+function(_c4cat_filter_additional_exts in out)
+    _c4cat_filter_extensions("${in}" "${C4_ADD_EXTS}" l)
+    set(${out} ${l} PARENT_SCOPE)
+endfunction()
+
+function(_c4cat_filter_extensions in filter out)
+    set(l)
+    foreach(fn ${in})  # don't quote the list here
+        _c4cat_get_file_ext("${fn}" ext)
+        _c4cat_one_of("${ext}" "${filter}" yes)
+        if(${yes})
+            list(APPEND l "${fn}")
+        endif()
+    endforeach()
+    set(${out} "${l}" PARENT_SCOPE)
+endfunction()
+
+function(_c4cat_get_file_ext in out)
+    # https://stackoverflow.com/questions/30049180/strip-filename-shortest-extension-by-cmake-get-filename-removing-the-last-ext
+    string(REGEX MATCH "^.*\\.([^.]*)$" dummy ${in})
+    set(${out} ${CMAKE_MATCH_1} PARENT_SCOPE)
+endfunction()
+
+function(_c4cat_one_of ext candidates out)
+    foreach(e ${candidates})
+        if("${ext}" STREQUAL "${e}")
+            set(${out} TRUE PARENT_SCOPE)
+            return()
+        endif()
+    endforeach()
+    set(${out} FALSE PARENT_SCOPE)
+endfunction()
+
+
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+
+# given a list of source files, return a list with full paths
+function(c4_to_full_path source_list source_list_with_full_paths)
+    set(l)
+    foreach(f ${source_list})
+        if(IS_ABSOLUTE "${f}")
+            list(APPEND l "${f}")
+        else()
+            list(APPEND l "${CMAKE_CURRENT_SOURCE_DIR}/${f}")
+        endif()
+    endforeach()
+    set(${source_list_with_full_paths} ${l} PARENT_SCOPE)
+endfunction()
+
+
+# convert a list to a string separated with spaces
+function(c4_separate_list input_list output_string)
+    set(s)
+    foreach(e ${input_list})
+        set(s "${s} ${e}")
+    endforeach()
+    set(${output_string} ${s} PARENT_SCOPE)
+endfunction()
+
+
+
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+endif(NOT _c4_project_included)

+ 292 - 0
3rdparty/rapidyaml/ext/c4core/cmake/c4SanitizeTarget.cmake

@@ -0,0 +1,292 @@
+# (C) 2017 Joao Paulo Magalhaes <dev@jpmag.me>
+if(NOT _c4_sanitize_target_included)
+set(_c4_sanitize_target_included ON)
+
+include(CMakeDependentOption)
+include(PrintVar)
+
+function(_c4_default_if_not_set var dft)
+    if("${${var}}" STREQUAL "")
+        option(${var} "" ${dft})
+    endif()
+endfunction()
+
+
+#------------------------------------------------------------------------------
+function(c4_setup_sanitize umbrella_option)
+    if("${CMAKE_BUILD_TYPE}" STREQUAL "Coverage")
+        return()
+    endif()
+    if(NOT ((CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") OR (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")))
+        return()
+    endif()
+
+    _c4_default_if_not_set(C4_SANITIZE ON)
+    _c4_default_if_not_set(C4_SANITIZE_ONLY OFF)
+    _c4_default_if_not_set(C4_ASAN ON)
+    _c4_default_if_not_set(C4_TSAN ON)
+    _c4_default_if_not_set(C4_MSAN ON)
+    _c4_default_if_not_set(C4_UBSAN ON)
+
+    cmake_dependent_option(${_c4_uprefix}SANITIZE "turn on clang sanitizer targets" ${C4_SANITIZE} ${umbrella_option} OFF)
+    cmake_dependent_option(${_c4_uprefix}SANITIZE_ONLY "compile only sanitize targets (not the regular unsanitized targets)" ${C4_SANITIZE_ONLY} ${umbrella_option} OFF)
+
+    # options for individual sanitizers - contingent on sanitize on/off
+    cmake_dependent_option(${_c4_uprefix}ASAN  "" ${C4_ASAN} "${_c4_uprefix}SANITIZE" OFF)
+    cmake_dependent_option(${_c4_uprefix}TSAN  "" ${C4_TSAN} "${_c4_uprefix}SANITIZE" OFF)
+    cmake_dependent_option(${_c4_uprefix}MSAN  "" ${C4_MSAN} "${_c4_uprefix}SANITIZE" OFF)
+    cmake_dependent_option(${_c4_uprefix}UBSAN "" ${C4_UBSAN} "${_c4_uprefix}SANITIZE" OFF)
+
+    if(${_c4_uprefix}SANITIZE)
+        string(REGEX REPLACE "([0-9]+\\.[0-9]+).*" "\\1" LLVM_VERSION "${CMAKE_CXX_COMPILER_VERSION}")
+        find_program(LLVM_SYMBOLIZER llvm-symbolizer
+            NAMES llvm-symbolizer-${LLVM_VERSION} llvm-symbolizer
+            DOC "symbolizer to use in sanitize tools")
+        if(NOT LLVM_SYMBOLIZER)
+            string(REGEX REPLACE "([0-9]+)\\.[0-9]+.*" "\\1" LLVM_VERSION "${CMAKE_CXX_COMPILER_VERSION}")
+            find_program(LLVM_SYMBOLIZER llvm-symbolizer
+                NAMES llvm-symbolizer-${LLVM_VERSION} llvm-symbolizer
+                DOC "symbolizer to use in sanitize tools")
+            if(NOT LLVM_SYMBOLIZER)
+                message(FATAL_ERROR "could not find symbolizer. LLVM_VERSION=${LLVM_VERSION}")
+            endif()
+        endif()
+
+        set(ss) # string to report enabled sanitizers
+
+        if(${_c4_uprefix}ASAN)
+            set(ss "asan")
+            set(${_c4_uprefix}ASAN_CFLAGS "-O1 -g -fsanitize=address -fno-omit-frame-pointer -fno-optimize-sibling-calls" CACHE STRING "compile flags for clang address sanitizer: https://clang.llvm.org/docs/AddressSanitizer.html")
+            set(${_c4_uprefix}ASAN_LFLAGS "-g -fsanitize=address" CACHE STRING "linker flags for clang address sanitizer: https://clang.llvm.org/docs/AddressSanitizer.html")
+            set(${_c4_uprefix}ASAN_RENV  "env ASAN_SYMBOLIZER_PATH=${LLVM_SYMBOLIZER} ASAN_OPTIONS=symbolize=1" CACHE STRING "run environment for clang address sanitizer: https://clang.llvm.org/docs/AddressSanitizer.html")
+            # the flags are strings; we need to separate them into a list
+            # to prevent cmake from quoting them when passing to the targets
+            separate_arguments(${_c4_uprefix}ASAN_CFLAGS_SEP UNIX_COMMAND ${${_c4_uprefix}ASAN_CFLAGS})
+            separate_arguments(${_c4_uprefix}ASAN_LFLAGS_SEP UNIX_COMMAND ${${_c4_uprefix}ASAN_LFLAGS})
+        endif()
+
+        if(${_c4_uprefix}TSAN)
+            set(ss "${ss} tsan")
+            set(${_c4_uprefix}TSAN_CFLAGS "-O1 -g -fsanitize=thread -fno-omit-frame-pointer" CACHE STRING "compile flags for clang thread sanitizer: https://clang.llvm.org/docs/ThreadSanitizer.html")
+            set(${_c4_uprefix}TSAN_LFLAGS "-g -fsanitize=thread" CACHE STRING "linker flags for clang thread sanitizer: https://clang.llvm.org/docs/ThreadSanitizer.html")
+            set(${_c4_uprefix}TSAN_RENV  "env TSAN_SYMBOLIZER_PATH=${LLVM_SYMBOLIZER} TSAN_OPTIONS=symbolize=1" CACHE STRING "run environment for clang thread sanitizer: https://clang.llvm.org/docs/ThreadSanitizer.html")
+            separate_arguments(${_c4_uprefix}TSAN_CFLAGS_SEP UNIX_COMMAND ${${_c4_uprefix}TSAN_CFLAGS})
+            separate_arguments(${_c4_uprefix}TSAN_LFLAGS_SEP UNIX_COMMAND ${${_c4_uprefix}TSAN_LFLAGS})
+        endif()
+
+        if(${_c4_uprefix}MSAN)
+            set(ss "${ss} msan")
+            set(${_c4_uprefix}MSAN_CFLAGS "-O1 -g -fsanitize=memory -fsanitize-memory-track-origins -fno-omit-frame-pointer -fno-optimize-sibling-calls" CACHE STRING "compile flags for clang memory sanitizer: https://clang.llvm.org/docs/MemorySanitizer.html")
+            set(${_c4_uprefix}MSAN_LFLAGS "-g -fsanitize=memory" CACHE STRING "linker flags for clang memory sanitizer: https://clang.llvm.org/docs/MemorySanitizer.html")
+            set(${_c4_uprefix}MSAN_RENV  "env MSAN_SYMBOLIZER_PATH=${LLVM_SYMBOLIZER} MSAN_OPTIONS=symbolize=1" CACHE STRING "run environment for clang memory sanitizer: https://clang.llvm.org/docs/MemorySanitizer.html")
+            separate_arguments(${_c4_uprefix}MSAN_CFLAGS_SEP UNIX_COMMAND ${${_c4_uprefix}MSAN_CFLAGS})
+            separate_arguments(${_c4_uprefix}MSAN_LFLAGS_SEP UNIX_COMMAND ${${_c4_uprefix}MSAN_LFLAGS})
+        endif()
+
+        if(${_c4_uprefix}UBSAN)
+            set(ss "${ss} ubsan")
+            set(${_c4_uprefix}UBSAN_CFLAGS "-g -fsanitize=undefined" CACHE STRING "compile flags for clang undefined behaviour sanitizer: https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html")
+            set(${_c4_uprefix}UBSAN_LFLAGS "-g -fsanitize=undefined" CACHE STRING "linker flags for clang undefined behaviour sanitizer: https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html")
+            set(${_c4_uprefix}UBSAN_RENV "env UBSAN_SYMBOLIZER_PATH=${LLVM_SYMBOLIZER} UBSAN_OPTIONS='symbolize=1 print_stacktrace=1'" CACHE STRING "run environment for clang undefined behaviour sanitizer: https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html")
+            separate_arguments(${_c4_uprefix}UBSAN_CFLAGS_SEP UNIX_COMMAND ${${_c4_uprefix}UBSAN_CFLAGS})
+            separate_arguments(${_c4_uprefix}UBSAN_LFLAGS_SEP UNIX_COMMAND ${${_c4_uprefix}UBSAN_LFLAGS})
+        endif()
+
+        c4_dbg("enabled clang sanitizers: ${ss}")
+    endif() # ${_c4_uprefix}SANITIZE
+
+endfunction()
+
+
+#------------------------------------------------------------------------------
+function(c4_sanitize_get_target_command name which_sanitizer_ output)
+    string(TOUPPER ${which_sanitizer_} which_sanitizer)
+    if("${which_sanitizer}" STREQUAL ASAN)
+    elseif("${which_sanitizer}" STREQUAL TSAN)
+    elseif("${which_sanitizer}" STREQUAL MSAN)
+    elseif("${which_sanitizer}" STREQUAL UBSAN)
+    else()
+        message(FATAL_ERROR "the sanitizer must be one of: ASAN, TSAN, MSAN, UBSAN")
+    endif()
+    separate_arguments(cmd UNIX_COMMAND "${${_c4_uprefix}${which_sanitizer}_RENV} ${name}")
+    set(${output} ${cmd} PARENT_SCOPE)
+endfunction()
+
+function(_sanitize_set_target_folder tgt folder)
+    if(folder)
+        set_target_properties(${tgt} PROPERTIES FOLDER "${folder}")
+    endif()
+endfunction()
+
+
+#------------------------------------------------------------------------------
+function(c4_sanitize_target name)
+    set(opt0arg
+        LIBRARY
+        EXECUTABLE
+    )
+    set(opt1arg
+        OUTPUT_TARGET_NAMES
+        FOLDER
+    )
+    set(optnarg
+        SOURCES
+        INC_DIRS # TODO public, interface, private
+        LIBS     # TODO public, interface, private
+        LIB_DIRS # TODO public, interface, private
+        DEFS     # TODO public, interface, private
+        CFLAGS   # TODO public, interface, private
+    )
+    cmake_parse_arguments("" "${opt0arg}" "${opt1arg}" "${optnarg}" ${ARGN})
+
+    if((NOT _LIBRARY) AND (NOT _EXECUTABLE))
+        c4_err("either LIBRARY or EXECUTABLE must be specified")
+    endif()
+
+    if(${_c4_uprefix}SANITIZE AND NOT TARGET ${_c4_lprefix}sanitize)
+        add_custom_target(${_c4_lprefix}sanitize)
+        _sanitize_set_target_folder(${_c4_lprefix}sanitize "${_FOLDER}")
+    endif()
+    if(${_c4_uprefix}ASAN AND NOT TARGET ${_c4_lprefix}asan-all)
+        add_custom_target(${_c4_lprefix}asan-all)
+        add_dependencies(${_c4_lprefix}sanitize ${_c4_lprefix}asan-all)
+        _sanitize_set_target_folder(${_c4_lprefix}asan-all "${_FOLDER}")
+    endif()
+    if(${_c4_uprefix}MSAN AND NOT TARGET ${_c4_lprefix}msan-all)
+        add_custom_target(${_c4_lprefix}msan-all)
+        add_dependencies(${_c4_lprefix}sanitize ${_c4_lprefix}msan-all)
+        _sanitize_set_target_folder(${_c4_lprefix}msan-all "${_FOLDER}")
+    endif()
+    if(${_c4_uprefix}TSAN AND NOT TARGET ${_c4_lprefix}tsan-all)
+        add_custom_target(${_c4_lprefix}tsan-all)
+        add_dependencies(${_c4_lprefix}sanitize ${_c4_lprefix}tsan-all)
+        _sanitize_set_target_folder(${_c4_lprefix}tsan-all "${_FOLDER}")
+    endif()
+    if(${_c4_uprefix}UBSAN AND NOT TARGET ${_c4_lprefix}ubsan-all)
+        add_custom_target(${_c4_lprefix}ubsan-all)
+        add_dependencies(${_c4_lprefix}sanitize ${_c4_lprefix}ubsan-all)
+        _sanitize_set_target_folder(${_c4_lprefix}ubsan-all "${_FOLDER}")
+    endif()
+
+    if(${_c4_uprefix}ASAN OR ${_c4_uprefix}MSAN OR ${_c4_uprefix}TSAN OR ${_c4_uprefix}UBSAN)
+        add_custom_target(${name}-sanitize-all)
+        _sanitize_set_target_folder(${name}-sanitize-all "${_FOLDER}")
+    endif()
+
+    set(targets)
+
+    # https://clang.llvm.org/docs/AddressSanitizer.html
+    if(${_c4_uprefix}ASAN)
+        if(${_LIBRARY})
+            add_library(${name}-asan EXCLUDE_FROM_ALL ${_SOURCES})
+        elseif(${_EXECUTABLE})
+            add_executable(${name}-asan EXCLUDE_FROM_ALL ${_SOURCES})
+        endif()
+        _sanitize_set_target_folder(${name}-asan "${_FOLDER}")
+        list(APPEND targets ${name}-asan)
+        target_include_directories(${name}-asan PUBLIC ${_INC_DIRS})
+        set(_real_libs)
+        foreach(_l ${_LIBS})
+            if(TARGET ${_l}-asan)
+                list(APPEND _real_libs ${_l}-asan)
+            else()
+                list(APPEND _real_libs ${_l})
+            endif()
+        endforeach()
+        target_link_libraries(${name}-asan PUBLIC ${_real_libs})
+        target_compile_definitions(${name}-asan PUBLIC ${_DEFS})
+        target_compile_options(${name}-asan PUBLIC ${_CFLAGS} ${${_c4_uprefix}ASAN_CFLAGS_SEP})
+        # http://stackoverflow.com/questions/25043458/does-cmake-have-something-like-target-link-options
+        target_link_libraries(${name}-asan PUBLIC ${${_c4_uprefix}ASAN_LFLAGS_SEP})
+        add_dependencies(${_c4_lprefix}asan-all ${name}-asan)
+        add_dependencies(${name}-sanitize-all ${name}-asan)
+    endif()
+
+    # https://clang.llvm.org/docs/ThreadSanitizer.html
+    if(${_c4_uprefix}TSAN)
+        if(${_LIBRARY})
+            add_library(${name}-tsan EXCLUDE_FROM_ALL ${_SOURCES})
+        elseif(${_EXECUTABLE})
+            add_executable(${name}-tsan EXCLUDE_FROM_ALL ${_SOURCES})
+        endif()
+        _sanitize_set_target_folder(${name}-tsan "${_FOLDER}")
+        list(APPEND targets ${name}-tsan)
+        target_include_directories(${name}-tsan PUBLIC ${_INC_DIRS})
+        set(_real_libs)
+        foreach(_l ${_LIBS})
+            if(TARGET ${_l}-tsan)
+                list(APPEND _real_libs ${_l}-tsan)
+            else()
+                list(APPEND _real_libs ${_l})
+            endif()
+        endforeach()
+        target_link_libraries(${name}-tsan PUBLIC ${_real_libs})
+        target_compile_definitions(${name}-tsan PUBLIC ${_DEFS})
+        target_compile_options(${name}-tsan PUBLIC ${_CFLAGS} ${${_c4_uprefix}TSAN_CFLAGS_SEP})
+        # http://stackoverflow.com/questions/25043458/does-cmake-have-something-like-target-link-options
+        target_link_libraries(${name}-tsan PUBLIC ${${_c4_uprefix}TSAN_LFLAGS_SEP})
+        add_dependencies(${_c4_lprefix}tsan-all ${name}-tsan)
+        add_dependencies(${name}-sanitize-all ${name}-tsan)
+    endif()
+
+    # https://clang.llvm.org/docs/MemorySanitizer.html
+    if(${_c4_uprefix}MSAN)
+        if(${_LIBRARY})
+            add_library(${name}-msan EXCLUDE_FROM_ALL ${_SOURCES})
+        elseif(${_EXECUTABLE})
+            add_executable(${name}-msan EXCLUDE_FROM_ALL ${_SOURCES})
+        endif()
+        _sanitize_set_target_folder(${name}-msan "${_FOLDER}")
+        list(APPEND targets ${name}-msan)
+        target_include_directories(${name}-msan PUBLIC ${_INC_DIRS})
+        set(_real_libs)
+        foreach(_l ${_LIBS})
+            if(TARGET ${_l}-msan)
+                list(APPEND _real_libs ${_l}-msan)
+            else()
+                list(APPEND _real_libs ${_l})
+            endif()
+        endforeach()
+        target_link_libraries(${name}-msan PUBLIC ${_real_libs})
+        target_compile_definitions(${name}-msan PUBLIC ${_DEFS})
+        target_compile_options(${name}-msan PUBLIC ${_CFLAGS} ${${_c4_uprefix}MSAN_CFLAGS_SEP})
+        # http://stackoverflow.com/questions/25043458/does-cmake-have-something-like-target-link-options
+        target_link_libraries(${name}-msan PUBLIC ${${_c4_uprefix}MSAN_LFLAGS_SEP})
+        add_dependencies(${_c4_lprefix}msan-all ${name}-msan)
+        add_dependencies(${name}-sanitize-all ${name}-msan)
+    endif()
+
+    # https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html
+    if(${_c4_uprefix}UBSAN)
+        if(${_LIBRARY})
+            add_library(${name}-ubsan EXCLUDE_FROM_ALL ${_SOURCES})
+        elseif(${_EXECUTABLE})
+            add_executable(${name}-ubsan EXCLUDE_FROM_ALL ${_SOURCES})
+        endif()
+        _sanitize_set_target_folder(${name}-ubsan "${_FOLDER}")
+        list(APPEND targets ${name}-ubsan)
+        target_include_directories(${name}-ubsan PUBLIC ${_INC_DIRS})
+        set(_real_libs)
+        foreach(_l ${_LIBS})
+            if(TARGET ${_l}-ubsan)
+                list(APPEND _real_libs ${_l}-ubsan)
+            else()
+                list(APPEND _real_libs ${_l})
+            endif()
+        endforeach()
+        target_link_libraries(${name}-ubsan PUBLIC ${_real_libs})
+        target_compile_definitions(${name}-ubsan PUBLIC ${_DEFS})
+        target_compile_options(${name}-ubsan PUBLIC ${_CFLAGS} ${${_c4_uprefix}UBSAN_CFLAGS_SEP})
+        # http://stackoverflow.com/questions/25043458/does-cmake-have-something-like-target-link-options
+        target_link_libraries(${name}-ubsan PUBLIC ${${_c4_uprefix}UBSAN_LFLAGS_SEP})
+        add_dependencies(${_c4_lprefix}ubsan-all ${name}-ubsan)
+        add_dependencies(${name}-sanitize-all ${name}-ubsan)
+    endif()
+
+    if(_OUTPUT_TARGET_NAMES)
+        set(${_OUTPUT_TARGET_NAMES} ${targets} PARENT_SCOPE)
+    endif()
+endfunction()
+
+
+endif(NOT _c4_sanitize_target_included)

+ 154 - 0
3rdparty/rapidyaml/ext/c4core/cmake/c4StaticAnalysis.cmake

@@ -0,0 +1,154 @@
+include(PVS-Studio)
+include(GetFlags)
+include(c4GetTargetPropertyRecursive)
+
+
+function(_c4sta_default_if_not_set var dft)
+    if("${${var}}" STREQUAL "")
+        set(${var} "${dft}" PARENT_SCOPE)
+    endif()
+endfunction()
+
+
+function(c4_setup_static_analysis umbrella_option)
+    if(WIN32)
+        c4_dbg("no static analyzer available in WIN32")
+        return()
+    endif()
+    if("${CMAKE_BUILD_TYPE}" STREQUAL "Coverage")
+        c4_dbg("Coverage build: disabling static analyzers")
+        return()
+    endif()
+    _c4sta_default_if_not_set(C4_LINT ${umbrella_option})
+    _c4sta_default_if_not_set(C4_LINT_TESTS ${umbrella_option})
+    _c4sta_default_if_not_set(C4_LINT_CLANG_TIDY ${umbrella_option})
+    _c4sta_default_if_not_set(C4_LINT_PVS_STUDIO OFF)
+    # option to turn lints on/off
+    cmake_dependent_option(${_c4_uprefix}LINT "add static analyzer targets" ${C4_LINT} ${umbrella_option} OFF)
+    cmake_dependent_option(${_c4_uprefix}LINT_TESTS "add tests to run static analyzer targets" ${C4_LINT_TESTS} ${umbrella_option} OFF)
+    # options for individual lints - contingent on linting on/off
+    cmake_dependent_option(${_c4_uprefix}LINT_CLANG_TIDY "use the clang-tidy static analyzer" ${C4_LINT_CLANG_TIDY} "${_c4_uprefix}LINT" ON)
+    cmake_dependent_option(${_c4_uprefix}LINT_PVS_STUDIO "use the PVS-Studio static analyzer https://www.viva64.com/en/b/0457/" ${C4_LINT_PVS_STUDIO} "${_c4_uprefix}LINT" OFF)
+    if(${_c4_uprefix}LINT_CLANG_TIDY)
+        find_program(CLANG_TIDY clang-tidy)
+    endif()
+    if(${_c4_uprefix}LINT_PVS_STUDIO)
+        set(${_c4_uprefix}LINT_PVS_STUDIO_FORMAT "errorfile" CACHE STRING "PVS-Studio output format. Choices: xml,csv,errorfile(like gcc/clang),tasklist(qtcreator)")
+    endif()
+    #
+    set(sa)
+    if(${_c4_uprefix}LINT_CLANG_TIDY)
+        set(sa "clang_tidy")
+    endif()
+    if(${_c4_uprefix}LINT_PVS_STUDIO)
+        set(sa "${sa} PVS-Studio")
+    endif()
+    if(sa)
+        c4_dbg("enabled static analyzers: ${sa}")
+    endif()
+endfunction()
+
+
+function(c4_static_analysis_target target_name folder generated_targets)
+    set(any_linter OFF)
+    if(${_c4_uprefix}LINT_CLANG_TIDY OR ${_c4_uprefix}LINT_PVS_STUDIO)
+        set(any_linter ON)
+    endif()
+    if(${_c4_uprefix}LINT AND any_linter)
+        # umbrella target for running all linters for this particular target
+        if(any_linter AND NOT TARGET ${_c4_lprefix}lint-all)
+            add_custom_target(${_c4_lprefix}lint-all)
+            if(folder)
+                #message(STATUS "${target_name}: folder=${folder}")
+                set_target_properties(${_c4_lprefix}lint-all PROPERTIES FOLDER "${folder}")
+            endif()
+        endif()
+        if(${_c4_uprefix}LINT_CLANG_TIDY)
+            c4_static_analysis_clang_tidy(${target_name}
+                ${target_name}-lint-clang_tidy
+                ${_c4_lprefix}lint-all-clang_tidy
+                "${folder}")
+            list(APPEND ${generated_targets} ${_c4_lprefix}lint-clang_tidy)
+            add_dependencies(${_c4_lprefix}lint-all ${_c4_lprefix}lint-all-clang_tidy)
+        endif()
+        if(${_c4_uprefix}LINT_PVS_STUDIO)
+            c4_static_analysis_pvs_studio(${target_name}
+                ${target_name}-lint-pvs_studio
+                ${_c4_lprefix}lint-all-pvs_studio
+                "${folder}")
+            list(APPEND ${generated_targets} ${_c4_lprefix}lint-pvs_studio)
+            add_dependencies(${_c4_lprefix}lint-all ${_c4_lprefix}lint-all-pvs_studio)
+        endif()
+    endif()
+endfunction()
+
+
+function(c4_static_analysis_add_tests target_name)
+    if(${_c4_uprefix}LINT_CLANG_TIDY AND ${_c4_uprefix}LINT_TESTS)
+        add_test(NAME ${target_name}-lint-clang_tidy-run
+            COMMAND
+            ${CMAKE_COMMAND} --build ${CMAKE_CURRENT_BINARY_DIR} --target ${target_name}-lint-clang_tidy)
+    endif()
+    if(${_c4_uprefix}LINT_PVS_STUDIO AND ${_c4_uprefix}LINT_TESTS)
+        add_test(NAME ${target_name}-lint-pvs_studio-run
+            COMMAND
+            ${CMAKE_COMMAND} --build ${CMAKE_CURRENT_BINARY_DIR} --target ${target_name}-lint-pvs_studio)
+    endif()
+endfunction()
+
+
+#------------------------------------------------------------------------------
+function(c4_static_analysis_clang_tidy subj_target lint_target umbrella_target folder)
+    c4_static_analysis_clang_tidy_get_cmd(${subj_target} ${lint_target} cmd)
+    string(REPLACE ";" " " cmd_str "${cmd}")
+    add_custom_target(${lint_target}
+        COMMAND ${CMAKE_COMMAND} -E echo "cd ${CMAKE_CURRENT_SOURCE_DIR} ; ${cmd_str}"
+        COMMAND ${cmd}
+        VERBATIM
+        COMMENT "clang-tidy: analyzing sources of ${subj_target}"
+        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
+    if(folder)
+        set_target_properties(${lint_target} PROPERTIES FOLDER "${folder}")
+    endif()
+    if(NOT TARGET ${umbrella_target})
+        add_custom_target(${umbrella_target})
+    endif()
+    add_dependencies(${umbrella_target} ${lint_target})
+endfunction()
+
+function(c4_static_analysis_clang_tidy_get_cmd subj_target lint_target cmd)
+    get_target_property(_clt_all_srcs ${subj_target} SOURCES)
+    _c4cat_filter_srcs_hdrs("${_clt_all_srcs}" _clt_srcs)
+    set(result "${CLANG_TIDY}" -p ${CMAKE_BINARY_DIR} --header-filter=.* ${_clt_srcs})
+    set(${cmd} ${result} PARENT_SCOPE)
+endfunction()
+
+
+#------------------------------------------------------------------------------
+function(c4_static_analysis_pvs_studio subj_target lint_target umbrella_target folder)
+    c4_get_target_property_recursive(_c4al_pvs_incs ${subj_target} INCLUDE_DIRECTORIES)
+    c4_get_include_flags(_c4al_pvs_incs ${_c4al_pvs_incs})
+    separate_arguments(_c4al_cxx_flags_sep UNIX_COMMAND "${CMAKE_CXX_FLAGS} ${_c4al_pvs_incs}")
+    separate_arguments(_c4al_c_flags_sep UNIX_COMMAND "${CMAKE_C_FLAGS} ${_c4al_pvs_incs}")
+    pvs_studio_add_target(TARGET ${lint_target}
+        ALL # indicates that the analysis starts when you build the project
+        #PREPROCESSOR ${_c4al_preproc}
+        FORMAT tasklist
+        LOG "${CMAKE_CURRENT_BINARY_DIR}/${subj_target}.pvs-analysis.tasks"
+        ANALYZE ${name} #main_target subtarget:path/to/subtarget
+        CXX_FLAGS ${_c4al_cxx_flags_sep}
+        C_FLAGS ${_c4al_c_flags_sep}
+        #CONFIG "/path/to/PVS-Studio.cfg"
+        )
+    if(folder)
+        set_target_properties(${lint_target} PROPERTIES FOLDER "${folder}")
+    endif()
+    if(NOT TARGET ${umbrella_target})
+        add_custom_target(${umbrella_target})
+    endif()
+    add_dependencies(${umbrella_target} ${lint_target})
+endfunction()
+
+function(c4_static_analysis_pvs_studio_get_cmd subj_target lint_target cmd)
+    set(${cmd} $<RULE_LAUNCH_CUSTOM:${subj_target}> PARENT_SCOPE)
+endfunction()

+ 5 - 0
3rdparty/rapidyaml/ext/c4core/cmake/c4stlAddTarget.cmake

@@ -0,0 +1,5 @@
+include(c4project)
+
+function(c4stl_add_target name)
+    c4_add_target(c4stl ${name} ${ARGN})
+endfunction() # c4stl_add_target

+ 69 - 0
3rdparty/rapidyaml/ext/c4core/cmake/compat/c4/gcc-4.8.hpp

@@ -0,0 +1,69 @@
+#ifndef _C4_COMPAT_GCC_4_8_HPP_
+#define _C4_COMPAT_GCC_4_8_HPP_
+
+#if __GNUC__ == 4 && __GNUC_MINOR__ >= 8
+/* STL polyfills for old GNU compilers */
+
+_Pragma("GCC diagnostic ignored \"-Wshadow\"")
+_Pragma("GCC diagnostic ignored \"-Wmissing-field-initializers\"")
+
+#if __cplusplus
+#include <cstdint>
+#include <type_traits>
+
+namespace std {
+
+template<typename _Tp>
+struct is_trivially_copyable : public integral_constant<bool,
+    is_destructible<_Tp>::value && __has_trivial_destructor(_Tp) &&
+    (__has_trivial_constructor(_Tp) || __has_trivial_copy(_Tp) || __has_trivial_assign(_Tp))>
+{ };
+
+template<typename _Tp>
+using is_trivially_copy_constructible = has_trivial_copy_constructor<_Tp>;
+
+template<typename _Tp>
+using is_trivially_default_constructible = has_trivial_default_constructor<_Tp>;
+
+template<typename _Tp>
+using is_trivially_copy_assignable = has_trivial_copy_assign<_Tp>;
+
+/* not supported */
+template<typename _Tp>
+struct is_trivially_move_constructible : false_type
+{ };
+
+/* not supported */
+template<typename _Tp>
+struct is_trivially_move_assignable : false_type
+{ };
+
+inline void *align(size_t __align, size_t __size, void*& __ptr, size_t& __space) noexcept
+{
+    if (__space < __size)
+        return nullptr;
+    const auto __intptr = reinterpret_cast<uintptr_t>(__ptr);
+    const auto __aligned = (__intptr - 1u + __align) & -__align;
+    const auto __diff = __aligned - __intptr;
+    if (__diff > (__space - __size))
+        return nullptr;
+    else
+    {
+        __space -= __diff;
+        return __ptr = reinterpret_cast<void*>(__aligned);
+    }
+}
+typedef long double max_align_t ;
+
+}
+#else // __cplusplus
+
+#include <string.h>
+// see https://sourceware.org/bugzilla/show_bug.cgi?id=25399 (ubuntu gcc-4.8)
+#define memset(s, c, count) __builtin_memset(s, c, count)
+
+#endif // __cplusplus
+
+#endif // __GNUC__ == 4 && __GNUC_MINOR__ >= 8
+
+#endif // _C4_COMPAT_GCC_4_8_HPP_

+ 97 - 0
3rdparty/rapidyaml/ext/c4core/cmake/compat/gtest_gcc-4.8.patch

@@ -0,0 +1,97 @@
+Multi-line macros support is not guaranteed with gcc-4.8.
+
+This uses temporary objects to work-arround this limitation, main drawback is
+that compared code is not displayed in message anymore (only "val" placeholders).
+
+--- googletest/include/gtest/gtest.h
++++ googletest/include/gtest/gtest.h
+@@ -2040,6 +2040,80 @@ class TestWithParam : public Test, publi
+ //   ASSERT_LT(i, array_size);
+ //   ASSERT_GT(records.size(), 0) << "There is no record left.";
+ 
++#if __GNUC__ == 4 && __GNUC_MINOR__ >= 8
++/*
++ * multi-line macros support is not guaranteed with gcc-4.8.
++ * This uses temporary objects to work-arround this limitation, main drawback is
++ * that compared code is not displayed in message anymore (only "val" placeholders)
++ */
++
++enum class CompatExpectSelector { EQ, NE, LE, LT, GE, GT };
++
++template <CompatExpectSelector T>
++struct CompatExpect
++{
++  const char *file;
++  int line;
++  ::testing::AssertionResult gtest_ar = AssertionSuccess();
++  ::testing::Message msg;
++
++  CompatExpect(const char *file, int line) : file(file), line(line) {}
++  ~CompatExpect() {
++    if (!gtest_ar)
++      GTEST_MESSAGE_AT_(file, line, gtest_ar.failure_message(), ::testing::TestPartResult::kNonFatalFailure) << msg;
++  }
++
++  template <typename T1, typename T2, CompatExpectSelector SEL = T, typename std::enable_if<SEL == CompatExpectSelector::EQ, int>::type = 0>
++  CompatExpect<T> &operator ()(const T1 &val1, const T2 &val2) {
++    gtest_ar = ::testing::internal::EqHelper::Compare("val1", "val2", val1, val2);
++    return *this;
++  }
++
++  template <typename T1, typename T2, CompatExpectSelector SEL = T, typename std::enable_if<SEL == CompatExpectSelector::NE, int>::type = 0>
++  CompatExpect<T> &operator ()(const T1 &val1, const T2 &val2) {
++    gtest_ar = ::testing::internal::CmpHelperNE("val1", "val2", val1, val2);
++    return *this;
++  }
++
++  template <typename T1, typename T2, CompatExpectSelector SEL = T, typename std::enable_if<SEL == CompatExpectSelector::LE, int>::type = 0>
++  CompatExpect<T> &operator ()(const T1 &val1, const T2 &val2) {
++    gtest_ar = ::testing::internal::CmpHelperLE("val1", "val2", val1, val2);
++    return *this;
++  }
++
++  template <typename T1, typename T2, CompatExpectSelector SEL = T, typename std::enable_if<SEL == CompatExpectSelector::LT, int>::type = 0>
++  CompatExpect<T> &operator ()(const T1 &val1, const T2 &val2) {
++    gtest_ar = ::testing::internal::CmpHelperLT("val1", "val2", val1, val2);
++    return *this;
++  }
++
++  template <typename T1, typename T2, CompatExpectSelector SEL = T, typename std::enable_if<SEL == CompatExpectSelector::GE, int>::type = 0>
++  CompatExpect<T> &operator ()(const T1 &val1, const T2 &val2) {
++    gtest_ar = ::testing::internal::CmpHelperGE("val1", "val2", val1, val2);
++    return *this;
++  }
++
++  template <typename T1, typename T2, CompatExpectSelector SEL = T, typename std::enable_if<SEL == CompatExpectSelector::GT, int>::type = 0>
++  CompatExpect<T> &operator ()(const T1 &val1, const T2 &val2) {
++    gtest_ar = ::testing::internal::CmpHelperGT("val1", "val2", val1, val2);
++    return *this;
++  }
++
++  template <typename T1>
++  CompatExpect &operator << (const T1 &t) {
++    msg << t;
++    return *this;
++  }
++};
++#define EXPECT_EQ ::testing::CompatExpect<::testing::CompatExpectSelector::EQ>{__FILE__,__LINE__}
++#define EXPECT_NE ::testing::CompatExpect<::testing::CompatExpectSelector::NE>{__FILE__,__LINE__}
++#define EXPECT_LE ::testing::CompatExpect<::testing::CompatExpectSelector::LE>{__FILE__,__LINE__}
++#define EXPECT_LT ::testing::CompatExpect<::testing::CompatExpectSelector::LT>{__FILE__,__LINE__}
++#define EXPECT_GE ::testing::CompatExpect<::testing::CompatExpectSelector::GE>{__FILE__,__LINE__}
++#define EXPECT_GT ::testing::CompatExpect<::testing::CompatExpectSelector::GT>{__FILE__,__LINE__}
++
++#else
++
+ #define EXPECT_EQ(val1, val2) \
+   EXPECT_PRED_FORMAT2(::testing::internal::EqHelper::Compare, val1, val2)
+ #define EXPECT_NE(val1, val2) \
+@@ -2053,6 +2127,8 @@ class TestWithParam : public Test, publi
+ #define EXPECT_GT(val1, val2) \
+   EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperGT, val1, val2)
+ 
++#endif
++
+ #define GTEST_ASSERT_EQ(val1, val2) \
+   ASSERT_PRED_FORMAT2(::testing::internal::EqHelper::Compare, val1, val2)
+ #define GTEST_ASSERT_NE(val1, val2) \

+ 3 - 0
3rdparty/rapidyaml/ext/c4core/cmake/requirements_doc.txt

@@ -0,0 +1,3 @@
+sphinx
+sphinx_rtd_theme
+breathe

Some files were not shown because too many files changed in this diff