From dc04c86561bbe8cdaeabef1e94ecd80484e85020 Mon Sep 17 00:00:00 2001 From: Caitlin Ross Date: Wed, 1 Jul 2026 16:32:28 -0500 Subject: [PATCH 1/5] third-party: add third party infrastructure and vendor RapidYAML The planned YAML/JSON config front-end needs a YAML parser. Since the legacy config will be phased out, we vendor RapidYAML as an always-built single-header library. Follows the Kitware third-party update convention (as used across VTK, ParaView, etc): thirdparty/update-common.sh is Kitware's shared import script and thirdparty/rapidyaml/update.sh is the per-package driver that re-imports the pinned release's amalgamation. Code from vendored libraries should NOT be updated by hand, but only using the update scripts. --- CMakeLists.txt | 4 + thirdparty/CMakeLists.txt | 25 +++ thirdparty/UPDATING.md | 172 ++++++++++++++++++ thirdparty/rapidyaml/CMakeLists.txt | 20 ++ thirdparty/rapidyaml/README.md | 21 +++ thirdparty/rapidyaml/codes_ryml.hpp | 17 ++ thirdparty/rapidyaml/ryml.cpp | 8 + thirdparty/rapidyaml/update.sh | 54 ++++++ thirdparty/update-common.sh | 271 ++++++++++++++++++++++++++++ 9 files changed, 592 insertions(+) create mode 100644 thirdparty/CMakeLists.txt create mode 100644 thirdparty/UPDATING.md create mode 100644 thirdparty/rapidyaml/CMakeLists.txt create mode 100644 thirdparty/rapidyaml/README.md create mode 100644 thirdparty/rapidyaml/codes_ryml.hpp create mode 100644 thirdparty/rapidyaml/ryml.cpp create mode 100755 thirdparty/rapidyaml/update.sh create mode 100755 thirdparty/update-common.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index d308e9dd..b24f1051 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -261,6 +261,10 @@ else() message(STATUS "ZeroMQ director-client/zmqml surrogate disabled") endif() + +# Vendored third-party dependencies (built before src, which links them). +add_subdirectory(thirdparty) + add_subdirectory(src) diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt new file mode 100644 index 00000000..f0368929 --- /dev/null +++ b/thirdparty/CMakeLists.txt @@ -0,0 +1,25 @@ +# Vendored third-party dependencies. +# +# Each subdirectory is an upstream drop managed with the Kitware third-party +# update convention (the shared update-common.sh here, driven by a per-package +# update.sh) plus a thin CMake wrapper that exposes a codes::thirdparty:: +# target. See each package's README.md for its upstream origin, pinned +# version, and how to re-import it. + +# Disable warnings in thirdparty code +if(CMAKE_C_COMPILER_ID MATCHES + "^(GNU|Clang|AppleClang|XLClang|XL|VisualAge|SunPro|HP|Intel|IntelLLVM|PGI|NVIDIA|NVHPC)$") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -w") +endif() +if(CMAKE_CXX_COMPILER_ID MATCHES + "^(GNU|Clang|AppleClang|XLClang|XL|VisualAge|SunPro|HP|Intel|IntelLLVM|PGI|NVIDIA|NVHPC)$") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -w") +endif() + +function(message_start_thirdparty) + get_filename_component(tp_base "${CMAKE_CURRENT_LIST_DIR}" NAME_WE) + message("") + message(STATUS "CODES ThirdParty: Configuring ${tp_base}") +endfunction() + +add_subdirectory(rapidyaml) diff --git a/thirdparty/UPDATING.md b/thirdparty/UPDATING.md new file mode 100644 index 00000000..dca410f1 --- /dev/null +++ b/thirdparty/UPDATING.md @@ -0,0 +1,172 @@ +# Updating Third Party Projects + +CODES vendors a small number of third-party libraries under `thirdparty/`. Each +one is a reduced copy of an upstream project, imported with the Kitware +third-party update convention so that every change we carry is tracked as an +explicit, diffable commit rather than an ad-hoc edit. + +This document explains how that machinery works: the +directory layout, how to re-import a project at a new version, and how to add a +new one. + +## The two scripts + +The update machinery is split in two: + +- **`update-common.sh`** — the shared engine. It does all the real work: clone + upstream, check out the requested version, run the package's extraction, and + merge the result into our tree as a single import commit. This file is + **Kitware's, vendored verbatim** (see the license header). Do **not** fork or + hand-edit it — keeping it pristine is what lets us pull in upstream fixes to + the convention itself with a clean diff. +- **`/update.sh`** — a thin per-package driver. It sets a handful of + metadata variables, defines an `extract_source` function, and then sources + `update-common.sh`. This is the only script you normally edit, and usually the + only line you touch is `tag`. + +## Anatomy of a vendored package + +Each package directory is **nested**, and the distinction is important: + +``` +thirdparty/ + googletest/ <- CODES-maintained "wrapper" directory + CMakeLists.txt <- thin wrapper exposing a codes::thirdparty:: target + README.md <- package-specific notes + update.sh <- the per-package driver + googletest/ <- the imported subtree (pristine upstream, do not edit) + CMakeLists.txt + include/ src/ ... +``` + +The **outer** directory (`thirdparty//`) holds files CODES maintains: the +wrapper `CMakeLists.txt`, the `README.md`, the `update.sh` driver, and any glue +code (for example rapidyaml's `codes_ryml.hpp` shim and `ryml.cpp` translation +unit). These are **preserved across re-imports**. + +The **inner** directory (`thirdparty///`) is the imported subtree — +this is the `subtree` variable in `update.sh`. It is a pristine drop of upstream +and is **replaced wholesale** on every import. + +**Never edit files in the inner subtree directly.** They will be overwritten on +the next re-import, and the change will be lost. Changes belong upstream (see +below); local glue belongs in the outer wrapper directory. + +## Prerequisites + +- **Git 2.5 or newer** — the engine uses `git worktree` to make the imported + commits available to the main checkout. +- **`python3`** — for projects whose `extract_source` generates code. RapidYAML, + for example, regenerates its single-header amalgamation with upstream's + `tools/amalgamate.py` (standard library only; no extra packages). +- **`git-archive-all`** — only for projects that vendor a subset of an upstream + tree that *contains submodules*, via the engine's `git_archive_all` helper. + No current packages need it. Install with `pip install git-archive-all` + and make sure the installed script (often `$HOME/.local/bin`) is on your + `PATH`. + +## Updating an existing project + +### 1. Get the change upstream first + +Any *code* change to a vendored library should first go to the upstream project, +using whatever workflow they require. Only once it is accepted do we bring it +into CODES. If an important fix stalls upstream, we can temporarily point `tag` +at a fork/branch carrying the patch, but the goal is always to track upstream. + +### 2. Bump the version and re-import + +The pinned version lives in exactly one place: the `tag` variable in the +package's `update.sh`. It is usually a release tag but may be `master`, a branch, +or any commit SHA. + +```sh +# from the repo root +$ git checkout -b update_ryml_YYYY_MM_DD +# edit `tag` in thirdparty/rapidyaml/update.sh, then commit that edit +$ git commit -am 'thirdparty: bump rapidyaml to ' +# now run the driver +$ ./thirdparty/rapidyaml/update.sh +``` + +`update.sh` creates the import commit(s) for you. Dating the branch name is not +required; it just avoids collisions if you have an old update branch sitting around. + +### 3. Validate, then open a PR + +Re-configure and build to confirm the new drop compiles, and run the relevant +tests (for example, a `BUILD_TESTING` build exercises GoogleTest). Then review +the import commit and open a pull request from the branch as normal. + +## Adding a new project + +Create the package's outer directory and write its `update.sh`, filling in the +metadata the engine expects (all documented at the top of `update-common.sh`): + +- `name` — the project name. +- `ownership` — a git author `Name ` for the import commits. Use the + `" Upstream "` convention. **Keep this stable across + imports** (see "How re-import finds the previous drop" below). +- `subtree` — where the import lands, i.e. the nested + `thirdparty//` path. +- `repo` — the upstream git URL. +- `tag` — the version to import. +- `shortlog` — optional; `true` to include an upstream shortlog in the commit + message. +- `exact_tree_match` — see below; reduced or generated imports must set this to + `false`. + +The key piece is the **`extract_source`** function, run inside a checkout of +upstream at `tag`. It must place the desired tree at `$extractdir/$name-reduced`. +If you only need to keep a subset of upstream's files verbatim, set the `paths` +variable and call the provided `git_archive` helper (or `git_archive_all` for +submoduled upstreams). If you need to generate or transform files, do that +instead (see rapidyaml's `update.sh` for a generated example). + +Make `update.sh` executable and stage it before committing: + +```sh +$ chmod u+x thirdparty//update.sh && git add thirdparty//update.sh +``` + +Finally, add the package to `thirdparty/CMakeLists.txt` and give it a wrapper +`CMakeLists.txt` and `README.md`. + +## How it works under the hood + +When you run a package's `update.sh`, the engine: + +1. Clones upstream (recursively, for submodules) and checks out `tag`. +2. Runs your `extract_source`, producing `$name-reduced`. +3. Commits that tree as a **single squashed import commit** on a temporary + `upstream-` branch, authored by `ownership` and dated to match + upstream. +4. Merges that branch onto the real tree under `subtree/` (a subtree merge for + an existing package, or an initial subtree read for a brand-new one) and + deletes the temporary branch. + +### The import-commit contract + +Import commits have a summary of the form `name YYYY-MM-DD (shorthash)`. This +is not cosmetic — re-import uses it to locate the previous drop (see below), so +don't reword these commits. + +### How re-import finds the previous drop + +To merge a new version onto the old one, the engine has to find the previous +import commit. It does this by searching history for commits authored by +`ownership` whose message matches the `name YYYY-MM-DD (hash)` pattern. Two +consequences: + +- **`ownership` must stay identical between imports.** Change it and the engine + can't find the prior drop; it will treat the re-import as a fresh initial + import (or fail outright). +- **`exact_tree_match`** controls how strictly the match is made. The default + (`true`) additionally requires the committed tree object to match what's + currently in the checkout — correct only when you vendor upstream's tree + *verbatim*. If `extract_source` vendors a **reduced subset** or **generates** + files (as both current packages do), the trees won't match, so you must set + `exact_tree_match=false` to fall back to log-based matching. The trap is + delayed: the initial import succeeds regardless, and only the *first + re-import* fails with "No previous import commit found" if this is left at the + default. diff --git a/thirdparty/rapidyaml/CMakeLists.txt b/thirdparty/rapidyaml/CMakeLists.txt new file mode 100644 index 00000000..f998f1a2 --- /dev/null +++ b/thirdparty/rapidyaml/CMakeLists.txt @@ -0,0 +1,20 @@ +# RapidYAML +# +# Vendored as the upstream single-header amalgamation (see README.md and +# the rapidyaml/ subdirectory). It is always built: the config front-end is core, +# not an optional feature. +# +# We deliberately do NOT create a linkable target here. codes is a STATIC library +# in an install/export set, and linking any in-project target into it would force +# that target into the export set too -- the build-tree export() evaluates +# generator expressions, so even a $ build-only link counts. +# Instead we publish the amalgamation's implementation TU and include dirs, and +# src/ compiles the TU straight into codes with these headers on its PRIVATE +# include path. The ryml objects then live inside codes.a with nothing extra to +# install, export, or link downstream. +message_start_thirdparty() +set(CODES_RYML_IMPL_TU "${CMAKE_CURRENT_SOURCE_DIR}/ryml.cpp" + CACHE INTERNAL "RapidYAML amalgamation implementation translation unit") +set(CODES_RYML_INCLUDE_DIRS + "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/rapidyaml" + CACHE INTERNAL "RapidYAML amalgamation include directories (shim + upstream header)") diff --git a/thirdparty/rapidyaml/README.md b/thirdparty/rapidyaml/README.md new file mode 100644 index 00000000..2dbc0cef --- /dev/null +++ b/thirdparty/rapidyaml/README.md @@ -0,0 +1,21 @@ +# RapidYAML + +A reduced copy of [RapidYAML][ryml] (ryml) vendored into CODES to back the +YAML/JSON configuration front-end. + +See [../UPDATING.md](../UPDATING.md) for the vendoring layout and how to +re-import. In short: do not edit the imported `rapidyaml/` subtree directly; the +pinned version is the `tag` in `update.sh`, and re-importing is `./update.sh` +after bumping it. + +## How CODES builds it + +A single-header amalgamation must have its implementation compiled in exactly one +translation unit. `ryml.cpp` is that TU (it defines `RYML_SINGLE_HDR_DEFINE_NOW` +and includes the header), and the wrapper `CMakeLists.txt` compiles it straight +into the `codes` library — `codes` is an installed/exported static library, so it +can't link a separate in-tree lib without dragging it into the export set. +Consumers just `#include `, the stable shim that forwards to +`ryml_all.hpp`. + +[ryml]: https://github.com/biojppm/rapidyaml diff --git a/thirdparty/rapidyaml/codes_ryml.hpp b/thirdparty/rapidyaml/codes_ryml.hpp new file mode 100644 index 00000000..05ac8a5b --- /dev/null +++ b/thirdparty/rapidyaml/codes_ryml.hpp @@ -0,0 +1,17 @@ +/* + * Stable include point for the vendored RapidYAML amalgamation. + * + * Consumers #include instead of the amalgamation header + * directly. The amalgamation has a stable, unversioned name (ryml_all.hpp, + * regenerated in place by thirdparty/rapidyaml/update.sh), so a version bump + * touches nothing here — not the call sites and not even this line. + * + * Do not edit the amalgamation itself — it is a generated upstream drop. See + * README.md. + */ +#ifndef CODES_THIRDPARTY_RYML_HPP +#define CODES_THIRDPARTY_RYML_HPP + +#include + +#endif /* CODES_THIRDPARTY_RYML_HPP */ diff --git a/thirdparty/rapidyaml/ryml.cpp b/thirdparty/rapidyaml/ryml.cpp new file mode 100644 index 00000000..92121a21 --- /dev/null +++ b/thirdparty/rapidyaml/ryml.cpp @@ -0,0 +1,8 @@ +/* + * The single translation unit that compiles the vendored RapidYAML + * amalgamation's implementation. Every other consumer includes + * without this define and gets only declarations, so the ~44k-line + * implementation is compiled exactly once. See README.md. + */ +#define RYML_SINGLE_HDR_DEFINE_NOW +#include diff --git a/thirdparty/rapidyaml/update.sh b/thirdparty/rapidyaml/update.sh new file mode 100755 index 00000000..17d12944 --- /dev/null +++ b/thirdparty/rapidyaml/update.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash + +#============================================================================= +# Re-import the vendored RapidYAML amalgamation from upstream. +# +# Usage (run from anywhere in the checkout): +# +# ./thirdparty/rapidyaml/update.sh +# +# This follows the Kitware third-party update convention: it sets the variables +# below and defines extract_source, then sources the shared update-common.sh, +# which clones upstream at $tag and merges the extracted tree onto the +# $subtree/ subtree as a single import commit. +# +# To move to a new version: change $tag below — a release tag OR any branch/commit +# SHA — and re-run. Nothing else needs editing: the amalgamation is regenerated in +# place under the stable name ryml_all.hpp, which codes_ryml.hpp includes. +#============================================================================= + +set -e +shopt -s dotglob + +readonly name="rapidyaml" +readonly ownership="RapidYAML Upstream " +readonly subtree="thirdparty/rapidyaml/rapidyaml" +readonly repo="https://github.com/biojppm/rapidyaml.git" +readonly tag="v0.15.2" +readonly shortlog="false" +# The imported tree is the generated amalgamation, not a subset of upstream's own +# tree, so tree-object matching against upstream can't find the previous import; +# fall back to log-based matching on the import commit message. +readonly exact_tree_match="false" + +extract_source () { + # RapidYAML's single-header amalgamation is a generated artifact (upstream does + # not commit it to their tree). Rather than downloading the pre-built header from + # a GitHub *release* — which exists only for tagged versions — regenerate it from + # the checked-out ref with upstream's own tool, so $tag can be a release tag OR an + # arbitrary branch/commit (e.g. an unreleased upstream fix). + # + # tools/amalgamate.py is pure python3 (standard library only) and imports its + # helpers from ext/c4core, so it needs nothing but python3 plus the recursive + # checkout update-common.sh already produces. Its defaults (c4core + fastfloat + + # stl, the tree event handler) are exactly what the released amalgamation ships, + # so the output matches the release artifact. The LICENSEs live in the tagged + # checkout (c4core is a submodule); extract_source runs with that checkout as its + # working dir. + mkdir -p "$extractdir/$name-reduced" + cp -v LICENSE.txt "$extractdir/$name-reduced/LICENSE.txt" + cp -v ext/c4core/LICENSE.txt "$extractdir/$name-reduced/LICENSE.c4core.txt" + python3 tools/amalgamate.py "$extractdir/$name-reduced/ryml_all.hpp" +} + +. "${BASH_SOURCE%/*}/../update-common.sh" diff --git a/thirdparty/update-common.sh b/thirdparty/update-common.sh new file mode 100755 index 00000000..0fea65a3 --- /dev/null +++ b/thirdparty/update-common.sh @@ -0,0 +1,271 @@ +#============================================================================= +# Copyright 2015-2016 Kitware, Inc. +# +# 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 +# +# https://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. +#============================================================================= + +set -e + +# Disable noise from `pre-commit` when no configuration is present on the +# imported tree. +export PRE_COMMIT_ALLOW_NO_CONFIG=1 + +######################################################################## +# Script for updating third party packages. +# +# This script should be sourced in a project-specific script which sets +# the following variables: +# +# name +# The name of the project. +# ownership +# A git author name/email for the commits. +# subtree +# The location of the third-party package within the main source +# tree. +# repo +# The git repository to use as upstream. +# tag +# The tag, branch or commit hash to use for upstream. +# shortlog +# Optional. Set to 'true' to get a shortlog in the commit message. +# exact_tree_match +# Optional. Set to 'false' to disable tree-object based matching for +# previous import commit (required for projects that allow modifying +# imported trees). In such cases, log-based searching is performed. +# +# Additionally, an "extract_source" function must be defined. It will be +# run within the checkout of the project on the requested tag. It should +# should place the desired tree into $extractdir/$name-reduced. This +# directory will be used as the newest commit for the project. +# +# For convenience, the function may use the "git_archive" function which +# does a standard "git archive" extraction using the (optional) "paths" +# variable to only extract a subset of the source tree. +# +# Dependencies +# +# To update third party packages from git repositories with submodule, +# you will need to install the "git-archive-all" Python package with +# +# pip install git-archive-all +# +# or install it from https://github.com/Kentzo/git-archive-all. +# +# This package installs a script named "git-archive-all" where pip +# installs executables. If you run pip under your user privileges (i.e., +# not using "sudo"), this location may be $HOME/.local/bin. Make sure +# that directory is in your path so that git can find the +# "git-archive-all" script. +# +######################################################################## + +######################################################################## +# Utility functions +######################################################################## +git_archive () { + git archive --worktree-attributes --prefix="$name-reduced/" HEAD -- $paths | \ + tar -C "$extractdir" -x +} + +confirm_archive_all_exists () { + which git-archive-all || die "git requires an archive-all command. Please run 'pip install git-archive-all'" +} + +git_archive_all () { + confirm_archive_all_exists + local tmptarball="temp.tar" + git archive-all --prefix="" "$tmptarball" + mkdir -p "$extractdir/$name-reduced" + tar -C "$extractdir/$name-reduced" -xf "$tmptarball" $paths + rm -f "$tmptarball" +} + +disable_custom_gitattributes() { + pushd "${extractdir}/${name}-reduced" + # Git does not allow custom attributes in a subdirectory where we + # are about to merge the `.gitattributes` file, so disable them. + sed -i.bak -e '/^\[attr\]/ {s/^/#/;}' .gitattributes + rm .gitattributes.bak + popd +} + +die () { + echo >&2 "$@" + exit 1 +} + +warn () { + echo >&2 "warning:" "$@" +} + +readonly regex_date='20[0-9][0-9]-[0-9][0-9]-[0-9][0-9]' +readonly basehash_regex="$name $regex_date ([0-9a-f]*)" +toplevel_dir="$( git rev-parse --show-toplevel )" +readonly toplevel_dir + +cd "$toplevel_dir" + +######################################################################## +# Sanity checking +######################################################################## +[ -n "$name" ] || \ + die "'name' is empty" +[ -n "$ownership" ] || \ + die "'ownership' is empty" +[ -n "$subtree" ] || \ + die "'subtree' is empty" +[ -n "$repo" ] || \ + die "'repo' is empty" +[ -n "$tag" ] || \ + die "'tag' is empty" +[ -n "$exact_tree_match" ] || \ + exact_tree_match=true + +# Check for an empty destination directory on disk. By checking on disk and +# not in the repo it allows a library to be freshly re-initialized in a single +# commit rather than first deleting the old copy in one commit and adding the +# new copy in a separate commit. +if [ ! -d "$( git rev-parse --show-toplevel )/$subtree" ]; then + basehash="" +elif $exact_tree_match; then + # Find the tree object for the current subtree. + current_tree="$( git rev-parse "HEAD:$subtree" )" + # Search history for a commit whose subtree matches this tree object. + basehash="" + # Limit candidate commits to those with expected import commit messages for efficiency. + for commit in $( git rev-list --author="$ownership" --grep="$basehash_regex" HEAD ); do + imported_tree="$( git rev-parse "$commit^{tree}" )" + # Verify the imported tree is what is currently imported. If so, we + # have found the desired import commit. + if [ "$imported_tree" = "$current_tree" ] && [ -n "$imported_tree" ]; then + basehash="$commit" + break + fi + done + if [ -z "$basehash" ]; then + die "No previous import commit found with matching tree object for $subtree. (exact_tree_match enabled)" + fi +else + basehash="$( git rev-list --author="$ownership" --grep="$basehash_regex" -n 1 HEAD )" +fi +readonly basehash + +[ -n "$basehash" ] || \ + warn "'basehash' is empty; performing initial import" +readonly do_shortlog="${shortlog-false}" + +readonly workdir="$PWD/work" +readonly upstreamdir="$workdir/upstream" +readonly extractdir="$workdir/extract" + +[ -d "$workdir" ] && \ + die "error: workdir '$workdir' already exists" + +trap "rm -rf '$workdir'" EXIT + +# Skip LFS downloading; imports should not need LFS data. +export GIT_LFS_SKIP_SMUDGE=1 + +# Get upstream +git clone --recursive "$repo" "$upstreamdir" + +if [ -n "$basehash" ]; then + # Remove old worktrees + git worktree prune + # Use the existing package's history + git worktree add "$extractdir" "$basehash" + # Clear out the working tree + pushd "$extractdir" + git ls-files -z --recurse-submodules | xargs -0 rm -v + find . -type d -empty -delete + popd +else + # Create a repo to hold this package's history + mkdir -p "$extractdir" + git -C "$extractdir" init +fi + +# Extract the subset of upstream we care about +pushd "$upstreamdir" +git checkout "$tag" +git submodule sync --recursive +git submodule update --recursive --init +upstream_hash="$( git rev-parse HEAD )" +readonly upstream_hash +upstream_hash_short="$( git rev-parse --short=8 "$upstream_hash" )" +readonly upstream_hash_short +upstream_datetime="$( git rev-list "$upstream_hash" --format='%ci' -n 1 | grep -e "^$regex_date" )" +readonly upstream_datetime +upstream_date="$( echo "$upstream_datetime" | grep -o -e "$regex_date" )" +readonly upstream_date +if $do_shortlog && [ -n "$basehash" ]; then + upstream_old_short="$( git -C "$toplevel_dir" cat-file commit "$basehash" | sed -n '/'"$basehash_regex"'/ {s/.*(//;s/)//;p;}' | grep -E '^[0-9a-f]+$' )" + readonly upstream_old_short + + commit_shortlog=" + +Upstream Shortlog +----------------- + +$( git shortlog --no-merges --abbrev=8 --format='%h %s' "$upstream_old_short".."$upstream_hash" )" +else + commit_shortlog="" +fi +readonly commit_shortlog +extract_source || \ + die "failed to extract source" +popd + +[ -d "$extractdir/$name-reduced" ] || \ + die "expected directory to extract does not exist" +readonly commit_summary="$name $upstream_date ($upstream_hash_short)" + +# Commit the subset +pushd "$extractdir" +mv -v "$name-reduced/"* . +rmdir "$name-reduced/" +git add -A . +git commit --no-verify --no-post-rewrite --author="$ownership" --date="$upstream_datetime" -F - <<-EOF +$commit_summary + +Code extracted from: + + $repo + +at commit $upstream_hash ($tag).$commit_shortlog +EOF +git branch -f "upstream-$name" +popd + +# Merge the subset into this repository +if [ -n "$basehash" ]; then + git merge --log -s recursive "-Xsubtree=$subtree/" --no-commit "upstream-$name" +else + # Note: on Windows 'git merge --help' will open a browser, and the check + # will fail, so use the flag by default. Apple opens an editor instead; + # assume the flag is understood there too. + unrelated_histories_flag="" + if git --version | grep -q -E 'windows|Apple'; then + unrelated_histories_flag="--allow-unrelated-histories" + elif git merge --help | grep -q -e allow-unrelated-histories; then + unrelated_histories_flag="--allow-unrelated-histories" + fi + readonly unrelated_histories_flag + + git fetch "$extractdir" "+upstream-$name:upstream-$name" + git merge --log -s ours --no-commit $unrelated_histories_flag "upstream-$name" + git read-tree -u --prefix="$subtree/" "upstream-$name" +fi +git commit --no-edit +git branch -d "upstream-$name" From 7a906ae3f0d9243f2d2d916ab52162b96a62de73 Mon Sep 17 00:00:00 2001 From: RapidYAML Upstream Date: Mon, 8 Jun 2026 14:31:29 +0100 Subject: [PATCH 2/5] rapidyaml 2026-06-08 (85bfe56e) Code extracted from: https://github.com/biojppm/rapidyaml.git at commit 85bfe56e32b1cfe715aa1c39885b1b10761e7482 (v0.15.2). --- LICENSE.c4core.txt | 20 + LICENSE.txt | 20 + ryml_all.hpp | 51093 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 51133 insertions(+) create mode 100644 LICENSE.c4core.txt create mode 100644 LICENSE.txt create mode 100644 ryml_all.hpp diff --git a/LICENSE.c4core.txt b/LICENSE.c4core.txt new file mode 100644 index 00000000..47b6b439 --- /dev/null +++ b/LICENSE.c4core.txt @@ -0,0 +1,20 @@ +Copyright (c) 2018, Joao Paulo Magalhaes + +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. + diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 00000000..47b6b439 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2018, Joao Paulo Magalhaes + +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. + diff --git a/ryml_all.hpp b/ryml_all.hpp new file mode 100644 index 00000000..ef78f07c --- /dev/null +++ b/ryml_all.hpp @@ -0,0 +1,51093 @@ +#ifndef _RYML_SINGLE_HEADER_AMALGAMATED_HPP_ +#define _RYML_SINGLE_HEADER_AMALGAMATED_HPP_ + +// +// Rapid YAML - a library to parse and emit YAML, and do it fast. +// +// https://github.com/biojppm/rapidyaml +// +// DO NOT EDIT. This file is generated automatically. +// This is an amalgamated single-header version of the library. +// +// INSTRUCTIONS: +// +// - Include at will in any header of your project. Because the +// amalgamated header file is large, to speed up compilation of +// your project, protect the include with its include guard +// `_RYML_SINGLE_HEADER_AMALGAMATED_HPP_`, ie like this: +// ``` +// #ifndef _RYML_SINGLE_HEADER_AMALGAMATED_HPP_ +// #include +// #endif +// ``` +// +// - In one -- and only one -- of your project source files, #define +// RYML_SINGLE_HDR_DEFINE_NOW and then include this header. This will enable +// the function and class definitions in the header file. +// +// - To compile into a shared library, define the preprocessor symbol +// RYML_SHARED before including the header. This will take care of +// symbol export/import. +// +// + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// LICENSE.txt +//-------------------------------------------------------------------------------- +//******************************************************************************** + +// Copyright (c) 2018, Joao Paulo Magalhaes +// +// 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. +// + + // shared library: export when defining +#if defined(RYML_SHARED) && defined(RYML_SINGLE_HDR_DEFINE_NOW) && !defined(RYML_EXPORTS) +#define RYML_EXPORTS +#endif + + + // propagate defines to c4core +#if defined(RYML_SINGLE_HDR_DEFINE_NOW) && !defined(C4CORE_SINGLE_HDR_DEFINE_NOW) +#define C4CORE_SINGLE_HDR_DEFINE_NOW +#endif + +#if defined(RYML_EXPORTS) && !defined(C4CORE_EXPORTS) +#define C4CORE_EXPORTS +#endif + +#if defined(RYML_SHARED) && !defined(C4CORE_SHARED) +#define C4CORE_SHARED +#endif + +// workaround for include removal while amalgamating +// resulting in missing in arm-none-eabi-g++ +// https://github.com/biojppm/rapidyaml/issues/193 +#include + + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/c4core_all.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4CORE_SINGLE_HEADER_AMALGAMATED_HPP_ +#define _C4CORE_SINGLE_HEADER_AMALGAMATED_HPP_ + +// +// c4core - C++ utilities +// +// https://github.com/biojppm/c4core +// +// DO NOT EDIT. This file is generated automatically. +// This is an amalgamated single-header version of the library. +// +// INSTRUCTIONS: +// - 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 this header. +// This will enable the function and class definitions in +// the header file. +// - To compile into a shared library, just define the +// preprocessor symbol C4CORE_SHARED . This will take +// care of symbol export/import. +// + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// LICENSE.txt +//-------------------------------------------------------------------------------- +//******************************************************************************** + +// Copyright (c) 2018, Joao Paulo Magalhaes +// +// 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. +// + +// shared library: export when defining +#if defined(C4CORE_SHARED) && defined(C4CORE_SINGLE_HDR_DEFINE_NOW) && !defined(C4CORE_EXPORTS) +#define C4CORE_EXPORTS +#endif + + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/export.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef C4_EXPORT_HPP_ +#define C4_EXPORT_HPP_ + +#ifdef _WIN32 + #ifdef C4CORE_SHARED + #ifdef C4CORE_EXPORTS + #define C4CORE_EXPORT __declspec(dllexport) + #define C4CORE_EXPORT_EXTERN + #else + #define C4CORE_EXPORT __declspec(dllimport) + #define C4CORE_EXPORT_EXTERN extern + #endif + #else + #define C4CORE_EXPORT + #define C4CORE_EXPORT_EXTERN + #endif +#else + #define C4CORE_EXPORT + #define C4CORE_EXPORT_EXTERN +#endif + +#endif /* C4CORE_EXPORT_HPP_ */ + + +// (end src/c4/export.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/version.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_VERSION_HPP_ +#define _C4_VERSION_HPP_ + +/** @file version.hpp */ + +#define C4CORE_VERSION "0.4.0" +#define C4CORE_VERSION_MAJOR 0 +#define C4CORE_VERSION_MINOR 4 +#define C4CORE_VERSION_PATCH 0 + +// amalgamate: removed include of +// c4/export.hpp +//#include +#if !defined(C4_EXPORT_HPP_) && !defined(_C4_EXPORT_HPP_) +#error "amalgamate: file c4/export.hpp must have been included at this point" +#endif /* C4_EXPORT_HPP_ */ + + +namespace c4 { + +C4CORE_EXPORT const char* version(); +C4CORE_EXPORT int version_major(); +C4CORE_EXPORT int version_minor(); +C4CORE_EXPORT int version_patch(); + +} // namespace c4 + +#endif /* _C4_VERSION_HPP_ */ + + +// (end src/c4/version.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/preprocessor.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_PREPROCESSOR_HPP_ +#define _C4_PREPROCESSOR_HPP_ + +/** @file preprocessor.hpp Contains basic macros and preprocessor utilities. + * @ingroup basic_headers */ + +#ifdef __clang__ + /* NOTE: using , ## __VA_ARGS__ to deal with zero-args calls to + * variadic macros is not portable, but works in clang, gcc, msvc, icc. + * clang requires switching off compiler warnings for pedantic mode. + * @see http://stackoverflow.com/questions/32047685/variadic-macro-without-arguments */ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" // warning: token pasting of ',' and __VA_ARGS__ is a GNU extension +#elif defined(__GNUC__) + /* GCC also issues a warning for zero-args calls to variadic macros. + * This warning is switched on with -pedantic and apparently there is no + * easy way to turn it off as with clang. But marking this as a system + * header works. + * @see https://gcc.gnu.org/onlinedocs/cpp/System-Headers.html + * @see http://stackoverflow.com/questions/35587137/ */ +# pragma GCC system_header +#endif + +#define C4_WIDEN(str) L"" str + +#define C4_COUNTOF(arr) (sizeof(arr)/sizeof((arr)[0])) + +#define C4_EXPAND(arg) arg + +/** useful in some macro calls with template arguments */ +#define C4_COMMA , +/** useful in some macro calls with template arguments + * @see C4_COMMA */ +#define C4_COMMA_X C4_COMMA + +/** expand and quote */ +#define C4_XQUOTE(arg) _C4_XQUOTE(arg) +#define _C4_XQUOTE(arg) C4_QUOTE(arg) +#define C4_QUOTE(arg) #arg + +/** expand and concatenate */ +#define C4_XCAT(arg1, arg2) _C4_XCAT(arg1, arg2) +#define _C4_XCAT(arg1, arg2) C4_CAT(arg1, arg2) +#define C4_CAT(arg1, arg2) arg1##arg2 + +#define C4_VERSION_CAT(major, minor, patch) ((major)*10000 + (minor)*100 + (patch)) + +/** A preprocessor foreach. Spectacular trick taken from: + * http://stackoverflow.com/a/1872506/5875572 + * The first argument is for a macro receiving a single argument, + * which will be called with every subsequent argument. There is + * currently a limit of 32 arguments, and at least 1 must be provided. + * +Example: +@code{.cpp} +struct Example { + int a; + int b; + int c; +}; +// define a one-arg macro to be called +#define PRN_STRUCT_OFFSETS(field) PRN_STRUCT_OFFSETS_(Example, field) +#define PRN_STRUCT_OFFSETS_(structure, field) printf(C4_XQUOTE(structure) ":" C4_XQUOTE(field)" - offset=%zu\n", offsetof(structure, field)); + +// now call the macro for a, b and c +C4_FOR_EACH(PRN_STRUCT_OFFSETS, a, b, c); +@endcode */ +#define C4_FOR_EACH(what, ...) C4_FOR_EACH_SEP(what, ;, __VA_ARGS__) + +/** same as C4_FOR_EACH(), but use a custom separator between statements. + * If a comma is needed as the separator, use the C4_COMMA macro. + * @see C4_FOR_EACH + * @see C4_COMMA + */ +#define C4_FOR_EACH_SEP(what, sep, ...) _C4_FOR_EACH_(_C4_FOR_EACH_NARG(__VA_ARGS__), what, sep, __VA_ARGS__) + +/// @cond dev + +#define _C4_FOR_EACH_01(what, sep, x) what(x) sep +#define _C4_FOR_EACH_02(what, sep, x, ...) what(x) sep _C4_FOR_EACH_01(what, sep, __VA_ARGS__) +#define _C4_FOR_EACH_03(what, sep, x, ...) what(x) sep _C4_FOR_EACH_02(what, sep, __VA_ARGS__) +#define _C4_FOR_EACH_04(what, sep, x, ...) what(x) sep _C4_FOR_EACH_03(what, sep, __VA_ARGS__) +#define _C4_FOR_EACH_05(what, sep, x, ...) what(x) sep _C4_FOR_EACH_04(what, sep, __VA_ARGS__) +#define _C4_FOR_EACH_06(what, sep, x, ...) what(x) sep _C4_FOR_EACH_05(what, sep, __VA_ARGS__) +#define _C4_FOR_EACH_07(what, sep, x, ...) what(x) sep _C4_FOR_EACH_06(what, sep, __VA_ARGS__) +#define _C4_FOR_EACH_08(what, sep, x, ...) what(x) sep _C4_FOR_EACH_07(what, sep, __VA_ARGS__) +#define _C4_FOR_EACH_09(what, sep, x, ...) what(x) sep _C4_FOR_EACH_08(what, sep, __VA_ARGS__) +#define _C4_FOR_EACH_10(what, sep, x, ...) what(x) sep _C4_FOR_EACH_09(what, sep, __VA_ARGS__) +#define _C4_FOR_EACH_11(what, sep, x, ...) what(x) sep _C4_FOR_EACH_10(what, sep, __VA_ARGS__) +#define _C4_FOR_EACH_12(what, sep, x, ...) what(x) sep _C4_FOR_EACH_11(what, sep, __VA_ARGS__) +#define _C4_FOR_EACH_13(what, sep, x, ...) what(x) sep _C4_FOR_EACH_12(what, sep, __VA_ARGS__) +#define _C4_FOR_EACH_14(what, sep, x, ...) what(x) sep _C4_FOR_EACH_13(what, sep, __VA_ARGS__) +#define _C4_FOR_EACH_15(what, sep, x, ...) what(x) sep _C4_FOR_EACH_14(what, sep, __VA_ARGS__) +#define _C4_FOR_EACH_16(what, sep, x, ...) what(x) sep _C4_FOR_EACH_15(what, sep, __VA_ARGS__) +#define _C4_FOR_EACH_17(what, sep, x, ...) what(x) sep _C4_FOR_EACH_16(what, sep, __VA_ARGS__) +#define _C4_FOR_EACH_18(what, sep, x, ...) what(x) sep _C4_FOR_EACH_17(what, sep, __VA_ARGS__) +#define _C4_FOR_EACH_19(what, sep, x, ...) what(x) sep _C4_FOR_EACH_18(what, sep, __VA_ARGS__) +#define _C4_FOR_EACH_20(what, sep, x, ...) what(x) sep _C4_FOR_EACH_19(what, sep, __VA_ARGS__) +#define _C4_FOR_EACH_21(what, sep, x, ...) what(x) sep _C4_FOR_EACH_20(what, sep, __VA_ARGS__) +#define _C4_FOR_EACH_22(what, sep, x, ...) what(x) sep _C4_FOR_EACH_21(what, sep, __VA_ARGS__) +#define _C4_FOR_EACH_23(what, sep, x, ...) what(x) sep _C4_FOR_EACH_22(what, sep, __VA_ARGS__) +#define _C4_FOR_EACH_24(what, sep, x, ...) what(x) sep _C4_FOR_EACH_23(what, sep, __VA_ARGS__) +#define _C4_FOR_EACH_25(what, sep, x, ...) what(x) sep _C4_FOR_EACH_24(what, sep, __VA_ARGS__) +#define _C4_FOR_EACH_26(what, sep, x, ...) what(x) sep _C4_FOR_EACH_25(what, sep, __VA_ARGS__) +#define _C4_FOR_EACH_27(what, sep, x, ...) what(x) sep _C4_FOR_EACH_26(what, sep, __VA_ARGS__) +#define _C4_FOR_EACH_28(what, sep, x, ...) what(x) sep _C4_FOR_EACH_27(what, sep, __VA_ARGS__) +#define _C4_FOR_EACH_29(what, sep, x, ...) what(x) sep _C4_FOR_EACH_28(what, sep, __VA_ARGS__) +#define _C4_FOR_EACH_30(what, sep, x, ...) what(x) sep _C4_FOR_EACH_29(what, sep, __VA_ARGS__) +#define _C4_FOR_EACH_31(what, sep, x, ...) what(x) sep _C4_FOR_EACH_30(what, sep, __VA_ARGS__) +#define _C4_FOR_EACH_32(what, sep, x, ...) what(x) sep _C4_FOR_EACH_31(what, sep, __VA_ARGS__) +#define _C4_FOR_EACH_NARG(...) _C4_FOR_EACH_NARG_(__VA_ARGS__, _C4_FOR_EACH_RSEQ_N()) +#define _C4_FOR_EACH_NARG_(...) _C4_FOR_EACH_ARG_N(__VA_ARGS__) +#define _C4_FOR_EACH_ARG_N(_01, _02, _03, _04, _05, _06, _07, _08, _09, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, N, ...) N +#define _C4_FOR_EACH_RSEQ_N() 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 09, 08, 07, 06, 05, 04, 03, 02, 01 +#define _C4_FOR_EACH_(N, what, sep, ...) C4_XCAT(_C4_FOR_EACH_, N)(what, sep, __VA_ARGS__) + +/// @endcond + +#ifdef __clang__ +# pragma clang diagnostic pop +#endif + +#endif /* _C4_PREPROCESSOR_HPP_ */ + + +// (end src/c4/preprocessor.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/platform.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_PLATFORM_HPP_ +#define _C4_PLATFORM_HPP_ + +/** @file platform.hpp Provides platform information macros + * @ingroup basic_headers */ + +// see also https://sourceforge.net/p/predef/wiki/OperatingSystems/ + +#if defined(_WIN64) +# define C4_WIN +# define C4_WIN64 +#elif defined(_WIN32) +# define C4_WIN +# define C4_WIN32 +#elif defined(__ANDROID__) +# define C4_ANDROID +#elif defined(__APPLE__) +# include "TargetConditionals.h" +# if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR +# define C4_IOS +# elif TARGET_OS_MAC || TARGET_OS_OSX +# define C4_MACOS +# else +# error "Unknown Apple platform" +# endif +#elif defined(__linux__) || defined(__linux) +# define C4_UNIX +# define C4_LINUX +#elif defined(__unix__) || defined(__unix) +# define C4_UNIX +#elif defined(__arm__) || defined(__aarch64__) +# define C4_ARM +#elif defined(__xtensa__) || defined(__XTENSA__) +# define C4_XTENSA +#elif defined(SWIG) +# define C4_SWIG +#else +# error "unknown platform" +#endif + +#if defined(__posix) || defined(C4_UNIX) || defined(C4_LINUX) +# define C4_POSIX +#endif + + +#endif /* _C4_PLATFORM_HPP_ */ + + +// (end src/c4/platform.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/cpu.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_CPU_HPP_ +#define _C4_CPU_HPP_ + +/** @file cpu.hpp Provides processor information macros + * @ingroup basic_headers */ + +// see also https://sourceforge.net/p/predef/wiki/Architectures/ +// see also https://sourceforge.net/p/predef/wiki/Endianness/ +// see also https://github.com/googlesamples/android-ndk/blob/android-mk/hello-jni/jni/hello-jni.c +// see also http://code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/global/qprocessordetection.h + +#ifdef __ORDER_LITTLE_ENDIAN__ +# define _C4EL __ORDER_LITTLE_ENDIAN__ +#else +# define _C4EL 1234 +#endif + +#ifdef __ORDER_BIG_ENDIAN__ +# define _C4EB __ORDER_BIG_ENDIAN__ +#else +# define _C4EB 4321 +#endif + +// mixed byte order (eg, PowerPC or ia64) +#define _C4EM 1111 // NOLINT + + +// NOTE: to find defined macros in a platform, +// g++ -dM -E - = 8) \ + || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM >= 8) +# define C4_CPU_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(__ARM_ARCH_7EM__) \ + || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM >= 7) \ + || (defined(_M_ARM) && _M_ARM >= 7) +# define C4_CPU_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(__ARM_ARCH_6KZ__) \ + || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM >= 6) +# define C4_CPU_ARMV6 +# elif (defined(__ARM_ARCH) && __ARM_ARCH == 5) \ + || defined(__ARM_ARCH_5TEJ__) \ + || defined(__ARM_ARCH_5TE__) \ + || defined(__ARM_ARCH_5T__) \ + || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM >= 5) +# define C4_CPU_ARMV5 +# elif (defined(__ARM_ARCH) && __ARM_ARCH == 4) \ + || defined(__ARM_ARCH_4T__) \ + || defined(__ARM_ARCH_4__) \ + || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM >= 4) +# define C4_CPU_ARMV4 +# else +# error "unknown CPU architecture: ARM" +# endif +# endif +# if defined(__ARMEL__) || defined(__LITTLE_ENDIAN__) || defined(__AARCH64EL__) \ + || (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) \ + || defined(_MSC_VER) // winarm64 does not provide any of the above macros, + // but advises little-endianess: + // https://docs.microsoft.com/en-us/cpp/build/overview-of-arm-abi-conventions?view=msvc-170 + // So if it is visual studio compiling, we'll assume little endian. +# define C4_BYTE_ORDER _C4EL +# elif defined(__ARMEB__) || defined(__BIG_ENDIAN__) || defined(__AARCH64EB__) \ + || (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) +# define C4_BYTE_ORDER _C4EB +# elif defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_PDP_ENDIAN__) +# define C4_BYTE_ORDER _C4EM +# else +# error "unknown endianness" +# endif + +#elif defined(__ia64) || defined(__ia64__) || defined(_M_IA64) +# define C4_CPU_IA64 +# define C4_WORDSIZE 8 +# define C4_BYTE_ORDER _C4EM + // itanium is bi-endian - check byte order below + +#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__) +# define C4_CPU_PPC64 +# define C4_WORDSIZE 8 +# else +# define C4_CPU_PPC +# define C4_WORDSIZE 4 +# endif +# define C4_BYTE_ORDER _C4EM + +#elif defined(__s390x__) || defined(__zarch__) || defined(__SYSC_ZARCH_) +# define C4_CPU_S390_X +# define C4_WORDSIZE 8 +# define C4_BYTE_ORDER _C4EB + +#elif defined(__xtensa__) || defined(__XTENSA__) +# define C4_CPU_XTENSA +# define C4_WORDSIZE 4 +// not sure about this... +# if defined(__XTENSA_EL__) || defined(__xtensa_el__) +# define C4_BYTE_ORDER _C4EL +# else +# define C4_BYTE_ORDER _C4EB +# endif + +#elif defined(__riscv) +# if __riscv_xlen == 64 +# define C4_CPU_RISCV64 +# define C4_WORDSIZE 8 +# else +# define C4_CPU_RISCV32 +# define C4_WORDSIZE 4 +# endif +# define C4_BYTE_ORDER _C4EL + +#elif defined(__EMSCRIPTEN__) +# define C4_BYTE_ORDER _C4EL +# define C4_WORDSIZE 4 + +#elif defined(__loongarch__) +# if defined(__loongarch64) +# define C4_CPU_LOONGARCH64 +# define C4_WORDSIZE 8 +# else +# define C4_CPU_LOONGARCH +# define C4_WORDSIZE 4 +# endif +# define C4_BYTE_ORDER _C4EL + +#elif defined(__mips__) || defined(_mips) || defined(mips) +# if defined(__mips) +# if __mips == 64 +# define C4_CPU_MIPS64 +# define C4_WORDSIZE 8 +# elif __mips == 32 +# define C4_CPU_MIPS32 +# define C4_WORDSIZE 4 +# endif +# elif defined(__arch64__) || (defined(__SIZE_WIDTH__) && __SIZE_WIDTH__ == 64) || (defined(__LP64__) && __LP64__) +# define C4_CPU_MIPS64 +# define C4_WORDSIZE 8 +# elif defined(__arch32__) || (defined(__SIZE_WIDTH__) && __SIZE_WIDTH__ == 32) || (defined(__LP32__) && __LP32__) +# define C4_CPU_MIPS32 +# define C4_WORDSIZE 4 +# else +# error "unknown mips architecture" +# endif +# if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +# define C4_BYTE_ORDER _C4EB +# elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +# define C4_BYTE_ORDER _C4EL +# else +# error "unknown mips endianness" +# endif + +#elif defined(__sparc__) || defined(__sparc) || defined(sparc) +# if defined(__arch64__) || (defined(__SIZE_WIDTH__) && __SIZE_WIDTH__ == 64) || (defined(__LP64__) && __LP64__) +# define C4_CPU_SPARC64 +# define C4_WORDSIZE 8 +# elif defined(__arch32__) || (defined(__SIZE_WIDTH__) && __SIZE_WIDTH__ == 32) || (defined(__LP32__) && __LP32__) +# define C4_CPU_SPARC32 +# define C4_WORDSIZE 4 +# else +# error "unknown sparc architecture" +# endif +# define C4_BYTE_ORDER _C4EB + +#elif defined(SWIG) +# error "please define CPU architecture macros when compiling with swig" + +#else +# error "unknown CPU architecture" +#endif + +#define C4_LITTLE_ENDIAN (C4_BYTE_ORDER == _C4EL) +#define C4_BIG_ENDIAN (C4_BYTE_ORDER == _C4EB) +#define C4_MIXED_ENDIAN (C4_BYTE_ORDER == _C4EM) + +#endif /* _C4_CPU_HPP_ */ + + +// (end src/c4/cpu.hpp) + +// (amalgamate) these includes are needed to work around +// conditional includes in the gcc4.8 shim +#include +#include +#include + + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/gcc-4.8.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_GCC_4_8_HPP_ +#define _C4_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 +//included above: +//#include +//included above: +//#include + +namespace std { + +template +struct is_trivially_copyable : public integral_constant::value && __has_trivial_destructor(_Tp) && + (__has_trivial_constructor(_Tp) || __has_trivial_copy(_Tp) || __has_trivial_assign(_Tp))> +{ }; + +template +using is_trivially_copy_constructible = has_trivial_copy_constructor<_Tp>; + +template +using is_trivially_default_constructible = has_trivial_default_constructor<_Tp>; + +template +using is_trivially_copy_assignable = has_trivial_copy_assign<_Tp>; + +/* not supported */ +template +struct is_trivially_move_constructible : false_type +{ }; + +/* not supported */ +template +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(__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(__aligned); + } +} + +#if __GNUC__ == 4 && __GNUC_MINOR__ == 8 +typedef long double max_align_t ; +#endif + +} +#else // __cplusplus + +//included above: +//#include +// 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_GCC_4_8_HPP_ + + +// (end src/c4/gcc-4.8.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/compiler.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_COMPILER_HPP_ +#define _C4_COMPILER_HPP_ + +/** @file compiler.hpp Provides compiler information macros + * @ingroup basic_headers */ + +// amalgamate: removed include of +// c4/platform.hpp +//#include "c4/platform.hpp" +#if !defined(C4_PLATFORM_HPP_) && !defined(_C4_PLATFORM_HPP_) +#error "amalgamate: file c4/platform.hpp must have been included at this point" +#endif /* C4_PLATFORM_HPP_ */ + + +// Compilers: +// C4_MSVC +// Visual Studio 2026: MSVC++ 18, 1950 +// Visual Studio 2022: MSVC++ 17, 1930 +// Visual Studio 2019: MSVC++ 16, 1920 +// Visual Studio 2017: MSVC++ 15 +// Visual Studio 2015: MSVC++ 14 +// Visual Studio 2013: MSVC++ 13 +// Visual Studio 2013: MSVC++ 12 +// Visual Studio 2012: MSVC++ 11 +// Visual Studio 2010: MSVC++ 10 +// Visual Studio 2008: MSVC++ 09 +// Visual Studio 2005: MSVC++ 08 +// C4_CLANG +// C4_GCC +// C4_ICC (intel compiler) +/** @see http://sourceforge.net/p/predef/wiki/Compilers/ for a list of compiler identifier macros */ +/** @see https://msdn.microsoft.com/en-us/library/b0084kay.aspx for VS2013 predefined macros */ + +#if defined(_MSC_VER) +# define C4_MSVC +# define C4_MSVC_VERSION_2026 18 +# define C4_MSVC_VERSION_2022 17 +# define C4_MSVC_VERSION_2019 16 +# define C4_MSVC_VERSION_2017 15 +# define C4_MSVC_VERSION_2015 14 +# define C4_MSVC_VERSION_2013 12 +# define C4_MSVC_VERSION_2012 11 +# if _MSC_VER >= 1950 +# define C4_MSVC_VERSION C4_MSVC_VERSION_2026 // visual studio 2026 +# define C4_MSVC_2026 +# elif _MSC_VER >= 1930 +# define C4_MSVC_VERSION C4_MSVC_VERSION_2022 // visual studio 2022 +# define C4_MSVC_2022 +# elif _MSC_VER >= 1920 +# define C4_MSVC_VERSION C4_MSVC_VERSION_2019 // visual studio 2019 +# define C4_MSVC_2019 +# elif _MSC_VER >= 1910 +# define C4_MSVC_VERSION C4_MSVC_VERSION_2017 // visual studio 2017 +# define C4_MSVC_2017 +# elif _MSC_VER == 1900 +# define C4_MSVC_VERSION C4_MSVC_VERSION_2015 // visual studio 2015 +# define C4_MSVC_2015 +# elif _MSC_VER == 1800 +# error "MSVC version not supported" +# define C4_MSVC_VERSION C4_MSVC_VERSION_2013 // visual studio 2013 +# define C4_MSVC_2013 +# elif _MSC_VER == 1700 +# error "MSVC version not supported" +# define C4_MSVC_VERSION C4_MSVC_VERSION_2012 // visual studio 2012 +# define C4_MSVC_2012 +# elif _MSC_VER == 1600 +# error "MSVC version not supported" +# define C4_MSVC_VERSION 10 // visual studio 2010 +# define C4_MSVC_2010 +# elif _MSC_VER == 1500 +# error "MSVC version not supported" +# define C4_MSVC_VERSION 09 // visual studio 2008 +# define C4_MSVC_2008 +# elif _MSC_VER == 1400 +# error "MSVC version not supported" +# define C4_MSVC_VERSION 08 // visual studio 2005 +# define C4_MSVC_2005 +# else +# error "MSVC version not supported" +# endif // _MSC_VER +#else +# define C4_MSVC_VERSION 0 // visual studio not present +# define C4_GCC_LIKE +# ifdef __INTEL_COMPILER // check ICC before checking GCC, as ICC defines __GNUC__ too +# define C4_ICC +# define C4_ICC_VERSION __INTEL_COMPILER +# elif defined(__APPLE_CC__) +# define C4_XCODE +# if defined(__clang__) +# define C4_CLANG +# ifndef __apple_build_version__ +# define C4_CLANG_VERSION C4_VERSION_ENCODED(__clang_major__, __clang_minor__, __clang_patchlevel__) +# else +# define C4_CLANG_VERSION __apple_build_version__ +# endif +# else +# define C4_XCODE_VERSION __APPLE_CC__ +# endif +# elif defined(__clang__) +# define C4_CLANG +# ifndef __apple_build_version__ +# define C4_CLANG_VERSION C4_VERSION_ENCODED(__clang_major__, __clang_minor__, __clang_patchlevel__) +# else +# define C4_CLANG_VERSION __apple_build_version__ +# endif +# elif defined(__GNUC__) +# ifdef __MINGW32__ +# define C4_MINGW +# endif +# define C4_GCC +# if defined(__GNUC_PATCHLEVEL__) +# define C4_GCC_VERSION C4_VERSION_ENCODED(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) +# else +# define C4_GCC_VERSION C4_VERSION_ENCODED(__GNUC__, __GNUC_MINOR__, 0) +# endif +# if __GNUC__ < 5 +# if __GNUC__ == 4 && __GNUC_MINOR__ >= 8 +// provided by cmake sub-project +// amalgamate: removed include of +// c4/gcc-4.8.hpp +//# include "c4/gcc-4.8.hpp" +#if !defined(C4_GCC_4_8_HPP_) && !defined(_C4_GCC_4_8_HPP_) +#error "amalgamate: file c4/gcc-4.8.hpp must have been included at this point" +#endif /* C4_GCC_4_8_HPP_ */ + +# else +// we do not support GCC < 4.8: +// * misses std::is_trivially_copyable +// * misses std::align +// * -Wshadow has false positives when a local function parameter has the same name as a method +# error "GCC < 4.8 is not supported" +# endif +# endif +# endif +#endif // defined(C4_WIN) && defined(_MSC_VER) + +#endif /* _C4_COMPILER_HPP_ */ + + +// (end src/c4/compiler.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/language.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_LANGUAGE_HPP_ +#define _C4_LANGUAGE_HPP_ + +/** @file language.hpp Provides language standard information macros and + * compiler agnostic utility macros: namespace facilities, function attributes, + * variable attributes, etc. + * @ingroup basic_headers */ + +// amalgamate: removed include of +// c4/preprocessor.hpp +//#include "c4/preprocessor.hpp" +#if !defined(C4_PREPROCESSOR_HPP_) && !defined(_C4_PREPROCESSOR_HPP_) +#error "amalgamate: file c4/preprocessor.hpp must have been included at this point" +#endif /* C4_PREPROCESSOR_HPP_ */ + +// amalgamate: removed include of +// c4/compiler.hpp +//#include "c4/compiler.hpp" +#if !defined(C4_COMPILER_HPP_) && !defined(_C4_COMPILER_HPP_) +#error "amalgamate: file c4/compiler.hpp must have been included at this point" +#endif /* C4_COMPILER_HPP_ */ + +// amalgamate: removed include of +// c4/export.hpp +//#include "c4/export.hpp" +#if !defined(C4_EXPORT_HPP_) && !defined(_C4_EXPORT_HPP_) +#error "amalgamate: file c4/export.hpp must have been included at this point" +#endif /* C4_EXPORT_HPP_ */ + + +/* Detect C++ standard. + * @see http://stackoverflow.com/a/7132549/5875572 */ +#ifndef C4_CPP +# if defined(_MSC_VER) && !defined(__clang__) +# if _MSC_VER >= 1910 // >VS2015: VS2017, VS2019, VS2022 +# if (!defined(_MSVC_LANG)) +# error _MSVC not defined +# endif +# if _MSVC_LANG >= 201705L +# define C4_CPP 20 +# define C4_CPP20 +# elif _MSVC_LANG == 201703L +# define C4_CPP 17 +# define C4_CPP17 +# elif _MSVC_LANG >= 201402L +# define C4_CPP 14 +# define C4_CPP14 +# elif _MSVC_LANG >= 201103L +# define C4_CPP 11 +# define C4_CPP11 +# else +# error C++ lesser than C++11 not supported +# endif +# else +# if _MSC_VER == 1900 +# define C4_CPP 14 // VS2015 is c++14 https://devblogs.microsoft.com/cppblog/c111417-features-in-vs-2015-rtm/ +# define C4_CPP14 +# elif _MSC_VER == 1800 // VS2013 +# define C4_CPP 11 +# define C4_CPP11 +# else +# error C++ lesser than C++11 not supported +# endif +# endif +# elif defined(__INTEL_COMPILER) // https://software.intel.com/en-us/node/524490 +# ifdef __INTEL_CXX20_MODE__ // not sure about this +# define C4_CPP 20 +# define C4_CPP20 +# elif defined __INTEL_CXX17_MODE__ // not sure about this +# define C4_CPP 17 +# define C4_CPP17 +# elif defined __INTEL_CXX14_MODE__ // not sure about this +# define C4_CPP 14 +# define C4_CPP14 +# elif defined __INTEL_CXX11_MODE__ +# define C4_CPP 11 +# define C4_CPP11 +# else +# error C++ lesser than C++11 not supported +# endif +# else +# ifndef __cplusplus +# error __cplusplus is not defined? +# endif +# if __cplusplus == 1 +# error cannot handle __cplusplus==1 +# elif __cplusplus >= 201709L +# define C4_CPP 20 +# define C4_CPP20 +# elif __cplusplus >= 201703L +# define C4_CPP 17 +# define C4_CPP17 +# elif __cplusplus >= 201402L +# define C4_CPP 14 +# define C4_CPP14 +# elif __cplusplus >= 201103L +# define C4_CPP 11 +# define C4_CPP11 +# elif __cplusplus >= 199711L +# error C++ lesser than C++11 not supported +# endif +# endif +#else +# ifdef C4_CPP == 20 +# define C4_CPP20 +# elif C4_CPP == 17 +# define C4_CPP17 +# elif C4_CPP == 14 +# define C4_CPP14 +# elif C4_CPP == 11 +# define C4_CPP11 +# elif C4_CPP == 98 +# define C4_CPP98 +# error C++ lesser than C++11 not supported +# else +# error C4_CPP must be one of 20, 17, 14, 11, 98 +# endif +#endif + +#ifdef C4_CPP20 +# define C4_CPP17 +# define C4_CPP14 +# define C4_CPP11 +#elif defined(C4_CPP17) +# define C4_CPP14 +# define C4_CPP11 +#elif defined(C4_CPP14) +# define C4_CPP11 +#endif + +/** lifted from this answer: http://stackoverflow.com/a/20170989/5875572 */ +#if defined(_MSC_VER) && !defined(__clang__) +# if _MSC_VER < 1900 +# define C4_CONSTEXPR11 +# define C4_CONSTEXPR14 +# elif _MSC_VER < 2000 +# define C4_CONSTEXPR11 constexpr +# define C4_CONSTEXPR14 +# else +# define C4_CONSTEXPR11 constexpr +# define C4_CONSTEXPR14 constexpr +# endif +#else +# if __cplusplus < 201103 +# define C4_CONSTEXPR11 +# define C4_CONSTEXPR14 +# elif __cplusplus == 201103 +# define C4_CONSTEXPR11 constexpr +# define C4_CONSTEXPR14 +# else +# define C4_CONSTEXPR11 constexpr +# define C4_CONSTEXPR14 constexpr +# endif +#endif // _MSC_VER + + +#if C4_CPP < 17 +#define C4_IF_CONSTEXPR +#define C4_INLINE_CONSTEXPR constexpr +#else +#define C4_IF_CONSTEXPR constexpr +#define C4_INLINE_CONSTEXPR inline constexpr +#endif + +#if defined(_MSC_VER) && !defined(__clang__) +# if (defined(_CPPUNWIND) && (_CPPUNWIND == 1)) +# define C4_EXCEPTIONS +# endif +#else +# if defined(__EXCEPTIONS) || defined(__cpp_exceptions) +# define C4_EXCEPTIONS +# endif +#endif + +#ifdef C4_EXCEPTIONS +# define C4_IF_EXCEPTIONS_(exc_code, setjmp_code) exc_code +# define C4_IF_EXCEPTIONS(exc_code, setjmp_code) do { exc_code } while(0) +#else +# define C4_IF_EXCEPTIONS_(exc_code, setjmp_code) setjmp_code +# define C4_IF_EXCEPTIONS(exc_code, setjmp_code) do { setjmp_code } while(0) +#endif + +#if defined(_MSC_VER) && !defined(__clang__) +# if defined(_CPPRTTI) +# define C4_RTTI +# endif +#else +# if defined(__GXX_RTTI) +# define C4_RTTI +# endif +#endif + +#ifdef C4_RTTI +# define C4_IF_RTTI_(code_rtti, code_no_rtti) code_rtti +# define C4_IF_RTTI(code_rtti, code_no_rtti) do { code_rtti } while(0) +#else +# define C4_IF_RTTI_(code_rtti, code_no_rtti) code_no_rtti +# define C4_IF_RTTI(code_rtti, code_no_rtti) do { code_no_rtti } while(0) +#endif + + +//------------------------------------------------------------ + +#define _C4_BEGIN_NAMESPACE(ns) namespace ns { +#define _C4_END_NAMESPACE(ns) } + +// MSVC cant handle the C4_FOR_EACH macro... need to fix this +//#define C4_BEGIN_NAMESPACE(...) C4_FOR_EACH_SEP(_C4_BEGIN_NAMESPACE, , __VA_ARGS__) +//#define C4_END_NAMESPACE(...) C4_FOR_EACH_SEP(_C4_END_NAMESPACE, , __VA_ARGS__) +#define C4_BEGIN_NAMESPACE(ns) namespace ns { +#define C4_END_NAMESPACE(ns) } + +#define C4_BEGIN_HIDDEN_NAMESPACE namespace /*hidden*/ { +#define C4_END_HIDDEN_NAMESPACE } /* namespace hidden */ + +//------------------------------------------------------------ + +#ifndef C4_API +# if defined(_MSC_VER) && !defined(__clang__) +# if defined(C4_EXPORT) +# define C4_API __declspec(dllexport) +# elif defined(C4_IMPORT) +# define C4_API __declspec(dllimport) +# else +# define C4_API +# endif +# else +# define C4_API +# endif +#endif + +#if defined(_MSC_VER) && !defined(__clang__) +# define C4_RESTRICT __restrict +# define C4_RESTRICT_FN __declspec(restrict) +# define C4_NO_INLINE __declspec(noinline) +# define C4_ALWAYS_INLINE inline __forceinline +/** these are not available in VS AFAIK */ +# define C4_CONST +# define C4_PURE +# define C4_FLATTEN +# define C4_HOT /** @todo */ +# define C4_COLD /** @todo */ +# define C4_ASSUME(...) __assume(__VA_ARGS__) +# define C4_EXPECT(x, y) x /** @todo */ +# define C4_LIKELY(x) x +# define C4_UNLIKELY(x) x +# define C4_UNREACHABLE() _c4_msvc_unreachable() +# define C4_ATTR_FORMAT(...) /** */ +# define C4_NORETURN [[noreturn]] +# if _MSC_VER >= 1700 // VS2012 +# define C4_NODISCARD _Check_return_ +# else +# define C4_NODISCARD +# endif +#if C4_CPP >= 17 +# define C4_MAYBE_UNUSED [[maybe_unused]] +#else +# define C4_MAYBE_UNUSED +#endif +[[noreturn]] __forceinline void _c4_msvc_unreachable() { __assume(false); } ///< https://stackoverflow.com/questions/60802864/emulating-gccs-builtin-unreachable-in-visual-studio +# define C4_UNREACHABLE_AFTER_ERR() /* */ +#else + ///< @todo assuming gcc-like compiler. check it is actually so. +/** for function attributes in GCC, + * @see https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes */ +/** for __builtin functions in GCC, + * @see https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html */ +# define C4_RESTRICT __restrict__ +# define C4_RESTRICT_FN __attribute__((restrict)) +# define C4_NO_INLINE __attribute__((noinline)) +# define C4_ALWAYS_INLINE inline __attribute__((always_inline)) +# define C4_CONST __attribute__((const)) +# define C4_PURE __attribute__((pure)) +/** force inlining of every callee function */ +# define C4_FLATTEN __atribute__((flatten)) +/** mark a function as hot, ie as having a visible impact in CPU time + * thus making it more likely to inline, etc + * @see http://stackoverflow.com/questions/15028990/semantics-of-gcc-hot-attribute */ +# define C4_HOT __attribute__((hot)) +/** mark a function as cold, ie as NOT having a visible impact in CPU time + * @see http://stackoverflow.com/questions/15028990/semantics-of-gcc-hot-attribute */ +# define C4_COLD __attribute__((cold)) +# define C4_EXPECT(x, y) __builtin_expect(x, y) ///< @see https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html +# define C4_LIKELY(x) __builtin_expect(x, 1) +# define C4_UNLIKELY(x) __builtin_expect(x, 0) +# define C4_UNREACHABLE() __builtin_unreachable() +# define C4_ATTR_FORMAT(...) //__attribute__((format (__VA_ARGS__))) ///< @see https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes +# define C4_NORETURN __attribute__((noreturn)) +# define C4_NODISCARD __attribute__((warn_unused_result)) +#if C4_CPP >= 17 +# define C4_MAYBE_UNUSED [[maybe_unused]] +#else +# define C4_MAYBE_UNUSED __attribute__((unused)) +#endif +# define C4_UNREACHABLE_AFTER_ERR() C4_UNREACHABLE() +// C4_ASSUME +// see https://stackoverflow.com/questions/63493968/reproducing-clangs-builtin-assume-for-gcc +// preferred option: C++ standard attribute +# ifdef __has_cpp_attribute +# if __has_cpp_attribute(assume) >= 202207L +# define C4_ASSUME(...) [[assume(__VA_ARGS__)]] +# endif +# endif +// first fallback: compiler intrinsics/attributes for assumptions +# ifndef C4_ASSUME +# if defined(__clang__) +# define C4_ASSUME(...) __builtin_assume(__VA_ARGS__) +# elif defined(__GNUC__) +# if __GNUC__ >= 13 +# define C4_ASSUME(...) __attribute__((__assume__(__VA_ARGS__))) +# endif +# endif +# endif +// second fallback: possibly evaluating uses of unreachable() +// Set this to 1 if you want to allow assumptions to possibly evaluate. +# ifndef C4_ASSUME_ALLOW_EVAL +# define C4_ASSUME_ALLOW_EVAL 0 +# endif +# if !defined(C4_ASSUME) && (C4_ASSUME_ALLOW_EVAL) +# define C4_ASSUME(...) do { if (!bool(__VA_ARGS__)) C4_UNREACHABLE(); ) while(0) +# endif +// last fallback: define macro as doing nothing +# ifndef C4_ASSUME +# define C4_ASSUME(...) +# endif +#endif + + +#if C4_CPP >= 14 +# define C4_DEPRECATED(msg) [[deprecated(msg)]] +#else +# if defined(_MSC_VER) +# define C4_DEPRECATED(msg) __declspec(deprecated(msg)) +# else // defined(__GNUC__) || defined(__clang__) +# define C4_DEPRECATED(msg) __attribute__((deprecated(msg))) +# endif +#endif + + +#ifdef _MSC_VER +# define C4_FUNC __FUNCTION__ +# define C4_PRETTY_FUNC __FUNCSIG__ +#else /// @todo assuming gcc-like compiler. check it is actually so. +# define C4_FUNC __FUNCTION__ +# define C4_PRETTY_FUNC __PRETTY_FUNCTION__ +#endif + + +/** prevent compiler warnings about a specific var being unused */ +#define C4_UNUSED(var) (void)var + +#if C4_CPP >= 17 +#define C4_STATIC_ASSERT(cond) static_assert(cond) +#else +#define C4_STATIC_ASSERT(cond) static_assert((cond), #cond) +#endif +#define C4_STATIC_ASSERT_MSG(cond, msg) static_assert((cond), #cond ": " msg) + + +/** @def C4_DONT_OPTIMIZE() idea taken from GoogleBenchmark. + * @see https://github.com/google/benchmark/blob/master/include/benchmark/benchmark_api.h */ +namespace c4 { +namespace detail { +#if defined(__GNUC__) +# define C4_DONT_OPTIMIZE(var) c4::detail::dont_optimize(var) +template< class T > +C4_ALWAYS_INLINE void dont_optimize(T const& value) { asm volatile("" : : "g"(value) : "memory"); } // NOLINT +#else +# define C4_DONT_OPTIMIZE(var) c4::detail::use_char_pointer(reinterpret_cast< const char* >(&var)) +C4CORE_EXPORT void use_char_pointer(char const volatile*); +#endif +} // namespace detail +} // namespace c4 + + +/** @def C4_KEEP_EMPTY_LOOP() prevent an empty loop from being optimized out. + * @see http://stackoverflow.com/a/7084193/5875572 */ +#if defined(_MSC_VER) && !defined(__clang__) +# define C4_KEEP_EMPTY_LOOP { char c; C4_DONT_OPTIMIZE(c); } +#else +# define C4_KEEP_EMPTY_LOOP { asm(""); } +#endif + +#endif /* _C4_LANGUAGE_HPP_ */ + + +// (end src/c4/language.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/types.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_TYPES_HPP_ +#define _C4_TYPES_HPP_ + +//included above: +//#include +#include +//included above: +//#include + +#if __cplusplus >= 201103L +#include // for integer_sequence and friends +#endif + +// amalgamate: removed include of +// c4/preprocessor.hpp +//#include "c4/preprocessor.hpp" +#if !defined(C4_PREPROCESSOR_HPP_) && !defined(_C4_PREPROCESSOR_HPP_) +#error "amalgamate: file c4/preprocessor.hpp must have been included at this point" +#endif /* C4_PREPROCESSOR_HPP_ */ + +// amalgamate: removed include of +// c4/language.hpp +//#include "c4/language.hpp" +#if !defined(C4_LANGUAGE_HPP_) && !defined(_C4_LANGUAGE_HPP_) +#error "amalgamate: file c4/language.hpp must have been included at this point" +#endif /* C4_LANGUAGE_HPP_ */ + + +/** @file types.hpp basic types, and utility macros and traits for types. + * @ingroup basic_headers */ + +/** @defgroup types Type utilities */ + +// NOLINTBEGIN(bugprone-macro-parentheses) + +namespace c4 { + +/** @defgroup intrinsic_types Intrinsic types + * @ingroup types + * @{ */ + +using cbyte = const char; /**< a constant byte */ +using byte = char; /**< a mutable byte */ + +using i8 = int8_t; +using i16 = int16_t; +using i32 = int32_t; +using i64 = int64_t; +using u8 = uint8_t; +using u16 = uint16_t; +using u32 = uint32_t; +using u64 = uint64_t; + +using f32 = float; +using f64 = double; + +using ssize_t = typename std::make_signed::type; // NOLINT + +/** @} */ + +//-------------------------------------------------- + +/** @defgroup utility_types Utility types + * @ingroup types + * @{ */ + +// some tag types + +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic push +#if __GNUC__ >= 6 +#pragma GCC diagnostic ignored "-Wunused-const-variable" +#endif +#endif + +/** a tag type for initializing the containers with variadic arguments a la + * initializer_list, minus the initializer_list overload problems. + */ +struct aggregate_t {}; +/** @see aggregate_t */ +constexpr const aggregate_t aggregate{}; + +/** a tag type for specifying the initial capacity of allocatable contiguous storage */ +struct with_capacity_t {}; +/** @see with_capacity_t */ +constexpr const with_capacity_t with_capacity{}; + +/** a tag type for disambiguating template parameter packs in variadic template overloads */ +struct varargs_t {}; +/** @see with_capacity_t */ +constexpr const varargs_t varargs{}; + +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + + +//-------------------------------------------------- + +/** whether a value should be used in place of a const-reference in argument passing. */ +template +struct cref_uses_val +{ + enum { value = ( + std::is_scalar::value + || + ( +#if C4_CPP >= 20 + (std::is_trivially_copyable::value && std::is_standard_layout::value) +#else + std::is_pod::value +#endif + && + sizeof(T) <= sizeof(size_t))) }; +}; +/** utility macro to override the default behaviour for c4::fastcref + @see fastcref */ +#define C4_CREF_USES_VAL(T) \ +template<> \ +struct cref_uses_val \ +{ \ + enum { value = true }; \ +}; + +/** Whether to use pass-by-value or pass-by-const-reference in a function argument + * or return type. */ +template +using fastcref = typename std::conditional::value, T, T const&>::type; + +//-------------------------------------------------- + +/** Just what its name says. Useful sometimes as a default empty policy class. */ +struct EmptyStruct // NOLINT +{ + template EmptyStruct(T && ...){} // NOLINT +}; + +/** Just what its name says. Useful sometimes as a default policy class to + * be inherited from. */ +struct EmptyStructVirtual // NOLINT +{ + virtual ~EmptyStructVirtual() = default; + template EmptyStructVirtual(T && ...){} // NOLINT +}; + + +/** */ +template +struct inheritfrom : public 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. + +/** how many bytes must be added to size such that the result is at least minsize? */ +C4_ALWAYS_INLINE constexpr size_t min_remainder(size_t size, size_t minsize) noexcept +{ + return size < minsize ? minsize-size : 0; +} + +/** how many bytes must be added to size such that the result is a multiple of multipleof? */ +C4_ALWAYS_INLINE constexpr size_t mult_remainder(size_t size, size_t multipleof) noexcept +{ + return (((size % multipleof) != 0) ? (multipleof-(size % multipleof)) : 0); +} + +/* force the following class to be tightly packed. */ +#pragma pack(push, 1) +/** pad a class with more bytes at the end. + * @see http://stackoverflow.com/questions/21092415/force-c-structure-to-pack-tightly */ +template +struct Padded : public T +{ + using T::T; + using T::operator=; + Padded(T const& val) : T(val) {} + Padded(T && val) : T(std::forward(val)) {} // NOLINT + char ___c4padspace___[BytesToPadAtEnd]; +}; +#pragma pack(pop) +/** When the padding argument is 0, we cannot declare the char[] array. */ +template +struct Padded : public T +{ + using T::T; + using T::operator=; + Padded(T const& val) : T(val) {} + Padded(T && val) : T(std::forward(val)) {} // NOLINT +}; + +/** make T have a size which is at least Min bytes */ +template +using MinSized = Padded; + +/** make T have a size which is a multiple of Mult bytes */ +template +using MultSized = Padded; + +/** make T have a size which is simultaneously: + * -bigger or equal than Min + * -a multiple of Mult */ +template +using MinMultSized = MultSized, Mult>; + +/** make T be suitable for use as a uniform buffer. (at least with DirectX). */ +template +using UbufSized = MinMultSized; + + +//----------------------------------------------------------------------------- + +#define C4_NO_COPY_CTOR(ty) ty(ty const&) = delete +#define C4_NO_MOVE_CTOR(ty) ty(ty &&) = delete +#define C4_NO_COPY_ASSIGN(ty) ty& operator=(ty const&) = delete +#define C4_NO_MOVE_ASSIGN(ty) ty& operator=(ty &&) = delete +#define C4_DEFAULT_COPY_CTOR(ty) ty(ty const&) noexcept = default +#define C4_DEFAULT_MOVE_CTOR(ty) ty(ty &&) noexcept = default +#define C4_DEFAULT_COPY_ASSIGN(ty) ty& operator=(ty const&) noexcept = default +#define C4_DEFAULT_MOVE_ASSIGN(ty) ty& operator=(ty &&) noexcept = default + +#define C4_NO_COPY_OR_MOVE_CTOR(ty) \ + C4_NO_COPY_CTOR(ty); \ + C4_NO_MOVE_CTOR(ty) + +#define C4_NO_COPY_OR_MOVE_ASSIGN(ty) \ + C4_NO_COPY_ASSIGN(ty); \ + C4_NO_MOVE_ASSIGN(ty) + +#define C4_NO_COPY_OR_MOVE(ty) \ + C4_NO_COPY_OR_MOVE_CTOR(ty); \ + C4_NO_COPY_OR_MOVE_ASSIGN(ty) + +#define C4_DEFAULT_COPY_AND_MOVE_CTOR(ty) \ + C4_DEFAULT_COPY_CTOR(ty); \ + C4_DEFAULT_MOVE_CTOR(ty) + +#define C4_DEFAULT_COPY_AND_MOVE_ASSIGN(ty) \ + C4_DEFAULT_COPY_ASSIGN(ty); \ + C4_DEFAULT_MOVE_ASSIGN(ty) + +#define C4_DEFAULT_COPY_AND_MOVE(ty) \ + C4_DEFAULT_COPY_AND_MOVE_CTOR(ty); \ + C4_DEFAULT_COPY_AND_MOVE_ASSIGN(ty) + +/** @see https://en.cppreference.com/w/cpp/named_req/TriviallyCopyable */ +#define C4_MUST_BE_TRIVIAL_COPY(ty) \ + static_assert(std::is_trivially_copyable::value, #ty " must be trivially copyable") + +/** @} */ + + +//----------------------------------------------------------------------------- + +/** @defgroup traits_types Type traits utilities + * @ingroup types + * @{ */ + +// http://stackoverflow.com/questions/10821380/is-t-an-instance-of-a-template-in-c +template class X, typename T> struct is_instance_of_tpl : std::false_type {}; +template class X, typename... Y> struct is_instance_of_tpl> : std::true_type {}; + +//----------------------------------------------------------------------------- + +/** SFINAE. use this macro to enable a template function overload +based on a compile-time condition. +@code +// define an overload for a non-pod type +template::value)> +void foo() { std::cout << "pod type\n"; } + +// define an overload for a non-pod type +template::value)> +void foo() { std::cout << "nonpod type\n"; } + +struct non_pod +{ + non_pod() : name("asdfkjhasdkjh") {} + const char *name; +}; + +int main() +{ + foo(); // prints "pod type" + foo(); // prints "nonpod type" +} +@endcode */ +#define C4_REQUIRE_T(cond) typename std::enable_if::type* = nullptr + +/** enable_if for a return type + * @see C4_REQUIRE_T */ +#define C4_REQUIRE_R(cond, type_) typename std::enable_if::type + +//----------------------------------------------------------------------------- +/** define a traits class reporting whether a type provides a member typedef */ +#define C4_DEFINE_HAS_TYPEDEF(member_typedef) \ +template \ +struct has_##stype \ +{ \ +private: \ + \ + typedef char yes; \ + typedef struct { char array[2]; } no; \ + \ + template \ + static yes _test(typename C::member_typedef*); \ + \ + template \ + static no _test(...); \ + \ +public: \ + \ + enum { value = (sizeof(_test(0)) == sizeof(yes)) }; \ + \ +} + + +/** @} */ + + +//----------------------------------------------------------------------------- + + +/** @defgroup type_declarations Type declaration utilities + * @ingroup types + * @{ */ + +#define _c4_DEFINE_ARRAY_TYPES_WITHOUT_ITERATOR(T, I) \ + \ + using size_type = I; \ + using ssize_type = typename std::make_signed::type; \ + using difference_type = typename std::make_signed::type; \ + \ + using value_type = T; \ + using pointer = T*; \ + using const_pointer = T const*; \ + using reference = T&; \ + using const_reference = T const& + +#define _c4_DEFINE_TUPLE_ARRAY_TYPES_WITHOUT_ITERATOR(interior_types, I) \ + \ + using size_type = I; \ + using ssize_type = typename std::make_signed::type; \ + using difference_type = typename std::make_signed::type; \ + \ + template using value_type = typename std::tuple_element< n, std::tuple>::type; \ + template using pointer = value_type*; \ + template using const_pointer = value_type const*; \ + template using reference = value_type&; \ + template using const_reference = value_type const& + + +#define _c4_DEFINE_ARRAY_TYPES(T, I) \ + \ + _c4_DEFINE_ARRAY_TYPES_WITHOUT_ITERATOR(T, I); \ + \ + using iterator = T*; \ + using const_iterator = T const*; \ + using reverse_iterator = std::reverse_iterator; \ + using const_reverse_iterator = std::reverse_iterator + + +#define _c4_DEFINE_TUPLE_ARRAY_TYPES(interior_types, I) \ + \ + _c4_DEFINE_TUPLE_ARRAY_TYPES_WITHOUT_ITERATOR(interior_types, I); \ + \ + template using iterator = value_type*; \ + template using const_iterator = value_type const*; \ + template using reverse_iterator = std::reverse_iterator< value_type*>; \ + template using const_reverse_iterator = std::reverse_iterator< value_type const*> + + + +/** @} */ + + +//----------------------------------------------------------------------------- + + +/** @defgroup compatility_utilities Backport implementation of some Modern C++ utilities + * @ingroup types + * @{ */ + +//----------------------------------------------------------------------------- +// index_sequence and friends are available only for C++14 and later. +// A C++11 implementation is provided here. +// This implementation was copied over from clang. +// see http://llvm.org/viewvc/llvm-project/libcxx/trunk/include/utility?revision=211563&view=markup#l687 + +#if __cplusplus > 201103L + +using std::integer_sequence; +using std::index_sequence; +using std::make_integer_sequence; +using std::make_index_sequence; +using std::index_sequence_for; + +#else + +/** C++11 implementation of integer sequence + * @see https://en.cppreference.com/w/cpp/utility/integer_sequence + * @see taken from clang: http://llvm.org/viewvc/llvm-project/libcxx/trunk/include/utility?revision=211563&view=markup#l687 */ +template +struct integer_sequence +{ + static_assert(std::is_integral<_Tp>::value, + "std::integer_sequence can only be instantiated with an integral type" ); + using value_type = _Tp; + static constexpr size_t size() noexcept { return sizeof...(_Ip); } +}; + +/** C++11 implementation of index sequence + * @see https://en.cppreference.com/w/cpp/utility/integer_sequence + * @see taken from clang: http://llvm.org/viewvc/llvm-project/libcxx/trunk/include/utility?revision=211563&view=markup#l687 */ +template +using index_sequence = integer_sequence; + +/** @cond DONT_DOCUMENT_THIS */ +namespace __detail { + +template +struct __repeat; + +template +struct __repeat, _Extra...> +{ + using type = integer_sequence<_Tp, + _Np..., + sizeof...(_Np) + _Np..., + (2 * sizeof...(_Np)) + _Np..., + (3 * sizeof...(_Np)) + _Np..., + (4 * sizeof...(_Np)) + _Np..., + (5 * sizeof...(_Np)) + _Np..., + (6 * sizeof...(_Np)) + _Np..., + (7 * sizeof...(_Np)) + _Np..., + _Extra...>; +}; + +template struct __parity; +template struct __make : __parity<_Np % 8>::template __pmake<_Np> {}; + +template<> struct __make<0> { using type = integer_sequence; }; +template<> struct __make<1> { using type = integer_sequence; }; +template<> struct __make<2> { using type = integer_sequence; }; +template<> struct __make<3> { using type = integer_sequence; }; +template<> struct __make<4> { using type = integer_sequence; }; +template<> struct __make<5> { using type = integer_sequence; }; +template<> struct __make<6> { using type = integer_sequence; }; +template<> struct __make<7> { using type = integer_sequence; }; + +template<> struct __parity<0> { template struct __pmake : __repeat::type> {}; }; +template<> struct __parity<1> { template struct __pmake : __repeat::type, _Np - 1> {}; }; +template<> struct __parity<2> { template struct __pmake : __repeat::type, _Np - 2, _Np - 1> {}; }; +template<> struct __parity<3> { template struct __pmake : __repeat::type, _Np - 3, _Np - 2, _Np - 1> {}; }; +template<> struct __parity<4> { template struct __pmake : __repeat::type, _Np - 4, _Np - 3, _Np - 2, _Np - 1> {}; }; +template<> struct __parity<5> { template struct __pmake : __repeat::type, _Np - 5, _Np - 4, _Np - 3, _Np - 2, _Np - 1> {}; }; +template<> struct __parity<6> { template struct __pmake : __repeat::type, _Np - 6, _Np - 5, _Np - 4, _Np - 3, _Np - 2, _Np - 1> {}; }; +template<> struct __parity<7> { template struct __pmake : __repeat::type, _Np - 7, _Np - 6, _Np - 5, _Np - 4, _Np - 3, _Np - 2, _Np - 1> {}; }; + +template +struct __convert +{ + template struct __result; + template<_Tp ..._Np> struct __result> + { + using type = integer_sequence<_Up, _Np...>; + }; +}; + +template +struct __convert<_Tp, _Tp> +{ + template struct __result + { + using type = _Up; + }; +}; + +template +using __make_integer_sequence_unchecked = typename __detail::__convert::template __result::type>::type; + +template +struct __make_integer_sequence +{ + static_assert(std::is_integral<_Tp>::value, + "std::make_integer_sequence can only be instantiated with an integral type" ); + static_assert(0 <= _Ep, "std::make_integer_sequence input shall not be negative"); + typedef __make_integer_sequence_unchecked<_Tp, _Ep> type; +}; + +} // namespace __detail +/** @endcond */ + + +/** C++11 implementation of index sequence + * @see https://en.cppreference.com/w/cpp/utility/integer_sequence + * @see taken from clang: http://llvm.org/viewvc/llvm-project/libcxx/trunk/include/utility?revision=211563&view=markup#l687 */ +template +using make_integer_sequence = typename __detail::__make_integer_sequence<_Tp, _Np>::type; + +/** C++11 implementation of index sequence + * @see https://en.cppreference.com/w/cpp/utility/integer_sequence + * @see taken from clang: http://llvm.org/viewvc/llvm-project/libcxx/trunk/include/utility?revision=211563&view=markup#l687 */ +template +using make_index_sequence = make_integer_sequence; + +/** C++11 implementation of index sequence + * @see https://en.cppreference.com/w/cpp/utility/integer_sequence + * @see taken from clang: http://llvm.org/viewvc/llvm-project/libcxx/trunk/include/utility?revision=211563&view=markup#l687 */ +template +using index_sequence_for = make_index_sequence; +#endif + +/** @} */ + + +} // namespace c4 + +// NOLINTEND(bugprone-macro-parentheses) + +#endif /* _C4_TYPES_HPP_ */ + + +// (end src/c4/types.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/config.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_CONFIG_HPP_ +#define _C4_CONFIG_HPP_ + +/** @defgroup basic_headers Basic headers + * @brief Headers providing basic macros, platform+cpu+compiler information, + * C++ facilities and basic typedefs. */ + +/** @file config.hpp Contains configuration defines and includes the basic_headers. + * @ingroup basic_headers */ + +//#define C4_DEBUG + +#define C4_ERROR_SHOWS_FILELINE +//#define C4_ERROR_SHOWS_FUNC +//#define C4_ERROR_THROWS_EXCEPTION +//#define C4_NO_ALLOC_DEFAULTS +//#define C4_REDEFINE_CPPNEW + +#ifndef C4_SIZE_TYPE +# define C4_SIZE_TYPE size_t +#endif + +#ifndef C4_STR_SIZE_TYPE +# define C4_STR_SIZE_TYPE C4_SIZE_TYPE +#endif + +#ifndef C4_TIME_TYPE +# define C4_TIME_TYPE double +#endif + +// amalgamate: removed include of +// c4/export.hpp +//#include "c4/export.hpp" +#if !defined(C4_EXPORT_HPP_) && !defined(_C4_EXPORT_HPP_) +#error "amalgamate: file c4/export.hpp must have been included at this point" +#endif /* C4_EXPORT_HPP_ */ + +// amalgamate: removed include of +// c4/preprocessor.hpp +//#include "c4/preprocessor.hpp" +#if !defined(C4_PREPROCESSOR_HPP_) && !defined(_C4_PREPROCESSOR_HPP_) +#error "amalgamate: file c4/preprocessor.hpp must have been included at this point" +#endif /* C4_PREPROCESSOR_HPP_ */ + +// amalgamate: removed include of +// c4/platform.hpp +//#include "c4/platform.hpp" +#if !defined(C4_PLATFORM_HPP_) && !defined(_C4_PLATFORM_HPP_) +#error "amalgamate: file c4/platform.hpp must have been included at this point" +#endif /* C4_PLATFORM_HPP_ */ + +// amalgamate: removed include of +// c4/cpu.hpp +//#include "c4/cpu.hpp" +#if !defined(C4_CPU_HPP_) && !defined(_C4_CPU_HPP_) +#error "amalgamate: file c4/cpu.hpp must have been included at this point" +#endif /* C4_CPU_HPP_ */ + +// amalgamate: removed include of +// c4/compiler.hpp +//#include "c4/compiler.hpp" +#if !defined(C4_COMPILER_HPP_) && !defined(_C4_COMPILER_HPP_) +#error "amalgamate: file c4/compiler.hpp must have been included at this point" +#endif /* C4_COMPILER_HPP_ */ + +// amalgamate: removed include of +// c4/language.hpp +//#include "c4/language.hpp" +#if !defined(C4_LANGUAGE_HPP_) && !defined(_C4_LANGUAGE_HPP_) +#error "amalgamate: file c4/language.hpp must have been included at this point" +#endif /* C4_LANGUAGE_HPP_ */ + + +#endif // _C4_CONFIG_HPP_ + + +// (end src/c4/config.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/ext/debugbreak/debugbreak.h +//-------------------------------------------------------------------------------- +//******************************************************************************** + +/* Copyright (c) 2011-2021, Scott Tsai + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef DEBUG_BREAK_H +#define DEBUG_BREAK_H + +#ifdef _MSC_VER + +#define debug_break __debugbreak + +#else + +#ifdef __cplusplus +extern "C" { +#endif + +#define DEBUG_BREAK_USE_TRAP_INSTRUCTION 1 +#define DEBUG_BREAK_USE_BUILTIN_TRAP 2 +#define DEBUG_BREAK_USE_SIGTRAP 3 +#define DEBUG_BREAK_USE_BUILTIN_DEBUGTRAP 4 + +#if defined(__i386__) || defined(__x86_64__) + #define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_TRAP_INSTRUCTION +__inline__ static void trap_instruction(void) +{ + __asm__ volatile("int $0x03"); +} +#elif defined(__thumb__) + #define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_TRAP_INSTRUCTION +/* FIXME: handle __THUMB_INTERWORK__ */ +__attribute__((always_inline)) +__inline__ static void trap_instruction(void) +{ + /* See 'arm-linux-tdep.c' in GDB source. + * Both instruction sequences below work. */ +#if 1 + /* 'eabi_linux_thumb_le_breakpoint' */ + __asm__ volatile(".inst 0xde01"); +#else + /* 'eabi_linux_thumb2_le_breakpoint' */ + __asm__ volatile(".inst.w 0xf7f0a000"); +#endif + + /* Known problem: + * After a breakpoint hit, can't 'stepi', 'step', or 'continue' in GDB. + * 'step' would keep getting stuck on the same instruction. + * + * Workaround: use the new GDB commands 'debugbreak-step' and + * 'debugbreak-continue' that become available + * after you source the script from GDB: + * + * $ gdb -x debugbreak-gdb.py <... USUAL ARGUMENTS ...> + * + * 'debugbreak-step' would jump over the breakpoint instruction with + * roughly equivalent of: + * (gdb) set $instruction_len = 2 + * (gdb) tbreak *($pc + $instruction_len) + * (gdb) jump *($pc + $instruction_len) + */ +} +#elif defined(__arm__) && !defined(__thumb__) + #define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_TRAP_INSTRUCTION +__attribute__((always_inline)) +__inline__ static void trap_instruction(void) +{ + /* See 'arm-linux-tdep.c' in GDB source, + * 'eabi_linux_arm_le_breakpoint' */ + __asm__ volatile(".inst 0xe7f001f0"); + /* Known problem: + * Same problem and workaround as Thumb mode */ +} +#elif defined(__aarch64__) && defined(__APPLE__) + #define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_BUILTIN_DEBUGTRAP +#elif defined(__aarch64__) + #define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_TRAP_INSTRUCTION +__attribute__((always_inline)) +__inline__ static void trap_instruction(void) +{ + /* See 'aarch64-tdep.c' in GDB source, + * 'aarch64_default_breakpoint' */ + __asm__ volatile(".inst 0xd4200000"); +} +#elif defined(__powerpc__) + /* PPC 32 or 64-bit, big or little endian */ + #define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_TRAP_INSTRUCTION +__attribute__((always_inline)) +__inline__ static void trap_instruction(void) +{ + /* See 'rs6000-tdep.c' in GDB source, + * 'rs6000_breakpoint' */ + __asm__ volatile(".4byte 0x7d821008"); + + /* Known problem: + * After a breakpoint hit, can't 'stepi', 'step', or 'continue' in GDB. + * 'step' stuck on the same instruction ("twge r2,r2"). + * + * The workaround is the same as ARM Thumb mode: use debugbreak-gdb.py + * or manually jump over the instruction. */ +} +#elif defined(__riscv) + /* RISC-V 32 or 64-bit, whether the "C" extension + * for compressed, 16-bit instructions are supported or not */ + #define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_TRAP_INSTRUCTION +__attribute__((always_inline)) +__inline__ static void trap_instruction(void) +{ + /* See 'riscv-tdep.c' in GDB source, + * 'riscv_sw_breakpoint_from_kind' */ + __asm__ volatile(".4byte 0x00100073"); +} +#else + #define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_SIGTRAP +#endif + + +#ifndef DEBUG_BREAK_IMPL +#error "debugbreak.h is not supported on this target" +#elif DEBUG_BREAK_IMPL == DEBUG_BREAK_USE_TRAP_INSTRUCTION +__attribute__((always_inline)) +__inline__ static void debug_break(void) +{ + trap_instruction(); +} +#elif DEBUG_BREAK_IMPL == DEBUG_BREAK_USE_BUILTIN_DEBUGTRAP +__attribute__((always_inline)) +__inline__ static void debug_break(void) +{ + __builtin_debugtrap(); +} +#elif DEBUG_BREAK_IMPL == DEBUG_BREAK_USE_BUILTIN_TRAP +__attribute__((always_inline)) +__inline__ static void debug_break(void) +{ + __builtin_trap(); +} +#elif DEBUG_BREAK_IMPL == DEBUG_BREAK_USE_SIGTRAP +#include +__attribute__((always_inline)) +__inline__ static void debug_break(void) +{ + raise(SIGTRAP); +} +#else +#error "invalid DEBUG_BREAK_IMPL value" +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* ifdef _MSC_VER */ + +#endif /* ifndef DEBUG_BREAK_H */ + + +// (end src/c4/ext/debugbreak/debugbreak.h) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/error.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_ERROR_HPP_ +#define _C4_ERROR_HPP_ + +/** @file error.hpp Facilities for error reporting and runtime assertions. */ + +/** @defgroup error_checking Error checking */ + +//included above: +//#include +//included above: +//#include + +// amalgamate: removed include of +// c4/config.hpp +//#include "c4/config.hpp" +#if !defined(C4_CONFIG_HPP_) && !defined(_C4_CONFIG_HPP_) +#error "amalgamate: file c4/config.hpp must have been included at this point" +#endif /* C4_CONFIG_HPP_ */ + +// amalgamate: removed include of +// c4/export.hpp +//#include "c4/export.hpp" +#if !defined(C4_EXPORT_HPP_) && !defined(_C4_EXPORT_HPP_) +#error "amalgamate: file c4/export.hpp must have been included at this point" +#endif /* C4_EXPORT_HPP_ */ + +// amalgamate: removed include of +// c4/compiler.hpp +//#include "c4/compiler.hpp" +#if !defined(C4_COMPILER_HPP_) && !defined(_C4_COMPILER_HPP_) +#error "amalgamate: file c4/compiler.hpp must have been included at this point" +#endif /* C4_COMPILER_HPP_ */ + +// amalgamate: removed include of +// c4/language.hpp +//#include "c4/language.hpp" +#if !defined(C4_LANGUAGE_HPP_) && !defined(_C4_LANGUAGE_HPP_) +#error "amalgamate: file c4/language.hpp must have been included at this point" +#endif /* C4_LANGUAGE_HPP_ */ + + +#ifdef _DOXYGEN_ + /** if this is defined and exceptions are enabled, then calls to C4_ERROR() + * will throw an exception + * @ingroup error_checking */ +# define C4_EXCEPTIONS_ENABLED + /** if this is defined and exceptions are enabled, then calls to C4_ERROR() + * will throw an exception + * @see C4_EXCEPTIONS_ENABLED + * @ingroup error_checking */ +# define C4_ERROR_THROWS_EXCEPTION + /** evaluates to noexcept when C4_ERROR might be called and + * exceptions are disabled. Otherwise, defaults to nothing. + * @ingroup error_checking */ +# define C4_NOEXCEPT +#endif // _DOXYGEN_ + +#if defined(C4_EXCEPTIONS_ENABLED) && defined(C4_ERROR_THROWS_EXCEPTION) +# define C4_NOEXCEPT +#else +# define C4_NOEXCEPT noexcept +#endif + + +namespace c4 { +namespace detail { +struct fail_type__ {}; +} // detail +} // c4 +#define C4_STATIC_ERROR(dummy_type, errmsg) \ + static_assert(std::is_same::value, errmsg) + + +//----------------------------------------------------------------------------- + +#define C4_ASSERT_SAME_TYPE(ty1, ty2) \ + C4_STATIC_ASSERT(std::is_same::value) + +#define C4_ASSERT_DIFF_TYPE(ty1, ty2) \ + C4_STATIC_ASSERT( ! std::is_same::value) + + +//----------------------------------------------------------------------------- + +#ifdef _DOXYGEN_ +/** utility macro that triggers a breakpoint when + * the debugger is attached and NDEBUG is not defined. + * @ingroup error_checking */ +# define C4_DEBUG_BREAK() +#endif // _DOXYGEN_ + + +#if defined(NDEBUG) || defined(C4_NO_DEBUG_BREAK) +# define C4_DEBUG_BREAK() +#else +# ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wundef" +# if !defined(__APPLE_CC__) +# if __clang_major__ >= 10 +# pragma clang diagnostic ignored "-Wgnu-inline-cpp-without-extern" // debugbreak/debugbreak.h:50:16: error: 'gnu_inline' attribute without 'extern' in C++ treated as externally available, this changed in Clang 10 [-Werror,-Wgnu-inline-cpp-without-extern] +# endif +# else +# if __clang_major__ >= 13 +# pragma clang diagnostic ignored "-Wgnu-inline-cpp-without-extern" // debugbreak/debugbreak.h:50:16: error: 'gnu_inline' attribute without 'extern' in C++ treated as externally available, this changed in Clang 10 [-Werror,-Wgnu-inline-cpp-without-extern] +# endif +# endif +# elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wundef" +# endif +// amalgamate: removed include of +// c4/ext/debugbreak/debugbreak.h +//# include +#if !defined(DEBUG_BREAK_H) && !defined(_DEBUG_BREAK_H) +#error "amalgamate: file c4/ext/debugbreak/debugbreak.h must have been included at this point" +#endif /* DEBUG_BREAK_H */ + +# define C4_DEBUG_BREAK() if(c4::is_debugger_attached()) { ::debug_break(); } +# ifdef __clang__ +# pragma clang diagnostic pop +# elif defined(__GNUC__) +# pragma GCC diagnostic pop +# endif +#endif + +namespace c4 { +C4CORE_EXPORT bool is_debugger_attached(); +} // namespace c4 + + +//----------------------------------------------------------------------------- + +#ifdef __clang__ + /* NOTE: using , ## __VA_ARGS__ to deal with zero-args calls to + * variadic macros is not portable, but works in clang, gcc, msvc, icc. + * clang requires switching off compiler warnings for pedantic mode. + * @see http://stackoverflow.com/questions/32047685/variadic-macro-without-arguments */ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" // warning: token pasting of ',' and __VA_ARGS__ is a GNU extension +#elif defined(__GNUC__) + /* GCC also issues a warning for zero-args calls to variadic macros. + * This warning is switched on with -pedantic and apparently there is no + * easy way to turn it off as with clang. But marking this as a system + * header works. + * @see https://gcc.gnu.org/onlinedocs/cpp/System-Headers.html + * @see http://stackoverflow.com/questions/35587137/ */ +# pragma GCC system_header +#endif + + +//----------------------------------------------------------------------------- + +namespace c4 { + +typedef enum : uint32_t { + /** when an error happens and the debugger is attached, call C4_DEBUG_BREAK(). + * Without effect otherwise. */ + ON_ERROR_DEBUGBREAK = 0x01u << 0u, + /** when an error happens log a message. */ + ON_ERROR_LOG = 0x01u << 1u, + /** when an error happens invoke a callback if it was set with + * set_error_callback(). */ + ON_ERROR_CALLBACK = 0x01u << 2u, + /** when an error happens call std::terminate(). */ + ON_ERROR_ABORT = 0x01u << 3u, + /** when an error happens and exceptions are enabled throw an exception. + * Without effect otherwise. */ + ON_ERROR_THROW = 0x01u << 4u, + /** the default flags. */ + ON_ERROR_DEFAULTS = ON_ERROR_DEBUGBREAK|ON_ERROR_LOG|ON_ERROR_CALLBACK|ON_ERROR_ABORT +} ErrorFlags_e; +using error_flags = uint32_t; +C4CORE_EXPORT void set_error_flags(error_flags f); +C4CORE_EXPORT error_flags get_error_flags(); + + +using error_callback_type = void (*)(const char* msg, size_t msg_size); +C4CORE_EXPORT void set_error_callback(error_callback_type cb); +C4CORE_EXPORT error_callback_type get_error_callback(); + + +//----------------------------------------------------------------------------- +/** RAII class controling the error settings inside a scope. */ +struct ScopedErrorSettings // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) +{ + error_flags m_flags; + error_callback_type m_callback; + + explicit ScopedErrorSettings(error_callback_type cb) + : m_flags(get_error_flags()), + m_callback(get_error_callback()) + { + set_error_callback(cb); + } + explicit ScopedErrorSettings(error_flags flags) + : m_flags(get_error_flags()), + m_callback(get_error_callback()) + { + set_error_flags(flags); + } + explicit ScopedErrorSettings(error_flags flags, error_callback_type cb) + : m_flags(get_error_flags()), + m_callback(get_error_callback()) + { + set_error_flags(flags); + set_error_callback(cb); + } + ~ScopedErrorSettings() + { + set_error_flags(m_flags); + set_error_callback(m_callback); + } +}; + + +//----------------------------------------------------------------------------- + +/** source location */ +struct C4CORE_EXPORT srcloc +{ + const char *file; + const char *func; + int line; + srcloc(const char *file_="", const char *func_="", int line_=0) noexcept : file(file_), func(func_), line(line_) {} +}; + + +#if defined(C4_ERROR_SHOWS_FILELINE) && defined(C4_ERROR_SHOWS_FUNC) +#define C4_SRCLOC() c4::srcloc(__FILE__, C4_PRETTY_FUNC, __LINE__) +#elif defined(C4_ERROR_SHOWS_FILELINE) +#define C4_SRCLOC() c4::srcloc(__FILE__, "", __LINE__) +#elif ! defined(C4_ERROR_SHOWS_FUNC) +#define C4_SRCLOC() c4::srcloc{} +#else +# error not implemented +#endif + + +#define C4_ERROR(msg, ...) \ + do { \ + if(c4::get_error_flags() & c4::ON_ERROR_DEBUGBREAK) \ + { \ + C4_DEBUG_BREAK() \ + } \ + c4::handle_error(C4_SRCLOC(), msg, ## __VA_ARGS__); \ + } while(0) + + +#define C4_WARNING(msg, ...) \ + c4::handle_warning(C4_SRCLOC(), msg, ## __VA_ARGS__) + + +// watchout: for VS the [[noreturn]] needs to come before other annotations like C4CORE_EXPORT +[[noreturn]] C4CORE_EXPORT void handle_error(srcloc s, const char *fmt, ...); +C4CORE_EXPORT void handle_warning(srcloc s, const char *fmt, ...); + + +//----------------------------------------------------------------------------- +// assertions + +// Doxygen needs this so that only one definition counts +#ifdef _DOXYGEN_ + /** Explicitly enables assertions, independently of NDEBUG status. + * This is meant to allow enabling assertions even when NDEBUG is defined. + * Defaults to undefined. + * @ingroup error_checking */ +# define C4_USE_ASSERT + /** assert that a condition is true; this is turned off when NDEBUG + * is defined and C4_USE_ASSERT is not true. + * @ingroup error_checking */ +# define C4_ASSERT + /** same as C4_ASSERT(), additionally prints a printf-formatted message + * @ingroup error_checking */ +# define C4_ASSERT_MSG + /** evaluates to C4_NOEXCEPT when C4_XASSERT is disabled; otherwise, defaults + * to noexcept + * @ingroup error_checking */ +# define C4_NOEXCEPT_A +#endif // _DOXYGEN_ + + +#ifndef C4_USE_ASSERT +# ifdef NDEBUG +# define C4_USE_ASSERT 0 +# else +# define C4_USE_ASSERT 1 +# endif +#endif + + +#if C4_USE_ASSERT +# define C4_ASSERT(cond) C4_CHECK(cond) +# define C4_ASSERT_MSG(cond, /*fmt, */...) C4_CHECK_MSG(cond, ## __VA_ARGS__) +# define C4_ASSERT_IF(predicate, cond) if(predicate) { C4_ASSERT(cond); } +# define C4_NOEXCEPT_A C4_NOEXCEPT +#else +# define C4_ASSERT(cond) +# define C4_ASSERT_MSG(cond, /*fmt, */...) +# define C4_ASSERT_IF(predicate, cond) +# define C4_NOEXCEPT_A noexcept +#endif + + +//----------------------------------------------------------------------------- +// extreme assertions + +// Doxygen needs this so that only one definition counts +#ifdef _DOXYGEN_ + /** Explicitly enables extreme assertions; this is meant to allow enabling + * assertions even when NDEBUG is defined. Defaults to undefined. + * @ingroup error_checking */ +# define C4_USE_XASSERT + /** extreme assertion: can be switched off independently of + * the regular assertion; use for example for bounds checking in hot code. + * Turned on only when C4_USE_XASSERT is defined + * @ingroup error_checking */ +# define C4_XASSERT + /** same as C4_XASSERT(), and additionally prints a printf-formatted message + * @ingroup error_checking */ +# define C4_XASSERT_MSG + /** evaluates to C4_NOEXCEPT when C4_XASSERT is disabled; otherwise, defaults to noexcept + * @ingroup error_checking */ +# define C4_NOEXCEPT_X +#endif // _DOXYGEN_ + + +#ifndef C4_USE_XASSERT +# define C4_USE_XASSERT C4_USE_ASSERT +#endif + + +#if C4_USE_XASSERT +# define C4_XASSERT(cond) C4_CHECK(cond) +# define C4_XASSERT_MSG(cond, /*fmt, */...) C4_CHECK_MSG(cond, ## __VA_ARGS__) +# define C4_XASSERT_IF(predicate, cond) if(predicate) { C4_XASSERT(cond); } +# define C4_NOEXCEPT_X C4_NOEXCEPT +#else +# define C4_XASSERT(cond) +# define C4_XASSERT_MSG(cond, /*fmt, */...) +# define C4_XASSERT_IF(predicate, cond) +# define C4_NOEXCEPT_X noexcept +#endif + + +//----------------------------------------------------------------------------- +// checks: never switched-off + +/** Check that a condition is true, or raise an error when not + * true. Unlike C4_ASSERT(), this check is not disabled in non-debug + * builds. + * @see C4_ASSERT + * @ingroup error_checking + * + * @todo add constexpr-compatible compile-time assert: + * https://akrzemi1.wordpress.com/2017/05/18/asserts-in-constexpr-functions/ + */ +#define C4_CHECK(cond) \ + do { \ + if(C4_UNLIKELY(!(cond))) \ + { \ + C4_ERROR("check failed: %s", #cond); \ + } \ + } while(0) + + +/** like C4_CHECK(), and additionally log a printf-style message. + * @see C4_CHECK + * @ingroup error_checking */ +#define C4_CHECK_MSG(cond, fmt, ...) \ + do { \ + if(C4_UNLIKELY(!(cond))) \ + { \ + C4_ERROR("check failed: " #cond "\n" fmt, ## __VA_ARGS__); \ + } \ + } while(0) + + +//----------------------------------------------------------------------------- +// Common error conditions + +#define C4_NOT_IMPLEMENTED() C4_ERROR("NOT IMPLEMENTED") +#define C4_NOT_IMPLEMENTED_MSG(/*msg, */...) C4_ERROR("NOT IMPLEMENTED: " __VA_ARGS__) +#define C4_NOT_IMPLEMENTED_IF(condition) do { if(C4_UNLIKELY(condition)) { C4_ERROR("NOT IMPLEMENTED"); } } while(0) +#define C4_NOT_IMPLEMENTED_IF_MSG(condition, /*msg, */...) do { if(C4_UNLIKELY(condition)) { C4_ERROR("NOT IMPLEMENTED: " __VA_ARGS__); } } while(0) + +#define C4_NEVER_REACH() do { C4_ERROR("never reach this point"); C4_UNREACHABLE(); } while(0) +#define C4_NEVER_REACH_MSG(/*msg, */...) do { C4_ERROR("never reach this point: " __VA_ARGS__); C4_UNREACHABLE(); } while(0) + + + +//----------------------------------------------------------------------------- +// helpers for warning suppression +// idea adapted from https://github.com/onqtam/doctest/ + +// TODO: add C4_MESSAGE() https://stackoverflow.com/questions/18252351/custom-preprocessor-macro-for-a-conditional-pragma-message-xxx?rq=1 + + +#if defined(C4_MSVC) && !defined(__clang__) +#define C4_SUPPRESS_WARNING_MSVC_PUSH __pragma(warning(push)) +#define C4_SUPPRESS_WARNING_MSVC(w) __pragma(warning(disable : w)) +#define C4_SUPPRESS_WARNING_MSVC_POP __pragma(warning(pop)) +#else // C4_MSVC +#define C4_SUPPRESS_WARNING_MSVC_PUSH +#define C4_SUPPRESS_WARNING_MSVC(w) +#define C4_SUPPRESS_WARNING_MSVC_POP +#endif // C4_MSVC + + +#if defined(C4_CLANG) || defined(__clang__) +#define C4_PRAGMA_TO_STR(x) _Pragma(#x) +#define C4_SUPPRESS_WARNING_CLANG_PUSH _Pragma("clang diagnostic push") +#define C4_SUPPRESS_WARNING_CLANG(w) C4_PRAGMA_TO_STR(clang diagnostic ignored w) +#define C4_SUPPRESS_WARNING_CLANG_POP _Pragma("clang diagnostic pop") +#else // C4_CLANG +#define C4_SUPPRESS_WARNING_CLANG_PUSH +#define C4_SUPPRESS_WARNING_CLANG(w) +#define C4_SUPPRESS_WARNING_CLANG_POP +#endif // C4_CLANG + + +#ifdef C4_GCC +#define C4_PRAGMA_TO_STR(x) _Pragma(#x) +#define C4_SUPPRESS_WARNING_GCC_PUSH _Pragma("GCC diagnostic push") +#define C4_SUPPRESS_WARNING_GCC(w) C4_PRAGMA_TO_STR(GCC diagnostic ignored w) +#define C4_SUPPRESS_WARNING_GCC_POP _Pragma("GCC diagnostic pop") +#else // C4_GCC +#define C4_SUPPRESS_WARNING_GCC_PUSH +#define C4_SUPPRESS_WARNING_GCC(w) +#define C4_SUPPRESS_WARNING_GCC_POP +#endif // C4_GCC + + +#define C4_SUPPRESS_WARNING_MSVC_WITH_PUSH(w) \ + C4_SUPPRESS_WARNING_MSVC_PUSH \ + C4_SUPPRESS_WARNING_MSVC(w) + +#define C4_SUPPRESS_WARNING_CLANG_WITH_PUSH(w) \ + C4_SUPPRESS_WARNING_CLANG_PUSH \ + C4_SUPPRESS_WARNING_CLANG(w) + +#define C4_SUPPRESS_WARNING_GCC_WITH_PUSH(w) \ + C4_SUPPRESS_WARNING_GCC_PUSH \ + C4_SUPPRESS_WARNING_GCC(w) + + +#define C4_SUPPRESS_WARNING_PUSH \ + C4_SUPPRESS_WARNING_GCC_PUSH \ + C4_SUPPRESS_WARNING_CLANG_PUSH \ + C4_SUPPRESS_WARNING_MSVC_PUSH + +#define C4_SUPPRESS_WARNING_GCC_CLANG_PUSH \ + C4_SUPPRESS_WARNING_GCC_PUSH \ + C4_SUPPRESS_WARNING_CLANG_PUSH + +#define C4_SUPPRESS_WARNING_GCC_CLANG(w) \ + C4_SUPPRESS_WARNING_GCC(w) \ + C4_SUPPRESS_WARNING_CLANG(w) + +#define C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH(w) \ + C4_SUPPRESS_WARNING_GCC_WITH_PUSH(w) \ + C4_SUPPRESS_WARNING_CLANG_WITH_PUSH(w) + +#define C4_SUPPRESS_WARNING_GCC_CLANG_POP \ + C4_SUPPRESS_WARNING_GCC_POP \ + C4_SUPPRESS_WARNING_CLANG_POP + +#define C4_SUPPRESS_WARNING_POP \ + C4_SUPPRESS_WARNING_GCC_POP \ + C4_SUPPRESS_WARNING_CLANG_POP \ + C4_SUPPRESS_WARNING_MSVC_POP + +} // namespace c4 + +#ifdef __clang__ +# pragma clang diagnostic pop +#endif + +#endif /* _C4_ERROR_HPP_ */ + + +// (end src/c4/error.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/memory_util.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_MEMORY_UTIL_HPP_ +#define _C4_MEMORY_UTIL_HPP_ + +// amalgamate: removed include of +// c4/config.hpp +//#include "c4/config.hpp" +#if !defined(C4_CONFIG_HPP_) && !defined(_C4_CONFIG_HPP_) +#error "amalgamate: file c4/config.hpp must have been included at this point" +#endif /* C4_CONFIG_HPP_ */ + +// amalgamate: removed include of +// c4/error.hpp +//#include "c4/error.hpp" +#if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) +#error "amalgamate: file c4/error.hpp must have been included at this point" +#endif /* C4_ERROR_HPP_ */ + +// amalgamate: removed include of +// c4/compiler.hpp +//#include "c4/compiler.hpp" +#if !defined(C4_COMPILER_HPP_) && !defined(_C4_COMPILER_HPP_) +#error "amalgamate: file c4/compiler.hpp must have been included at this point" +#endif /* C4_COMPILER_HPP_ */ + +// amalgamate: removed include of +// c4/cpu.hpp +//#include "c4/cpu.hpp" +#if !defined(C4_CPU_HPP_) && !defined(_C4_CPU_HPP_) +#error "amalgamate: file c4/cpu.hpp must have been included at this point" +#endif /* C4_CPU_HPP_ */ + +//included above: +//#include +#ifdef C4_MSVC +#include +#endif +//included above: +//#include + +#if (defined(__GNUC__) && __GNUC__ >= 10) || defined(__has_builtin) +#define _C4_USE_LSB_INTRINSIC(which) __has_builtin(which) +#define _C4_USE_MSB_INTRINSIC(which) __has_builtin(which) +#elif defined(C4_MSVC) +#define _C4_USE_LSB_INTRINSIC(which) true +#define _C4_USE_MSB_INTRINSIC(which) true +#else +// let's try our luck +#define _C4_USE_LSB_INTRINSIC(which) true +#define _C4_USE_MSB_INTRINSIC(which) true +#endif + + +/** @file memory_util.hpp Some memory utilities. */ + +// NOLINTBEGIN(google-runtime-int) + +namespace c4 { + +C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wold-style-cast") + +/** set the given memory to zero */ +C4_ALWAYS_INLINE void mem_zero(void* mem, size_t num_bytes) +{ + memset(mem, 0, num_bytes); +} +/** set the given memory to zero */ +template +C4_ALWAYS_INLINE void mem_zero(T* mem, size_t num_elms) +{ + memset(mem, 0, sizeof(T) * num_elms); +} +/** set the given memory to zero */ +template +C4_ALWAYS_INLINE void mem_zero(T* mem) +{ + memset(mem, 0, sizeof(T)); +} + +C4_ALWAYS_INLINE C4_CONST bool mem_overlaps(void const* a, void const* b, size_t sza, size_t szb) +{ + // thanks @timwynants + return (((const char*)b + szb) > a && b < ((const char*)a+sza)); +} + +C4CORE_EXPORT void mem_repeat(void* dest, void const* pattern, size_t pattern_size, size_t num_times); + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +template +C4_ALWAYS_INLINE C4_CONST bool is_aligned(T const* ptr, uintptr_t alignment=alignof(T)) +{ + return (uintptr_t(ptr) & (alignment - uintptr_t(1))) == uintptr_t(0); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// least significant bit + +/** @name msb Compute the least significant bit + * @note the input value must be nonzero + * @note the input type must be unsigned + */ +/** @{ */ + +// https://graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightLinear +#define _c4_lsb_fallback \ + unsigned c = 0; \ + v = (v ^ (v - 1)) >> 1; /* Set v's trailing 0s to 1s and zero rest */ \ + for(; v; ++c) \ + v >>= 1; \ + return (unsigned) c + +// u8 +template +C4_CONSTEXPR14 +auto lsb(I v) noexcept + -> typename std::enable_if::type +{ + C4_STATIC_ASSERT(std::is_unsigned::value); + C4_ASSERT(v != 0); + #if _C4_USE_LSB_INTRINSIC(__builtin_ctz) + // upcast to use the intrinsic, it's cheaper. + #ifdef C4_MSVC + #if !defined(C4_CPU_ARM64) && !defined(C4_CPU_ARM) + unsigned long bit = {}; + _BitScanForward(&bit, (unsigned long)v); + return bit; + #else + _c4_lsb_fallback; + #endif + #else + return (unsigned)__builtin_ctz((unsigned)v); + #endif + #else + _c4_lsb_fallback; + #endif +} + +// u16 +template +C4_CONSTEXPR14 +auto lsb(I v) noexcept + -> typename std::enable_if::type +{ + C4_STATIC_ASSERT(std::is_unsigned::value); + C4_ASSERT(v != 0); + #if _C4_USE_LSB_INTRINSIC(__builtin_ctz) + // upcast to use the intrinsic, it's cheaper. + // Then remember that the upcast makes it to 31bits + #ifdef C4_MSVC + #if !defined(C4_CPU_ARM64) && !defined(C4_CPU_ARM) + unsigned long bit = {}; + _BitScanForward(&bit, (unsigned long)v); + return bit; + #else + _c4_lsb_fallback; + #endif + #else + return (unsigned)__builtin_ctz((unsigned)v); + #endif + #else + _c4_lsb_fallback; + #endif +} + +// u32 +template +C4_CONSTEXPR14 +auto lsb(I v) noexcept + -> typename std::enable_if::type +{ + C4_STATIC_ASSERT(std::is_unsigned::value); + C4_ASSERT(v != 0); + #if _C4_USE_LSB_INTRINSIC(__builtin_ctz) + #ifdef C4_MSVC + #if !defined(C4_CPU_ARM64) && !defined(C4_CPU_ARM) + unsigned long bit = {}; + _BitScanForward(&bit, v); + return bit; + #else + _c4_lsb_fallback; + #endif + #else + return (unsigned)__builtin_ctz((unsigned)v); + #endif + #else + _c4_lsb_fallback; + #endif +} + +// u64 in 64bits +template +C4_CONSTEXPR14 +auto lsb(I v) noexcept + -> typename std::enable_if::type +{ + C4_STATIC_ASSERT(std::is_unsigned::value); + C4_ASSERT(v != 0); + #if _C4_USE_LSB_INTRINSIC(__builtin_ctzl) + #if defined(C4_MSVC) + #if !defined(C4_CPU_ARM64) && !defined(C4_CPU_ARM) + unsigned long bit = {}; + _BitScanForward64(&bit, v); + return bit; + #else + _c4_lsb_fallback; + #endif + #else + return (unsigned)__builtin_ctzl((unsigned long)v); + #endif + #else + _c4_lsb_fallback; + #endif +} + +// u64 in 32bits +template +C4_CONSTEXPR14 +auto lsb(I v) noexcept + -> typename std::enable_if::type +{ + C4_STATIC_ASSERT(std::is_unsigned::value); + C4_ASSERT(v != 0); + #if _C4_USE_LSB_INTRINSIC(__builtin_ctzll) + #if defined(C4_MSVC) + #if !defined(C4_CPU_X86) && !defined(C4_CPU_ARM64) && !defined(C4_CPU_ARM) + unsigned long bit = {}; + _BitScanForward64(&bit, v); + return bit; + #else + _c4_lsb_fallback; + #endif + #else + return (unsigned)__builtin_ctzll((unsigned long long)v); + #endif + #else + _c4_lsb_fallback; + #endif +} + +#undef _c4_lsb_fallback + +/** @} */ + + +namespace detail { +template struct _lsb11; +template +struct _lsb11 +{ + enum : unsigned { num = _lsb11>1), num_bits+I(1), (((val>>1)&I(1))!=I(0))>::num }; +}; +template +struct _lsb11 +{ + enum : unsigned { num = num_bits }; +}; +} // namespace detail + + +/** TMP version of lsb(); this needs to be implemented with template + * meta-programming because C++11 cannot use a constexpr function with + * local variables + * @see lsb */ +template +struct lsb11 +{ + static_assert(number != 0, "lsb: number must be nonzero"); + enum : unsigned { value = detail::_lsb11::num}; +}; + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// most significant bit + + +/** @name msb Compute the most significant bit + * @note the input value must be nonzero + * @note the input type must be unsigned + */ +/** @{ */ + + +#define _c4_msb8_fallback \ + unsigned n = 0; \ + if(v & I(0xf0)) v >>= 4, n |= I(4); \ + if(v & I(0x0c)) v >>= 2, n |= I(2); \ + if(v & I(0x02)) v >>= 1, n |= I(1); \ + return n + +#define _c4_msb16_fallback \ + unsigned n = 0; \ + if(v & I(0xff00)) v >>= 8, n |= I(8); \ + if(v & I(0x00f0)) v >>= 4, n |= I(4); \ + if(v & I(0x000c)) v >>= 2, n |= I(2); \ + if(v & I(0x0002)) v >>= 1, n |= I(1); \ + return n + +#define _c4_msb32_fallback \ + unsigned n = 0; \ + if(v & I(0xffff0000)) v >>= 16, n |= 16; \ + if(v & I(0x0000ff00)) v >>= 8, n |= 8; \ + if(v & I(0x000000f0)) v >>= 4, n |= 4; \ + if(v & I(0x0000000c)) v >>= 2, n |= 2; \ + if(v & I(0x00000002)) v >>= 1, n |= 1; \ + return n + +#define _c4_msb64_fallback \ + unsigned n = 0; \ + if(v & I(0xffffffff00000000)) v >>= 32, n |= I(32); \ + if(v & I(0x00000000ffff0000)) v >>= 16, n |= I(16); \ + if(v & I(0x000000000000ff00)) v >>= 8, n |= I(8); \ + if(v & I(0x00000000000000f0)) v >>= 4, n |= I(4); \ + if(v & I(0x000000000000000c)) v >>= 2, n |= I(2); \ + if(v & I(0x0000000000000002)) v >>= 1, n |= I(1); \ + return n + + +// u8 +template +C4_CONSTEXPR14 +auto msb(I v) noexcept + -> typename std::enable_if::type +{ + C4_STATIC_ASSERT(std::is_unsigned::value); + C4_ASSERT(v != 0); + #if _C4_USE_MSB_INTRINSIC(__builtin_clz) + // upcast to use the intrinsic, it's cheaper. + // Then remember that the upcast makes it to 31bits + #ifdef C4_MSVC + #if !defined(C4_CPU_ARM64) && !defined(C4_CPU_ARM) + unsigned long bit = {}; + _BitScanReverse(&bit, (unsigned long)v); + return bit; + #else + _c4_msb8_fallback; + #endif + #else + return 31u - (unsigned)__builtin_clz((unsigned)v); + #endif + #else + _c4_msb8_fallback; + #endif +} + +// u16 +template +C4_CONSTEXPR14 +auto msb(I v) noexcept + -> typename std::enable_if::type +{ + C4_STATIC_ASSERT(std::is_unsigned::value); + C4_ASSERT(v != 0); + #if _C4_USE_MSB_INTRINSIC(__builtin_clz) + // upcast to use the intrinsic, it's cheaper. + // Then remember that the upcast makes it to 31bits + #ifdef C4_MSVC + #if !defined(C4_CPU_ARM64) && !defined(C4_CPU_ARM) + unsigned long bit = {}; + _BitScanReverse(&bit, (unsigned long)v); + return bit; + #else + _c4_msb16_fallback; + #endif + #else + return 31u - (unsigned)__builtin_clz((unsigned)v); + #endif + #else + _c4_msb16_fallback; + #endif +} + +// u32 +template +C4_CONSTEXPR14 +auto msb(I v) noexcept + -> typename std::enable_if::type +{ + C4_STATIC_ASSERT(std::is_unsigned::value); + C4_ASSERT(v != 0); + #if _C4_USE_MSB_INTRINSIC(__builtin_clz) + #ifdef C4_MSVC + #if !defined(C4_CPU_ARM64) && !defined(C4_CPU_ARM) + unsigned long bit = {}; + _BitScanReverse(&bit, v); + return bit; + #else + _c4_msb32_fallback; + #endif + #else + return 31u - (unsigned)__builtin_clz((unsigned)v); + #endif + #else + _c4_msb32_fallback; + #endif +} + +// u64 in 64bits +template +C4_CONSTEXPR14 +auto msb(I v) noexcept + -> typename std::enable_if::type +{ + C4_STATIC_ASSERT(std::is_unsigned::value); + C4_ASSERT(v != 0); + #if _C4_USE_MSB_INTRINSIC(__builtin_clzl) + #ifdef C4_MSVC + #if !defined(C4_CPU_ARM64) && !defined(C4_CPU_ARM) + unsigned long bit = {}; + _BitScanReverse64(&bit, v); + return bit; + #else + _c4_msb64_fallback; + #endif + #else + return 63u - (unsigned)__builtin_clzl((unsigned long)v); + #endif + #else + _c4_msb64_fallback; + #endif +} + +// u64 in 32bits +template +C4_CONSTEXPR14 +auto msb(I v) noexcept + -> typename std::enable_if::type +{ + C4_STATIC_ASSERT(std::is_unsigned::value); + C4_ASSERT(v != 0); + #if _C4_USE_MSB_INTRINSIC(__builtin_clzll) + #ifdef C4_MSVC + #if !defined(C4_CPU_X86) && !defined(C4_CPU_ARM64) && !defined(C4_CPU_ARM) + unsigned long bit = {}; + _BitScanReverse64(&bit, v); + return bit; + #else + _c4_msb64_fallback; + #endif + #else + return 63u - (unsigned)__builtin_clzll((unsigned long long)v); + #endif + #else + _c4_msb64_fallback; + #endif +} + +#undef _c4_msb8_fallback +#undef _c4_msb16_fallback +#undef _c4_msb32_fallback +#undef _c4_msb64_fallback + +/** @} */ + + +namespace detail { +template struct _msb11; +template +struct _msb11< I, val, num_bits, false> +{ + enum : unsigned { num = _msb11>1), num_bits+I(1), ((val>>1)==I(0))>::num }; +}; +template +struct _msb11 +{ + static_assert(val == 0, "bad implementation"); + enum : unsigned { num = (unsigned)(num_bits-1) }; +}; +} // namespace detail + + +/** TMP version of msb(); this needs to be implemented with template + * meta-programming because C++11 cannot use a constexpr function with + * local variables + * @see msb */ +template +struct msb11 +{ + enum : unsigned { value = detail::_msb11::num }; +}; + + + +#undef _C4_USE_LSB_INTRINSIC +#undef _C4_USE_MSB_INTRINSIC + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +// there is an implicit conversion below; it happens when E or B are +// narrower than int, and thus any operation will upcast the result to +// int, and then downcast to assign +C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wconversion") + +/** integer power; this function is constexpr-14 because of the local + * variables */ +template +C4_CONSTEXPR14 C4_CONST auto ipow(B base, E exponent) noexcept -> typename std::enable_if::value, B>::type +{ + C4_STATIC_ASSERT(std::is_integral::value); + B r = B(1); + if(exponent >= 0) + { + for(E e = 0; e < exponent; ++e) + r *= base; + } + else + { + exponent *= E(-1); + for(E e = 0; e < exponent; ++e) + r /= base; + } + return r; +} + +/** integer power; this function is constexpr-14 because of the local + * variables */ +template +C4_CONSTEXPR14 C4_CONST auto ipow(E exponent) noexcept -> typename std::enable_if::value, B>::type +{ + C4_STATIC_ASSERT(std::is_integral::value); + B r = B(1); + if(exponent >= 0) + { + for(E e = 0; e < exponent; ++e) + r *= base; + } + else + { + exponent *= E(-1); + for(E e = 0; e < exponent; ++e) + r /= base; + } + return r; +} + +/** integer power; this function is constexpr-14 because of the local + * variables */ +template +C4_CONSTEXPR14 C4_CONST auto ipow(E exponent) noexcept -> typename std::enable_if::value, B>::type +{ + C4_STATIC_ASSERT(std::is_integral::value); + B r = B(1); + B bbase = B(base); + if(exponent >= 0) + { + for(E e = 0; e < exponent; ++e) + r *= bbase; + } + else + { + exponent *= E(-1); + for(E e = 0; e < exponent; ++e) + r /= bbase; + } + return r; +} + +/** integer power; this function is constexpr-14 because of the local + * variables */ +template +C4_CONSTEXPR14 C4_CONST auto ipow(B base, E exponent) noexcept -> typename std::enable_if::value, B>::type +{ + C4_STATIC_ASSERT(std::is_integral::value); + B r = B(1); + for(E e = 0; e < exponent; ++e) + r *= base; + return r; +} + +/** integer power; this function is constexpr-14 because of the local + * variables */ +template +C4_CONSTEXPR14 C4_CONST auto ipow(E exponent) noexcept -> typename std::enable_if::value, B>::type +{ + C4_STATIC_ASSERT(std::is_integral::value); + B r = B(1); + for(E e = 0; e < exponent; ++e) + r *= base; + return r; +} +/** integer power; this function is constexpr-14 because of the local + * variables */ +template +C4_CONSTEXPR14 C4_CONST auto ipow(E exponent) noexcept -> typename std::enable_if::value, B>::type +{ + C4_STATIC_ASSERT(std::is_integral::value); + B r = B(1); + B bbase = B(base); + for(E e = 0; e < exponent; ++e) + r *= bbase; + return r; +} + +C4_SUPPRESS_WARNING_GCC_CLANG_POP + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** return a mask with all bits set [first_bit,last_bit[; this function + * is constexpr-14 because of the local variables */ +template +C4_CONSTEXPR14 I contiguous_mask(I first_bit, I last_bit) +{ + I r = 0; + for(I i = first_bit; i < last_bit; ++i) + { + r |= (I(1) << i); + } + return r; +} + + +namespace detail { + +template +struct _ctgmsk11; + +template +struct _ctgmsk11< I, val, first, last, true> +{ + enum : I { value = _ctgmsk11::value }; +}; + +template +struct _ctgmsk11< I, val, first, last, false> +{ + enum : I { value = val }; +}; + +} // namespace detail + + +/** TMP version of contiguous_mask(); this needs to be implemented with template + * meta-programming because C++11 cannot use a constexpr function with + * local variables + * @see contiguous_mask */ +template +struct contiguous_mask11 +{ + enum : I { value = detail::_ctgmsk11::value }; +}; + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +/** use Empty Base Class Optimization to reduce the size of a pair of + * potentially empty types*/ + +namespace detail { +typedef enum { + tpc_same, + tpc_same_empty, + tpc_both_empty, + tpc_first_empty, + tpc_second_empty, + tpc_general +} TightPairCase_e; + +template +constexpr TightPairCase_e tpc_which_case() +{ + return std::is_same::value ? + std::is_empty::value ? + tpc_same_empty + : + tpc_same + : + std::is_empty::value && std::is_empty::value ? + tpc_both_empty + : + std::is_empty::value ? + tpc_first_empty + : + std::is_empty::value ? + tpc_second_empty + : + tpc_general + ; +} + +template +struct tight_pair +{ +private: + + First m_first; + Second m_second; + +public: + + using first_type = First; + using second_type = Second; + + tight_pair() : m_first(), m_second() {} + tight_pair(First const& f, Second const& s) : m_first(f), m_second(s) {} + + C4_ALWAYS_INLINE C4_CONSTEXPR14 First & first () { return m_first; } + C4_ALWAYS_INLINE C4_CONSTEXPR14 First const& first () const { return m_first; } + C4_ALWAYS_INLINE C4_CONSTEXPR14 Second & second() { return m_second; } + C4_ALWAYS_INLINE C4_CONSTEXPR14 Second const& second() const { return m_second; } +}; + +template +struct tight_pair : public First +{ + static_assert(std::is_same::value, "bad implementation"); + + using first_type = First; + using second_type = Second; + + tight_pair() : First() {} + tight_pair(First const& f, Second const& /*s*/) : First(f) {} + + C4_ALWAYS_INLINE C4_CONSTEXPR14 First & first () { return static_cast(*this); } + C4_ALWAYS_INLINE C4_CONSTEXPR14 First const& first () const { return static_cast(*this); } + C4_ALWAYS_INLINE C4_CONSTEXPR14 Second & second() { return reinterpret_cast(*this); } // NOLINT + C4_ALWAYS_INLINE C4_CONSTEXPR14 Second const& second() const { return reinterpret_cast(*this); } // NOLINT +}; + +template +struct tight_pair : public First, public Second +{ + using first_type = First; + using second_type = Second; + + tight_pair() : First(), Second() {} + tight_pair(First const& f, Second const& s) : First(f), Second(s) {} + + C4_ALWAYS_INLINE C4_CONSTEXPR14 First & first () { return static_cast(*this); } + C4_ALWAYS_INLINE C4_CONSTEXPR14 First const& first () const { return static_cast(*this); } + C4_ALWAYS_INLINE C4_CONSTEXPR14 Second & second() { return static_cast(*this); } + C4_ALWAYS_INLINE C4_CONSTEXPR14 Second const& second() const { return static_cast(*this); } +}; + +template +struct tight_pair : public First +{ + Second m_second; + + using first_type = First; + using second_type = Second; + + tight_pair() : First() {} + tight_pair(First const& f, Second const& s) : First(f), m_second(s) {} + + C4_ALWAYS_INLINE C4_CONSTEXPR14 First & first () { return static_cast(*this); } + C4_ALWAYS_INLINE C4_CONSTEXPR14 First const& first () const { return static_cast(*this); } + C4_ALWAYS_INLINE C4_CONSTEXPR14 Second & second() { return m_second; } + C4_ALWAYS_INLINE C4_CONSTEXPR14 Second const& second() const { return m_second; } +}; + +template +struct tight_pair : public First +{ + Second m_second; + + using first_type = First; + using second_type = Second; + + tight_pair() : First(), m_second() {} + tight_pair(First const& f, Second const& s) : First(f), m_second(s) {} + + C4_ALWAYS_INLINE C4_CONSTEXPR14 First & first () { return static_cast(*this); } + C4_ALWAYS_INLINE C4_CONSTEXPR14 First const& first () const { return static_cast(*this); } + C4_ALWAYS_INLINE C4_CONSTEXPR14 Second & second() { return m_second; } + C4_ALWAYS_INLINE C4_CONSTEXPR14 Second const& second() const { return m_second; } +}; + +template +struct tight_pair : public Second +{ + First m_first; + + using first_type = First; + using second_type = Second; + + tight_pair() : Second(), m_first() {} + tight_pair(First const& f, Second const& s) : Second(s), m_first(f) {} + + C4_ALWAYS_INLINE C4_CONSTEXPR14 First & first () { return m_first; } + C4_ALWAYS_INLINE C4_CONSTEXPR14 First const& first () const { return m_first; } + C4_ALWAYS_INLINE C4_CONSTEXPR14 Second & second() { return static_cast(*this); } + C4_ALWAYS_INLINE C4_CONSTEXPR14 Second const& second() const { return static_cast(*this); } +}; + +} // namespace detail + +template +using tight_pair = detail::tight_pair()>; + +C4_SUPPRESS_WARNING_GCC_CLANG_POP + +} // namespace c4 + +// NOLINTEND(google-runtime-int) + +#endif /* _C4_MEMORY_UTIL_HPP_ */ + + +// (end src/c4/memory_util.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/alloc.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_ALLOC_HPP_ +#define _C4_ALLOC_HPP_ + +/** @file alloc.hpp Provides c-style facilities to allocate raw + * memory. This API provides aligned allocation functions, forwarding + * the call to a user-modifiable function. */ + +/** @defgroup memory memory utilities */ + +/** @defgroup raw_memory_alloc Raw memory allocation + * @ingroup memory + */ + +// amalgamate: removed include of +// c4/export.hpp +//#include "c4/export.hpp" +#if !defined(C4_EXPORT_HPP_) && !defined(_C4_EXPORT_HPP_) +#error "amalgamate: file c4/export.hpp must have been included at this point" +#endif /* C4_EXPORT_HPP_ */ + +//included above: +//#include + +namespace c4 { + + +// aligned allocation. + +/** Aligned allocation. Merely calls the current get_aalloc() function. + * @see get_aalloc() + * @ingroup raw_memory_alloc */ +C4CORE_EXPORT void* aalloc(size_t sz, size_t alignment); + +/** Aligned free. Merely calls the current get_afree() function. + * @see get_afree() + * @ingroup raw_memory_alloc */ +C4CORE_EXPORT void afree(void* ptr); + +/** Aligned reallocation. Merely calls the current get_arealloc() function. + * @see get_arealloc() + * @ingroup raw_memory_alloc */ +C4CORE_EXPORT void* arealloc(void* ptr, size_t oldsz, size_t newsz, size_t alignment); + + + +// allocation setup facilities: function pointer setters/getters + +/** Function pointer type for aligned allocation + * @see set_aalloc() + * @ingroup raw_memory_alloc */ +using aalloc_pfn = void* (*)(size_t size, size_t alignment); + +/** Function pointer type for aligned deallocation + * @see set_afree() + * @ingroup raw_memory_alloc */ +using afree_pfn = void (*)(void *ptr); + +/** Function pointer type for aligned reallocation + * @see set_arealloc() + * @ingroup raw_memory_alloc */ +using arealloc_pfn = void* (*)(void *ptr, size_t oldsz, size_t newsz, size_t alignment); + + + +/** Set the global aligned allocation function. + * @see aalloc() + * @see get_aalloc() + * @ingroup raw_memory_alloc */ +C4CORE_EXPORT void set_aalloc(aalloc_pfn fn); + +/** Set the global aligned deallocation function. + * @see afree() + * @see get_afree() + * @ingroup raw_memory_alloc */ +C4CORE_EXPORT void set_afree(afree_pfn fn); + +/** Set the global aligned reallocation function. + * @see arealloc() + * @see get_arealloc() + * @ingroup raw_memory_alloc */ +C4CORE_EXPORT void set_arealloc(arealloc_pfn fn); + + + +/** Get the global aligned reallocation function. + * @see arealloc() + * @ingroup raw_memory_alloc */ +C4CORE_EXPORT aalloc_pfn get_aalloc(); + +/** Get the global aligned deallocation function. + * @see afree() + * @ingroup raw_memory_alloc */ +C4CORE_EXPORT afree_pfn get_afree(); + +/** Get the global aligned reallocation function. + * @see arealloc() + * @ingroup raw_memory_alloc */ +C4CORE_EXPORT arealloc_pfn get_arealloc(); + +} // namespace c4 + +#endif + + +// (end src/c4/alloc.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/memory_resource.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_MEMORY_RESOURCE_HPP_ +#define _C4_MEMORY_RESOURCE_HPP_ + +/** @file memory_resource.hpp Provides facilities to allocate typeless + * memory, via the memory resource model consecrated with C++17. */ + +/** @defgroup memory_resources Memory resources + * @ingroup memory + */ + +// amalgamate: removed include of +// c4/alloc.hpp +//#include "c4/alloc.hpp" +#if !defined(C4_ALLOC_HPP_) && !defined(_C4_ALLOC_HPP_) +#error "amalgamate: file c4/alloc.hpp must have been included at this point" +#endif /* C4_ALLOC_HPP_ */ + +// amalgamate: removed include of +// c4/error.hpp +//#include "c4/error.hpp" +#if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) +#error "amalgamate: file c4/error.hpp must have been included at this point" +#endif /* C4_ERROR_HPP_ */ + +// amalgamate: removed include of +// c4/types.hpp +//#include "c4/types.hpp" +#if !defined(C4_TYPES_HPP_) && !defined(_C4_TYPES_HPP_) +#error "amalgamate: file c4/types.hpp must have been included at this point" +#endif /* C4_TYPES_HPP_ */ + + +namespace c4 { + +// need these forward decls here +struct MemoryResource; +struct MemoryResourceMalloc; + + +/** get the current global memory resource. To avoid static initialization + * order problems, this is implemented using a function call to ensure + * that it is available when first used. + * @ingroup memory_resources */ +C4CORE_EXPORT MemoryResource* get_memory_resource(); + +/** set the global memory resource + * @ingroup memory_resources */ +C4CORE_EXPORT void set_memory_resource(MemoryResource* mr); + + +// c++-style allocation ------------------------------------------------------- + + +/** C++17-style memory_resource base class. See http://en.cppreference.com/w/cpp/experimental/memory_resource + * @ingroup memory_resources */ +struct C4CORE_EXPORT MemoryResource // NOLINT(*-member-functions) +{ + const char *name = nullptr; + MemoryResource(const char *name_=nullptr) noexcept : name(name_) {} + virtual ~MemoryResource() noexcept = default; + + void* allocate(size_t sz, size_t alignment=alignof(max_align_t), void *hint=nullptr) + { + void *mem = this->do_allocate(sz, alignment, hint); + 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, void* hint) = 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. + * @ingroup memory_resources */ +struct C4CORE_EXPORT MemoryResourceMalloc : public MemoryResource // NOLINT(*-member-functions) +{ + + MemoryResourceMalloc() noexcept : MemoryResource("malloc") {} + +protected: + + void* do_allocate(size_t sz, size_t alignment, void *hint) override + { + C4_UNUSED(hint); + return c4::aalloc(sz, alignment); + } + + void do_deallocate(void* ptr, size_t sz, size_t alignment) override + { + C4_UNUSED(sz); + C4_UNUSED(alignment); + c4::afree(ptr); + } + + void* do_reallocate(void* ptr, size_t oldsz, size_t newsz, size_t alignment) override + { + return c4::arealloc(ptr, oldsz, newsz, alignment); + } + +}; + +/** returns a malloc-based memory resource + * @ingroup memory_resources */ +C4CORE_EXPORT MemoryResourceMalloc* get_memory_resource_malloc(); + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +namespace detail { + +/** Allows a memory resource to obtain its memory from another memory resource. + * @ingroup memory_resources */ +struct C4CORE_EXPORT DerivedMemoryResource : public MemoryResource +{ +public: + + DerivedMemoryResource(MemoryResource *mr_=nullptr) : m_local(mr_ ? mr_ : get_memory_resource()) {} + +private: + + MemoryResource *m_local; + +protected: + + void* do_allocate(size_t sz, size_t alignment, void* hint) override + { + return m_local->allocate(sz, alignment, hint); + } + + void* do_reallocate(void* ptr, size_t oldsz, size_t newsz, size_t alignment) override + { + return m_local->reallocate(ptr, oldsz, newsz, alignment); + } + + void do_deallocate(void* ptr, size_t sz, size_t alignment) override + { + m_local->deallocate(ptr, sz, alignment); + } +}; + +/** Provides common facilities for memory resource consisting of a single memory block + * @ingroup memory_resources */ +struct C4CORE_EXPORT _MemoryResourceSingleChunk : public DerivedMemoryResource +{ + + C4_NO_COPY_OR_MOVE(_MemoryResourceSingleChunk); + + using impl_type = DerivedMemoryResource; + +public: + + _MemoryResourceSingleChunk(MemoryResource *impl=nullptr) : DerivedMemoryResource(impl) { name = "linear_malloc"; } + + /** initialize with owned memory, allocated from the given (or the global) memory resource */ + _MemoryResourceSingleChunk(size_t sz, MemoryResource *impl=nullptr) : _MemoryResourceSingleChunk(impl) { acquire(sz); } + /** initialize with borrowed memory */ + _MemoryResourceSingleChunk(void *mem, size_t sz) : _MemoryResourceSingleChunk() { acquire(mem, sz); } + + ~_MemoryResourceSingleChunk() override { release(); } + +public: + + void const* mem() const { return m_mem; } + + size_t capacity() const { return m_size; } + size_t size() const { return m_pos; } + size_t slack() const { C4_ASSERT(m_size >= m_pos); return m_size - m_pos; } + +public: + + char *m_mem{nullptr}; + size_t m_size{0}; + size_t m_pos{0}; + bool m_owner; + +public: + + /** set the internal pointer to the beginning of the linear buffer */ + void clear() { m_pos = 0; } + + /** initialize with owned memory, allocated from the global memory resource */ + void acquire(size_t sz); + /** initialize with borrowed memory */ + void acquire(void *mem, size_t sz); + /** release the memory */ + void release(); + +}; + +} // namespace detail + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +/** provides a linear memory resource. Allocates incrementally from a linear + * buffer, without ever deallocating. Deallocations are a no-op, and the + * memory is freed only when the resource is release()d. The memory used by + * this object can be either owned or borrowed. When borrowed, no calls to + * malloc/free take place. + * + * @ingroup memory_resources */ +struct C4CORE_EXPORT MemoryResourceLinear : public detail::_MemoryResourceSingleChunk // NOLINT(*-member-functions) +{ + + C4_NO_COPY_OR_MOVE(MemoryResourceLinear); + +public: + + using detail::_MemoryResourceSingleChunk::_MemoryResourceSingleChunk; + +protected: + + void* do_allocate(size_t sz, size_t alignment, void *hint) override; + void do_deallocate(void* ptr, size_t sz, size_t alignment) override; + void* do_reallocate(void* ptr, size_t oldsz, size_t newsz, size_t alignment) override; +}; + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +/** provides a linear array-based memory resource. + * @see MemoryResourceLinear + * @ingroup memory_resources */ +template +struct MemoryResourceLinearArr : public MemoryResourceLinear +{ + C4_SUPPRESS_WARNING_MSVC_WITH_PUSH(4324) // structure was padded due to alignment specifier + alignas(alignof(max_align_t)) char m_arr[N]; + C4_SUPPRESS_WARNING_MSVC_POP + MemoryResourceLinearArr() : MemoryResourceLinear(m_arr, N) { name = "linear_arr"; } // NOLINT +}; + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +struct C4CORE_EXPORT AllocationCounts +{ + struct Item + { + ssize_t allocs; + ssize_t size; + + void add(size_t sz) + { + ++allocs; + size += static_cast(sz); + } + void rem(size_t sz) + { + --allocs; + size -= static_cast(sz); + } + Item max(Item const& that) const + { + Item r(*this); + r.allocs = r.allocs > that.allocs ? r.allocs : that.allocs; + r.size = r.size > that.size ? r.size : that.size; + return r; + } + }; + + Item curr = {0, 0}; + Item total = {0, 0}; + Item max = {0, 0}; + + void clear_counts() + { + curr = {0, 0}; + total = {0, 0}; + max = {0, 0}; + } + + void update(AllocationCounts const& that) + { + curr.allocs += that.curr.allocs; + curr.size += that.curr.size; + total.allocs += that.total.allocs; + total.size += that.total.size; + max.allocs += that.max.allocs; + max.size += that.max.size; + } + + void add_counts(void* ptr, size_t sz) + { + if(ptr == nullptr) return; + curr.add(sz); + total.add(sz); + max = max.max(curr); + } + + void rem_counts(void *ptr, size_t sz) + { + if(ptr == nullptr) return; + curr.rem(sz); + } + + AllocationCounts operator- (AllocationCounts const& that) const + { + AllocationCounts r(*this); + r.curr.allocs -= that.curr.allocs; + r.curr.size -= that.curr.size; + r.total.allocs -= that.total.allocs; + r.total.size -= that.total.size; + r.max.allocs -= that.max.allocs; + r.max.size -= that.max.size; + return r; + } + + AllocationCounts operator+ (AllocationCounts const& that) const + { + AllocationCounts r(*this); + r.curr.allocs += that.curr.allocs; + r.curr.size += that.curr.size; + r.total.allocs += that.total.allocs; + r.total.size += that.total.size; + r.max.allocs += that.max.allocs; + r.max.size += that.max.size; + return r; + } +}; + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +/** a MemoryResource which latches onto another MemoryResource + * and counts allocations and sizes. + * @ingroup memory_resources */ +class C4CORE_EXPORT MemoryResourceCounts : public MemoryResource +{ +public: + + MemoryResourceCounts() : m_resource(get_memory_resource()) + { + C4_ASSERT(m_resource != this); + name = "MemoryResourceCounts"; + } + MemoryResourceCounts(MemoryResource *res) : m_resource(res) + { + C4_ASSERT(m_resource != this); + name = "MemoryResourceCounts"; + } + + MemoryResource *resource() { return m_resource; } + AllocationCounts const& counts() const { return m_counts; } + +protected: + + MemoryResource *m_resource; // NOLINT + AllocationCounts m_counts; // NOLINT + +protected: + + void* do_allocate(size_t sz, size_t alignment, void * /*hint*/) override + { + void *ptr = m_resource->allocate(sz, alignment); + m_counts.add_counts(ptr, sz); + return ptr; + } + + void do_deallocate(void* ptr, size_t sz, size_t alignment) override + { + m_counts.rem_counts(ptr, sz); + m_resource->deallocate(ptr, sz, alignment); + } + + void* do_reallocate(void* ptr, size_t oldsz, size_t newsz, size_t alignment) override + { + m_counts.rem_counts(ptr, oldsz); + void* nptr = m_resource->reallocate(ptr, oldsz, newsz, alignment); + m_counts.add_counts(nptr, newsz); + return nptr; + } + +}; + +//----------------------------------------------------------------------------- +/** RAII class which binds a memory resource with a scope duration. + * @ingroup memory_resources */ +struct C4CORE_EXPORT ScopedMemoryResource // NOLINT(*-member-functions) +{ + MemoryResource *m_original; + + ScopedMemoryResource(MemoryResource *r) + : + m_original(get_memory_resource()) + { + set_memory_resource(r); + } + + ~ScopedMemoryResource() + { + set_memory_resource(m_original); + } +}; + +//----------------------------------------------------------------------------- +/** RAII class which counts allocations and frees inside a scope. Can + * optionally set also the memory resource to be used. + * @ingroup memory_resources */ +struct C4CORE_EXPORT ScopedMemoryResourceCounts // NOLINT(*-member-functions) +{ + MemoryResourceCounts mr; + + ScopedMemoryResourceCounts() : mr() + { + set_memory_resource(&mr); + } + ScopedMemoryResourceCounts(MemoryResource *m) : mr(m) + { + set_memory_resource(&mr); + } + ~ScopedMemoryResourceCounts() + { + set_memory_resource(mr.resource()); + } +}; + +} // namespace c4 + +#endif /* _C4_MEMORY_RESOURCE_HPP_ */ + + +// (end src/c4/memory_resource.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/ctor_dtor.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_CTOR_DTOR_HPP_ +#define _C4_CTOR_DTOR_HPP_ + +// amalgamate: removed include of +// c4/preprocessor.hpp +//#include "c4/preprocessor.hpp" +#if !defined(C4_PREPROCESSOR_HPP_) && !defined(_C4_PREPROCESSOR_HPP_) +#error "amalgamate: file c4/preprocessor.hpp must have been included at this point" +#endif /* C4_PREPROCESSOR_HPP_ */ + +// amalgamate: removed include of +// c4/language.hpp +//#include "c4/language.hpp" +#if !defined(C4_LANGUAGE_HPP_) && !defined(_C4_LANGUAGE_HPP_) +#error "amalgamate: file c4/language.hpp must have been included at this point" +#endif /* C4_LANGUAGE_HPP_ */ + +// amalgamate: removed include of +// c4/memory_util.hpp +//#include "c4/memory_util.hpp" +#if !defined(C4_MEMORY_UTIL_HPP_) && !defined(_C4_MEMORY_UTIL_HPP_) +#error "amalgamate: file c4/memory_util.hpp must have been included at this point" +#endif /* C4_MEMORY_UTIL_HPP_ */ + +// amalgamate: removed include of +// c4/error.hpp +//#include "c4/error.hpp" +#if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) +#error "amalgamate: file c4/error.hpp must have been included at this point" +#endif /* C4_ERROR_HPP_ */ + + +//included above: +//#include +//included above: +//#include // std::forward + +/** @file ctor_dtor.hpp object construction and destruction facilities. + * Some of these are not yet available in C++11. */ + +namespace c4 { + +C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wold-style-cast") + +/** default-construct an object, trivial version */ +template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type +construct(U *ptr) noexcept +{ + memset(ptr, 0, sizeof(U)); +} +/** default-construct an object, non-trivial version */ +template C4_ALWAYS_INLINE typename std ::enable_if< ! std::is_trivially_default_constructible::value, void>::type +construct(U* ptr) noexcept +{ + new ((void*)ptr) U(); +} + +/** default-construct n objects, trivial version */ +template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type +construct_n(U* ptr, I n) noexcept +{ + memset(ptr, 0, n * sizeof(U)); +} +/** default-construct n objects, non-trivial version */ +template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_default_constructible::value, void>::type +construct_n(U* ptr, I n) noexcept +{ + for(I i = 0; i < n; ++i) + { + new ((void*)(ptr + i)) U(); + } +} + +#ifdef __clang__ +# pragma clang diagnostic push +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# if __GNUC__ >= 6 +# pragma GCC diagnostic ignored "-Wnull-dereference" +# endif +#endif + +template +inline void construct(U* ptr, Args&&... args) +{ + new ((void*)ptr) U(std::forward(args)...); +} +template +inline void construct_n(U* ptr, I n, Args&&... args) // NOLINT +{ + for(I i = 0; i < n; ++i) + { + new ((void*)(ptr + i)) U(args...); + } +} + +#ifdef __clang__ +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#endif + + +//----------------------------------------------------------------------------- +// copy-construct + +template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type +copy_construct(U* dst, U const* src) noexcept +{ + C4_ASSERT(dst != src); + memcpy(dst, src, sizeof(U)); +} +template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_copy_constructible::value, void>::type +copy_construct(U* dst, U const* src) +{ + C4_ASSERT(dst != src); + new ((void*)dst) U(*src); +} +template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type +copy_construct_n(U* dst, U const* src, I n) noexcept +{ + C4_ASSERT(dst != src); + memcpy(dst, src, n * sizeof(U)); +} +template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_copy_constructible::value, void>::type +copy_construct_n(U* dst, U const* src, I n) +{ + C4_ASSERT(dst != src); + for(I i = 0; i < n; ++i) + { + new ((void*)(dst + i)) U(*(src + i)); + } +} + +template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type +copy_construct(U* dst, U src) noexcept // pass by value for scalar types +{ + *dst = src; +} +template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_scalar::value, void>::type +copy_construct(U* dst, U const& src) // pass by reference for non-scalar types +{ + C4_ASSERT(dst != &src); + new ((void*)dst) U(src); +} +template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type +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 C4_ALWAYS_INLINE typename std::enable_if< ! std::is_scalar::value, void>::type +copy_construct_n(U* dst, U const& src, I n) // pass by reference for non-scalar types +{ + C4_ASSERT(dst != &src); + for(I i = 0; i < n; ++i) + { + new ((void*)(dst + i)) U(src); + } +} + +template +C4_ALWAYS_INLINE void copy_construct(U (&dst)[N], U const (&src)[N]) noexcept +{ + copy_construct_n(dst, src, N); +} + +//----------------------------------------------------------------------------- +// copy-assign + +template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type +copy_assign(U* dst, U const* src) noexcept +{ + C4_ASSERT(dst != src); + memcpy(dst, src, sizeof(U)); +} +template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_copy_assignable::value, void>::type +copy_assign(U* dst, U const* src) noexcept +{ + C4_ASSERT(dst != src); + *dst = *src; +} +template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type +copy_assign_n(U* dst, U const* src, I n) noexcept +{ + C4_ASSERT(dst != src); + memcpy(dst, src, n * sizeof(U)); +} +template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_copy_assignable::value, void>::type +copy_assign_n(U* dst, U const* src, I n) noexcept +{ + C4_ASSERT(dst != src); + for(I i = 0; i < n; ++i) + { + dst[i] = src[i]; + } +} + +template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type +copy_assign(U* dst, U src) noexcept // pass by value for scalar types +{ + *dst = src; +} +template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_scalar::value, void>::type +copy_assign(U* dst, U const& src) noexcept // pass by reference for non-scalar types +{ + C4_ASSERT(dst != &src); + *dst = src; +} +template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type +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 C4_ALWAYS_INLINE typename std::enable_if< ! std::is_scalar::value, void>::type +copy_assign_n(U* dst, U const& src, I n) noexcept // pass by reference for non-scalar types +{ + C4_ASSERT(dst != &src); + for(I i = 0; i < n; ++i) + { + dst[i] = src; + } +} + +template +C4_ALWAYS_INLINE void copy_assign(U (&dst)[N], U const (&src)[N]) noexcept +{ + copy_assign_n(dst, src, N); +} + +//----------------------------------------------------------------------------- +// move-construct + +template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type +move_construct(U* dst, U* src) noexcept +{ + C4_ASSERT(dst != src); + memcpy(dst, src, sizeof(U)); +} +template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_move_constructible::value, void>::type +move_construct(U* dst, U* src) noexcept +{ + C4_ASSERT(dst != src); + new ((void*)dst) U(std::move(*src)); +} +template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type +move_construct_n(U* dst, U* src, I n) noexcept +{ + C4_ASSERT(dst != src); + memcpy(dst, src, n * sizeof(U)); +} +template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_move_constructible::value, void>::type +move_construct_n(U* dst, U* src, I n) noexcept +{ + C4_ASSERT(dst != src); + for(I i = 0; i < n; ++i) + { + new ((void*)(dst + i)) U(std::move(src[i])); + } +} + +//----------------------------------------------------------------------------- +// move-assign + +template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type +move_assign(U* dst, U* src) noexcept +{ + C4_ASSERT(dst != src); + memcpy(dst, src, sizeof(U)); +} +template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_move_assignable::value, void>::type +move_assign(U* dst, U* src) noexcept +{ + C4_ASSERT(dst != src); + *dst = std::move(*src); +} +template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type +move_assign_n(U* dst, U* src, I n) noexcept +{ + C4_ASSERT(dst != src); + memcpy(dst, src, n * sizeof(U)); +} +template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_move_assignable::value, void>::type +move_assign_n(U* dst, U* src, I n) noexcept +{ + C4_ASSERT(dst != src); + for(I i = 0; i < n; ++i) + { + *(dst + i) = std::move(*(src + i)); + } +} + +//----------------------------------------------------------------------------- +// destroy + +template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type +destroy(U* ptr) noexcept +{ + C4_UNUSED(ptr); // nothing to do +} +template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_destructible::value, void>::type +destroy(U* ptr) noexcept +{ + ptr->~U(); +} +template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type +destroy_n(U* ptr, I n) noexcept +{ + C4_UNUSED(ptr); + C4_UNUSED(n); // nothing to do +} +template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_destructible::value, void>::type +destroy_n(U* ptr, I n) noexcept +{ + for(I i = 0; i C4_ALWAYS_INLINE typename std::enable_if::value, void>::type +make_room(U *buf, I bufsz, I room) C4_NOEXCEPT_A +{ + C4_ASSERT(bufsz >= 0 && room >= 0); + if(room >= bufsz) + { + memcpy (buf + room, buf, bufsz * sizeof(U)); + } + else + { + memmove(buf + room, buf, bufsz * sizeof(U)); + } +} +/** makes room at the beginning of buf, which has a current size of bufsz */ +template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_move_constructible::value, void>::type +make_room(U *buf, I bufsz, I room) C4_NOEXCEPT_A +{ + C4_ASSERT(bufsz >= 0 && room >= 0); + if(room >= bufsz) + { + for(I i = 0; i < bufsz; ++i) + { + new ((void*)(buf + (i + room))) U(std::move(buf[i])); + } + } + else + { + for(I i = 0; i < bufsz; ++i) + { + I w = bufsz-1 - i; // do a backwards loop + new ((void*)(buf + (w + room))) U(std::move(buf[w])); + } + } +} + +/** make room to the right of pos */ +template +C4_ALWAYS_INLINE void make_room(U *buf, I bufsz, I currsz, I pos, I room) +{ + C4_ASSERT(pos >= 0 && pos <= currsz); + C4_ASSERT(currsz <= bufsz); + C4_ASSERT(room + currsz <= bufsz); + C4_UNUSED(bufsz); + make_room(buf + pos, currsz - pos, room); +} + + +/** make room to the right of pos, copying to the beginning of a different buffer */ +template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type +make_room(U *dst, U const* src, I srcsz, I room, I pos) C4_NOEXCEPT_A +{ + C4_ASSERT(srcsz >= 0 && room >= 0 && pos >= 0); + C4_ASSERT(pos < srcsz || (pos == 0 && srcsz == 0)); + memcpy(dst , src , pos * sizeof(U)); + memcpy(dst + room + pos, src + pos, (srcsz - pos) * sizeof(U)); +} +/** make room to the right of pos, copying to the beginning of a different buffer */ +template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_move_constructible::value, void>::type +make_room(U *dst, U const* src, I srcsz, I room, I pos) +{ + C4_ASSERT(srcsz >= 0 && room >= 0 && pos >= 0); + C4_ASSERT(pos < srcsz || (pos == 0 && srcsz == 0)); + for(I i = 0; i < pos; ++i) + { + new ((void*)(dst + i)) U(std::move(src[i])); + } + src += pos; + dst += room + pos; + for(I i = 0, e = srcsz - pos; i < e; ++i) + { + new ((void*)(dst + i)) U(std::move(src[i])); + } +} + +template +C4_ALWAYS_INLINE void make_room +( + U * dst, I dstsz, + U const* src, I srcsz, + I room, I pos +) +{ + C4_ASSERT(pos >= 0 && pos < srcsz || (srcsz == 0 && pos == 0)); + C4_ASSERT(pos >= 0 && pos < dstsz || (dstsz == 0 && pos == 0)); + C4_ASSERT(srcsz+room <= dstsz); + C4_UNUSED(dstsz); + make_room(dst, src, srcsz, room, pos); +} + + +//----------------------------------------------------------------------------- +/** destroy room at the beginning of buf, which has a current size of n */ +template C4_ALWAYS_INLINE typename std::enable_if::value || (std::is_standard_layout::value && std::is_trivial::value), void>::type +destroy_room(U *buf, I n, I room) C4_NOEXCEPT_A +{ + C4_ASSERT(n >= 0 && room >= 0); + C4_ASSERT(room <= n); + if(room < n) + { + memmove(buf, buf + room, (n - room) * sizeof(U)); + } + else + { + // nothing to do - no need to destroy scalar types + } +} +/** destroy room at the beginning of buf, which has a current size of n */ +template C4_ALWAYS_INLINE typename std::enable_if< ! (std::is_scalar::value || (std::is_standard_layout::value && std::is_trivial::value)), void>::type +destroy_room(U *buf, I n, I room) +{ + C4_ASSERT(n >= 0 && room >= 0); + C4_ASSERT(room <= n); + if(room < n) + { + for(I i = 0, e = n - room; i < e; ++i) + { + buf[i] = std::move(buf[i + room]); + } + } + else + { + for(I i = 0; i < n; ++i) + { + buf[i].~U(); + } + } +} + +/** destroy room to the right of pos, copying to a different buffer */ +template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type +destroy_room(U *dst, U const* src, I n, I room, I pos) C4_NOEXCEPT_A +{ + C4_ASSERT(n >= 0 && room >= 0 && pos >= 0); + C4_ASSERT(pos C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_move_constructible::value, void>::type +destroy_room(U *dst, U const* src, I n, I room, I pos) +{ + C4_ASSERT(n >= 0 && room >= 0 && pos >= 0); + C4_ASSERT(pos < n); + C4_ASSERT(pos + room <= n); + for(I i = 0; i < pos; ++i) + { + new ((void*)(dst + i)) U(std::move(src[i])); + } + src += room + pos; + dst += pos; + for(I i = 0, e = n - pos - room; i < e; ++i) + { + new ((void*)(dst + i)) U(std::move(src[i])); + } +} + +C4_SUPPRESS_WARNING_GCC_CLANG_POP + +} // namespace c4 + +#endif /* _C4_CTOR_DTOR_HPP_ */ + + +// (end src/c4/ctor_dtor.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/allocator.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_ALLOCATOR_HPP_ +#define _C4_ALLOCATOR_HPP_ + +// amalgamate: removed include of +// c4/memory_resource.hpp +//#include "c4/memory_resource.hpp" +#if !defined(C4_MEMORY_RESOURCE_HPP_) && !defined(_C4_MEMORY_RESOURCE_HPP_) +#error "amalgamate: file c4/memory_resource.hpp must have been included at this point" +#endif /* C4_MEMORY_RESOURCE_HPP_ */ + +// amalgamate: removed include of +// c4/ctor_dtor.hpp +//#include "c4/ctor_dtor.hpp" +#if !defined(C4_CTOR_DTOR_HPP_) && !defined(_C4_CTOR_DTOR_HPP_) +#error "amalgamate: file c4/ctor_dtor.hpp must have been included at this point" +#endif /* C4_CTOR_DTOR_HPP_ */ + + +#include // std::allocator_traits +//included above: +//#include + +/** @file allocator.hpp Contains classes to make typeful allocations (note + * that memory resources are typeless) */ + +/** @defgroup mem_res_providers Memory resource providers + * @brief Policy classes which provide a memory resource for + * use in an allocator. + * @ingroup memory + */ + +/** @defgroup allocators Allocators + * @brief Lightweight classes that act as handles to specific memory + * resources and provide typeful memory. + * @ingroup memory + */ + +namespace c4 { + +C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wold-style-cast") + +namespace detail { +template inline size_t size_for (size_t num_objs) noexcept { return num_objs * sizeof(T); } +template< > inline size_t size_for(size_t num_objs) noexcept { return num_objs; } +} // namespace detail + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** provides a per-allocator memory resource + * @ingroup mem_res_providers */ +class MemRes +{ +public: + + MemRes() : m_resource(get_memory_resource()) {} + MemRes(MemoryResource* r) noexcept : m_resource(r ? r : get_memory_resource()) {} + + MemoryResource* resource() const { return m_resource; } + +private: + + MemoryResource* m_resource; + +}; + + +/** the allocators using this will default to the global memory resource + * @ingroup mem_res_providers */ +class MemResGlobal +{ +public: + + MemResGlobal() = default; + MemResGlobal(MemoryResource* r) noexcept { C4_UNUSED(r); C4_ASSERT(r == get_memory_resource()); } + + static MemoryResource* resource() { return get_memory_resource(); } +}; + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +namespace detail { +template +struct _AllocatorUtil; + +template +struct has_no_alloc + : public std::integral_constant::value) + && std::is_constructible::value> {}; + +// std::uses_allocator_v && std::is_constructible +// ie can construct(std::allocator_arg_t, MemoryResource*, Args...) +template +struct has_alloc_arg + : public std::integral_constant::value + && std::is_constructible::value> {}; +// std::uses_allocator && std::is_constructible +// ie, can construct(Args..., MemoryResource*) +template +struct has_alloc + : public std::integral_constant::value + && std::is_constructible::value> {}; + +} // namespace detail + + +template +struct detail::_AllocatorUtil : public MemRes +{ + using MemRes::MemRes; + + /** for construct: + * @see http://en.cppreference.com/w/cpp/experimental/polymorphic_allocator/construct */ + + // 1. types with no allocators + template + C4_ALWAYS_INLINE typename std::enable_if::value, void>::type + construct(U *ptr, Args &&...args) + { + c4::construct(ptr, std::forward(args)...); + } + template + C4_ALWAYS_INLINE typename std::enable_if::value, void>::type + construct_n(U* ptr, I n, Args&&... args) + { + c4::construct_n(ptr, n, std::forward(args)...); + } + + // 2. types using allocators (ie, containers) + + // 2.1. can construct(std::allocator_arg_t, MemoryResource*, Args...) + template + C4_ALWAYS_INLINE typename std::enable_if::value, void>::type + construct(U* ptr, Args&&... args) + { + c4::construct(ptr, std::allocator_arg, this->resource(), std::forward(args)...); + } + template + C4_ALWAYS_INLINE typename std::enable_if::value, void>::type + construct_n(U* ptr, I n, Args&&... args) + { + c4::construct_n(ptr, n, std::allocator_arg, this->resource(), std::forward(args)...); + } + + // 2.2. can construct(Args..., MemoryResource*) + template + C4_ALWAYS_INLINE typename std::enable_if::value, void>::type + construct(U* ptr, Args&&... args) + { + c4::construct(ptr, std::forward(args)..., this->resource()); + } + template + C4_ALWAYS_INLINE typename std::enable_if::value, void>::type + construct_n(U* ptr, I n, Args&&... args) + { + c4::construct_n(ptr, n, std::forward(args)..., this->resource()); + } + + template + static C4_ALWAYS_INLINE void destroy(U* ptr) + { + c4::destroy(ptr); + } + template + static C4_ALWAYS_INLINE void destroy_n(U* ptr, I n) + { + c4::destroy_n(ptr, n); + } +}; + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** An allocator is simply a proxy to a memory resource. + * @param T + * @param MemResProvider + * @ingroup allocators */ +template +class Allocator : public detail::_AllocatorUtil // NOLINT(*-member-functions) +{ +public: + + using impl_type = detail::_AllocatorUtil; + + using value_type = T; + using pointer = T*; + using const_pointer = T const*; + using reference = T&; + using const_reference = T const&; + using size_type = size_t; + using difference_type = std::ptrdiff_t; + using propagate_on_container_move_assigment = std::true_type; + +public: + + template + bool operator== (Allocator const& that) const + { + return this->resource() == that.resource(); + } + template + bool operator!= (Allocator const& that) const + { + return this->resource() != that.resource(); + } + +public: + + template friend class Allocator; + template + struct rebind + { + using other = Allocator; + }; + template + typename rebind::other rebound() + { + return typename rebind::other(*this); + } + +public: + + using impl_type::impl_type; + Allocator() : impl_type() {} // VS demands this + + template Allocator(Allocator const& that) : impl_type(that.resource()) {} + + Allocator(Allocator const&) = default; + Allocator(Allocator &&) = default; + + Allocator& operator= (Allocator const&) = default; // 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(this->resource() != nullptr); + C4_ASSERT(alignment >= alignof(T)); + void* vmem = this->resource()->allocate(detail::size_for(num_objs), alignment); + T* mem = static_cast(vmem); + return mem; + } + + void deallocate(T * ptr, size_t num_objs, size_t alignment=alignof(T)) + { + C4_ASSERT(this->resource() != nullptr); + C4_ASSERT(alignment>= alignof(T)); + this->resource()->deallocate(ptr, detail::size_for(num_objs), alignment); + } + + T* reallocate(T* ptr, size_t oldnum, size_t newnum, size_t alignment=alignof(T)) + { + C4_ASSERT(this->resource() != nullptr); + C4_ASSERT(alignment >= alignof(T)); + void* vmem = this->resource()->reallocate(ptr, detail::size_for(oldnum), detail::size_for(newnum), alignment); + T* mem = static_cast(vmem); + return mem; + } + +}; + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** @ingroup allocators */ +template +class SmallAllocator : public detail::_AllocatorUtil // NOLINT(*-member-functions) +{ + static_assert(Alignment >= alignof(T), "invalid alignment"); + + using impl_type = detail::_AllocatorUtil; + + alignas(Alignment) char m_arr[N * sizeof(T)]; + size_t m_num{0}; + +public: + + using value_type = T; + using pointer = T*; + using const_pointer = T const*; + using reference = T&; + using const_reference = T const&; + using size_type = size_t; + using difference_type = std::ptrdiff_t; + using propagate_on_container_move_assigment = std::true_type; + + template + bool operator== (SmallAllocator const&) const + { + return false; + } + template + bool operator!= (SmallAllocator const&) const + { + return true; + } + +public: + + template friend class SmallAllocator; + template + struct rebind + { + using other = SmallAllocator; + }; + template + typename rebind::other rebound() + { + return typename rebind::other(*this); + } + +public: + + using impl_type::impl_type; + SmallAllocator() : impl_type() {} // VS demands this + + template + SmallAllocator(SmallAllocator const& that) : impl_type(that.resource()) + { + C4_ASSERT(that.m_num == 0); + } + + SmallAllocator(SmallAllocator const&) = default; + SmallAllocator(SmallAllocator &&) = default; + + SmallAllocator& operator= (SmallAllocator const&) = default; // 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=Alignment) + { + C4_ASSERT(this->resource() != nullptr); + C4_ASSERT(alignment >= alignof(T)); + void *vmem; + if(m_num + num_objs <= N) + { + vmem = (m_arr + (m_num * sizeof(T))); + } + else + { + vmem = this->resource()->allocate(num_objs * sizeof(T), alignment); + } + m_num += num_objs; + T *mem = static_cast(vmem); + return mem; + } + + void deallocate(T * ptr, size_t num_objs, size_t alignment=Alignment) + { + C4_ASSERT(m_num >= num_objs); + m_num -= num_objs; + if((char*)ptr >= m_arr && (char*)ptr < m_arr + (N * sizeof(T))) + { + return; + } + C4_ASSERT(this->resource() != nullptr); + C4_ASSERT(alignment >= alignof(T)); + this->resource()->deallocate(ptr, num_objs * sizeof(T), alignment); + } + + T* reallocate(T * ptr, size_t oldnum, size_t newnum, size_t alignment=Alignment) + { + C4_ASSERT(this->resource() != nullptr); + C4_ASSERT(alignment >= alignof(T)); + 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 = this->resource()->reallocate(ptr, oldnum * sizeof(T), newnum * sizeof(T), alignment); + T* mem = static_cast(vmem); + return mem; + } + +}; + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** An allocator making use of the global memory resource. + * @ingroup allocators */ +template using allocator = Allocator; +/** An allocator with a per-instance memory resource + * @ingroup allocators */ +template using allocator_mr = Allocator; + +/** @ingroup allocators */ +template using small_allocator = SmallAllocator; +/** @ingroup allocators */ +template using small_allocator_mr = SmallAllocator; + +C4_SUPPRESS_WARNING_GCC_CLANG_POP + +} // namespace c4 + +#endif /* _C4_ALLOCATOR_HPP_ */ + + +// (end src/c4/allocator.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/char_traits.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_CHAR_TRAITS_HPP_ +#define _C4_CHAR_TRAITS_HPP_ + +// amalgamate: removed include of +// c4/config.hpp +//#include "c4/config.hpp" +#if !defined(C4_CONFIG_HPP_) && !defined(_C4_CONFIG_HPP_) +#error "amalgamate: file c4/config.hpp must have been included at this point" +#endif /* C4_CONFIG_HPP_ */ + + +#include // needed because of std::char_traits +#include +#include + +namespace c4 { + +C4_ALWAYS_INLINE bool isspace(char c) { return std::isspace(c) != 0; } +C4_ALWAYS_INLINE bool isspace(wchar_t c) { return std::iswspace(static_cast(c)) != 0; } + +//----------------------------------------------------------------------------- +template +struct char_traits; + +template<> +struct char_traits : public std::char_traits +{ + constexpr static const char whitespace_chars[] = " \f\n\r\t\v"; + constexpr static const size_t num_whitespace_chars = sizeof(whitespace_chars) - 1; +}; + +template<> +struct char_traits : public std::char_traits +{ + constexpr static const wchar_t whitespace_chars[] = L" \f\n\r\t\v"; + constexpr static const size_t num_whitespace_chars = sizeof(whitespace_chars) - 1; +}; + + +//----------------------------------------------------------------------------- +namespace detail { +template +struct needed_chars; +template<> +struct needed_chars +{ + template + C4_ALWAYS_INLINE constexpr static SizeType for_bytes(SizeType num_bytes) + { + return num_bytes; + } +}; +template<> +struct needed_chars +{ + template + C4_ALWAYS_INLINE constexpr static SizeType for_bytes(SizeType num_bytes) + { + // wchar_t is not necessarily 2 bytes. + return (num_bytes / static_cast(sizeof(wchar_t))) + ((num_bytes & static_cast(SizeType(sizeof(wchar_t)) - SizeType(1))) != 0); + } +}; +} // namespace detail + +/** get the number of C characters needed to store a number of bytes */ +template +C4_ALWAYS_INLINE constexpr SizeType num_needed_chars(SizeType num_bytes) +{ + return detail::needed_chars::for_bytes(num_bytes); +} + + +//----------------------------------------------------------------------------- + +/** get the given text string as either char or wchar_t according to the given type */ +#define C4_TXTTY(txt, type) \ + /* is there a smarter way to do this? */\ + c4::detail::literal_as::get(txt, C4_WIDEN(txt)) + +namespace detail { +template +struct literal_as; + +template<> +struct literal_as +{ + C4_ALWAYS_INLINE static constexpr const char* get(const char* str, const wchar_t *) + { + return str; + } +}; +template<> +struct literal_as +{ + C4_ALWAYS_INLINE static constexpr const wchar_t* get(const char*, const wchar_t *wstr) + { + return wstr; + } +}; +} // namespace detail + +} // namespace c4 + +#endif /* _C4_CHAR_TRAITS_HPP_ */ + + +// (end src/c4/char_traits.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/hash.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_HASH_HPP_ +#define _C4_HASH_HPP_ + +// amalgamate: removed include of +// c4/config.hpp +//#include "c4/config.hpp" +#if !defined(C4_CONFIG_HPP_) && !defined(_C4_CONFIG_HPP_) +#error "amalgamate: file c4/config.hpp must have been included at this point" +#endif /* C4_CONFIG_HPP_ */ + +#include +//included above: +//#include +//included above: +//#include +//included above: +//#include + +/** @file hash.hpp */ + +/** @defgroup hash Hash utils + * @see http://aras-p.info/blog/2016/08/02/Hash-Functions-all-the-way-down/ */ + +namespace c4 { + +namespace detail { + +/** @internal + * @ingroup hash + * @see this was taken a great answer in stackoverflow: + * https://stackoverflow.com/a/34597785/5875572 + * @see http://aras-p.info/blog/2016/08/02/Hash-Functions-all-the-way-down/ */ +template +class basic_fnv1a final +{ + + static_assert(std::is_unsigned::value, "need unsigned integer"); + +public: + + using result_type = ResultT; + +private: + + result_type state_ {}; + +public: + + C4_CONSTEXPR14 basic_fnv1a() noexcept : state_ {OffsetBasis} {} + + C4_CONSTEXPR14 void update(const void *const data, const size_t size) noexcept + { + auto const* cdata = static_cast(data); + auto acc = this->state_; + for(size_t i = 0; i < size; ++i) + { + const auto next = size_t(cdata[i]); + acc = (acc ^ next) * Prime; + } + this->state_ = acc; + } + + C4_CONSTEXPR14 result_type digest() const noexcept + { + return this->state_; + } + +}; + +using fnv1a_32 = basic_fnv1a; +using fnv1a_64 = basic_fnv1a; + +template struct fnv1a; +template<> struct fnv1a<32> { using type = fnv1a_32; }; +template<> struct fnv1a<64> { using type = fnv1a_64; }; + +} // namespace detail + + +/** @ingroup hash */ +template +using fnv1a_t = typename detail::fnv1a::type; + + +/** @ingroup hash */ +C4_CONSTEXPR14 inline size_t hash_bytes(const void *const data, const size_t size) noexcept +{ + fnv1a_t fn{}; + fn.update(data, size); + return fn.digest(); +} + +/** + * @overload hash_bytes + * @ingroup hash */ +template +C4_CONSTEXPR14 inline size_t hash_bytes(const char (&str)[N]) noexcept +{ + fnv1a_t fn{}; + fn.update(str, N); + return fn.digest(); +} + +} // namespace c4 + + +#endif // _C4_HASH_HPP_ + + +// (end src/c4/hash.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/szconv.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_SZCONV_HPP_ +#define _C4_SZCONV_HPP_ + +/** @file szconv.hpp utilities to deal safely with narrowing conversions */ + +// amalgamate: removed include of +// c4/config.hpp +//#include "c4/config.hpp" +#if !defined(C4_CONFIG_HPP_) && !defined(_C4_CONFIG_HPP_) +#error "amalgamate: file c4/config.hpp must have been included at this point" +#endif /* C4_CONFIG_HPP_ */ + +// amalgamate: removed include of +// c4/error.hpp +//#include "c4/error.hpp" +#if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) +#error "amalgamate: file c4/error.hpp must have been included at this point" +#endif /* C4_ERROR_HPP_ */ + + +#include +//included above: +//#include + +namespace c4 { + +C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wold-style-cast") + +/** @todo this would be so much easier with calls to numeric_limits::max()... */ +template +struct is_narrower_size : std::conditional +< + (std::is_signed::value == std::is_signed::value) + ? + (sizeof(SizeOut) < sizeof(SizeIn)) + : + ( + (sizeof(SizeOut) < sizeof(SizeIn)) + || + ( + (sizeof(SizeOut) == sizeof(SizeIn)) + && + (std::is_signed::value && std::is_unsigned::value) + ) + ), + std::true_type, + std::false_type +>::type +{ + static_assert(std::is_integral::value, "must be integral type"); + static_assert(std::is_integral::value, "must be integral type"); +}; + + +/** when SizeOut is wider than SizeIn, assignment can occur without reservations */ +template +C4_ALWAYS_INLINE +typename std::enable_if< ! is_narrower_size::value, SizeOut>::type +szconv(SizeIn sz) noexcept +{ + return static_cast(sz); +} + +/** when SizeOut is narrower than SizeIn, narrowing will occur, so we check + * for overflow. Note that this check is done only if C4_XASSERT is enabled. + * @see C4_XASSERT */ +template +C4_ALWAYS_INLINE +typename std::enable_if::value, SizeOut>::type +szconv(SizeIn sz) +{ + C4_XASSERT(sz >= 0); + C4_XASSERT_MSG((SizeIn)sz <= (SizeIn)std::numeric_limits::max(), "size conversion overflow: in=%zu", (size_t)sz); + SizeOut szo = static_cast(sz); + return szo; +} + +C4_SUPPRESS_WARNING_GCC_CLANG_POP + +} // namespace c4 + +#endif /* _C4_SZCONV_HPP_ */ + + +// (end src/c4/szconv.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/blob.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_BLOB_HPP_ +#define _C4_BLOB_HPP_ + +#ifndef _C4_ERROR_HPP_ +// amalgamate: removed include of +// c4/error.hpp +//#include "c4/error.hpp" +#if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) +#error "amalgamate: file c4/error.hpp must have been included at this point" +#endif /* C4_ERROR_HPP_ */ + +#endif +#ifndef _C4_TYPES_HPP_ +// amalgamate: removed include of +// c4/types.hpp +//#include "c4/types.hpp" +#if !defined(C4_TYPES_HPP_) && !defined(_C4_TYPES_HPP_) +#error "amalgamate: file c4/types.hpp must have been included at this point" +#endif /* C4_TYPES_HPP_ */ + +#endif +#ifndef _C4_MEMORY_UTIL_HPP_ +// amalgamate: removed include of +// c4/memory_util.hpp +//#include "c4/memory_util.hpp" +#if !defined(C4_MEMORY_UTIL_HPP_) && !defined(_C4_MEMORY_UTIL_HPP_) +#error "amalgamate: file c4/memory_util.hpp must have been included at this point" +#endif /* C4_MEMORY_UTIL_HPP_ */ + +#endif + +/** @file blob.hpp Mutable and immutable binary data blobs. +*/ + +namespace c4 { + +template +struct blob_; + +namespace detail { +template struct is_blob_type : std::false_type {}; +template struct is_blob_type> : std::true_type {}; +template struct is_blob_value_type : std::integral_constant::value || std::is_trivially_copyable::value)> {}; +} // namespace + +// NOLINTBEGIN(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) + +template +struct blob_ +{ + static_assert(std::is_same::value || std::is_same::value, "must be either byte or cbyte"); + static_assert(sizeof(T) == 1u, "must be either byte or cbyte"); + +public: + + T * buf; + size_t len; + +public: + + C4_ALWAYS_INLINE blob_() noexcept = default; + C4_ALWAYS_INLINE blob_(blob_ const& that) noexcept = default; + C4_ALWAYS_INLINE blob_(blob_ && that) noexcept = default; + C4_ALWAYS_INLINE blob_& operator=(blob_ && that) noexcept = default; + C4_ALWAYS_INLINE blob_& operator=(blob_ const& that) noexcept = default; + + template::value && std::is_same::type, T>::value, U>::type> C4_ALWAYS_INLINE blob_(blob_ const& that) noexcept : buf(that.buf), len(that.len) {} // NOLINT + template::value && std::is_same::type, T>::value, U>::type> C4_ALWAYS_INLINE blob_(blob_ && that) noexcept : buf(that.buf), len(that.len) {} // NOLINT + template::value && std::is_same::type, T>::value, U>::type> C4_ALWAYS_INLINE blob_& operator=(blob_ && that) noexcept { buf = that.buf; len = that.len; } // NOLINT + template::value && std::is_same::type, T>::value, U>::type> C4_ALWAYS_INLINE blob_& operator=(blob_ const& that) noexcept { buf = that.buf; len = that.len; } // NOLINT + + C4_ALWAYS_INLINE blob_(void *ptr, size_t n) noexcept : buf(reinterpret_cast(ptr)), len(n) {} // NOLINT + C4_ALWAYS_INLINE blob_(void const *ptr, size_t n) noexcept : buf(reinterpret_cast(ptr)), len(n) {} // NOLINT + + #define _C4_REQUIRE_BLOBTYPE(ty) typename std::enable_if<((!detail::is_blob_type::value) && (detail::is_blob_value_type::value)), T>::type + template C4_ALWAYS_INLINE blob_(U &var) noexcept : buf(reinterpret_cast(&var)), len(sizeof(U)) {} // NOLINT + template C4_ALWAYS_INLINE blob_(U *ptr, size_t n) noexcept : buf(reinterpret_cast(ptr)), len(sizeof(U) * n) { C4_ASSERT(is_aligned(ptr)); } // NOLINT + template C4_ALWAYS_INLINE blob_& operator= (U &var) noexcept { buf = reinterpret_cast(&var); len = sizeof(U); return *this; } // NOLINT + template C4_ALWAYS_INLINE blob_(U (&arr)[N]) noexcept : buf(reinterpret_cast(arr)), len(sizeof(U) * N) {} // NOLINT + template C4_ALWAYS_INLINE blob_& operator= (U (&arr)[N]) noexcept { buf = reinterpret_cast(arr); len = sizeof(U) * N; return *this; } // NOLINT + #undef _C4_REQUIRE_BLOBTYPE +}; + +// NOLINTEND(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) + +/** an immutable binary blob */ +using cblob = blob_; +/** a mutable binary blob */ +using blob = blob_< byte>; + +C4_MUST_BE_TRIVIAL_COPY(blob); +C4_MUST_BE_TRIVIAL_COPY(cblob); + +} // namespace c4 + +#endif // _C4_BLOB_HPP_ + + +// (end src/c4/blob.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/substr_fwd.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_SUBSTR_FWD_HPP_ +#define _C4_SUBSTR_FWD_HPP_ + +namespace c4 { + +#ifndef __DOXYGEN__ +template struct basic_substring; +using csubstr = basic_substring; +using substr = basic_substring; +template struct is_string; +template struct is_writeable_string; +#endif // !__DOXYGEN__ + +} // namespace c4 + +#endif /* _C4_SUBSTR_FWD_HPP_ */ + + +// (end src/c4/substr_fwd.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/substr.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_SUBSTR_HPP_ +#define _C4_SUBSTR_HPP_ + +/** @file substr.hpp read+write string views */ + +//included above: +//#include +//included above: +//#include +//included above: +//#include + +// amalgamate: removed include of +// c4/export.hpp +//#include "c4/export.hpp" +#if !defined(C4_EXPORT_HPP_) && !defined(_C4_EXPORT_HPP_) +#error "amalgamate: file c4/export.hpp must have been included at this point" +#endif /* C4_EXPORT_HPP_ */ + +// amalgamate: removed include of +// c4/language.hpp +//#include "c4/language.hpp" +#if !defined(C4_LANGUAGE_HPP_) && !defined(_C4_LANGUAGE_HPP_) +#error "amalgamate: file c4/language.hpp must have been included at this point" +#endif /* C4_LANGUAGE_HPP_ */ + +// amalgamate: removed include of +// c4/error.hpp +//#include "c4/error.hpp" +#if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) +#error "amalgamate: file c4/error.hpp must have been included at this point" +#endif /* C4_ERROR_HPP_ */ + +// amalgamate: removed include of +// c4/substr_fwd.hpp +//#include "c4/substr_fwd.hpp" +#if !defined(C4_SUBSTR_FWD_HPP_) && !defined(_C4_SUBSTR_FWD_HPP_) +#error "amalgamate: file c4/substr_fwd.hpp must have been included at this point" +#endif /* C4_SUBSTR_FWD_HPP_ */ + + +C4_SUPPRESS_WARNING_GCC_CLANG_PUSH +C4_SUPPRESS_WARNING_GCC_CLANG("-Wold-style-cast") +C4_SUPPRESS_WARNING_GCC("-Wuseless-cast") +C4_SUPPRESS_WARNING_GCC("-Wtype-limits") // disable warnings on size_t>=0, used heavily in assertions below. These assertions are a preparation step for providing the index type as a template parameter. + +namespace c4 { + + +/** @cond dev */ +namespace detail { + +// implementation of is_string/is_writeable_string +template struct is_string : public std::false_type {}; +template struct is_writeable_string : public std::false_type {}; + +template<> struct is_string : public std::true_type {}; +template<> struct is_writeable_string : public std::true_type {}; + +template<> struct is_string : public std::true_type {}; +template<> struct is_writeable_string : public std::false_type {}; + +template struct is_string : public std::true_type {}; +template struct is_writeable_string : public std::false_type {}; + +template struct is_string : public std::true_type {}; +template struct is_writeable_string : public std::true_type {}; + +template struct is_string : public std::true_type {}; +template struct is_writeable_string : public std::false_type {}; + +template struct is_string : public std::true_type {}; +template struct is_writeable_string : public std::true_type {}; + +template struct is_string : public std::true_type {}; +template struct is_writeable_string : public std::false_type {}; + +template struct is_string : public std::true_type {}; +template struct is_writeable_string : public std::true_type {}; + + +// utility trait to remove restrict keyword +template struct remove_restrict { using type = T; }; +template struct remove_restrict { using type = T*; }; +template struct remove_restrict { using type = T&; }; // clang<8 does not match this!!! +// utility trait to remove references from pointer reference +template struct remove_ptrref { using type = T; }; +template struct remove_ptrref { using type = T*; }; +template struct remove_ptrref { using type = T* const; }; +template struct remove_ptrref { using type = T *C4_RESTRICT; }; +template struct remove_ptrref { using type = T *C4_RESTRICT const; }; +// utility trait to remove const, but only from pointer types +template struct remove_ptrconst { using type = T; }; +template struct remove_ptrconst { using type = T*; }; +template struct remove_ptrconst { using type = T* C4_RESTRICT; }; + + +// clean a type of qualifiers for enabling +// SFINAE on raw-pointer types irrespective of the qualifiers. +template +struct bare_pointer_type +{ + using type = typename remove_restrict< + typename remove_ptrconst< + typename remove_ptrref< + T>::type + >::type + >::type; +}; + + +template +struct _is_comp_char_ptr : std::integral_constant< + bool, + std::is_same::type *>::value + || + std::is_same::value> {}; + + +template +struct _can_borrow_char_ptr : std::integral_constant< + bool, + _is_comp_char_ptr::value + && + ( + std::is_const::value + || + ! std::is_const::type>::value + )> {}; + + +template +static inline void _do_reverse(C *C4_RESTRICT first, C *C4_RESTRICT last) noexcept +{ + while(last > first) + { + C tmp = *last; + *last-- = *first; + *first++ = tmp; + } +} +} // namespace detail +/** @endcond */ + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** @defgroup string_traits String traits classes + * @{ */ + +/** a traits class to mark a type as a string type, meaning @ref + * c4::to_csubstr() can be used directly instead of @ref + * c4::to_chars() when formatting the string. */ +template struct is_string + : public detail::is_string< + typename detail::bare_pointer_type::type + > {}; + + +/** a traits class to mark a type as a writeable string type, meaning + * @ref c4::to_substr() can be used directly instead of @ref + * c4::from_chars() when reading the string. */ +template struct is_writeable_string + : public detail::is_writeable_string< + typename detail::bare_pointer_type::type + > {}; + + +template struct is_string> : public std::true_type {}; +template<> struct is_string> : public std::true_type {}; +template<> struct is_string> : public std::true_type {}; +template<> struct is_writeable_string> : public std::true_type {}; +template<> struct is_writeable_string> : public std::true_type {}; + + +/* traits class to query whether a pointer-type stripped of qualifiers + * like `C4_RESTRICT` is one of `char*` or `const char*`, compatible + * with a destination value type (one of char or const char). + * + * This is used in @ref c4::basic_substring to enable SFINAE on + * `char*` and `const char*` overloads and prevent these of overriding + * coexisting array overloads. + * + * FromPointerType is the SFINAE-d type, and ToValueType is the char + * or const char destination type. See @ref c4::basic_substring below + * for examples of usage. */ +template +struct is_compatible_char_ptr + : detail::_is_comp_char_ptr< + typename detail::bare_pointer_type::type, + ToValueType> {}; + + +/* traits class to query whether a pointer-type stripped of qualifiers + * like or `C4_RESTRICT` is one of `char*` or `const char*`, + * compatible with a destination value type (one of char or const + * char) and further can be used to initialize a substring of that + * destination value type. + * + * This is used in @ref c4::basic_substring to enable SFINAE on + * `char*` and `const char*` overloads and prevent these of overriding + * coexisting array overloads. + * + * FromPointerType is the SFINAE-d type, and ToValueType is the char + * or const char destination type. See @ref c4::basic_substring below + * for examples of usage. */ +template +struct can_borrow_char_ptr + : detail::_can_borrow_char_ptr< + typename detail::bare_pointer_type::type, + ToValueType> {}; + +/** @} */ + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** @defgroup doc_substr Substring: read/write string views + * @{ */ + + +/** a non-owning string-view, consisting of a character pointer + * and a length. + * + * @note The pointer is explicitly restricted. + * + * @see a [quickstart + * sample](https://rapidyaml.readthedocs.io/latest/doxygen/group__doc__quickstart.html#ga43e253da0692c13967019446809c1113) + * in rapidyaml's documentation. + */ +template +struct basic_substring // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) +{ +public: + + /** a restricted pointer to the first character of the substring */ + C * C4_RESTRICT str; + /** the length of the substring */ + size_t len; + +public: + + /** @name Types */ + /** @{ */ + + using CC = typename std::add_const::type; //!< CC=const char + using NCC_ = typename std::remove_const::type; //!< NCC_=non const char + + using ro_substr = basic_substring; + using rw_substr = basic_substring; + + using value_type = C; + using char_type = C; + using size_type = size_t; + + using iterator = C*; + using const_iterator = CC*; + + enum : size_t { npos = (size_t)-1, NONE = (size_t)-1 }; + + /// convert automatically from substr (of C) to csubstr (of const C) + template + C4_ALWAYS_INLINE operator + typename std::enable_if::value, ro_substr const&>::type () const noexcept + { + return *(ro_substr const*)this; // don't call the str+len ctor because it does a check + } + + /** @} */ + +public: + + /** @name Default construction and assignment */ + /** @{ */ + + C4_ALWAYS_INLINE constexpr basic_substring() noexcept : str(), len() {} + + C4_ALWAYS_INLINE basic_substring(basic_substring const&) noexcept = default; + C4_ALWAYS_INLINE basic_substring(basic_substring &&) noexcept = default; + C4_ALWAYS_INLINE basic_substring(std::nullptr_t) noexcept : str(nullptr), len(0) {} + + C4_ALWAYS_INLINE basic_substring& operator= (basic_substring const&) noexcept = default; + C4_ALWAYS_INLINE basic_substring& operator= (basic_substring &&) noexcept = default; + C4_ALWAYS_INLINE basic_substring& operator= (std::nullptr_t) noexcept { str = nullptr; len = 0; return *this; } + + C4_ALWAYS_INLINE void clear() noexcept { str = nullptr; len = 0; } + + /** @} */ + +public: + + /** @name Construction and assignment from characters with the same type */ + /** @{ */ + + /** Construct from an array. + * @warning the input string need not be zero terminated, but the + * length is taken as if the string was zero terminated */ + template + C4_ALWAYS_INLINE constexpr basic_substring(C (&s_)[N]) noexcept : str(s_), len(N-1) {} + /** Construct from a pointer and length. + * @warning the input string need not be zero terminated. */ + C4_ALWAYS_INLINE basic_substring(C *s_, size_t len_) noexcept : str(s_), len(len_) { C4_ASSERT(str || !len_); } + /** Construct from two pointers. + * @warning the end pointer MUST BE larger than or equal to the begin pointer + * @warning the input string need not be zero terminated */ + C4_ALWAYS_INLINE basic_substring(C *beg_, C *end_) noexcept : str(beg_), len(static_cast(end_ - beg_)) { C4_ASSERT(end_ >= beg_); } + /** Construct from a C-string (zero-terminated string) + * @warning the input string MUST BE zero terminated. + * @warning will call strlen() + * @note this overload uses SFINAE to prevent it from overriding the array ctor + * @see For a more detailed explanation on why the plain overloads cannot + * coexist, see http://cplusplus.bordoon.com/specializeForCharacterArrays.html */ + template::value, int>::type=0> + C4_ALWAYS_INLINE basic_substring(CharPtr s_) noexcept : str(s_), len(s_ ? strlen(s_) : 0) {} + + + /** Assign from an array. + * @warning the input string need not be zero terminated, but the + * length is taken as if the string was zero terminated */ + template + C4_ALWAYS_INLINE void assign(C (&s_)[N]) noexcept { str = s_; len = (N-1); } + /** Assign from a pointer and length. + * @warning the input string need not be zero terminated. */ + C4_ALWAYS_INLINE void assign(C *s_, size_t len_) noexcept { str = s_; len = len_; C4_ASSERT(str || !len_); } + /** Assign from two pointers. + * @warning the end pointer MUST BE larger than or equal to the begin pointer + * @warning the input string need not be zero terminated. */ + C4_ALWAYS_INLINE void assign(C *beg_, C *end_) noexcept { C4_ASSERT(end_ >= beg_); str = beg_; len = static_cast(end_ - beg_); } + /** Assign from a C-string (zero-terminated string of type const C* or C*) + * @warning the input string must be zero terminated. + * @warning will call strlen() + * @note this overload uses SFINAE to prevent it from overriding the array assignment + * @see For a more detailed explanation on why the plain pointer overloads cannot + * coexist with the array overloads, see http://cplusplus.bordoon.com/specializeForCharacterArrays.html */ + template::value, int>::type=0> + C4_ALWAYS_INLINE void assign(CharPtr s_) noexcept + { + // the SFINAE is catching const types, so that on calls like + // s.assign(const char*) we can do a static_assert, and so + // the user will see an informative error message + static_assert(can_borrow_char_ptr::value, "string pointer is not mutable"); + str = s_; + len = (s_ ? strlen(s_) : 0); + } + + + /** Assign from an array. + * @warning the input string need not be zero terminated. */ + template + C4_ALWAYS_INLINE basic_substring& operator= (C (&s_)[N]) noexcept { str = s_; len = (N-1); return *this; } + /** Assign from a C-string (zero-terminated string of type const C* or C*) + * @warning the input string MUST BE zero terminated. + * @warning will call strlen() + * @note this overload uses SFINAE to prevent it from overriding the array assignment + * @see For a more detailed explanation on why the plain pointer overloads cannot + * coexist with the array overloads, see http://cplusplus.bordoon.com/specializeForCharacterArrays.html */ + template::value, int>::type=0> + C4_ALWAYS_INLINE basic_substring& operator= (CharPtr s_) noexcept + { + // the SFINAE is catching const types, so that on calls like + // substr(const char*) we can do a static_assert, and so + // the user will see an informative error message + static_assert(can_borrow_char_ptr::value, "string pointer is not mutable"); + str = s_; + len = s_ ? strlen(s_) : 0; + return *this; + } + + /** @} */ + +public: + + /** @name Standard accessor methods */ + /** @{ */ + + C4_ALWAYS_INLINE C4_PURE bool has_str() const noexcept { return ! empty() && str[0] != C(0); } + C4_ALWAYS_INLINE C4_PURE bool empty() const noexcept { return (len == 0 || str == nullptr); } + C4_ALWAYS_INLINE C4_PURE bool not_empty() const noexcept { return (len != 0 && str != nullptr); } + C4_ALWAYS_INLINE C4_PURE size_t size() const noexcept { return len; } + + C4_ALWAYS_INLINE C4_PURE iterator begin() noexcept { return str; } + C4_ALWAYS_INLINE C4_PURE iterator end () noexcept { return str + len; } + + C4_ALWAYS_INLINE C4_PURE const_iterator begin() const noexcept { return str; } + C4_ALWAYS_INLINE C4_PURE const_iterator end () const noexcept { return str + len; } + + C4_ALWAYS_INLINE C4_PURE C * data() noexcept { return str; } + C4_ALWAYS_INLINE C4_PURE C const* data() const noexcept { return str; } + + C4_ALWAYS_INLINE C4_PURE C & operator[] (size_t i) noexcept { C4_ASSERT(i >= 0 && i < len); return str[i]; } + C4_ALWAYS_INLINE C4_PURE C const& operator[] (size_t i) const noexcept { C4_ASSERT(i >= 0 && i < len); return str[i]; } + + C4_ALWAYS_INLINE C4_PURE C & front() noexcept { C4_ASSERT(len > 0 && str != nullptr); return *str; } + C4_ALWAYS_INLINE C4_PURE C const& front() const noexcept { C4_ASSERT(len > 0 && str != nullptr); return *str; } + + C4_ALWAYS_INLINE C4_PURE C & back() noexcept { C4_ASSERT(len > 0 && str != nullptr); return *(str + len - 1); } + C4_ALWAYS_INLINE C4_PURE C const& back() const noexcept { C4_ASSERT(len > 0 && str != nullptr); return *(str + len - 1); } + + /** @} */ + +public: + + /** @name Comparison methods */ + /** @{ */ + + C4_ALWAYS_INLINE C4_PURE int compare(C const c) const noexcept + { + C4_XASSERT((str != nullptr) || len == 0); + if(C4_LIKELY(str != nullptr && len > 0)) + return (*str != c) ? *str - c : (static_cast(len) - 1); + else + return -1; + } + + C4_PURE int compare(C const* C4_RESTRICT that, size_t sz) const noexcept + { + #if defined(__GNUC__) && (__GNUC__ >= 6) + C4_SUPPRESS_WARNING_GCC_WITH_PUSH("-Wnull-dereference") + #endif + C4_XASSERT(that || sz == 0); + C4_XASSERT(str || len == 0); + if(C4_LIKELY(str && that)) + { + { + const size_t min = len < sz ? len : sz; + for(size_t i = 0; i < min; ++i) + if(str[i] != that[i]) + return str[i] < that[i] ? -1 : 1; + } + if(len < sz) + return -1; + else if(len == sz) + return 0; + else + return 1; + } + else if(len == sz) + { + C4_XASSERT(len == 0 && sz == 0); + return 0; + } + return len < sz ? -1 : 1; + #if defined(__GNUC__) && (__GNUC__ >= 6) + C4_SUPPRESS_WARNING_GCC_POP + #endif + } + + template + C4_ALWAYS_INLINE C4_PURE auto compare(CharPtr c_str) const noexcept + -> typename std::enable_if::value, int>::type + { + return compare(c_str, strlen(c_str)); + } + + template + C4_ALWAYS_INLINE C4_PURE int compare(basic_substring const that) const noexcept + { + return this->compare(that.str, that.len); + } + + C4_ALWAYS_INLINE C4_PURE bool operator== (std::nullptr_t) const noexcept { return str == nullptr; } + C4_ALWAYS_INLINE C4_PURE bool operator!= (std::nullptr_t) const noexcept { return str != nullptr; } + + C4_ALWAYS_INLINE C4_PURE bool operator== (C const c) const noexcept { return this->compare(c) == 0; } + C4_ALWAYS_INLINE C4_PURE bool operator!= (C const c) const noexcept { return this->compare(c) != 0; } + C4_ALWAYS_INLINE C4_PURE bool operator< (C const c) const noexcept { return this->compare(c) < 0; } + C4_ALWAYS_INLINE C4_PURE bool operator> (C const c) const noexcept { return this->compare(c) > 0; } + C4_ALWAYS_INLINE C4_PURE bool operator<= (C const c) const noexcept { return this->compare(c) <= 0; } + C4_ALWAYS_INLINE C4_PURE bool operator>= (C const c) const noexcept { return this->compare(c) >= 0; } + + template C4_ALWAYS_INLINE C4_PURE bool operator== (basic_substring const that) const noexcept { return this->compare(that) == 0; } + template C4_ALWAYS_INLINE C4_PURE bool operator!= (basic_substring const that) const noexcept { return this->compare(that) != 0; } + template C4_ALWAYS_INLINE C4_PURE bool operator< (basic_substring const that) const noexcept { return this->compare(that) < 0; } + template C4_ALWAYS_INLINE C4_PURE bool operator> (basic_substring const that) const noexcept { return this->compare(that) > 0; } + template C4_ALWAYS_INLINE C4_PURE bool operator<= (basic_substring const that) const noexcept { return this->compare(that) <= 0; } + template C4_ALWAYS_INLINE C4_PURE bool operator>= (basic_substring const that) const noexcept { return this->compare(that) >= 0; } + + template C4_ALWAYS_INLINE C4_PURE bool operator== (const char (&arr)[N]) const noexcept { return this->compare(arr, N-1) == 0; } + template C4_ALWAYS_INLINE C4_PURE bool operator!= (const char (&arr)[N]) const noexcept { return this->compare(arr, N-1) != 0; } + template C4_ALWAYS_INLINE C4_PURE bool operator< (const char (&arr)[N]) const noexcept { return this->compare(arr, N-1) < 0; } + template C4_ALWAYS_INLINE C4_PURE bool operator> (const char (&arr)[N]) const noexcept { return this->compare(arr, N-1) > 0; } + template C4_ALWAYS_INLINE C4_PURE bool operator<= (const char (&arr)[N]) const noexcept { return this->compare(arr, N-1) <= 0; } + template C4_ALWAYS_INLINE C4_PURE bool operator>= (const char (&arr)[N]) const noexcept { return this->compare(arr, N-1) >= 0; } + + template C4_ALWAYS_INLINE C4_PURE auto operator== (CharPtr c_str) const noexcept -> typename std::enable_if::value, int>::type{ return this->compare(c_str, strlen(c_str)) == 0; } + template C4_ALWAYS_INLINE C4_PURE auto operator!= (CharPtr c_str) const noexcept -> typename std::enable_if::value, int>::type{ return this->compare(c_str, strlen(c_str)) != 0; } + template C4_ALWAYS_INLINE C4_PURE auto operator< (CharPtr c_str) const noexcept -> typename std::enable_if::value, int>::type{ return this->compare(c_str, strlen(c_str)) < 0; } + template C4_ALWAYS_INLINE C4_PURE auto operator> (CharPtr c_str) const noexcept -> typename std::enable_if::value, int>::type{ return this->compare(c_str, strlen(c_str)) > 0; } + template C4_ALWAYS_INLINE C4_PURE auto operator<= (CharPtr c_str) const noexcept -> typename std::enable_if::value, int>::type{ return this->compare(c_str, strlen(c_str)) <= 0; } + template C4_ALWAYS_INLINE C4_PURE auto operator>= (CharPtr c_str) const noexcept -> typename std::enable_if::value, int>::type{ return this->compare(c_str, strlen(c_str)) >= 0; } + + /** @} */ + +public: + + /** @name Sub-selection methods */ + /** @{ */ + + /** true if *this is a substring of that (ie, from the same buffer) */ + C4_ALWAYS_INLINE C4_PURE bool is_sub(ro_substr const that) const noexcept + { + return that.is_super(*this); + } + + /** true if that is a substring of *this (ie, from the same buffer) */ + C4_ALWAYS_INLINE C4_PURE bool is_super(ro_substr const that) const noexcept + { + if(C4_LIKELY(len > 0)) + return that.str >= str && that.str+that.len <= str+len; + else + return that.len == 0 && that.str == str && str != nullptr; + } + + /** true if there is overlap of at least one element between that and *this */ + C4_ALWAYS_INLINE C4_PURE bool overlaps(ro_substr const that) const noexcept + { + // thanks @timwynants + return that.str+that.len > str && that.str < str+len; + } + +public: + + /** return [first,len[ */ + C4_ALWAYS_INLINE C4_PURE basic_substring sub(size_t first) const noexcept + { + C4_ASSERT(first >= 0 && first <= len); + return basic_substring(str + first, len - first); + } + + /** return [first,first+num[. If num==npos, return [first,len[ */ + C4_ALWAYS_INLINE C4_PURE basic_substring sub(size_t first, size_t num) const noexcept + { + C4_ASSERT(first >= 0 && first <= len); + C4_ASSERT((num >= 0 && num <= len) || (num == npos)); + size_t rnum = num != npos ? num : len - first; + C4_ASSERT((first >= 0 && first + rnum <= len) || (num == 0)); + return basic_substring(str + first, rnum); + } + + /** return [first,last[. If last==npos, return [first,len[ */ + C4_ALWAYS_INLINE C4_PURE basic_substring range(size_t first, size_t last=npos) const noexcept + { + C4_ASSERT(first >= 0 && first <= len); + last = last != npos ? last : len; + C4_ASSERT(first <= last); + C4_ASSERT(last >= 0 && last <= len); + return basic_substring(str + first, last - first); + } + + /** return the first @p num elements: [0,num[*/ + C4_ALWAYS_INLINE C4_PURE basic_substring first(size_t num) const noexcept + { + C4_ASSERT(num <= len || num == npos); + return basic_substring(str, num != npos ? num : len); + } + + /** return the last @p num elements: [len-num,len[*/ + C4_ALWAYS_INLINE C4_PURE basic_substring last(size_t num) const noexcept + { + C4_ASSERT(num <= len || num == npos); + return num != npos ? + basic_substring(str + len - num, num) : + *this; + } + + /** offset from the ends: return [left,len-right[ ; ie, trim a + number of characters from the left and right. This is + equivalent to python's negative list indices. */ + C4_ALWAYS_INLINE C4_PURE basic_substring offs(size_t left, size_t right) const noexcept + { + C4_ASSERT(left >= 0 && left <= len); + C4_ASSERT(right >= 0 && right <= len); + C4_ASSERT(left <= len - right + 1); + return basic_substring(str + left, len - right - left); + } + + /** return [0, pos[ . Same as .first(pos), but provided for compatibility with .right_of() */ + C4_ALWAYS_INLINE C4_PURE basic_substring left_of(size_t pos) const noexcept + { + C4_ASSERT(pos <= len || pos == npos); + return (pos != npos) ? + basic_substring(str, pos) : + *this; + } + + /** return [0, pos+include_pos[ . Same as .first(pos+1), but provided for compatibility with .right_of() */ + C4_ALWAYS_INLINE C4_PURE basic_substring left_of(size_t pos, bool include_pos) const noexcept + { + C4_ASSERT(pos <= len || pos == npos); + return (pos != npos) ? + basic_substring(str, pos+include_pos) : + *this; + } + + /** return [pos+1, len[ */ + C4_ALWAYS_INLINE C4_PURE basic_substring right_of(size_t pos) const noexcept + { + C4_ASSERT(pos <= len || pos == npos); + return (pos != npos) ? + basic_substring(str + (pos + 1), len - (pos + 1)) : + basic_substring(str + len, size_t(0)); + } + + /** return [pos+!include_pos, len[ */ + C4_ALWAYS_INLINE C4_PURE basic_substring right_of(size_t pos, bool include_pos) const noexcept + { + C4_ASSERT(pos <= len || pos == npos); + return (pos != npos) ? + basic_substring(str + (pos + !include_pos), len - (pos + !include_pos)) : + basic_substring(str + len, size_t(0)); + } + +public: + + /** given @p subs a substring of the current string, get the + * portion of the current string to the left of it */ + C4_ALWAYS_INLINE C4_PURE basic_substring left_of(ro_substr const subs) const noexcept + { + C4_ASSERT(is_super(subs) || subs.empty()); + auto ssb = subs.begin(); + auto b = begin(); + auto e = end(); + if(ssb >= b && ssb <= e) + return sub(0, static_cast(ssb - b)); + else + return sub(0, 0); + } + + /** given @p subs a substring of the current string, get the + * portion of the current string to the right of it */ + C4_ALWAYS_INLINE C4_PURE basic_substring right_of(ro_substr const subs) const noexcept + { + C4_ASSERT(is_super(subs) || subs.empty()); + auto sse = subs.end(); + auto b = begin(); + auto e = end(); + if(sse >= b && sse <= e) + return sub(static_cast(sse - b), static_cast(e - sse)); + else + return sub(0, 0); + } + + /** @} */ + +public: + + /** @name Removing characters (trim()) / patterns (strip()) from the tips of the string */ + /** @{ */ + + /** trim left */ + basic_substring triml(const C c) const + { + if( ! empty()) + { + size_t pos = first_not_of(c); + if(pos != npos) + return sub(pos); + } + return sub(0, 0); + } + /** trim left ANY of the characters. + * @see stripl() to remove a pattern from the left */ + basic_substring triml(ro_substr chars) const + { + if( ! empty()) + { + size_t pos = first_not_of(chars); + if(pos != npos) + return sub(pos); + } + return sub(0, 0); + } + + /** trim the character c from the right */ + basic_substring trimr(const C c) const + { + if( ! empty()) + { + size_t pos = last_not_of(c, npos); + if(pos != npos) + return sub(0, pos+1); + } + return sub(0, 0); + } + /** trim right ANY of the characters + * @see stripr() to remove a pattern from the right */ + basic_substring trimr(ro_substr chars) const + { + if( ! empty()) + { + size_t pos = last_not_of(chars, npos); + if(pos != npos) + return sub(0, pos+1); + } + return sub(0, 0); + } + + /** trim the character c left and right */ + basic_substring trim(const C c) const + { + return triml(c).trimr(c); + } + /** trim left and right ANY of the characters + * @see strip() to remove a pattern from the left and right */ + basic_substring trim(ro_substr const chars) const + { + return triml(chars).trimr(chars); + } + + /** remove a pattern from the left + * @see triml() to remove characters*/ + basic_substring stripl(ro_substr pattern) const + { + if( ! begins_with(pattern)) + return *this; + return sub(pattern.len < len ? pattern.len : len); + } + + /** remove a pattern from the right + * @see trimr() to remove characters*/ + basic_substring stripr(ro_substr pattern) const + { + if( ! ends_with(pattern)) + return *this; + return left_of(len - (pattern.len < len ? pattern.len : len)); + } + + /** @} */ + +public: + + /** @name Lookup methods */ + /** @{ */ + + size_t find(const C c, size_t start_pos=0) const + { + return first_of(c, start_pos); + } + size_t find(ro_substr pattern, size_t start_pos=0) const + { + C4_ASSERT(start_pos == npos || (start_pos >= 0 && start_pos <= len)); + if(len < pattern.len) return npos; + for(size_t i = start_pos, e = len - pattern.len + 1; i < e; ++i) + { + bool gotit = true; + for(size_t j = 0; j < pattern.len; ++j) + { + C4_ASSERT(i + j < len); + if(str[i + j] != pattern.str[j]) + { + gotit = false; + break; + } + } + if(gotit) + { + return i; + } + } + return npos; + } + +public: + + /** count the number of occurrences of c */ + size_t count(const C c, size_t pos=0) const + { + C4_ASSERT(pos >= 0 && pos <= len); + size_t num = 0; + pos = find(c, pos); + while(pos != npos) + { + ++num; + pos = find(c, pos + 1); + } + return num; + } + + /** count the number of occurrences of s */ + size_t count(ro_substr c, size_t pos=0) const + { + C4_ASSERT(pos >= 0 && pos <= len); + size_t num = 0; + pos = find(c, pos); + while(pos != npos) + { + ++num; + pos = find(c, pos + c.len); + } + return num; + } + + /** get the substr consisting of the first occurrence of @p c after @p pos, or an empty substr if none occurs */ + basic_substring select(const C c, size_t pos=0) const + { + pos = find(c, pos); + return pos != npos ? sub(pos, 1) : basic_substring(); + } + + /** get the substr consisting of the first occurrence of @p pattern after @p pos, or an empty substr if none occurs */ + basic_substring select(ro_substr pattern, size_t pos=0) const + { + pos = find(pattern, pos); + return pos != npos ? sub(pos, pattern.len) : basic_substring(); + } + +public: + + struct first_of_any_result + { + size_t which; + size_t pos; + operator bool() const { return which != NONE && pos != npos; } + }; + + first_of_any_result first_of_any(ro_substr s0, ro_substr s1) const + { + ro_substr s[2] = {s0, s1}; + return first_of_any_iter(&s[0], &s[0] + 2); + } + + first_of_any_result first_of_any(ro_substr s0, ro_substr s1, ro_substr s2) const + { + ro_substr s[3] = {s0, s1, s2}; + return first_of_any_iter(&s[0], &s[0] + 3); + } + + first_of_any_result first_of_any(ro_substr s0, ro_substr s1, ro_substr s2, ro_substr s3) const + { + ro_substr s[4] = {s0, s1, s2, s3}; + return first_of_any_iter(&s[0], &s[0] + 4); + } + + first_of_any_result first_of_any(ro_substr s0, ro_substr s1, ro_substr s2, ro_substr s3, ro_substr s4) const + { + ro_substr s[5] = {s0, s1, s2, s3, s4}; + return first_of_any_iter(&s[0], &s[0] + 5); + } + + template + first_of_any_result first_of_any_iter(It first_span, It last_span) const + { + for(size_t i = 0; i < len; ++i) + { + size_t curr = 0; + for(It it = first_span; it != last_span; ++curr, ++it) + { + auto const& chars = *it; + if((i + chars.len) > len) continue; + bool gotit = true; + for(size_t j = 0; j < chars.len; ++j) + { + C4_ASSERT(i + j < len); + if(str[i + j] != chars[j]) + { + gotit = false; + break; + } + } + if(gotit) + { + return {curr, i}; + } + } + } + return {NONE, npos}; + } + +public: + + /** true if the first character of the string is @p c */ + bool begins_with(const C c) const noexcept + { + C4_SUPPRESS_WARNING_GCC_PUSH + #if defined(__GNUC__) && (__GNUC__ >= 6) + C4_SUPPRESS_WARNING_GCC("-Wnull-dereference") + #endif + return len && str[0] == c; + C4_SUPPRESS_WARNING_GCC_POP + } + + /** true if the first @p num characters of the string are @p c */ + bool begins_with(const C c, size_t num) const noexcept + { + if(len < num) + return false; + for(size_t i = 0; i < num; ++i) + if(str[i] != c) + return false; + return num > 0; + } + + /** true if the string begins with the given @p pattern */ + bool begins_with(ro_substr pattern) const noexcept + { + if(len < pattern.len) + return false; + for(size_t i = 0; i < pattern.len; ++i) + if(str[i] != pattern.str[i]) + return false; + return pattern.len > 0; + } + + /** true if the first character of the string is any of the given @p chars */ + bool begins_with_any(ro_substr chars) const noexcept + { + if(len) + for(size_t i = 0; i < chars.len; ++i) + if(str[0] == chars.str[i]) + return true; + return false; + } + + + /** true if the last character of the string is @p c */ + bool ends_with(const C c) const noexcept + { + return len && str[len-1] == c; + } + + /** true if the last @p num characters of the string are @p c */ + bool ends_with(const C c, const size_t num) const noexcept + { + if(len < num) + return false; + for(size_t i = len - num; i < len; ++i) + if(str[i] != c) + return false; + return num > 0; + } + + /** true if the string ends with the given @p pattern */ + bool ends_with(ro_substr pattern) const noexcept + { + if(len < pattern.len) + return false; + for(size_t i = 0, s = len-pattern.len; i < pattern.len; ++i) + if(str[s+i] != pattern[i]) + return false; + return pattern.len > 0; + } + + /** true if the last character of the string is any of the given @p chars */ + bool ends_with_any(ro_substr chars) const noexcept + { + if(len) + for(size_t i = 0; i < chars.len; ++i) + if(str[len - 1] == chars[i]) + return true; + return false; + } + +public: + + /** @return the first position where c is found in the string, or npos if none is found */ + size_t first_of(const C c, size_t start=0) const + { + C4_ASSERT(start == npos || (start >= 0 && start <= len)); + for(size_t i = start; i < len; ++i) + { + if(str[i] == c) + return i; + } + return npos; + } + + /** @return the last position where c is found in the string, or npos if none is found */ + size_t last_of(const C c, size_t start=npos) const + { + C4_ASSERT(start == npos || (start >= 0 && start <= len)); + if(start == npos) + start = len; + for(size_t i = start-1; i != size_t(-1); --i) + { + if(str[i] == c) + return i; + } + return npos; + } + + /** @return the first position where ANY of the chars is found in the string, or npos if none is found */ + size_t first_of(ro_substr chars, size_t start=0) const + { + C4_ASSERT(start == npos || (start >= 0 && start <= len)); + for(size_t i = start; i < len; ++i) + { + for(size_t j = 0; j < chars.len; ++j) + { + if(str[i] == chars.str[j]) + return i; + } + } + return npos; + } + + /** @return the last position where ANY of the chars is found in the string, or npos if none is found */ + size_t last_of(ro_substr chars, size_t start=npos) const + { + C4_ASSERT(start == npos || (start >= 0 && start <= len)); + if(start == npos) + start = len; + for(size_t i = start-1; i != size_t(-1); --i) + { + for(size_t j = 0; j < chars.len; ++j) + { + if(str[i] == chars[j]) + return i; + } + } + return npos; + } + +public: + + size_t first_not_of(const C c) const + { + for(size_t i = 0; i < len; ++i) + { + if(str[i] != c) + return i; + } + return npos; + } + + size_t first_not_of(const C c, size_t start) const + { + C4_ASSERT((start >= 0 && start <= len) || (start == len && len == 0)); + for(size_t i = start; i < len; ++i) + { + if(str[i] != c) + return i; + } + return npos; + } + + size_t last_not_of(const C c) const + { + for(size_t i = len-1; i != size_t(-1); --i) + { + if(str[i] != c) + return i; + } + return npos; + } + + size_t last_not_of(const C c, size_t start) const + { + C4_ASSERT(start == npos || (start >= 0 && start <= len)); + if(start == npos) + start = len; + for(size_t i = start-1; i != size_t(-1); --i) + { + if(str[i] != c) + return i; + } + return npos; + } + + size_t first_not_of(ro_substr chars) const + { + for(size_t i = 0; i < len; ++i) + { + bool gotit = true; + for(size_t j = 0; j < chars.len; ++j) + { + if(str[i] == chars.str[j]) + { + gotit = false; + break; + } + } + if(gotit) + { + return i; + } + } + return npos; + } + + size_t first_not_of(ro_substr chars, size_t start) const + { + C4_ASSERT((start >= 0 && start <= len) || (start == len && len == 0)); + for(size_t i = start; i < len; ++i) + { + bool gotit = true; + for(size_t j = 0; j < chars.len; ++j) + { + if(str[i] == chars.str[j]) + { + gotit = false; + break; + } + } + if(gotit) + { + return i; + } + } + return npos; + } + + size_t last_not_of(ro_substr chars) const + { + for(size_t i = len-1; i != size_t(-1); --i) + { + bool gotit = true; + for(size_t j = 0; j < chars.len; ++j) + { + if(str[i] == chars.str[j]) + { + gotit = false; + break; + } + } + if(gotit) + { + return i; + } + } + return npos; + } + + size_t last_not_of(ro_substr chars, size_t start) const + { + C4_ASSERT(start == npos || (start >= 0 && start <= len)); + if(start == npos) + start = len; + for(size_t i = start-1; i != size_t(-1); --i) + { + bool gotit = true; + for(size_t j = 0; j < chars.len; ++j) + { + if(str[i] == chars.str[j]) + { + gotit = false; + break; + } + } + if(gotit) + { + return i; + } + } + return npos; + } + + /** @} */ + +public: + + /** @name Range lookup methods */ + /** @{ */ + + /** get the range delimited by an open-close pair of characters. + * @note There must be no nested pairs. + * @note No checks for escapes are performed. */ + basic_substring pair_range(CC open, CC close) const + { + size_t b = find(open); + if(b == npos) + return basic_substring(); + size_t e = find(close, b+1); + if(e == npos) + return basic_substring(); + basic_substring ret = range(b, e+1); + C4_ASSERT(ret.sub(1).find(open) == npos); + return ret; + } + + /** get the range delimited by a single open-close character (eg, quotes). + * @note The open-close character can be escaped. */ + basic_substring pair_range_esc(CC open_close, CC escape=CC('\\')) + { + size_t b = find(open_close); + if(b == npos) return basic_substring(); + for(size_t i = b+1; i < len; ++i) + { + CC c = str[i]; + if(c == open_close) + { + if(str[i-1] != escape) + { + return range(b, i+1); + } + } + } + return basic_substring(); + } + + /** get the range delimited by an open-close pair of characters, + * with possibly nested occurrences. No checks for escapes are + * performed. */ + basic_substring pair_range_nested(CC open, CC close) const + { + size_t b = find(open); + if(b == npos) return basic_substring(); + size_t e, curr = b+1, count = 0; + const char both[] = {open, close, '\0'}; + while((e = first_of(both, curr)) != npos) + { + if(str[e] == open) + { + ++count; + curr = e+1; + } + else if(str[e] == close) + { + if(count == 0) return range(b, e+1); + --count; + curr = e+1; + } + } + return basic_substring(); + } + + basic_substring unquoted() const + { + constexpr const C dq('"'), sq('\''); + if(len >= 2 && (str[len - 2] != C('\\')) && + ((begins_with(sq) && ends_with(sq)) + || + (begins_with(dq) && ends_with(dq)))) + { + return range(1, len -1); + } + return *this; + } + + /** @} */ + +public: + + /** @name Number-matching query methods */ + /** @{ */ + + /** @return true if the substring contents are a floating-point or integer number. + * @note any leading or trailing whitespace will return false. */ + bool is_number() const + { + if(empty() || (first_non_empty_span().empty())) + return false; + if(first_uint_span() == *this) + return true; + if(first_int_span() == *this) + return true; + if(first_real_span() == *this) + return true; + return false; + } + + /** @return true if the substring contents are a real number. + * @note any leading or trailing whitespace will return false. */ + bool is_real() const + { + if(empty() || (first_non_empty_span().empty())) + return false; + if(first_real_span() == *this) + return true; + return false; + } + + /** @return true if the substring contents are an integer number. + * @note any leading or trailing whitespace will return false. */ + bool is_integer() const + { + if(empty() || (first_non_empty_span().empty())) + return false; + if(first_uint_span() == *this) + return true; + if(first_int_span() == *this) + return true; + return false; + } + + /** @return true if the substring contents are an unsigned integer number. + * @note any leading or trailing whitespace will return false. */ + bool is_unsigned_integer() const + { + if(empty() || (first_non_empty_span().empty())) + return false; + if(first_uint_span() == *this) + return true; + return false; + } + + /** get the first span consisting exclusively of non-empty characters */ + basic_substring first_non_empty_span() const + { + constexpr const ro_substr empty_chars(" \n\r\t"); + size_t pos = first_not_of(empty_chars); + if(pos == npos) + return first(0); + auto ret = sub(pos); + pos = ret.first_of(empty_chars); + return ret.first(pos); + } + + /** get the first span which can be interpreted as an unsigned integer */ + basic_substring first_uint_span() const + { + basic_substring ne = first_non_empty_span(); + if(ne.empty()) + return ne; + if(ne.str[0] == '-') + return first(0); + size_t skip_start = size_t(ne.str[0] == '+'); + return ne._first_integral_span(skip_start); + } + + /** get the first span which can be interpreted as a signed integer */ + basic_substring first_int_span() const + { + basic_substring ne = first_non_empty_span(); + if(ne.empty()) + return ne; + size_t skip_start = size_t(ne.str[0] == '+' || ne.str[0] == '-'); + return ne._first_integral_span(skip_start); + } + + basic_substring _first_integral_span(size_t skip_start) const + { + C4_ASSERT(!empty()); + if(skip_start == len) + return first(0); + C4_ASSERT(skip_start < len); + if(len >= skip_start + 3) + { + if(str[skip_start] != '0') + { + for(size_t i = skip_start; i < len; ++i) + { + char c = str[i]; + if(c < '0' || c > '9') + return i > skip_start && _is_delim_char(c) ? first(i) : first(0); + } + } + else + { + char next = str[skip_start + 1]; + if(next == 'x' || next == 'X') + { + skip_start += 2; + for(size_t i = skip_start; i < len; ++i) + { + const char c = str[i]; + if( ! _is_hex_char(c)) + return i > skip_start && _is_delim_char(c) ? first(i) : first(0); + } + return *this; + } + else if(next == 'b' || next == 'B') + { + skip_start += 2; + for(size_t i = skip_start; i < len; ++i) + { + const char c = str[i]; + if(c != '0' && c != '1') + return i > skip_start && _is_delim_char(c) ? first(i) : first(0); + } + return *this; + } + else if(next == 'o' || next == 'O') + { + skip_start += 2; + for(size_t i = skip_start; i < len; ++i) + { + const char c = str[i]; + if(c < '0' || c > '7') + return i > skip_start && _is_delim_char(c) ? first(i) : first(0); + } + return *this; + } + } + } + // must be a decimal, or it is not a an number + for(size_t i = skip_start; i < len; ++i) + { + const char c = str[i]; + if(c < '0' || c > '9') + return i > skip_start && _is_delim_char(c) ? first(i) : first(0); + } + return *this; + } + + /** get the first span which can be interpreted as a real (floating-point) number */ + basic_substring first_real_span() const + { + basic_substring ne = first_non_empty_span(); + if(ne.empty()) + return ne; + const size_t skip_start = (ne.str[0] == '+' || ne.str[0] == '-'); + C4_ASSERT(skip_start == 0 || skip_start == 1); + // if we have at least three digits after the leading sign, it + // can be decimal, or hex, or bin or oct. Ex: + // non-decimal: 0x0, 0b0, 0o0 + // decimal: 1.0, 10., 1e1, 100, inf, nan, infinity + if(ne.len >= skip_start+3) + { + // if it does not have leading 0, it must be decimal, or it is not a real + if(ne.str[skip_start] != '0') + { + if(ne.str[skip_start] == 'i') // is it infinity or inf? + { + basic_substring word = ne._word_follows(skip_start + 1, "nfinity"); + if(word.len) + return word; + return ne._word_follows(skip_start + 1, "nf"); + } + else if(ne.str[skip_start] == 'n') // is it nan? + { + return ne._word_follows(skip_start + 1, "an"); + } + else // must be a decimal, or it is not a real + { + return ne._first_real_span_dec(skip_start); + } + } + else // starts with 0. is it 0x, 0b or 0o? + { + const char next = ne.str[skip_start + 1]; + // hexadecimal + if(next == 'x' || next == 'X') + return ne._first_real_span_hex(skip_start + 2); + // binary + else if(next == 'b' || next == 'B') + return ne._first_real_span_bin(skip_start + 2); + // octal + else if(next == 'o' || next == 'O') + return ne._first_real_span_oct(skip_start + 2); + // none of the above. may still be a decimal. + else + return ne._first_real_span_dec(skip_start); // do not skip the 0. + } + } + // less than 3 chars after the leading sign. It is either a + // decimal or it is not a real. (cannot be any of 0x0, etc). + return ne._first_real_span_dec(skip_start); + } + + /** true if the character is a delimiter character *at the end* */ + static constexpr C4_ALWAYS_INLINE C4_CONST bool _is_delim_char(char c) noexcept + { + return c == ' ' || c == '\n' + || c == ']' || c == ')' || c == '}' + || c == ',' || c == ';' || c == '\r' || c == '\t' || c == '\0'; + } + + /** true if the character is in [0-9a-fA-F] */ + static constexpr C4_ALWAYS_INLINE C4_CONST bool _is_hex_char(char c) noexcept + { + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); + } + + C4_NO_INLINE C4_PURE basic_substring _word_follows(size_t pos, csubstr word) const noexcept + { + size_t posend = pos + word.len; + if(len >= posend && sub(pos, word.len) == word) + if(len == posend || _is_delim_char(str[posend])) + return first(posend); + return first(0); + } + + // this function is declared inside the class to avoid a VS error with __declspec(dllimport) + C4_NO_INLINE C4_PURE basic_substring _first_real_span_dec(size_t pos) const noexcept + { + bool intchars = false; + bool fracchars = false; + bool powchars; + // integral part + for( ; pos < len; ++pos) + { + const char c = str[pos]; + if(c >= '0' && c <= '9') + { + intchars = true; + } + else if(c == '.') + { + ++pos; + goto fractional_part_dec; // NOLINT + } + else if(c == 'e' || c == 'E') + { + ++pos; + goto power_part_dec; // NOLINT + } + else if(_is_delim_char(c)) + { + return intchars ? first(pos) : first(0); + } + else + { + return first(0); + } + } + // no . or p were found; this is either an integral number + // or not a number at all + return intchars ? + *this : + first(0); + fractional_part_dec: + C4_ASSERT(pos > 0); + C4_ASSERT(str[pos - 1] == '.'); + for( ; pos < len; ++pos) + { + const char c = str[pos]; + if(c >= '0' && c <= '9') + { + fracchars = true; + } + else if(c == 'e' || c == 'E') + { + ++pos; + goto power_part_dec; // NOLINT + } + else if(_is_delim_char(c)) + { + return intchars || fracchars ? first(pos) : first(0); + } + else + { + return first(0); + } + } + return intchars || fracchars ? + *this : + first(0); + power_part_dec: + C4_ASSERT(pos > 0); + C4_ASSERT(str[pos - 1] == 'e' || str[pos - 1] == 'E'); + // either digits, or +, or - are expected here, followed by more digits. + if((len == pos) || ((!intchars) && (!fracchars))) + return first(0); + if(str[pos] == '-' || str[pos] == '+') + ++pos; // skip the sign + powchars = false; + for( ; pos < len; ++pos) + { + const char c = str[pos]; + if(c >= '0' && c <= '9') + powchars = true; + else if(powchars && _is_delim_char(c)) + return first(pos); + else + return first(0); + } + return powchars ? *this : first(0); + } + + // this function is declared inside the class to avoid a VS error with __declspec(dllimport) + C4_NO_INLINE C4_PURE basic_substring _first_real_span_hex(size_t pos) const noexcept + { + bool intchars = false; + bool fracchars = false; + bool powchars; + // integral part + for( ; pos < len; ++pos) + { + const char c = str[pos]; + if(_is_hex_char(c)) + { + intchars = true; + } + else if(c == '.') + { + ++pos; + goto fractional_part_hex; // NOLINT + } + else if(c == 'p' || c == 'P') + { + ++pos; + goto power_part_hex; // NOLINT + } + else if(_is_delim_char(c)) + { + return intchars ? first(pos) : first(0); + } + else + { + return first(0); + } + } + // no . or p were found; this is either an integral number + // or not a number at all + return intchars ? + *this : + first(0); + fractional_part_hex: + C4_ASSERT(pos > 0); + C4_ASSERT(str[pos - 1] == '.'); + for( ; pos < len; ++pos) + { + const char c = str[pos]; + if(_is_hex_char(c)) + { + fracchars = true; + } + else if(c == 'p' || c == 'P') + { + ++pos; + goto power_part_hex; // NOLINT + } + else if(_is_delim_char(c)) + { + return intchars || fracchars ? first(pos) : first(0); + } + else + { + return first(0); + } + } + return intchars || fracchars ? + *this : + first(0); + power_part_hex: + C4_ASSERT(pos > 0); + C4_ASSERT(str[pos - 1] == 'p' || str[pos - 1] == 'P'); + // either a + or a - is expected here, followed by more chars. + // also, using (pos+1) in this check will cause an early + // return when no more chars follow the sign. + if(len <= (pos+1) || (str[pos] != '+' && str[pos] != '-') || ((!intchars) && (!fracchars))) + return first(0); + ++pos; // this was the sign. + // ... so the (pos+1) ensures that we enter the loop and + // hence that there exist chars in the power part + powchars = false; + for( ; pos < len; ++pos) + { + const char c = str[pos]; + if(c >= '0' && c <= '9') + powchars = true; + else if(powchars && _is_delim_char(c)) + return first(pos); + else + return first(0); + } + return *this; + } + + // this function is declared inside the class to avoid a VS error with __declspec(dllimport) + C4_NO_INLINE C4_PURE basic_substring _first_real_span_bin(size_t pos) const noexcept + { + bool intchars = false; + bool fracchars = false; + bool powchars; + // integral part + for( ; pos < len; ++pos) + { + const char c = str[pos]; + if(c == '0' || c == '1') + { + intchars = true; + } + else if(c == '.') + { + ++pos; + goto fractional_part_bin; // NOLINT + } + else if(c == 'p' || c == 'P') + { + ++pos; + goto power_part_bin; // NOLINT + } + else if(_is_delim_char(c)) + { + return intchars ? first(pos) : first(0); + } + else + { + return first(0); + } + } + // no . or p were found; this is either an integral number + // or not a number at all + return intchars ? + *this : + first(0); + fractional_part_bin: + C4_ASSERT(pos > 0); + C4_ASSERT(str[pos - 1] == '.'); + for( ; pos < len; ++pos) + { + const char c = str[pos]; + if(c == '0' || c == '1') + { + fracchars = true; + } + else if(c == 'p' || c == 'P') + { + ++pos; + goto power_part_bin; // NOLINT + } + else if(_is_delim_char(c)) + { + return intchars || fracchars ? first(pos) : first(0); + } + else + { + return first(0); + } + } + return intchars || fracchars ? + *this : + first(0); + power_part_bin: + C4_ASSERT(pos > 0); + C4_ASSERT(str[pos - 1] == 'p' || str[pos - 1] == 'P'); + // either a + or a - is expected here, followed by more chars. + // also, using (pos+1) in this check will cause an early + // return when no more chars follow the sign. + if(len <= (pos+1) || (str[pos] != '+' && str[pos] != '-') || ((!intchars) && (!fracchars))) + return first(0); + ++pos; // this was the sign. + // ... so the (pos+1) ensures that we enter the loop and + // hence that there exist chars in the power part + powchars = false; + for( ; pos < len; ++pos) + { + const char c = str[pos]; + if(c >= '0' && c <= '9') + powchars = true; + else if(powchars && _is_delim_char(c)) + return first(pos); + else + return first(0); + } + return *this; + } + + // this function is declared inside the class to avoid a VS error with __declspec(dllimport) + C4_NO_INLINE C4_PURE basic_substring _first_real_span_oct(size_t pos) const noexcept + { + bool intchars = false; + bool fracchars = false; + bool powchars; + // integral part + for( ; pos < len; ++pos) + { + const char c = str[pos]; + if(c >= '0' && c <= '7') + { + intchars = true; + } + else if(c == '.') + { + ++pos; + goto fractional_part_oct; // NOLINT + } + else if(c == 'p' || c == 'P') + { + ++pos; + goto power_part_oct; // NOLINT + } + else if(_is_delim_char(c)) + { + return intchars ? first(pos) : first(0); + } + else + { + return first(0); + } + } + // no . or p were found; this is either an integral number + // or not a number at all + return intchars ? + *this : + first(0); + fractional_part_oct: + C4_ASSERT(pos > 0); + C4_ASSERT(str[pos - 1] == '.'); + for( ; pos < len; ++pos) + { + const char c = str[pos]; + if(c >= '0' && c <= '7') + { + fracchars = true; + } + else if(c == 'p' || c == 'P') + { + ++pos; + goto power_part_oct; // NOLINT + } + else if(_is_delim_char(c)) + { + return intchars || fracchars ? first(pos) : first(0); + } + else + { + return first(0); + } + } + return intchars || fracchars ? + *this : + first(0); + power_part_oct: + C4_ASSERT(pos > 0); + C4_ASSERT(str[pos - 1] == 'p' || str[pos - 1] == 'P'); + // either a + or a - is expected here, followed by more chars. + // also, using (pos+1) in this check will cause an early + // return when no more chars follow the sign. + if(len <= (pos+1) || (str[pos] != '+' && str[pos] != '-') || ((!intchars) && (!fracchars))) + return first(0); + ++pos; // this was the sign. + // ... so the (pos+1) ensures that we enter the loop and + // hence that there exist chars in the power part + powchars = false; + for( ; pos < len; ++pos) + { + const char c = str[pos]; + if(c >= '0' && c <= '9') + powchars = true; + else if(powchars && _is_delim_char(c)) + return first(pos); + else + return first(0); + } + return *this; + } + + /** @} */ + +public: + + /** @name Splitting methods */ + /** @{ */ + + /** returns true if the string has not been exhausted yet, meaning + * it's ok to call next_split() again. When no instance of sep + * exists in the string, returns the full string. When the input + * is an empty string, the output string is the empty string. */ + bool next_split(C sep, size_t *C4_RESTRICT start_pos, basic_substring *C4_RESTRICT out) const + { + if(C4_LIKELY(*start_pos < len)) + { + for(size_t i = *start_pos; i < len; i++) + { + if(str[i] == sep) + { + out->assign(str + *start_pos, i - *start_pos); + *start_pos = i+1; + return true; + } + } + out->assign(str + *start_pos, len - *start_pos); + *start_pos = len + 1; + return true; + } + else + { + bool valid = len > 0 && (*start_pos == len); + if(valid && str && str[len-1] == sep) + { + out->assign(str + len, size_t(0)); // the cast is needed to prevent overload ambiguity + } + else + { + out->assign(str + len + 1, size_t(0)); // the cast is needed to prevent overload ambiguity + } + *start_pos = len + 1; + return valid; + } + } + +private: + + struct split_proxy_impl + { + struct split_iterator_impl + { + split_proxy_impl const* m_proxy; + basic_substring m_str; + size_t m_pos; + NCC_ m_sep; + + split_iterator_impl(split_proxy_impl const* proxy, size_t pos, C sep) + : m_proxy(proxy), m_pos(pos), m_sep(sep) + { + _tick(); + } + + void _tick() + { + m_proxy->m_str.next_split(m_sep, &m_pos, &m_str); + } + + split_iterator_impl& operator++ () { _tick(); return *this; } + split_iterator_impl operator++ (int) { split_iterator_impl it = *this; _tick(); return it; } // NOLINT + + basic_substring& operator* () { return m_str; } + basic_substring* operator-> () { return &m_str; } + + bool operator!= (split_iterator_impl const& that) const + { + return !(this->operator==(that)); + } + bool operator== (split_iterator_impl const& that) const + { + C4_XASSERT((m_sep == that.m_sep) && "cannot compare split iterators with different separators"); + if(m_str.size() != that.m_str.size()) + return false; + if(m_str.data() != that.m_str.data()) + return false; + return m_pos == that.m_pos; + } + }; + + basic_substring m_str; + size_t m_start_pos; + C m_sep; + + split_proxy_impl(basic_substring str_, size_t start_pos, C sep) + : m_str(str_), m_start_pos(start_pos), m_sep(sep) + { + } + + split_iterator_impl begin() const + { + auto it = split_iterator_impl(this, m_start_pos, m_sep); + return it; + } + split_iterator_impl end() const + { + size_t pos = m_str.size() + 1; + auto it = split_iterator_impl(this, pos, m_sep); + return it; + } + }; + +public: + + using split_proxy = split_proxy_impl; + + /** a view into the splits */ + split_proxy split(C sep, size_t start_pos=0) const + { + C4_XASSERT((start_pos >= 0 && start_pos < len) || empty()); + auto ss = sub(0, len); + auto it = split_proxy(ss, start_pos, sep); + return it; + } + +public: + + /** pop right: return the first split from the right. Use + * gpop_left() to get the reciprocal part. + */ + basic_substring pop_right(C sep=C('/'), bool skip_empty=false) const + { + if(C4_LIKELY(len > 1)) + { + auto pos = last_of(sep); + if(pos != npos) + { + if(pos + 1 < len) // does not end with sep + { + return sub(pos + 1); // return from sep to end + } + else // the string ends with sep + { + if( ! skip_empty) + { + return sub(pos + 1, 0); + } + auto ppos = last_not_of(sep); // skip repeated seps + if(ppos == npos) // the string is all made of seps + { + return sub(0, 0); + } + // find the previous sep + auto pos0 = last_of(sep, ppos); + if(pos0 == npos) // only the last sep exists + { + return sub(0); // return the full string (because skip_empty is true) + } + ++pos0; + return sub(pos0); + } + } + else // no sep was found, return the full string + { + return *this; + } + } + else if(len == 1) + { + if(begins_with(sep)) + { + return sub(0, 0); + } + return *this; + } + else // an empty string + { + return basic_substring(); + } + } + + /** return the first split from the left. Use gpop_right() to get + * the reciprocal part. */ + basic_substring pop_left(C sep = C('/'), bool skip_empty=false) const + { + if(C4_LIKELY(len > 1)) + { + auto pos = first_of(sep); + if(pos != npos) + { + if(pos > 0) // does not start with sep + { + return sub(0, pos); // return everything up to it + } + else // the string starts with sep + { + if( ! skip_empty) + { + return sub(0, 0); + } + auto ppos = first_not_of(sep); // skip repeated seps + if(ppos == npos) // the string is all made of seps + { + return sub(0, 0); + } + // find the next sep + auto pos0 = first_of(sep, ppos); + if(pos0 == npos) // only the first sep exists + { + return sub(0); // return the full string (because skip_empty is true) + } + C4_XASSERT(pos0 > 0); + // return everything up to the second sep + return sub(0, pos0); + } + } + else // no sep was found, return the full string + { + return sub(0); + } + } + else if(len == 1) + { + if(begins_with(sep)) + { + return sub(0, 0); + } + return sub(0); + } + else // an empty string + { + return basic_substring(); + } + } + +public: + + /** greedy pop left. eg, csubstr("a/b/c").gpop_left('/')="c" */ + basic_substring gpop_left(C sep = C('/'), bool skip_empty=false) const + { + auto ss = pop_right(sep, skip_empty); + ss = left_of(ss); + if(ss.find(sep) != npos) + { + if(ss.ends_with(sep)) + { + if(skip_empty) + { + ss = ss.trimr(sep); + } + else + { + ss = ss.sub(0, ss.len-1); // safe to subtract because ends_with(sep) is true + } + } + } + return ss; + } + + /** greedy pop right. eg, csubstr("a/b/c").gpop_right('/')="a" */ + basic_substring gpop_right(C sep = C('/'), bool skip_empty=false) const + { + auto ss = pop_left(sep, skip_empty); + ss = right_of(ss); + if(ss.find(sep) != npos) + { + if(ss.begins_with(sep)) + { + if(skip_empty) + { + ss = ss.triml(sep); + } + else + { + ss = ss.sub(1); + } + } + } + return ss; + } + + /** @} */ + +public: + + /** @name Path-like manipulation methods */ + /** @{ */ + + basic_substring basename(C sep=C('/')) const + { + auto ss = pop_right(sep, /*skip_empty*/true); + ss = ss.trimr(sep); + return ss; + } + + basic_substring dirname(C sep=C('/')) const + { + auto ss = basename(sep); + ss = ss.empty() ? *this : left_of(ss); + return ss; + } + + C4_ALWAYS_INLINE basic_substring name_wo_extshort() const + { + return gpop_left('.'); + } + + C4_ALWAYS_INLINE basic_substring name_wo_extlong() const + { + return pop_left('.'); + } + + C4_ALWAYS_INLINE basic_substring extshort() const + { + return pop_right('.'); + } + + C4_ALWAYS_INLINE basic_substring extlong() const + { + return gpop_right('.'); + } + + /** @} */ + +public: + + /** @name Content-modification methods (only for non-const C) */ + /** @{ */ + + /** convert the string to upper-case + * @note this method requires that the string memory is writeable and is SFINAEd out for const C */ + template + auto toupper() + -> typename std::enable_if< ! std::is_const::value, void>::type + { + for(size_t i = 0; i < len; ++i) + { + str[i] = static_cast(::toupper(str[i])); + } + } + + /** convert the string to lower-case + * @note this method requires that the string memory is writeable and is SFINAEd out for const C */ + template + auto tolower() + -> typename std::enable_if< !std::is_const::value, void>::type + { + for(size_t i = 0; i < len; ++i) + { + str[i] = static_cast(::tolower(str[i])); + } + } + +public: + + /** fill the entire contents with the given @p val + * @note this method requires that the string memory is writeable and is SFINAEd out for const C */ + template + auto fill(C val) + -> typename std::enable_if< !std::is_const::value, void>::type + { + for(size_t i = 0; i < len; ++i) + str[i] = val; + } + +public: + + /** copy a string to this substr, starting at 0 + * @note this method requires that the string memory is writeable and is SFINAEd out for const C */ + template + auto copy_from(ro_substr that) + -> typename std::enable_if< !std::is_const::value, void>::type + { + C4_ASSERT(!overlaps(that)); + size_t num = that.len <= len ? that.len : len; + // calling memcpy with zero len is undefined behavior + // and will wreak havoc in calling code's branches. + // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637 + if(num) + memcpy(str, that.str, sizeof(C) * num); + } + + /** copy a string to this substr, starting at a specified given position + * @note this method requires that the string memory is writeable and is SFINAEd out for const C */ + template + auto copy_from(ro_substr that, size_t ifirst, size_t num=npos) + -> typename std::enable_if< !std::is_const::value, void>::type + { + C4_ASSERT(ifirst >= 0 && ifirst <= len); + num = num != npos ? num : len - ifirst; + num = num < that.len ? num : that.len; + C4_ASSERT(ifirst + num >= 0 && ifirst + num <= len); + // calling memcpy with zero len is undefined behavior + // and will wreak havoc in calling code's branches. + // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637 + if(num) + memcpy(str + (sizeof(C) * ifirst), that.str, sizeof(C) * num); + } + +public: + + /** reverse in place + * @note this method requires that the string memory is writeable and is SFINAEd out for const C */ + template + auto reverse() + -> typename std::enable_if< !std::is_const::value, void>::type + { + if(len == 0) return; + detail::_do_reverse(str, str + len - 1); + } + + /** revert a subpart in place + * @note this method requires that the string memory is writeable and is SFINAEd out for const C */ + template + auto reverse_sub(size_t ifirst, size_t num) + -> typename std::enable_if< !std::is_const::value, void>::type + { + C4_ASSERT(ifirst >= 0 && ifirst <= len); + C4_ASSERT(ifirst + num >= 0 && ifirst + num <= len); + if(num == 0) return; + detail::_do_reverse(str + ifirst, str + ifirst + num - 1); + } + + /** revert a range in place + * @note this method requires that the string memory is writeable and is SFINAEd out for const C */ + template + auto reverse_range(size_t ifirst, size_t ilast) + -> typename std::enable_if< !std::is_const::value, void>::type + { + C4_ASSERT(ifirst >= 0 && ifirst <= len); + C4_ASSERT(ilast >= 0 && ilast <= len); + if(ifirst == ilast) return; + detail::_do_reverse(str + ifirst, str + ilast - 1); + } + +public: + + /** erase part of the string. eg, with char s[] = "0123456789", + * substr(s).erase(3, 2) = "01256789", and s is now "0125678989" + * @note this method requires that the string memory is writeable and is SFINAEd out for const C */ + template + auto erase(size_t pos, size_t num) + -> typename std::enable_if< !std::is_const::value, basic_substring>::type + { + C4_ASSERT(pos >= 0 && pos+num <= len); + size_t num_to_move = len - pos - num; + memmove(str + pos, str + pos + num, sizeof(C) * num_to_move); + return basic_substring{str, len - num}; + } + + /** @note this method requires that the string memory is writeable and is SFINAEd out for const C */ + template + auto erase_range(size_t first, size_t last) + -> typename std::enable_if< !std::is_const::value, basic_substring>::type + { + C4_ASSERT(first <= last); + return erase(first, static_cast(last-first)); // NOLINT + } + + /** erase a part of the string. + * @note @p sub must be a substring of this string + * @note this method requires that the string memory is writeable and is SFINAEd out for const C */ + template + auto erase(ro_substr sub) + -> typename std::enable_if< !std::is_const::value, basic_substring>::type + { + C4_ASSERT(is_super(sub)); + C4_ASSERT(sub.str >= str); + return erase(static_cast(sub.str - str), sub.len); + } + +public: + + /** replace every occurrence of character @p value with the character @p repl + * @return the number of characters that were replaced + * @note this method requires that the string memory is writeable and is SFINAEd out for const C */ + template + auto replace(C value, C repl, size_t pos=0) + -> typename std::enable_if< ! std::is_const::value, size_t>::type + { + C4_ASSERT((pos >= 0 && pos <= len) || pos == npos); + size_t did_it = 0; + while((pos = find(value, pos)) != npos) + { + str[pos++] = repl; + ++did_it; + } + return did_it; + } + + /** replace every occurrence of each character in @p value with + * the character @p repl. + * @return the number of characters that were replaced + * @note this method requires that the string memory is writeable and is SFINAEd out for const C */ + template + auto replace(ro_substr chars, C repl, size_t pos=0) + -> typename std::enable_if< ! std::is_const::value, size_t>::type + { + C4_ASSERT((pos >= 0 && pos <= len) || pos == npos); + size_t did_it = 0; + while((pos = first_of(chars, pos)) != npos) + { + str[pos++] = repl; + ++did_it; + } + return did_it; + } + + /** replace @p pattern with @p repl, and write the result into + * @p dst. pattern and repl don't need equal sizes. + * + * @return the required size for dst. No overflow occurs if + * dst.len is smaller than the required size; this can be used to + * determine the required size for an existing container. */ + size_t replace_all(rw_substr dst, ro_substr pattern, ro_substr repl, size_t pos=0) const + { + C4_ASSERT( ! pattern.empty()); //!< @todo relax this precondition + C4_ASSERT( ! this ->overlaps(dst)); //!< @todo relax this precondition + C4_ASSERT( ! pattern.overlaps(dst)); + C4_ASSERT( ! repl .overlaps(dst)); + C4_ASSERT((pos >= 0 && pos <= len) || pos == npos); + C4_SUPPRESS_WARNING_GCC_PUSH + C4_SUPPRESS_WARNING_GCC("-Warray-bounds") // gcc11 has a false positive here + #if (!defined(__clang__)) && (defined(__GNUC__) && (__GNUC__ >= 7)) + C4_SUPPRESS_WARNING_GCC("-Wstringop-overflow") // gcc11 has a false positive here + #endif + #define _c4append(first, last) \ + { \ + C4_ASSERT((last) >= (first)); \ + size_t num = static_cast((last) - (first)); \ + if(num > 0 && sz + num <= dst.len) \ + { \ + memcpy(dst.str + sz, first, num * sizeof(C)); \ + } \ + sz += num; \ + } + size_t sz = 0; + size_t b = pos; + _c4append(str, str + pos); + do { + size_t e = find(pattern, b); + if(e == npos) + { + _c4append(str + b, str + len); + break; + } + _c4append(str + b, str + e); + _c4append(repl.begin(), repl.end()); + b = e + pattern.size(); + } while(b < len && b != npos); + return sz; + #undef _c4append + C4_SUPPRESS_WARNING_GCC_POP + } + + /** @} */ + +}; // template class basic_substring + +#ifdef __DOXYGEN__ +using substr = basic_substring; /**< a mutable string view */ +using csubstr = basic_substring; /**< an immutable string view */ +#endif + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + + +/** @defgroup doc_substr_adapters substr adapters + * + * @ref c4::to_substr() and @ref c4::to_csubstr() are used in generic + * code like @ref c4::format(). They enable the user to provide an + * entry point for the construction of substrings from custom types. + * + * @{ */ + + +/** @defgroup doc_substr_adapters_literal create substrings from a char literal + * @{ */ +template C4_ALWAYS_INLINE substr to_substr(char (&s)[N]) noexcept +{ + return substr(s, N-1); +} +template C4_ALWAYS_INLINE csubstr to_csubstr(const char (&s)[N]) noexcept +{ + return csubstr(s, N-1); +} +/** @} */ + + +/** @defgroup doc_substr_adapters_cstring create substrings from C strings + * @{ */ + +/** Create a substring from a C-string (char*-like pointer) + * + * @note this overload uses SFINAE to prevent it from overriding the + * literal/array overload. U must be is a non-const-char pointer for + * this function to be considered in the overload set. + * + * @see For a more detailed explanation on why the plain overloads cannot + * coexist, see http://cplusplus.bordoon.com/specializeForCharacterArrays.html */ +template C4_ALWAYS_INLINE auto to_substr(U s) noexcept + -> typename std::enable_if::value, substr>::type +{ + return substr(s); +} + +/** Create a substring from a const char*-like pointer + * + * @note this overload uses SFINAE to prevent it from overriding the + * literal/array overload + * + * @see For a more detailed explanation on why the plain overloads cannot + * coexist, see http://cplusplus.bordoon.com/specializeForCharacterArrays.html */ +template C4_ALWAYS_INLINE auto to_csubstr(U s) noexcept + -> typename std::enable_if::value, csubstr>::type +{ + return csubstr(s); +} +/** @} */ + + +/** @defgroup doc_substr_adapters_neutral neutral version for use in generic code + * @{ */ +C4_ALWAYS_INLINE substr to_substr(substr s) noexcept { return s; } +C4_ALWAYS_INLINE csubstr to_csubstr(substr s) noexcept { return csubstr{s.str, s.len}; } +C4_ALWAYS_INLINE csubstr to_csubstr(csubstr s) noexcept { return s; } +/** @} */ + +/** @} */ + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** @defgroup doc_substr_cmp substr left-comparison operators + * @{ */ + +/** @defgroup doc_substr_cmp_singlechar left-compare a single char with a csubstr + * @{ */ +template inline bool operator== (const char c, basic_substring const that) noexcept { return that.compare(c) == 0; } +template inline bool operator!= (const char c, basic_substring const that) noexcept { return that.compare(c) != 0; } +template inline bool operator< (const char c, basic_substring const that) noexcept { return that.compare(c) > 0; } +template inline bool operator> (const char c, basic_substring const that) noexcept { return that.compare(c) < 0; } +template inline bool operator<= (const char c, basic_substring const that) noexcept { return that.compare(c) >= 0; } +template inline bool operator>= (const char c, basic_substring const that) noexcept { return that.compare(c) <= 0; } +/** @} */ + +/** @defgroup doc_substr_cmp_literal left-compare a string literal with a csubstr + * @{ */ +template inline bool operator== (const char (&arr)[N], basic_substring const that) noexcept { return that.compare(arr, N-1) == 0; } +template inline bool operator!= (const char (&arr)[N], basic_substring const that) noexcept { return that.compare(arr, N-1) != 0; } +template inline bool operator< (const char (&arr)[N], basic_substring const that) noexcept { return that.compare(arr, N-1) > 0; } +template inline bool operator> (const char (&arr)[N], basic_substring const that) noexcept { return that.compare(arr, N-1) < 0; } +template inline bool operator<= (const char (&arr)[N], basic_substring const that) noexcept { return that.compare(arr, N-1) >= 0; } +template inline bool operator>= (const char (&arr)[N], basic_substring const that) noexcept { return that.compare(arr, N-1) <= 0; } +/** @} */ + +/** @defgroup doc_substr_cmp_cstring left-compare a C-string with a csubstr + * @{ */ +template inline auto operator== (U c_str, basic_substring const that) noexcept -> typename std::enable_if::value, bool>::type { return that.compare(c_str, strlen(c_str)) == 0; } +template inline auto operator!= (U c_str, basic_substring const that) noexcept -> typename std::enable_if::value, bool>::type { return that.compare(c_str, strlen(c_str)) != 0; } +template inline auto operator< (U c_str, basic_substring const that) noexcept -> typename std::enable_if::value, bool>::type { return that.compare(c_str, strlen(c_str)) > 0; } +template inline auto operator> (U c_str, basic_substring const that) noexcept -> typename std::enable_if::value, bool>::type { return that.compare(c_str, strlen(c_str)) < 0; } +template inline auto operator<= (U c_str, basic_substring const that) noexcept -> typename std::enable_if::value, bool>::type { return that.compare(c_str, strlen(c_str)) >= 0; } +template inline auto operator>= (U c_str, basic_substring const that) noexcept -> typename std::enable_if::value, bool>::type { return that.compare(c_str, strlen(c_str)) <= 0; } +/** @} */ + +/** @} */ + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/* C4_SUBSTR_NO_OSTREAM_LSHIFT doctest does not deal well with + * template operator<< + * @see https://github.com/onqtam/doctest/pull/431 */ +#ifndef C4_SUBSTR_NO_OSTREAM_LSHIFT + +/** output the string to an ostream-like type */ +template +inline OStream& operator<< (OStream& os, basic_substring s) +{ + C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wsign-conversion") + os.write(s.str, s.len); + C4_SUPPRESS_WARNING_GCC_CLANG_POP + return os; +} + +#endif // !C4_SUBSTR_NO_OSTREAM_LSHIFT + +/** @} */ + +} // namespace c4 + + +C4_SUPPRESS_WARNING_GCC_CLANG_POP + +#endif /* _C4_SUBSTR_HPP_ */ + + +// (end src/c4/substr.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/ext/fast_float.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_EXT_FAST_FLOAT_HPP_ +#define _C4_EXT_FAST_FLOAT_HPP_ + +#if defined(_MSC_VER) && !defined(__clang__) +# pragma warning(push) +# pragma warning(disable: 4127) // conditional expression is constant +# pragma warning(disable: 4365) // '=': conversion from 'const _Ty' to 'fast_float::limb', signed/unsigned mismatch +# pragma warning(disable: 4996) // snprintf/scanf: this function or variable may be unsafe +#elif defined(__clang__) || defined(__APPLE_CC__) || defined(_LIBCPP_VERSION) +# pragma clang diagnostic push +# if (defined(__clang_major__) && (__clang_major__ >= 9)) || defined(__APPLE_CC__) +# pragma clang diagnostic ignored "-Wfortify-source" +# endif +# pragma clang diagnostic ignored "-Wshift-count-overflow" +# pragma clang diagnostic ignored "-Wold-style-cast" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wnarrowing" +# pragma GCC diagnostic ignored "-Wconversion" +# pragma GCC diagnostic ignored "-Wsign-conversion" +# pragma GCC diagnostic ignored "-Wuseless-cast" +# pragma GCC diagnostic ignored "-Wold-style-cast" +# pragma GCC diagnostic ignored "-Warray-bounds" +# if __GNUC__ >= 5 +# pragma GCC diagnostic ignored "-Wshift-count-overflow" +# endif +#endif + +// fast_float by Daniel Lemire +// fast_float by João Paulo Magalhaes +// +// +// with contributions from Eugene Golushkov +// with contributions from Maksim Kita +// with contributions from Marcin Wojdyr +// with contributions from Neal Richardson +// with contributions from Tim Paine +// with contributions from Fabio Pellacini +// with contributions from Lénárd Szolnoki +// with contributions from Jan Pharago +// with contributions from Maya Warrier +// with contributions from Taha Khokhar +// with contributions from Anders Dalvander +// +// +// MIT License Notice +// +// MIT License +// +// Copyright (c) 2021 The fast_float authors +// +// 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. +// + +#ifndef FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H +#define FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H + +#ifdef __has_include +#if __has_include() +#include +#endif +#endif + +// Testing for https://wg21.link/N3652, adopted in C++14 +#if defined(__cpp_constexpr) && __cpp_constexpr >= 201304 +#define FASTFLOAT_CONSTEXPR14 constexpr +#else +#define FASTFLOAT_CONSTEXPR14 +#endif + +#if defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L +#define FASTFLOAT_HAS_BIT_CAST 1 +#else +#define FASTFLOAT_HAS_BIT_CAST 0 +#endif + +#if defined(__cpp_lib_is_constant_evaluated) && \ + __cpp_lib_is_constant_evaluated >= 201811L +#define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 1 +#else +#define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 0 +#endif + +#if defined(__cpp_if_constexpr) && __cpp_if_constexpr >= 201606L +#define FASTFLOAT_IF_CONSTEXPR17(x) if constexpr (x) +#else +#define FASTFLOAT_IF_CONSTEXPR17(x) if (x) +#endif + +// Testing for relevant C++20 constexpr library features +#if FASTFLOAT_HAS_IS_CONSTANT_EVALUATED && FASTFLOAT_HAS_BIT_CAST && \ + defined(__cpp_lib_constexpr_algorithms) && \ + __cpp_lib_constexpr_algorithms >= 201806L /*For std::copy and std::fill*/ +#define FASTFLOAT_CONSTEXPR20 constexpr +#define FASTFLOAT_IS_CONSTEXPR 1 +#else +#define FASTFLOAT_CONSTEXPR20 +#define FASTFLOAT_IS_CONSTEXPR 0 +#endif + +#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +#define FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE 0 +#else +#define FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE 1 +#endif + +#endif // FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H + +#ifndef FASTFLOAT_FLOAT_COMMON_H +#define FASTFLOAT_FLOAT_COMMON_H + +#include +//included above: +//#include +//included above: +//#include +#include +//included above: +//#include +//included above: +//#include +//included above: +//#include +#include +#ifdef __has_include +#if __has_include() && (__cplusplus > 202002L || (defined(_MSVC_LANG) && (_MSVC_LANG > 202002L))) +#include +#endif +#endif + +#define FASTFLOAT_VERSION_MAJOR 8 +#define FASTFLOAT_VERSION_MINOR 2 +#define FASTFLOAT_VERSION_PATCH 4 + +#define FASTFLOAT_STRINGIZE_IMPL(x) #x +#define FASTFLOAT_STRINGIZE(x) FASTFLOAT_STRINGIZE_IMPL(x) + +#define FASTFLOAT_VERSION_STR \ + FASTFLOAT_STRINGIZE(FASTFLOAT_VERSION_MAJOR) \ + "." FASTFLOAT_STRINGIZE(FASTFLOAT_VERSION_MINOR) "." FASTFLOAT_STRINGIZE( \ + FASTFLOAT_VERSION_PATCH) + +#define FASTFLOAT_VERSION \ + (FASTFLOAT_VERSION_MAJOR * 10000 + FASTFLOAT_VERSION_MINOR * 100 + \ + FASTFLOAT_VERSION_PATCH) + +namespace fast_float { + +enum class chars_format : uint64_t; + +namespace detail { +constexpr chars_format basic_json_fmt = chars_format(1 << 5); +constexpr chars_format basic_fortran_fmt = chars_format(1 << 6); +} // namespace detail + +enum class chars_format : uint64_t { + scientific = 1 << 0, + fixed = 1 << 2, + hex = 1 << 3, + no_infnan = 1 << 4, + // RFC 8259: https://datatracker.ietf.org/doc/html/rfc8259#section-6 + json = uint64_t(detail::basic_json_fmt) | fixed | scientific | no_infnan, + // Extension of RFC 8259 where, e.g., "inf" and "nan" are allowed. + json_or_infnan = uint64_t(detail::basic_json_fmt) | fixed | scientific, + fortran = uint64_t(detail::basic_fortran_fmt) | fixed | scientific, + general = fixed | scientific, + allow_leading_plus = 1 << 7, + skip_white_space = 1 << 8, +}; + +template struct from_chars_result_t { + UC const *ptr; + std::errc ec; + + // https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2497r0.html + constexpr explicit operator bool() const noexcept { + return ec == std::errc(); + } +}; + +using from_chars_result = from_chars_result_t; + +template struct parse_options_t { + constexpr explicit parse_options_t(chars_format fmt = chars_format::general, + UC dot = UC('.'), int b = 10) + : format(fmt), decimal_point(dot), base(b) {} + + /** Which number formats are accepted */ + chars_format format; + /** The character used as decimal point */ + UC decimal_point; + /** The base used for integers */ + int base; +}; + +using parse_options = parse_options_t; + +} // namespace fast_float + +#if FASTFLOAT_HAS_BIT_CAST +#include +#endif + +#if (defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \ + defined(__amd64) || defined(__aarch64__) || defined(_M_ARM64) || \ + defined(__MINGW64__) || defined(__s390x__) || \ + (defined(__ppc64__) || defined(__PPC64__) || defined(__ppc64le__) || \ + defined(__PPC64LE__)) || \ + defined(__loongarch64) || (defined(__riscv) && __riscv_xlen == 64)) +#define FASTFLOAT_64BIT 1 +#elif (defined(__i386) || defined(__i386__) || defined(_M_IX86) || \ + defined(__arm__) || defined(_M_ARM) || defined(__ppc__) || \ + defined(__MINGW32__) || defined(__EMSCRIPTEN__) || \ + (defined(__riscv) && __riscv_xlen == 32)) +#define FASTFLOAT_32BIT 1 +#else + // Need to check incrementally, since SIZE_MAX is a size_t, avoid overflow. +// We can never tell the register width, but the SIZE_MAX is a good +// approximation. UINTPTR_MAX and INTPTR_MAX are optional, so avoid them for max +// portability. +#if SIZE_MAX == 0xffff +#error Unknown platform (16-bit, unsupported) +#elif SIZE_MAX == 0xffffffff +#define FASTFLOAT_32BIT 1 +#elif SIZE_MAX == 0xffffffffffffffff +#define FASTFLOAT_64BIT 1 +#else +#error Unknown platform (not 32-bit, not 64-bit?) +#endif +#endif + +#if ((defined(_WIN32) || defined(_WIN64)) && !defined(__clang__)) || \ + (defined(_M_ARM64) && !defined(__MINGW32__)) +//included above: +//#include +#endif + +#if defined(_MSC_VER) && !defined(__clang__) +#define FASTFLOAT_VISUAL_STUDIO 1 +#endif + +#if defined __BYTE_ORDER__ && defined __ORDER_BIG_ENDIAN__ +#define FASTFLOAT_IS_BIG_ENDIAN (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) +#elif defined _WIN32 +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#else +#if defined(__APPLE__) || defined(__FreeBSD__) +#include +#elif defined(sun) || defined(__sun) +#include +#elif defined(__MVS__) +#include +#else +#ifdef __has_include +#if __has_include() +#include +#endif //__has_include() +#endif //__has_include +#endif +# +#ifndef __BYTE_ORDER__ +// safe choice +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#endif +# +#ifndef __ORDER_LITTLE_ENDIAN__ +// safe choice +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#endif +# +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#else +#define FASTFLOAT_IS_BIG_ENDIAN 1 +#endif +#endif + +#if defined(__SSE2__) || (defined(FASTFLOAT_VISUAL_STUDIO) && \ + (defined(_M_AMD64) || defined(_M_X64) || \ + (defined(_M_IX86_FP) && _M_IX86_FP == 2))) +#define FASTFLOAT_SSE2 1 +#endif + +#if defined(__aarch64__) || defined(_M_ARM64) +#define FASTFLOAT_NEON 1 +#endif + +#if defined(FASTFLOAT_SSE2) || defined(FASTFLOAT_NEON) +#define FASTFLOAT_HAS_SIMD 1 +#endif + +#if defined(__GNUC__) +// disable -Wcast-align=strict (GCC only) +#define FASTFLOAT_SIMD_DISABLE_WARNINGS \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wcast-align\"") +#else +#define FASTFLOAT_SIMD_DISABLE_WARNINGS +#endif + +#if defined(__GNUC__) +#define FASTFLOAT_SIMD_RESTORE_WARNINGS _Pragma("GCC diagnostic pop") +#else +#define FASTFLOAT_SIMD_RESTORE_WARNINGS +#endif + +#ifdef FASTFLOAT_VISUAL_STUDIO +#define fastfloat_really_inline __forceinline +#else +#define fastfloat_really_inline inline __attribute__((always_inline)) +#endif + +#ifndef FASTFLOAT_ASSERT +#define FASTFLOAT_ASSERT(x) \ + { ((void)(x)); } +#endif + +#ifndef FASTFLOAT_DEBUG_ASSERT +#define FASTFLOAT_DEBUG_ASSERT(x) \ + { ((void)(x)); } +#endif + +// rust style `try!()` macro, or `?` operator +#define FASTFLOAT_TRY(x) \ + { \ + if (!(x)) \ + return false; \ + } + +#define FASTFLOAT_ENABLE_IF(...) \ + typename std::enable_if<(__VA_ARGS__), int>::type + +namespace fast_float { + +fastfloat_really_inline constexpr bool cpp20_and_in_constexpr() { +#if FASTFLOAT_HAS_IS_CONSTANT_EVALUATED + return std::is_constant_evaluated(); +#else + return false; +#endif +} + +template +struct is_supported_float_type + : std::integral_constant< + bool, std::is_same::value || std::is_same::value +#ifdef __STDCPP_FLOAT64_T__ + || std::is_same::value +#endif +#ifdef __STDCPP_FLOAT32_T__ + || std::is_same::value +#endif +#ifdef __STDCPP_FLOAT16_T__ + || std::is_same::value +#endif +#ifdef __STDCPP_BFLOAT16_T__ + || std::is_same::value +#endif + > { +}; + +template +using equiv_uint_t = typename std::conditional< + sizeof(T) == 1, uint8_t, + typename std::conditional< + sizeof(T) == 2, uint16_t, + typename std::conditional::type>::type>::type; + +template struct is_supported_integer_type : std::is_integral {}; + +template +struct is_supported_char_type + : std::integral_constant::value || + std::is_same::value || + std::is_same::value || + std::is_same::value +#ifdef __cpp_char8_t + || std::is_same::value +#endif + > { +}; + +template +inline FASTFLOAT_CONSTEXPR14 bool +fastfloat_strncasecmp3(UC const *actual_mixedcase, + UC const *expected_lowercase) { + uint64_t mask{0}; + FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 1) { mask = 0x2020202020202020; } + else FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 2) { + mask = 0x0020002000200020; + } + else FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 4) { + mask = 0x0000002000000020; + } + else { + return false; + } + + uint64_t val1{0}, val2{0}; + if (cpp20_and_in_constexpr()) { + for (size_t i = 0; i < 3; i++) { + if ((actual_mixedcase[i] | 32) != expected_lowercase[i]) { + return false; + } + } + return true; + } else { + FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 1 || sizeof(UC) == 2) { + ::memcpy(&val1, actual_mixedcase, 3 * sizeof(UC)); + ::memcpy(&val2, expected_lowercase, 3 * sizeof(UC)); + val1 |= mask; + val2 |= mask; + return val1 == val2; + } + else FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 4) { + ::memcpy(&val1, actual_mixedcase, 2 * sizeof(UC)); + ::memcpy(&val2, expected_lowercase, 2 * sizeof(UC)); + val1 |= mask; + if (val1 != val2) { + return false; + } + return (actual_mixedcase[2] | 32) == (expected_lowercase[2]); + } + else { + return false; + } + } +} + +template +inline FASTFLOAT_CONSTEXPR14 bool +fastfloat_strncasecmp5(UC const *actual_mixedcase, + UC const *expected_lowercase) { + uint64_t mask{0}; + uint64_t val1{0}, val2{0}; + if (cpp20_and_in_constexpr()) { + for (size_t i = 0; i < 5; i++) { + if ((actual_mixedcase[i] | 32) != expected_lowercase[i]) { + return false; + } + } + return true; + } else { + FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 1) { + mask = 0x2020202020202020; + ::memcpy(&val1, actual_mixedcase, 5 * sizeof(UC)); + ::memcpy(&val2, expected_lowercase, 5 * sizeof(UC)); + val1 |= mask; + val2 |= mask; + return val1 == val2; + } + else FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 2) { + mask = 0x0020002000200020; + ::memcpy(&val1, actual_mixedcase, 4 * sizeof(UC)); + ::memcpy(&val2, expected_lowercase, 4 * sizeof(UC)); + val1 |= mask; + if (val1 != val2) { + return false; + } + return (actual_mixedcase[4] | 32) == (expected_lowercase[4]); + } + else FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 4) { + mask = 0x0000002000000020; + ::memcpy(&val1, actual_mixedcase, 2 * sizeof(UC)); + ::memcpy(&val2, expected_lowercase, 2 * sizeof(UC)); + val1 |= mask; + if (val1 != val2) { + return false; + } + ::memcpy(&val1, actual_mixedcase + 2, 2 * sizeof(UC)); + ::memcpy(&val2, expected_lowercase + 2, 2 * sizeof(UC)); + val1 |= mask; + if (val1 != val2) { + return false; + } + return (actual_mixedcase[4] | 32) == (expected_lowercase[4]); + } + else { + return false; + } + } +} + +// Compares two ASCII strings in a case insensitive manner. +template +inline FASTFLOAT_CONSTEXPR14 bool +fastfloat_strncasecmp(UC const *actual_mixedcase, UC const *expected_lowercase, + size_t length) { + uint64_t mask{0}; + FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 1) { mask = 0x2020202020202020; } + else FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 2) { + mask = 0x0020002000200020; + } + else FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 4) { + mask = 0x0000002000000020; + } + else { + return false; + } + + if (cpp20_and_in_constexpr()) { + for (size_t i = 0; i < length; i++) { + if ((actual_mixedcase[i] | 32) != expected_lowercase[i]) { + return false; + } + } + return true; + } else { + uint64_t val1{0}, val2{0}; + size_t sz{8 / (sizeof(UC))}; + for (size_t i = 0; i < length; i += sz) { + val1 = val2 = 0; + sz = std::min(sz, length - i); + ::memcpy(&val1, actual_mixedcase + i, sz * sizeof(UC)); + ::memcpy(&val2, expected_lowercase + i, sz * sizeof(UC)); + val1 |= mask; + val2 |= mask; + if (val1 != val2) { + return false; + } + } + return true; + } +} + +#ifndef FLT_EVAL_METHOD +#error "FLT_EVAL_METHOD should be defined, please include cfloat." +#endif + +// a pointer and a length to a contiguous block of memory +template struct span { + T const *ptr; + size_t length; + + constexpr span(T const *_ptr, size_t _length) : ptr(_ptr), length(_length) {} + + constexpr span() : ptr(nullptr), length(0) {} + + constexpr size_t len() const noexcept { return length; } + + FASTFLOAT_CONSTEXPR14 const T &operator[](size_t index) const noexcept { + FASTFLOAT_DEBUG_ASSERT(index < length); + return ptr[index]; + } +}; + +struct value128 { + uint64_t low; + uint64_t high; + + constexpr value128(uint64_t _low, uint64_t _high) : low(_low), high(_high) {} + + constexpr value128() : low(0), high(0) {} +}; + +/* Helper C++14 constexpr generic implementation of leading_zeroes */ +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 int +leading_zeroes_generic(uint64_t input_num, int last_bit = 0) { + if (input_num & uint64_t(0xffffffff00000000)) { + input_num >>= 32; + last_bit |= 32; + } + if (input_num & uint64_t(0xffff0000)) { + input_num >>= 16; + last_bit |= 16; + } + if (input_num & uint64_t(0xff00)) { + input_num >>= 8; + last_bit |= 8; + } + if (input_num & uint64_t(0xf0)) { + input_num >>= 4; + last_bit |= 4; + } + if (input_num & uint64_t(0xc)) { + input_num >>= 2; + last_bit |= 2; + } + if (input_num & uint64_t(0x2)) { /* input_num >>= 1; */ + last_bit |= 1; + } + return 63 - last_bit; +} + +/* result might be undefined when input_num is zero */ +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 int +leading_zeroes(uint64_t input_num) { + assert(input_num > 0); + if (cpp20_and_in_constexpr()) { + return leading_zeroes_generic(input_num); + } +#ifdef FASTFLOAT_VISUAL_STUDIO +#if defined(_M_X64) || defined(_M_ARM64) + unsigned long leading_zero = 0; + // Search the mask data from most significant bit (MSB) + // to least significant bit (LSB) for a set bit (1). + _BitScanReverse64(&leading_zero, input_num); + return (int)(63 - leading_zero); +#else + return leading_zeroes_generic(input_num); +#endif +#else + return __builtin_clzll(input_num); +#endif +} + +/* Helper C++14 constexpr generic implementation of countr_zero for 32-bit */ +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 int +countr_zero_generic_32(uint32_t input_num) { + if (input_num == 0) { + return 32; + } + int last_bit = 0; + if (!(input_num & 0x0000FFFF)) { + input_num >>= 16; + last_bit |= 16; + } + if (!(input_num & 0x00FF)) { + input_num >>= 8; + last_bit |= 8; + } + if (!(input_num & 0x0F)) { + input_num >>= 4; + last_bit |= 4; + } + if (!(input_num & 0x3)) { + input_num >>= 2; + last_bit |= 2; + } + if (!(input_num & 0x1)) { + last_bit |= 1; + } + return last_bit; +} + +/* count trailing zeroes for 32-bit integers */ +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 int +countr_zero_32(uint32_t input_num) { + if (cpp20_and_in_constexpr()) { + return countr_zero_generic_32(input_num); + } +#ifdef FASTFLOAT_VISUAL_STUDIO + unsigned long trailing_zero = 0; + if (_BitScanForward(&trailing_zero, input_num)) { + return (int)trailing_zero; + } + return 32; +#else + return input_num == 0 ? 32 : __builtin_ctz(input_num); +#endif +} + +// slow emulation routine for 32-bit +fastfloat_really_inline constexpr uint64_t emulu(uint32_t x, uint32_t y) { + return x * (uint64_t)y; +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint64_t +umul128_generic(uint64_t ab, uint64_t cd, uint64_t *hi) { + uint64_t ad = emulu((uint32_t)(ab >> 32), (uint32_t)cd); + uint64_t bd = emulu((uint32_t)ab, (uint32_t)cd); + uint64_t adbc = ad + emulu((uint32_t)ab, (uint32_t)(cd >> 32)); + uint64_t adbc_carry = (uint64_t)(adbc < ad); + uint64_t lo = bd + (adbc << 32); + *hi = emulu((uint32_t)(ab >> 32), (uint32_t)(cd >> 32)) + (adbc >> 32) + + (adbc_carry << 32) + (uint64_t)(lo < bd); + return lo; +} + +#ifdef FASTFLOAT_32BIT + +// slow emulation routine for 32-bit +#if !defined(__MINGW64__) +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint64_t _umul128(uint64_t ab, + uint64_t cd, + uint64_t *hi) { + return umul128_generic(ab, cd, hi); +} +#endif // !__MINGW64__ + +#endif // FASTFLOAT_32BIT + +// compute 64-bit a*b +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 value128 +full_multiplication(uint64_t a, uint64_t b) { + if (cpp20_and_in_constexpr()) { + value128 answer; + answer.low = umul128_generic(a, b, &answer.high); + return answer; + } + value128 answer; +#if defined(_M_ARM64) && !defined(__MINGW32__) + // ARM64 has native support for 64-bit multiplications, no need to emulate + // But MinGW on ARM64 doesn't have native support for 64-bit multiplications + answer.high = __umulh(a, b); + answer.low = a * b; +#elif defined(FASTFLOAT_32BIT) || (defined(_WIN64) && !defined(__clang__) && \ + !defined(_M_ARM64) && !defined(__GNUC__)) + answer.low = _umul128(a, b, &answer.high); // _umul128 not available on ARM64 +#elif defined(FASTFLOAT_64BIT) && defined(__SIZEOF_INT128__) + __uint128_t r = ((__uint128_t)a) * b; + answer.low = uint64_t(r); + answer.high = uint64_t(r >> 64); +#else + answer.low = umul128_generic(a, b, &answer.high); +#endif + return answer; +} + +struct adjusted_mantissa { + uint64_t mantissa{0}; + int32_t power2{0}; // a negative value indicates an invalid result + adjusted_mantissa() = default; + + constexpr bool operator==(adjusted_mantissa const &o) const { + return mantissa == o.mantissa && power2 == o.power2; + } + + constexpr bool operator!=(adjusted_mantissa const &o) const { + return mantissa != o.mantissa || power2 != o.power2; + } +}; + +// Bias so we can get the real exponent with an invalid adjusted_mantissa. +constexpr static int32_t invalid_am_bias = -0x8000; + +// used for binary_format_lookup_tables::max_mantissa +constexpr uint64_t constant_55555 = 5 * 5 * 5 * 5 * 5; + +template struct binary_format_lookup_tables; + +template struct binary_format : binary_format_lookup_tables { + using equiv_uint = equiv_uint_t; + + static constexpr int mantissa_explicit_bits(); + static constexpr int minimum_exponent(); + static constexpr int infinite_power(); + static constexpr int sign_index(); + static constexpr int + min_exponent_fast_path(); // used when fegetround() == FE_TONEAREST + static constexpr int max_exponent_fast_path(); + static constexpr int max_exponent_round_to_even(); + static constexpr int min_exponent_round_to_even(); + static constexpr uint64_t max_mantissa_fast_path(int64_t power); + static constexpr uint64_t + max_mantissa_fast_path(); // used when fegetround() == FE_TONEAREST + static constexpr int largest_power_of_ten(); + static constexpr int smallest_power_of_ten(); + static constexpr T exact_power_of_ten(int64_t power); + static constexpr size_t max_digits(); + static constexpr equiv_uint exponent_mask(); + static constexpr equiv_uint mantissa_mask(); + static constexpr equiv_uint hidden_bit_mask(); +}; + +template struct binary_format_lookup_tables { + static constexpr double powers_of_ten[] = { + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, + 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22}; + + // Largest integer value v so that (5**index * v) <= 1<<53. + // 0x20000000000000 == 1 << 53 + static constexpr uint64_t max_mantissa[] = { + 0x20000000000000, + 0x20000000000000 / 5, + 0x20000000000000 / (5 * 5), + 0x20000000000000 / (5 * 5 * 5), + 0x20000000000000 / (5 * 5 * 5 * 5), + 0x20000000000000 / (constant_55555), + 0x20000000000000 / (constant_55555 * 5), + 0x20000000000000 / (constant_55555 * 5 * 5), + 0x20000000000000 / (constant_55555 * 5 * 5 * 5), + 0x20000000000000 / (constant_55555 * 5 * 5 * 5 * 5), + 0x20000000000000 / (constant_55555 * constant_55555), + 0x20000000000000 / (constant_55555 * constant_55555 * 5), + 0x20000000000000 / (constant_55555 * constant_55555 * 5 * 5), + 0x20000000000000 / (constant_55555 * constant_55555 * 5 * 5 * 5), + 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555), + 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * 5), + 0x20000000000000 / + (constant_55555 * constant_55555 * constant_55555 * 5 * 5), + 0x20000000000000 / + (constant_55555 * constant_55555 * constant_55555 * 5 * 5 * 5), + 0x20000000000000 / + (constant_55555 * constant_55555 * constant_55555 * 5 * 5 * 5 * 5), + 0x20000000000000 / + (constant_55555 * constant_55555 * constant_55555 * constant_55555), + 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * + constant_55555 * 5), + 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * + constant_55555 * 5 * 5), + 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * + constant_55555 * 5 * 5 * 5), + 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * + constant_55555 * 5 * 5 * 5 * 5)}; +}; + +#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE + +template +constexpr double binary_format_lookup_tables::powers_of_ten[]; + +template +constexpr uint64_t binary_format_lookup_tables::max_mantissa[]; + +#endif + +template struct binary_format_lookup_tables { + static constexpr float powers_of_ten[] = {1e0f, 1e1f, 1e2f, 1e3f, 1e4f, 1e5f, + 1e6f, 1e7f, 1e8f, 1e9f, 1e10f}; + + // Largest integer value v so that (5**index * v) <= 1<<24. + // 0x1000000 == 1<<24 + static constexpr uint64_t max_mantissa[] = { + 0x1000000, + 0x1000000 / 5, + 0x1000000 / (5 * 5), + 0x1000000 / (5 * 5 * 5), + 0x1000000 / (5 * 5 * 5 * 5), + 0x1000000 / (constant_55555), + 0x1000000 / (constant_55555 * 5), + 0x1000000 / (constant_55555 * 5 * 5), + 0x1000000 / (constant_55555 * 5 * 5 * 5), + 0x1000000 / (constant_55555 * 5 * 5 * 5 * 5), + 0x1000000 / (constant_55555 * constant_55555), + 0x1000000 / (constant_55555 * constant_55555 * 5)}; +}; + +#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE + +template +constexpr float binary_format_lookup_tables::powers_of_ten[]; + +template +constexpr uint64_t binary_format_lookup_tables::max_mantissa[]; + +#endif + +template <> +inline constexpr int binary_format::min_exponent_fast_path() { +#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) + return 0; +#else + return -22; +#endif +} + +template <> +inline constexpr int binary_format::min_exponent_fast_path() { +#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) + return 0; +#else + return -10; +#endif +} + +template <> +inline constexpr int binary_format::mantissa_explicit_bits() { + return 52; +} + +template <> +inline constexpr int binary_format::mantissa_explicit_bits() { + return 23; +} + +template <> +inline constexpr int binary_format::max_exponent_round_to_even() { + return 23; +} + +template <> +inline constexpr int binary_format::max_exponent_round_to_even() { + return 10; +} + +template <> +inline constexpr int binary_format::min_exponent_round_to_even() { + return -4; +} + +template <> +inline constexpr int binary_format::min_exponent_round_to_even() { + return -17; +} + +template <> inline constexpr int binary_format::minimum_exponent() { + return -1023; +} + +template <> inline constexpr int binary_format::minimum_exponent() { + return -127; +} + +template <> inline constexpr int binary_format::infinite_power() { + return 0x7FF; +} + +template <> inline constexpr int binary_format::infinite_power() { + return 0xFF; +} + +template <> inline constexpr int binary_format::sign_index() { + return 63; +} + +template <> inline constexpr int binary_format::sign_index() { + return 31; +} + +template <> +inline constexpr int binary_format::max_exponent_fast_path() { + return 22; +} + +template <> +inline constexpr int binary_format::max_exponent_fast_path() { + return 10; +} + +template <> +inline constexpr uint64_t binary_format::max_mantissa_fast_path() { + return uint64_t(2) << mantissa_explicit_bits(); +} + +template <> +inline constexpr uint64_t binary_format::max_mantissa_fast_path() { + return uint64_t(2) << mantissa_explicit_bits(); +} + +// credit: Jakub Jelínek +#ifdef __STDCPP_FLOAT16_T__ +template struct binary_format_lookup_tables { + static constexpr std::float16_t powers_of_ten[] = {1e0f16, 1e1f16, 1e2f16, + 1e3f16, 1e4f16}; + + // Largest integer value v so that (5**index * v) <= 1<<11. + // 0x800 == 1<<11 + static constexpr uint64_t max_mantissa[] = {0x800, + 0x800 / 5, + 0x800 / (5 * 5), + 0x800 / (5 * 5 * 5), + 0x800 / (5 * 5 * 5 * 5), + 0x800 / (constant_55555)}; +}; + +#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE + +template +constexpr std::float16_t + binary_format_lookup_tables::powers_of_ten[]; + +template +constexpr uint64_t + binary_format_lookup_tables::max_mantissa[]; + +#endif + +template <> +inline constexpr std::float16_t +binary_format::exact_power_of_ten(int64_t power) { + // Work around clang bug https://godbolt.org/z/zedh7rrhc + return (void)powers_of_ten[0], powers_of_ten[power]; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::exponent_mask() { + return 0x7C00; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::mantissa_mask() { + return 0x03FF; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::hidden_bit_mask() { + return 0x0400; +} + +template <> +inline constexpr int binary_format::max_exponent_fast_path() { + return 4; +} + +template <> +inline constexpr int binary_format::mantissa_explicit_bits() { + return 10; +} + +template <> +inline constexpr uint64_t +binary_format::max_mantissa_fast_path() { + return uint64_t(2) << mantissa_explicit_bits(); +} + +template <> +inline constexpr uint64_t +binary_format::max_mantissa_fast_path(int64_t power) { + // caller is responsible to ensure that + // power >= 0 && power <= 4 + // + // Work around clang bug https://godbolt.org/z/zedh7rrhc + return (void)max_mantissa[0], max_mantissa[power]; +} + +template <> +inline constexpr int binary_format::min_exponent_fast_path() { + return 0; +} + +template <> +inline constexpr int +binary_format::max_exponent_round_to_even() { + return 5; +} + +template <> +inline constexpr int +binary_format::min_exponent_round_to_even() { + return -22; +} + +template <> +inline constexpr int binary_format::minimum_exponent() { + return -15; +} + +template <> +inline constexpr int binary_format::infinite_power() { + return 0x1F; +} + +template <> inline constexpr int binary_format::sign_index() { + return 15; +} + +template <> +inline constexpr int binary_format::largest_power_of_ten() { + return 4; +} + +template <> +inline constexpr int binary_format::smallest_power_of_ten() { + return -27; +} + +template <> +inline constexpr size_t binary_format::max_digits() { + return 22; +} +#endif // __STDCPP_FLOAT16_T__ + +// credit: Jakub Jelínek +#ifdef __STDCPP_BFLOAT16_T__ +template struct binary_format_lookup_tables { + static constexpr std::bfloat16_t powers_of_ten[] = {1e0bf16, 1e1bf16, 1e2bf16, + 1e3bf16}; + + // Largest integer value v so that (5**index * v) <= 1<<8. + // 0x100 == 1<<8 + static constexpr uint64_t max_mantissa[] = {0x100, 0x100 / 5, 0x100 / (5 * 5), + 0x100 / (5 * 5 * 5), + 0x100 / (5 * 5 * 5 * 5)}; +}; + +#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE + +template +constexpr std::bfloat16_t + binary_format_lookup_tables::powers_of_ten[]; + +template +constexpr uint64_t + binary_format_lookup_tables::max_mantissa[]; + +#endif + +template <> +inline constexpr std::bfloat16_t +binary_format::exact_power_of_ten(int64_t power) { + // Work around clang bug https://godbolt.org/z/zedh7rrhc + return (void)powers_of_ten[0], powers_of_ten[power]; +} + +template <> +inline constexpr int binary_format::max_exponent_fast_path() { + return 3; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::exponent_mask() { + return 0x7F80; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::mantissa_mask() { + return 0x007F; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::hidden_bit_mask() { + return 0x0080; +} + +template <> +inline constexpr int binary_format::mantissa_explicit_bits() { + return 7; +} + +template <> +inline constexpr uint64_t +binary_format::max_mantissa_fast_path() { + return uint64_t(2) << mantissa_explicit_bits(); +} + +template <> +inline constexpr uint64_t +binary_format::max_mantissa_fast_path(int64_t power) { + // caller is responsible to ensure that + // power >= 0 && power <= 3 + // + // Work around clang bug https://godbolt.org/z/zedh7rrhc + return (void)max_mantissa[0], max_mantissa[power]; +} + +template <> +inline constexpr int binary_format::min_exponent_fast_path() { + return 0; +} + +template <> +inline constexpr int +binary_format::max_exponent_round_to_even() { + return 3; +} + +template <> +inline constexpr int +binary_format::min_exponent_round_to_even() { + return -24; +} + +template <> +inline constexpr int binary_format::minimum_exponent() { + return -127; +} + +template <> +inline constexpr int binary_format::infinite_power() { + return 0xFF; +} + +template <> inline constexpr int binary_format::sign_index() { + return 15; +} + +template <> +inline constexpr int binary_format::largest_power_of_ten() { + return 38; +} + +template <> +inline constexpr int binary_format::smallest_power_of_ten() { + return -60; +} + +template <> +inline constexpr size_t binary_format::max_digits() { + return 98; +} +#endif // __STDCPP_BFLOAT16_T__ + +template <> +inline constexpr uint64_t +binary_format::max_mantissa_fast_path(int64_t power) { + // caller is responsible to ensure that + // power >= 0 && power <= 22 + // + // Work around clang bug https://godbolt.org/z/zedh7rrhc + return (void)max_mantissa[0], max_mantissa[power]; +} + +template <> +inline constexpr uint64_t +binary_format::max_mantissa_fast_path(int64_t power) { + // caller is responsible to ensure that + // power >= 0 && power <= 10 + // + // Work around clang bug https://godbolt.org/z/zedh7rrhc + return (void)max_mantissa[0], max_mantissa[power]; +} + +template <> +inline constexpr double +binary_format::exact_power_of_ten(int64_t power) { + // Work around clang bug https://godbolt.org/z/zedh7rrhc + return (void)powers_of_ten[0], powers_of_ten[power]; +} + +template <> +inline constexpr float binary_format::exact_power_of_ten(int64_t power) { + // Work around clang bug https://godbolt.org/z/zedh7rrhc + return (void)powers_of_ten[0], powers_of_ten[power]; +} + +template <> inline constexpr int binary_format::largest_power_of_ten() { + return 308; +} + +template <> inline constexpr int binary_format::largest_power_of_ten() { + return 38; +} + +template <> +inline constexpr int binary_format::smallest_power_of_ten() { + return -342; +} + +template <> inline constexpr int binary_format::smallest_power_of_ten() { + return -64; +} + +template <> inline constexpr size_t binary_format::max_digits() { + return 769; +} + +template <> inline constexpr size_t binary_format::max_digits() { + return 114; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::exponent_mask() { + return 0x7F800000; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::exponent_mask() { + return 0x7FF0000000000000; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::mantissa_mask() { + return 0x007FFFFF; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::mantissa_mask() { + return 0x000FFFFFFFFFFFFF; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::hidden_bit_mask() { + return 0x00800000; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::hidden_bit_mask() { + return 0x0010000000000000; +} + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void +to_float(bool negative, adjusted_mantissa am, T &value) { + using equiv_uint = equiv_uint_t; + equiv_uint word = equiv_uint(am.mantissa); + word = equiv_uint(word | equiv_uint(am.power2) + << binary_format::mantissa_explicit_bits()); + word = + equiv_uint(word | equiv_uint(negative) << binary_format::sign_index()); +#if FASTFLOAT_HAS_BIT_CAST + value = std::bit_cast(word); +#else + ::memcpy(&value, &word, sizeof(T)); +#endif +} + +template struct space_lut { + static constexpr bool value[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +}; + +#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE + +template constexpr bool space_lut::value[]; + +#endif + +template constexpr bool is_space(UC c) { + return c < 256 && space_lut<>::value[uint8_t(c)]; +} + +template static constexpr uint64_t int_cmp_zeros() { + static_assert((sizeof(UC) == 1) || (sizeof(UC) == 2) || (sizeof(UC) == 4), + "Unsupported character size"); + return (sizeof(UC) == 1) ? 0x3030303030303030 + : (sizeof(UC) == 2) + ? (uint64_t(UC('0')) << 48 | uint64_t(UC('0')) << 32 | + uint64_t(UC('0')) << 16 | UC('0')) + : (uint64_t(UC('0')) << 32 | UC('0')); +} + +template static constexpr int int_cmp_len() { + return sizeof(uint64_t) / sizeof(UC); +} + +template constexpr UC const *str_const_nan(); + +template <> constexpr char const *str_const_nan() { return "nan"; } + +template <> constexpr wchar_t const *str_const_nan() { return L"nan"; } + +template <> constexpr char16_t const *str_const_nan() { + return u"nan"; +} + +template <> constexpr char32_t const *str_const_nan() { + return U"nan"; +} + +#ifdef __cpp_char8_t +template <> constexpr char8_t const *str_const_nan() { + return u8"nan"; +} +#endif + +template constexpr UC const *str_const_inf(); + +template <> constexpr char const *str_const_inf() { return "infinity"; } + +template <> constexpr wchar_t const *str_const_inf() { + return L"infinity"; +} + +template <> constexpr char16_t const *str_const_inf() { + return u"infinity"; +} + +template <> constexpr char32_t const *str_const_inf() { + return U"infinity"; +} + +#ifdef __cpp_char8_t +template <> constexpr char8_t const *str_const_inf() { + return u8"infinity"; +} +#endif + +template struct int_luts { + static constexpr uint8_t chdigit[] = { + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, + 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 255, 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255}; + + static constexpr size_t maxdigits_u64[] = { + 64, 41, 32, 28, 25, 23, 22, 21, 20, 19, 18, 18, 17, 17, 16, 16, 16, 16, + 15, 15, 15, 15, 14, 14, 14, 14, 14, 14, 14, 13, 13, 13, 13, 13, 13}; + + static constexpr uint64_t min_safe_u64[] = { + 9223372036854775808ull, 12157665459056928801ull, 4611686018427387904, + 7450580596923828125, 4738381338321616896, 3909821048582988049, + 9223372036854775808ull, 12157665459056928801ull, 10000000000000000000ull, + 5559917313492231481, 2218611106740436992, 8650415919381337933, + 2177953337809371136, 6568408355712890625, 1152921504606846976, + 2862423051509815793, 6746640616477458432, 15181127029874798299ull, + 1638400000000000000, 3243919932521508681, 6221821273427820544, + 11592836324538749809ull, 876488338465357824, 1490116119384765625, + 2481152873203736576, 4052555153018976267, 6502111422497947648, + 10260628712958602189ull, 15943230000000000000ull, 787662783788549761, + 1152921504606846976, 1667889514952984961, 2386420683693101056, + 3379220508056640625, 4738381338321616896}; +}; + +#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE + +template constexpr uint8_t int_luts::chdigit[]; + +template constexpr size_t int_luts::maxdigits_u64[]; + +template constexpr uint64_t int_luts::min_safe_u64[]; + +#endif + +template +fastfloat_really_inline constexpr uint8_t ch_to_digit(UC c) { + // wchar_t and char can be signed, so we need to be careful. + using UnsignedUC = typename std::make_unsigned::type; + return int_luts<>::chdigit[static_cast( + static_cast(c) & + static_cast( + -((static_cast(c) & ~0xFFull) == 0)))]; +} + +fastfloat_really_inline constexpr size_t max_digits_u64(int base) { + return int_luts<>::maxdigits_u64[base - 2]; +} + +// If a u64 is exactly max_digits_u64() in length, this is +// the value below which it has definitely overflowed. +fastfloat_really_inline constexpr uint64_t min_safe_u64(int base) { + return int_luts<>::min_safe_u64[base - 2]; +} + +static_assert(std::is_same, uint64_t>::value, + "equiv_uint should be uint64_t for double"); +static_assert(std::numeric_limits::is_iec559, + "double must fulfill the requirements of IEC 559 (IEEE 754)"); + +static_assert(std::is_same, uint32_t>::value, + "equiv_uint should be uint32_t for float"); +static_assert(std::numeric_limits::is_iec559, + "float must fulfill the requirements of IEC 559 (IEEE 754)"); + +#ifdef __STDCPP_FLOAT64_T__ +static_assert(std::is_same, uint64_t>::value, + "equiv_uint should be uint64_t for std::float64_t"); +static_assert( + std::numeric_limits::is_iec559, + "std::float64_t must fulfill the requirements of IEC 559 (IEEE 754)"); + +template <> +struct binary_format : public binary_format {}; +#endif // __STDCPP_FLOAT64_T__ + +#ifdef __STDCPP_FLOAT32_T__ +static_assert(std::is_same, uint32_t>::value, + "equiv_uint should be uint32_t for std::float32_t"); +static_assert( + std::numeric_limits::is_iec559, + "std::float32_t must fulfill the requirements of IEC 559 (IEEE 754)"); + +template <> +struct binary_format : public binary_format {}; +#endif // __STDCPP_FLOAT32_T__ + +#ifdef __STDCPP_FLOAT16_T__ +static_assert( + std::is_same::equiv_uint, uint16_t>::value, + "equiv_uint should be uint16_t for std::float16_t"); +static_assert( + std::numeric_limits::is_iec559, + "std::float16_t must fulfill the requirements of IEC 559 (IEEE 754)"); +#endif // __STDCPP_FLOAT16_T__ + +#ifdef __STDCPP_BFLOAT16_T__ +static_assert( + std::is_same::equiv_uint, uint16_t>::value, + "equiv_uint should be uint16_t for std::bfloat16_t"); +static_assert( + std::numeric_limits::is_iec559, + "std::bfloat16_t must fulfill the requirements of IEC 559 (IEEE 754)"); +#endif // __STDCPP_BFLOAT16_T__ + +constexpr chars_format operator~(chars_format rhs) noexcept { + using int_type = std::underlying_type::type; + return static_cast(~static_cast(rhs)); +} + +constexpr chars_format operator&(chars_format lhs, chars_format rhs) noexcept { + using int_type = std::underlying_type::type; + return static_cast(static_cast(lhs) & + static_cast(rhs)); +} + +constexpr chars_format operator|(chars_format lhs, chars_format rhs) noexcept { + using int_type = std::underlying_type::type; + return static_cast(static_cast(lhs) | + static_cast(rhs)); +} + +constexpr chars_format operator^(chars_format lhs, chars_format rhs) noexcept { + using int_type = std::underlying_type::type; + return static_cast(static_cast(lhs) ^ + static_cast(rhs)); +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 chars_format & +operator&=(chars_format &lhs, chars_format rhs) noexcept { + return lhs = (lhs & rhs); +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 chars_format & +operator|=(chars_format &lhs, chars_format rhs) noexcept { + return lhs = (lhs | rhs); +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 chars_format & +operator^=(chars_format &lhs, chars_format rhs) noexcept { + return lhs = (lhs ^ rhs); +} + +namespace detail { +// adjust for deprecated feature macros +constexpr chars_format adjust_for_feature_macros(chars_format fmt) { + return fmt +#ifdef FASTFLOAT_ALLOWS_LEADING_PLUS + | chars_format::allow_leading_plus +#endif +#ifdef FASTFLOAT_SKIP_WHITE_SPACE + | chars_format::skip_white_space +#endif + ; +} +} // namespace detail +} // namespace fast_float + +#endif + + +#ifndef FASTFLOAT_FAST_FLOAT_H +#define FASTFLOAT_FAST_FLOAT_H + + +namespace fast_float { +/** + * This function parses the character sequence [first,last) for a number. It + * parses floating-point numbers expecting a locale-indepent format equivalent + * to what is used by std::strtod in the default ("C") locale. The resulting + * floating-point value is the closest floating-point values (using either float + * or double), using the "round to even" convention for values that would + * otherwise fall right in-between two values. That is, we provide exact parsing + * according to the IEEE standard. + * + * Given a successful parse, the pointer (`ptr`) in the returned value is set to + * point right after the parsed number, and the `value` referenced is set to the + * parsed value. In case of error, the returned `ec` contains a representative + * error, otherwise the default (`std::errc()`) value is stored. + * + * The implementation does not throw and does not allocate memory (e.g., with + * `new` or `malloc`). + * + * Like the C++17 standard, the `fast_float::from_chars` functions take an + * optional last argument of the type `fast_float::chars_format`. It is a bitset + * value: we check whether `fmt & fast_float::chars_format::fixed` and `fmt & + * fast_float::chars_format::scientific` are set to determine whether we allow + * the fixed point and scientific notation respectively. The default is + * `fast_float::chars_format::general` which allows both `fixed` and + * `scientific`. + */ +template ::value)> +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars(UC const *first, UC const *last, T &value, + chars_format fmt = chars_format::general) noexcept; + +/** + * Like from_chars, but accepts an `options` argument to govern number parsing. + * Both for floating-point types and integer types. + */ +template +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars_advanced(UC const *first, UC const *last, T &value, + parse_options_t options) noexcept; + +/** + * This function multiplies an integer number by a power of 10 and returns + * the result as a double precision floating-point value that is correctly + * rounded. The resulting floating-point value is the closest floating-point + * value, using the "round to nearest, tie to even" convention for values that + * would otherwise fall right in-between two values. That is, we provide exact + * conversion according to the IEEE standard. + * + * On overflow infinity is returned, on underflow 0 is returned. + * + * The implementation does not throw and does not allocate memory (e.g., with + * `new` or `malloc`). + */ +FASTFLOAT_CONSTEXPR20 inline double +integer_times_pow10(uint64_t mantissa, int decimal_exponent) noexcept; +FASTFLOAT_CONSTEXPR20 inline double +integer_times_pow10(int64_t mantissa, int decimal_exponent) noexcept; + +/** + * This function is a template overload of `integer_times_pow10()` + * that returns a floating-point value of type `T` that is one of + * supported floating-point types (e.g. `double`, `float`). + */ +template +FASTFLOAT_CONSTEXPR20 + typename std::enable_if::value, T>::type + integer_times_pow10(uint64_t mantissa, int decimal_exponent) noexcept; +template +FASTFLOAT_CONSTEXPR20 + typename std::enable_if::value, T>::type + integer_times_pow10(int64_t mantissa, int decimal_exponent) noexcept; + +/** + * from_chars for integer types. + */ +template ::value)> +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars(UC const *first, UC const *last, T &value, int base = 10) noexcept; + +} // namespace fast_float + +#endif // FASTFLOAT_FAST_FLOAT_H + +#ifndef FASTFLOAT_ASCII_NUMBER_H +#define FASTFLOAT_ASCII_NUMBER_H + +//included above: +//#include +//included above: +//#include +//included above: +//#include +#include +//included above: +//#include +//included above: +//#include + + +#ifdef FASTFLOAT_SSE2 +#include +#endif + +#ifdef FASTFLOAT_NEON +#include +#endif + +namespace fast_float { + +template fastfloat_really_inline constexpr bool has_simd_opt() { +#ifdef FASTFLOAT_HAS_SIMD + return std::is_same::value; +#else + return false; +#endif +} + +// Next function can be micro-optimized, but compilers are entirely +// able to optimize it well. +template +fastfloat_really_inline constexpr bool is_integer(UC c) noexcept { + return (unsigned)(c - UC('0')) <= 9u; +} + +fastfloat_really_inline constexpr uint64_t byteswap(uint64_t val) { + return (val & 0xFF00000000000000) >> 56 | (val & 0x00FF000000000000) >> 40 | + (val & 0x0000FF0000000000) >> 24 | (val & 0x000000FF00000000) >> 8 | + (val & 0x00000000FF000000) << 8 | (val & 0x0000000000FF0000) << 24 | + (val & 0x000000000000FF00) << 40 | (val & 0x00000000000000FF) << 56; +} + +fastfloat_really_inline constexpr uint32_t byteswap_32(uint32_t val) { + return (val >> 24) | ((val >> 8) & 0x0000FF00u) | ((val << 8) & 0x00FF0000u) | + (val << 24); +} + +// Read 8 UC into a u64. Truncates UC if not char. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t +read8_to_u64(UC const *chars) { + if (cpp20_and_in_constexpr() || !std::is_same::value) { + uint64_t val = 0; + for (int i = 0; i < 8; ++i) { + val |= uint64_t(uint8_t(*chars)) << (i * 8); + ++chars; + } + return val; + } + uint64_t val; + ::memcpy(&val, chars, sizeof(uint64_t)); +#if FASTFLOAT_IS_BIG_ENDIAN == 1 + // Need to read as-if the number was in little-endian order. + val = byteswap(val); +#endif + return val; +} + +// Read 4 UC into a u32. Truncates UC if not char. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint32_t +read4_to_u32(UC const *chars) { + if (cpp20_and_in_constexpr() || !std::is_same::value) { + uint32_t val = 0; + for (int i = 0; i < 4; ++i) { + val |= uint32_t(uint8_t(*chars)) << (i * 8); + ++chars; + } + return val; + } + uint32_t val; + ::memcpy(&val, chars, sizeof(uint32_t)); +#if FASTFLOAT_IS_BIG_ENDIAN == 1 + val = byteswap_32(val); +#endif + return val; +} +#ifdef FASTFLOAT_SSE2 + +fastfloat_really_inline uint64_t simd_read8_to_u64(__m128i const data) { + FASTFLOAT_SIMD_DISABLE_WARNINGS + __m128i const packed = _mm_packus_epi16(data, data); +#ifdef FASTFLOAT_64BIT + return uint64_t(_mm_cvtsi128_si64(packed)); +#else + uint64_t value; + // Visual Studio + older versions of GCC don't support _mm_storeu_si64 + _mm_storel_epi64(reinterpret_cast<__m128i *>(&value), packed); + return value; +#endif + FASTFLOAT_SIMD_RESTORE_WARNINGS +} + +fastfloat_really_inline uint64_t simd_read8_to_u64(char16_t const *chars) { + FASTFLOAT_SIMD_DISABLE_WARNINGS + return simd_read8_to_u64( + _mm_loadu_si128(reinterpret_cast<__m128i const *>(chars))); + FASTFLOAT_SIMD_RESTORE_WARNINGS +} + +#elif defined(FASTFLOAT_NEON) + +fastfloat_really_inline uint64_t simd_read8_to_u64(uint16x8_t const data) { + FASTFLOAT_SIMD_DISABLE_WARNINGS + uint8x8_t utf8_packed = vmovn_u16(data); + return vget_lane_u64(vreinterpret_u64_u8(utf8_packed), 0); + FASTFLOAT_SIMD_RESTORE_WARNINGS +} + +fastfloat_really_inline uint64_t simd_read8_to_u64(char16_t const *chars) { + FASTFLOAT_SIMD_DISABLE_WARNINGS + return simd_read8_to_u64( + vld1q_u16(reinterpret_cast(chars))); + FASTFLOAT_SIMD_RESTORE_WARNINGS +} + +#endif // FASTFLOAT_SSE2 + +// MSVC SFINAE is broken pre-VS2017 +#if defined(_MSC_VER) && _MSC_VER <= 1900 +template +#else +template ()) = 0> +#endif +// dummy for compile +uint64_t simd_read8_to_u64(UC const *) { + return 0; +} + +// credit @aqrit +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint32_t +parse_eight_digits_unrolled(uint64_t val) { + uint64_t const mask = 0x000000FF000000FF; + uint64_t const mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32) + uint64_t const mul2 = 0x0000271000000001; // 1 + (10000ULL << 32) + val -= 0x3030303030303030; + val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8; + val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32; + return uint32_t(val); +} + +// Call this if chars are definitely 8 digits. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint32_t +parse_eight_digits_unrolled(UC const *chars) noexcept { + if (cpp20_and_in_constexpr() || !has_simd_opt()) { + return parse_eight_digits_unrolled(read8_to_u64(chars)); // truncation okay + } + return parse_eight_digits_unrolled(simd_read8_to_u64(chars)); +} + +// credit @aqrit +fastfloat_really_inline constexpr bool +is_made_of_eight_digits_fast(uint64_t val) noexcept { + return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) & + 0x8080808080808080)); +} + +fastfloat_really_inline constexpr bool +is_made_of_four_digits_fast(uint32_t val) noexcept { + return !((((val + 0x46464646) | (val - 0x30303030)) & 0x80808080)); +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint32_t +parse_four_digits_unrolled(uint32_t val) noexcept { + val -= 0x30303030; + val = (val * 10) + (val >> 8); + return (((val & 0x00FF00FF) * 0x00640001) >> 16) & 0xFFFF; +} + +#ifdef FASTFLOAT_HAS_SIMD + +// Call this if chars might not be 8 digits. +// Using this style (instead of is_made_of_eight_digits_fast() then +// parse_eight_digits_unrolled()) ensures we don't load SIMD registers twice. +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool +simd_parse_if_eight_digits_unrolled(char16_t const *chars, + uint64_t &i) noexcept { + if (cpp20_and_in_constexpr()) { + return false; + } +#ifdef FASTFLOAT_SSE2 + FASTFLOAT_SIMD_DISABLE_WARNINGS + __m128i const data = + _mm_loadu_si128(reinterpret_cast<__m128i const *>(chars)); + + // (x - '0') <= 9 + // http://0x80.pl/articles/simd-parsing-int-sequences.html + __m128i const t0 = _mm_add_epi16(data, _mm_set1_epi16(32720)); + __m128i const t1 = _mm_cmpgt_epi16(t0, _mm_set1_epi16(-32759)); + + if (_mm_movemask_epi8(t1) == 0) { + i = i * 100000000 + parse_eight_digits_unrolled(simd_read8_to_u64(data)); + return true; + } else + return false; + FASTFLOAT_SIMD_RESTORE_WARNINGS +#elif defined(FASTFLOAT_NEON) + FASTFLOAT_SIMD_DISABLE_WARNINGS + uint16x8_t const data = vld1q_u16(reinterpret_cast(chars)); + + // (x - '0') <= 9 + // http://0x80.pl/articles/simd-parsing-int-sequences.html + uint16x8_t const t0 = vsubq_u16(data, vmovq_n_u16('0')); + uint16x8_t const mask = vcltq_u16(t0, vmovq_n_u16('9' - '0' + 1)); + + if (vminvq_u16(mask) == 0xFFFF) { + i = i * 100000000 + parse_eight_digits_unrolled(simd_read8_to_u64(data)); + return true; + } else + return false; + FASTFLOAT_SIMD_RESTORE_WARNINGS +#else + (void)chars; + (void)i; + return false; +#endif // FASTFLOAT_SSE2 +} + +#endif // FASTFLOAT_HAS_SIMD + +// MSVC SFINAE is broken pre-VS2017 +#if defined(_MSC_VER) && _MSC_VER <= 1900 +template +#else +template ()) = 0> +#endif +// dummy for compile +bool simd_parse_if_eight_digits_unrolled(UC const *, uint64_t &) { + return 0; +} + +template ::value) = 0> +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void +loop_parse_if_eight_digits(UC const *&p, UC const *const pend, uint64_t &i) { + if (!has_simd_opt()) { + return; + } + while ((std::distance(p, pend) >= 8) && + simd_parse_if_eight_digits_unrolled( + p, i)) { // in rare cases, this will overflow, but that's ok + p += 8; + } +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void +loop_parse_if_eight_digits(char const *&p, char const *const pend, + uint64_t &i) { + // optimizes better than parse_if_eight_digits_unrolled() for UC = char. + while ((std::distance(p, pend) >= 8) && + is_made_of_eight_digits_fast(read8_to_u64(p))) { + i = i * 100000000 + + parse_eight_digits_unrolled(read8_to_u64( + p)); // in rare cases, this will overflow, but that's ok + p += 8; + } +} + +enum class parse_error { + no_error, + // [JSON-only] The minus sign must be followed by an integer. + missing_integer_after_sign, + // A sign must be followed by an integer or dot. + missing_integer_or_dot_after_sign, + // [JSON-only] The integer part must not have leading zeros. + leading_zeros_in_integer_part, + // [JSON-only] The integer part must have at least one digit. + no_digits_in_integer_part, + // [JSON-only] If there is a decimal point, there must be digits in the + // fractional part. + no_digits_in_fractional_part, + // The mantissa must have at least one digit. + no_digits_in_mantissa, + // Scientific notation requires an exponential part. + missing_exponential_part, +}; + +template struct parsed_number_string_t { + int64_t exponent{0}; + uint64_t mantissa{0}; + UC const *lastmatch{nullptr}; + bool negative{false}; + bool valid{false}; + bool too_many_digits{false}; + // contains the range of the significant digits + span integer{}; // non-nullable + span fraction{}; // nullable + parse_error error{parse_error::no_error}; +}; + +using byte_span = span; +using parsed_number_string = parsed_number_string_t; + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 parsed_number_string_t +report_parse_error(UC const *p, parse_error error) { + parsed_number_string_t answer; + answer.valid = false; + answer.lastmatch = p; + answer.error = error; + return answer; +} + +// Assuming that you use no more than 19 digits, this will +// parse an ASCII string. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 parsed_number_string_t +parse_number_string(UC const *p, UC const *pend, + parse_options_t options) noexcept { + chars_format const fmt = detail::adjust_for_feature_macros(options.format); + UC const decimal_point = options.decimal_point; + + parsed_number_string_t answer; + answer.valid = false; + answer.too_many_digits = false; + // assume p < pend, so dereference without checks; + answer.negative = (*p == UC('-')); + // C++17 20.19.3.(7.1) explicitly forbids '+' sign here + if ((*p == UC('-')) || (uint64_t(fmt & chars_format::allow_leading_plus) && + !basic_json_fmt && *p == UC('+'))) { + ++p; + if (p == pend) { + return report_parse_error( + p, parse_error::missing_integer_or_dot_after_sign); + } + FASTFLOAT_IF_CONSTEXPR17(basic_json_fmt) { + if (!is_integer(*p)) { // a sign must be followed by an integer + return report_parse_error(p, + parse_error::missing_integer_after_sign); + } + } + else { + if (!is_integer(*p) && + (*p != + decimal_point)) { // a sign must be followed by an integer or the dot + return report_parse_error( + p, parse_error::missing_integer_or_dot_after_sign); + } + } + } + UC const *const start_digits = p; + + uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad) + + while ((p != pend) && is_integer(*p)) { + // a multiplication by 10 is cheaper than an arbitrary integer + // multiplication + i = 10 * i + + uint64_t(*p - + UC('0')); // might overflow, we will handle the overflow later + ++p; + } + UC const *const end_of_integer_part = p; + int64_t digit_count = int64_t(end_of_integer_part - start_digits); + answer.integer = span(start_digits, size_t(digit_count)); + FASTFLOAT_IF_CONSTEXPR17(basic_json_fmt) { + // at least 1 digit in integer part, without leading zeros + if (digit_count == 0) { + return report_parse_error(p, parse_error::no_digits_in_integer_part); + } + if ((start_digits[0] == UC('0') && digit_count > 1)) { + return report_parse_error(start_digits, + parse_error::leading_zeros_in_integer_part); + } + } + + int64_t exponent = 0; + bool const has_decimal_point = (p != pend) && (*p == decimal_point); + if (has_decimal_point) { + ++p; + UC const *before = p; + // can occur at most twice without overflowing, but let it occur more, since + // for integers with many digits, digit parsing is the primary bottleneck. + loop_parse_if_eight_digits(p, pend, i); + + while ((p != pend) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - UC('0')); + ++p; + i = i * 10 + digit; // in rare cases, this will overflow, but that's ok + } + exponent = before - p; + answer.fraction = span(before, size_t(p - before)); + digit_count -= exponent; + } + FASTFLOAT_IF_CONSTEXPR17(basic_json_fmt) { + // at least 1 digit in fractional part + if (has_decimal_point && exponent == 0) { + return report_parse_error(p, + parse_error::no_digits_in_fractional_part); + } + } + else if (digit_count == 0) { // we must have encountered at least one integer! + return report_parse_error(p, parse_error::no_digits_in_mantissa); + } + int64_t exp_number = 0; // explicit exponential part + if ((uint64_t(fmt & chars_format::scientific) && (p != pend) && + ((UC('e') == *p) || (UC('E') == *p))) || + (uint64_t(fmt & detail::basic_fortran_fmt) && (p != pend) && + ((UC('+') == *p) || (UC('-') == *p) || (UC('d') == *p) || + (UC('D') == *p)))) { + UC const *location_of_e = p; + if ((UC('e') == *p) || (UC('E') == *p) || (UC('d') == *p) || + (UC('D') == *p)) { + ++p; + } + bool neg_exp = false; + if ((p != pend) && (UC('-') == *p)) { + neg_exp = true; + ++p; + } else if ((p != pend) && + (UC('+') == + *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1) + ++p; + } + if ((p == pend) || !is_integer(*p)) { + if (!uint64_t(fmt & chars_format::fixed)) { + // The exponential part is invalid for scientific notation, so it must + // be a trailing token for fixed notation. However, fixed notation is + // disabled, so report a scientific notation error. + return report_parse_error(p, parse_error::missing_exponential_part); + } + // Otherwise, we will be ignoring the 'e'. + p = location_of_e; + } else { + while ((p != pend) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - UC('0')); + if (exp_number < 0x10000000) { + exp_number = 10 * exp_number + digit; + } + ++p; + } + if (neg_exp) { + exp_number = -exp_number; + } + exponent += exp_number; + } + } else { + // If it scientific and not fixed, we have to bail out. + if (uint64_t(fmt & chars_format::scientific) && + !uint64_t(fmt & chars_format::fixed)) { + return report_parse_error(p, parse_error::missing_exponential_part); + } + } + answer.lastmatch = p; + answer.valid = true; + + // If we frequently had to deal with long strings of digits, + // we could extend our code by using a 128-bit integer instead + // of a 64-bit integer. However, this is uncommon. + // + // We can deal with up to 19 digits. + if (digit_count > 19) { // this is uncommon + // It is possible that the integer had an overflow. + // We have to handle the case where we have 0.0000somenumber. + // We need to be mindful of the case where we only have zeroes... + // E.g., 0.000000000...000. + UC const *start = start_digits; + while ((start != pend) && (*start == UC('0') || *start == decimal_point)) { + if (*start == UC('0')) { + digit_count--; + } + start++; + } + + if (digit_count > 19) { + answer.too_many_digits = true; + // Let us start again, this time, avoiding overflows. + // We don't need to call if is_integer, since we use the + // pre-tokenized spans from above. + i = 0; + p = answer.integer.ptr; + UC const *int_end = p + answer.integer.len(); + uint64_t const minimal_nineteen_digit_integer{1000000000000000000}; + while ((i < minimal_nineteen_digit_integer) && (p != int_end)) { + i = i * 10 + uint64_t(*p - UC('0')); + ++p; + } + if (i >= minimal_nineteen_digit_integer) { // We have a big integer + exponent = end_of_integer_part - p + exp_number; + } else { // We have a value with a fractional component. + p = answer.fraction.ptr; + UC const *frac_end = p + answer.fraction.len(); + while ((i < minimal_nineteen_digit_integer) && (p != frac_end)) { + i = i * 10 + uint64_t(*p - UC('0')); + ++p; + } + exponent = answer.fraction.ptr - p + exp_number; + } + // We have now corrected both exponent and i, to a truncated value + } + } + answer.exponent = exponent; + answer.mantissa = i; + return answer; +} + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 from_chars_result_t +parse_int_string(UC const *p, UC const *pend, T &value, + parse_options_t options) { + chars_format const fmt = detail::adjust_for_feature_macros(options.format); + int const base = options.base; + + from_chars_result_t answer; + + UC const *const first = p; + + bool const negative = (*p == UC('-')); +#ifdef FASTFLOAT_VISUAL_STUDIO +#pragma warning(push) +#pragma warning(disable : 4127) +#endif + if (!std::is_signed::value && negative) { +#ifdef FASTFLOAT_VISUAL_STUDIO +#pragma warning(pop) +#endif + answer.ec = std::errc::invalid_argument; + answer.ptr = first; + return answer; + } + if ((*p == UC('-')) || + (uint64_t(fmt & chars_format::allow_leading_plus) && (*p == UC('+')))) { + ++p; + } + + UC const *const start_num = p; + + while (p != pend && *p == UC('0')) { + ++p; + } + + bool const has_leading_zeros = p > start_num; + + UC const *const start_digits = p; + + FASTFLOAT_IF_CONSTEXPR17((std::is_same::value)) { + if (base == 10) { + const size_t len = (size_t)(pend - p); + if (len == 0) { + if (has_leading_zeros) { + value = 0; + answer.ec = std::errc(); + answer.ptr = p; + } else { + answer.ec = std::errc::invalid_argument; + answer.ptr = first; + } + return answer; + } + + uint32_t digits; + +#if FASTFLOAT_HAS_IS_CONSTANT_EVALUATED && FASTFLOAT_HAS_BIT_CAST + if (std::is_constant_evaluated()) { + uint8_t str[4]{}; + for (size_t j = 0; j < 4 && j < len; ++j) { + str[j] = static_cast(p[j]); + } + digits = std::bit_cast(str); +#if FASTFLOAT_IS_BIG_ENDIAN + digits = byteswap_32(digits); +#endif + } +#else + if (false) { + } +#endif + else if (len >= 4) { + ::memcpy(&digits, p, 4); +#if FASTFLOAT_IS_BIG_ENDIAN + digits = byteswap_32(digits); +#endif + } else { + uint32_t b0 = static_cast(p[0]); + uint32_t b1 = (len > 1) ? static_cast(p[1]) : 0xFFu; + uint32_t b2 = (len > 2) ? static_cast(p[2]) : 0xFFu; + uint32_t b3 = 0xFFu; + digits = b0 | (b1 << 8) | (b2 << 16) | (b3 << 24); + } + + uint32_t magic = + ((digits + 0x46464646u) | (digits - 0x30303030u)) & 0x80808080u; + uint32_t tz = (uint32_t)countr_zero_32(magic); // 7, 15, 23, 31, or 32 + uint32_t nd = (tz == 32) ? 4 : (tz >> 3); + nd = (uint32_t)std::min((size_t)nd, len); + if (nd == 0) { + if (has_leading_zeros) { + value = 0; + answer.ec = std::errc(); + answer.ptr = p; + return answer; + } + answer.ec = std::errc::invalid_argument; + answer.ptr = first; + return answer; + } + if (nd > 3) { + const UC *q = p + nd; + size_t rem = len - nd; + while (rem) { + if (*q < UC('0') || *q > UC('9')) + break; + ++q; + --rem; + } + answer.ec = std::errc::result_out_of_range; + answer.ptr = q; + return answer; + } + + digits ^= 0x30303030u; + digits <<= ((4 - nd) * 8); + + uint32_t check = ((digits >> 24) & 0xff) | ((digits >> 8) & 0xff00) | + ((digits << 8) & 0xff0000); + if (check > 0x00020505) { + answer.ec = std::errc::result_out_of_range; + answer.ptr = p + nd; + return answer; + } + value = (uint8_t)((0x640a01 * digits) >> 24); + answer.ec = std::errc(); + answer.ptr = p + nd; + return answer; + } + } + + FASTFLOAT_IF_CONSTEXPR17((std::is_same::value)) { + if (base == 10) { + const size_t len = size_t(pend - p); + if (len == 0) { + if (has_leading_zeros) { + value = 0; + answer.ec = std::errc(); + answer.ptr = p; + } else { + answer.ec = std::errc::invalid_argument; + answer.ptr = first; + } + return answer; + } + + if (len >= 4) { + uint32_t digits = read4_to_u32(p); + if (is_made_of_four_digits_fast(digits)) { + uint32_t v = parse_four_digits_unrolled(digits); + if (len >= 5 && is_integer(p[4])) { + v = v * 10 + uint32_t(p[4] - '0'); + if (len >= 6 && is_integer(p[5])) { + answer.ec = std::errc::result_out_of_range; + const UC *q = p + 5; + while (q != pend && is_integer(*q)) { + q++; + } + answer.ptr = q; + return answer; + } + if (v > 65535) { + answer.ec = std::errc::result_out_of_range; + answer.ptr = p + 5; + return answer; + } + value = uint16_t(v); + answer.ec = std::errc(); + answer.ptr = p + 5; + return answer; + } + // 4 digits + value = uint16_t(v); + answer.ec = std::errc(); + answer.ptr = p + 4; + return answer; + } + } + } + } + + uint64_t i = 0; + if (base == 10) { + loop_parse_if_eight_digits(p, pend, i); // use SIMD if possible + } + while (p != pend) { + uint8_t digit = ch_to_digit(*p); + if (digit >= base) { + break; + } + i = uint64_t(base) * i + digit; // might overflow, check this later + p++; + } + + size_t digit_count = size_t(p - start_digits); + + if (digit_count == 0) { + if (has_leading_zeros) { + value = 0; + answer.ec = std::errc(); + answer.ptr = p; + } else { + answer.ec = std::errc::invalid_argument; + answer.ptr = first; + } + return answer; + } + + answer.ptr = p; + + // check u64 overflow + size_t max_digits = max_digits_u64(base); + if (digit_count > max_digits) { + answer.ec = std::errc::result_out_of_range; + return answer; + } + // this check can be eliminated for all other types, but they will all require + // a max_digits(base) equivalent + if (digit_count == max_digits && i < min_safe_u64(base)) { + answer.ec = std::errc::result_out_of_range; + return answer; + } + + // check other types overflow + if (!std::is_same::value) { + if (i > uint64_t(std::numeric_limits::max()) + uint64_t(negative)) { + answer.ec = std::errc::result_out_of_range; + return answer; + } + } + + if (negative) { +#ifdef FASTFLOAT_VISUAL_STUDIO +#pragma warning(push) +#pragma warning(disable : 4146) +#endif + // this weird workaround is required because: + // - converting unsigned to signed when its value is greater than signed max + // is UB pre-C++23. + // - reinterpret_casting (~i + 1) would work, but it is not constexpr + // this is always optimized into a neg instruction (note: T is an integer + // type) + value = T(-std::numeric_limits::max() - + T(i - uint64_t(std::numeric_limits::max()))); +#ifdef FASTFLOAT_VISUAL_STUDIO +#pragma warning(pop) +#endif + } else { + value = T(i); + } + + answer.ec = std::errc(); + return answer; +} + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_FAST_TABLE_H +#define FASTFLOAT_FAST_TABLE_H + +//included above: +//#include + +namespace fast_float { + +/** + * When mapping numbers from decimal to binary, + * we go from w * 10^q to m * 2^p but we have + * 10^q = 5^q * 2^q, so effectively + * we are trying to match + * w * 2^q * 5^q to m * 2^p. Thus the powers of two + * are not a concern since they can be represented + * exactly using the binary notation, only the powers of five + * affect the binary significand. + */ + +/** + * The smallest non-zero float (binary64) is 2^-1074. + * We take as input numbers of the form w x 10^q where w < 2^64. + * We have that w * 10^-343 < 2^(64-344) 5^-343 < 2^-1076. + * However, we have that + * (2^64-1) * 10^-342 = (2^64-1) * 2^-342 * 5^-342 > 2^-1074. + * Thus it is possible for a number of the form w * 10^-342 where + * w is a 64-bit value to be a non-zero floating-point number. + ********* + * Any number of form w * 10^309 where w>= 1 is going to be + * infinite in binary64 so we never need to worry about powers + * of 5 greater than 308. + */ +template struct powers_template { + + constexpr static int smallest_power_of_five = + binary_format::smallest_power_of_ten(); + constexpr static int largest_power_of_five = + binary_format::largest_power_of_ten(); + constexpr static int number_of_entries = + 2 * (largest_power_of_five - smallest_power_of_five + 1); + // Powers of five from 5^-342 all the way to 5^308 rounded toward one. + constexpr static uint64_t power_of_five_128[number_of_entries] = { + 0xeef453d6923bd65a, 0x113faa2906a13b3f, + 0x9558b4661b6565f8, 0x4ac7ca59a424c507, + 0xbaaee17fa23ebf76, 0x5d79bcf00d2df649, + 0xe95a99df8ace6f53, 0xf4d82c2c107973dc, + 0x91d8a02bb6c10594, 0x79071b9b8a4be869, + 0xb64ec836a47146f9, 0x9748e2826cdee284, + 0xe3e27a444d8d98b7, 0xfd1b1b2308169b25, + 0x8e6d8c6ab0787f72, 0xfe30f0f5e50e20f7, + 0xb208ef855c969f4f, 0xbdbd2d335e51a935, + 0xde8b2b66b3bc4723, 0xad2c788035e61382, + 0x8b16fb203055ac76, 0x4c3bcb5021afcc31, + 0xaddcb9e83c6b1793, 0xdf4abe242a1bbf3d, + 0xd953e8624b85dd78, 0xd71d6dad34a2af0d, + 0x87d4713d6f33aa6b, 0x8672648c40e5ad68, + 0xa9c98d8ccb009506, 0x680efdaf511f18c2, + 0xd43bf0effdc0ba48, 0x212bd1b2566def2, + 0x84a57695fe98746d, 0x14bb630f7604b57, + 0xa5ced43b7e3e9188, 0x419ea3bd35385e2d, + 0xcf42894a5dce35ea, 0x52064cac828675b9, + 0x818995ce7aa0e1b2, 0x7343efebd1940993, + 0xa1ebfb4219491a1f, 0x1014ebe6c5f90bf8, + 0xca66fa129f9b60a6, 0xd41a26e077774ef6, + 0xfd00b897478238d0, 0x8920b098955522b4, + 0x9e20735e8cb16382, 0x55b46e5f5d5535b0, + 0xc5a890362fddbc62, 0xeb2189f734aa831d, + 0xf712b443bbd52b7b, 0xa5e9ec7501d523e4, + 0x9a6bb0aa55653b2d, 0x47b233c92125366e, + 0xc1069cd4eabe89f8, 0x999ec0bb696e840a, + 0xf148440a256e2c76, 0xc00670ea43ca250d, + 0x96cd2a865764dbca, 0x380406926a5e5728, + 0xbc807527ed3e12bc, 0xc605083704f5ecf2, + 0xeba09271e88d976b, 0xf7864a44c633682e, + 0x93445b8731587ea3, 0x7ab3ee6afbe0211d, + 0xb8157268fdae9e4c, 0x5960ea05bad82964, + 0xe61acf033d1a45df, 0x6fb92487298e33bd, + 0x8fd0c16206306bab, 0xa5d3b6d479f8e056, + 0xb3c4f1ba87bc8696, 0x8f48a4899877186c, + 0xe0b62e2929aba83c, 0x331acdabfe94de87, + 0x8c71dcd9ba0b4925, 0x9ff0c08b7f1d0b14, + 0xaf8e5410288e1b6f, 0x7ecf0ae5ee44dd9, + 0xdb71e91432b1a24a, 0xc9e82cd9f69d6150, + 0x892731ac9faf056e, 0xbe311c083a225cd2, + 0xab70fe17c79ac6ca, 0x6dbd630a48aaf406, + 0xd64d3d9db981787d, 0x92cbbccdad5b108, + 0x85f0468293f0eb4e, 0x25bbf56008c58ea5, + 0xa76c582338ed2621, 0xaf2af2b80af6f24e, + 0xd1476e2c07286faa, 0x1af5af660db4aee1, + 0x82cca4db847945ca, 0x50d98d9fc890ed4d, + 0xa37fce126597973c, 0xe50ff107bab528a0, + 0xcc5fc196fefd7d0c, 0x1e53ed49a96272c8, + 0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7a, + 0x9faacf3df73609b1, 0x77b191618c54e9ac, + 0xc795830d75038c1d, 0xd59df5b9ef6a2417, + 0xf97ae3d0d2446f25, 0x4b0573286b44ad1d, + 0x9becce62836ac577, 0x4ee367f9430aec32, + 0xc2e801fb244576d5, 0x229c41f793cda73f, + 0xf3a20279ed56d48a, 0x6b43527578c1110f, + 0x9845418c345644d6, 0x830a13896b78aaa9, + 0xbe5691ef416bd60c, 0x23cc986bc656d553, + 0xedec366b11c6cb8f, 0x2cbfbe86b7ec8aa8, + 0x94b3a202eb1c3f39, 0x7bf7d71432f3d6a9, + 0xb9e08a83a5e34f07, 0xdaf5ccd93fb0cc53, + 0xe858ad248f5c22c9, 0xd1b3400f8f9cff68, + 0x91376c36d99995be, 0x23100809b9c21fa1, + 0xb58547448ffffb2d, 0xabd40a0c2832a78a, + 0xe2e69915b3fff9f9, 0x16c90c8f323f516c, + 0x8dd01fad907ffc3b, 0xae3da7d97f6792e3, + 0xb1442798f49ffb4a, 0x99cd11cfdf41779c, + 0xdd95317f31c7fa1d, 0x40405643d711d583, + 0x8a7d3eef7f1cfc52, 0x482835ea666b2572, + 0xad1c8eab5ee43b66, 0xda3243650005eecf, + 0xd863b256369d4a40, 0x90bed43e40076a82, + 0x873e4f75e2224e68, 0x5a7744a6e804a291, + 0xa90de3535aaae202, 0x711515d0a205cb36, + 0xd3515c2831559a83, 0xd5a5b44ca873e03, + 0x8412d9991ed58091, 0xe858790afe9486c2, + 0xa5178fff668ae0b6, 0x626e974dbe39a872, + 0xce5d73ff402d98e3, 0xfb0a3d212dc8128f, + 0x80fa687f881c7f8e, 0x7ce66634bc9d0b99, + 0xa139029f6a239f72, 0x1c1fffc1ebc44e80, + 0xc987434744ac874e, 0xa327ffb266b56220, + 0xfbe9141915d7a922, 0x4bf1ff9f0062baa8, + 0x9d71ac8fada6c9b5, 0x6f773fc3603db4a9, + 0xc4ce17b399107c22, 0xcb550fb4384d21d3, + 0xf6019da07f549b2b, 0x7e2a53a146606a48, + 0x99c102844f94e0fb, 0x2eda7444cbfc426d, + 0xc0314325637a1939, 0xfa911155fefb5308, + 0xf03d93eebc589f88, 0x793555ab7eba27ca, + 0x96267c7535b763b5, 0x4bc1558b2f3458de, + 0xbbb01b9283253ca2, 0x9eb1aaedfb016f16, + 0xea9c227723ee8bcb, 0x465e15a979c1cadc, + 0x92a1958a7675175f, 0xbfacd89ec191ec9, + 0xb749faed14125d36, 0xcef980ec671f667b, + 0xe51c79a85916f484, 0x82b7e12780e7401a, + 0x8f31cc0937ae58d2, 0xd1b2ecb8b0908810, + 0xb2fe3f0b8599ef07, 0x861fa7e6dcb4aa15, + 0xdfbdcece67006ac9, 0x67a791e093e1d49a, + 0x8bd6a141006042bd, 0xe0c8bb2c5c6d24e0, + 0xaecc49914078536d, 0x58fae9f773886e18, + 0xda7f5bf590966848, 0xaf39a475506a899e, + 0x888f99797a5e012d, 0x6d8406c952429603, + 0xaab37fd7d8f58178, 0xc8e5087ba6d33b83, + 0xd5605fcdcf32e1d6, 0xfb1e4a9a90880a64, + 0x855c3be0a17fcd26, 0x5cf2eea09a55067f, + 0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481e, + 0xd0601d8efc57b08b, 0xf13b94daf124da26, + 0x823c12795db6ce57, 0x76c53d08d6b70858, + 0xa2cb1717b52481ed, 0x54768c4b0c64ca6e, + 0xcb7ddcdda26da268, 0xa9942f5dcf7dfd09, + 0xfe5d54150b090b02, 0xd3f93b35435d7c4c, + 0x9efa548d26e5a6e1, 0xc47bc5014a1a6daf, + 0xc6b8e9b0709f109a, 0x359ab6419ca1091b, + 0xf867241c8cc6d4c0, 0xc30163d203c94b62, + 0x9b407691d7fc44f8, 0x79e0de63425dcf1d, + 0xc21094364dfb5636, 0x985915fc12f542e4, + 0xf294b943e17a2bc4, 0x3e6f5b7b17b2939d, + 0x979cf3ca6cec5b5a, 0xa705992ceecf9c42, + 0xbd8430bd08277231, 0x50c6ff782a838353, + 0xece53cec4a314ebd, 0xa4f8bf5635246428, + 0x940f4613ae5ed136, 0x871b7795e136be99, + 0xb913179899f68584, 0x28e2557b59846e3f, + 0xe757dd7ec07426e5, 0x331aeada2fe589cf, + 0x9096ea6f3848984f, 0x3ff0d2c85def7621, + 0xb4bca50b065abe63, 0xfed077a756b53a9, + 0xe1ebce4dc7f16dfb, 0xd3e8495912c62894, + 0x8d3360f09cf6e4bd, 0x64712dd7abbbd95c, + 0xb080392cc4349dec, 0xbd8d794d96aacfb3, + 0xdca04777f541c567, 0xecf0d7a0fc5583a0, + 0x89e42caaf9491b60, 0xf41686c49db57244, + 0xac5d37d5b79b6239, 0x311c2875c522ced5, + 0xd77485cb25823ac7, 0x7d633293366b828b, + 0x86a8d39ef77164bc, 0xae5dff9c02033197, + 0xa8530886b54dbdeb, 0xd9f57f830283fdfc, + 0xd267caa862a12d66, 0xd072df63c324fd7b, + 0x8380dea93da4bc60, 0x4247cb9e59f71e6d, + 0xa46116538d0deb78, 0x52d9be85f074e608, + 0xcd795be870516656, 0x67902e276c921f8b, + 0x806bd9714632dff6, 0xba1cd8a3db53b6, + 0xa086cfcd97bf97f3, 0x80e8a40eccd228a4, + 0xc8a883c0fdaf7df0, 0x6122cd128006b2cd, + 0xfad2a4b13d1b5d6c, 0x796b805720085f81, + 0x9cc3a6eec6311a63, 0xcbe3303674053bb0, + 0xc3f490aa77bd60fc, 0xbedbfc4411068a9c, + 0xf4f1b4d515acb93b, 0xee92fb5515482d44, + 0x991711052d8bf3c5, 0x751bdd152d4d1c4a, + 0xbf5cd54678eef0b6, 0xd262d45a78a0635d, + 0xef340a98172aace4, 0x86fb897116c87c34, + 0x9580869f0e7aac0e, 0xd45d35e6ae3d4da0, + 0xbae0a846d2195712, 0x8974836059cca109, + 0xe998d258869facd7, 0x2bd1a438703fc94b, + 0x91ff83775423cc06, 0x7b6306a34627ddcf, + 0xb67f6455292cbf08, 0x1a3bc84c17b1d542, + 0xe41f3d6a7377eeca, 0x20caba5f1d9e4a93, + 0x8e938662882af53e, 0x547eb47b7282ee9c, + 0xb23867fb2a35b28d, 0xe99e619a4f23aa43, + 0xdec681f9f4c31f31, 0x6405fa00e2ec94d4, + 0x8b3c113c38f9f37e, 0xde83bc408dd3dd04, + 0xae0b158b4738705e, 0x9624ab50b148d445, + 0xd98ddaee19068c76, 0x3badd624dd9b0957, + 0x87f8a8d4cfa417c9, 0xe54ca5d70a80e5d6, + 0xa9f6d30a038d1dbc, 0x5e9fcf4ccd211f4c, + 0xd47487cc8470652b, 0x7647c3200069671f, + 0x84c8d4dfd2c63f3b, 0x29ecd9f40041e073, + 0xa5fb0a17c777cf09, 0xf468107100525890, + 0xcf79cc9db955c2cc, 0x7182148d4066eeb4, + 0x81ac1fe293d599bf, 0xc6f14cd848405530, + 0xa21727db38cb002f, 0xb8ada00e5a506a7c, + 0xca9cf1d206fdc03b, 0xa6d90811f0e4851c, + 0xfd442e4688bd304a, 0x908f4a166d1da663, + 0x9e4a9cec15763e2e, 0x9a598e4e043287fe, + 0xc5dd44271ad3cdba, 0x40eff1e1853f29fd, + 0xf7549530e188c128, 0xd12bee59e68ef47c, + 0x9a94dd3e8cf578b9, 0x82bb74f8301958ce, + 0xc13a148e3032d6e7, 0xe36a52363c1faf01, + 0xf18899b1bc3f8ca1, 0xdc44e6c3cb279ac1, + 0x96f5600f15a7b7e5, 0x29ab103a5ef8c0b9, + 0xbcb2b812db11a5de, 0x7415d448f6b6f0e7, + 0xebdf661791d60f56, 0x111b495b3464ad21, + 0x936b9fcebb25c995, 0xcab10dd900beec34, + 0xb84687c269ef3bfb, 0x3d5d514f40eea742, + 0xe65829b3046b0afa, 0xcb4a5a3112a5112, + 0x8ff71a0fe2c2e6dc, 0x47f0e785eaba72ab, + 0xb3f4e093db73a093, 0x59ed216765690f56, + 0xe0f218b8d25088b8, 0x306869c13ec3532c, + 0x8c974f7383725573, 0x1e414218c73a13fb, + 0xafbd2350644eeacf, 0xe5d1929ef90898fa, + 0xdbac6c247d62a583, 0xdf45f746b74abf39, + 0x894bc396ce5da772, 0x6b8bba8c328eb783, + 0xab9eb47c81f5114f, 0x66ea92f3f326564, + 0xd686619ba27255a2, 0xc80a537b0efefebd, + 0x8613fd0145877585, 0xbd06742ce95f5f36, + 0xa798fc4196e952e7, 0x2c48113823b73704, + 0xd17f3b51fca3a7a0, 0xf75a15862ca504c5, + 0x82ef85133de648c4, 0x9a984d73dbe722fb, + 0xa3ab66580d5fdaf5, 0xc13e60d0d2e0ebba, + 0xcc963fee10b7d1b3, 0x318df905079926a8, + 0xffbbcfe994e5c61f, 0xfdf17746497f7052, + 0x9fd561f1fd0f9bd3, 0xfeb6ea8bedefa633, + 0xc7caba6e7c5382c8, 0xfe64a52ee96b8fc0, + 0xf9bd690a1b68637b, 0x3dfdce7aa3c673b0, + 0x9c1661a651213e2d, 0x6bea10ca65c084e, + 0xc31bfa0fe5698db8, 0x486e494fcff30a62, + 0xf3e2f893dec3f126, 0x5a89dba3c3efccfa, + 0x986ddb5c6b3a76b7, 0xf89629465a75e01c, + 0xbe89523386091465, 0xf6bbb397f1135823, + 0xee2ba6c0678b597f, 0x746aa07ded582e2c, + 0x94db483840b717ef, 0xa8c2a44eb4571cdc, + 0xba121a4650e4ddeb, 0x92f34d62616ce413, + 0xe896a0d7e51e1566, 0x77b020baf9c81d17, + 0x915e2486ef32cd60, 0xace1474dc1d122e, + 0xb5b5ada8aaff80b8, 0xd819992132456ba, + 0xe3231912d5bf60e6, 0x10e1fff697ed6c69, + 0x8df5efabc5979c8f, 0xca8d3ffa1ef463c1, + 0xb1736b96b6fd83b3, 0xbd308ff8a6b17cb2, + 0xddd0467c64bce4a0, 0xac7cb3f6d05ddbde, + 0x8aa22c0dbef60ee4, 0x6bcdf07a423aa96b, + 0xad4ab7112eb3929d, 0x86c16c98d2c953c6, + 0xd89d64d57a607744, 0xe871c7bf077ba8b7, + 0x87625f056c7c4a8b, 0x11471cd764ad4972, + 0xa93af6c6c79b5d2d, 0xd598e40d3dd89bcf, + 0xd389b47879823479, 0x4aff1d108d4ec2c3, + 0x843610cb4bf160cb, 0xcedf722a585139ba, + 0xa54394fe1eedb8fe, 0xc2974eb4ee658828, + 0xce947a3da6a9273e, 0x733d226229feea32, + 0x811ccc668829b887, 0x806357d5a3f525f, + 0xa163ff802a3426a8, 0xca07c2dcb0cf26f7, + 0xc9bcff6034c13052, 0xfc89b393dd02f0b5, + 0xfc2c3f3841f17c67, 0xbbac2078d443ace2, + 0x9d9ba7832936edc0, 0xd54b944b84aa4c0d, + 0xc5029163f384a931, 0xa9e795e65d4df11, + 0xf64335bcf065d37d, 0x4d4617b5ff4a16d5, + 0x99ea0196163fa42e, 0x504bced1bf8e4e45, + 0xc06481fb9bcf8d39, 0xe45ec2862f71e1d6, + 0xf07da27a82c37088, 0x5d767327bb4e5a4c, + 0x964e858c91ba2655, 0x3a6a07f8d510f86f, + 0xbbe226efb628afea, 0x890489f70a55368b, + 0xeadab0aba3b2dbe5, 0x2b45ac74ccea842e, + 0x92c8ae6b464fc96f, 0x3b0b8bc90012929d, + 0xb77ada0617e3bbcb, 0x9ce6ebb40173744, + 0xe55990879ddcaabd, 0xcc420a6a101d0515, + 0x8f57fa54c2a9eab6, 0x9fa946824a12232d, + 0xb32df8e9f3546564, 0x47939822dc96abf9, + 0xdff9772470297ebd, 0x59787e2b93bc56f7, + 0x8bfbea76c619ef36, 0x57eb4edb3c55b65a, + 0xaefae51477a06b03, 0xede622920b6b23f1, + 0xdab99e59958885c4, 0xe95fab368e45eced, + 0x88b402f7fd75539b, 0x11dbcb0218ebb414, + 0xaae103b5fcd2a881, 0xd652bdc29f26a119, + 0xd59944a37c0752a2, 0x4be76d3346f0495f, + 0x857fcae62d8493a5, 0x6f70a4400c562ddb, + 0xa6dfbd9fb8e5b88e, 0xcb4ccd500f6bb952, + 0xd097ad07a71f26b2, 0x7e2000a41346a7a7, + 0x825ecc24c873782f, 0x8ed400668c0c28c8, + 0xa2f67f2dfa90563b, 0x728900802f0f32fa, + 0xcbb41ef979346bca, 0x4f2b40a03ad2ffb9, + 0xfea126b7d78186bc, 0xe2f610c84987bfa8, + 0x9f24b832e6b0f436, 0xdd9ca7d2df4d7c9, + 0xc6ede63fa05d3143, 0x91503d1c79720dbb, + 0xf8a95fcf88747d94, 0x75a44c6397ce912a, + 0x9b69dbe1b548ce7c, 0xc986afbe3ee11aba, + 0xc24452da229b021b, 0xfbe85badce996168, + 0xf2d56790ab41c2a2, 0xfae27299423fb9c3, + 0x97c560ba6b0919a5, 0xdccd879fc967d41a, + 0xbdb6b8e905cb600f, 0x5400e987bbc1c920, + 0xed246723473e3813, 0x290123e9aab23b68, + 0x9436c0760c86e30b, 0xf9a0b6720aaf6521, + 0xb94470938fa89bce, 0xf808e40e8d5b3e69, + 0xe7958cb87392c2c2, 0xb60b1d1230b20e04, + 0x90bd77f3483bb9b9, 0xb1c6f22b5e6f48c2, + 0xb4ecd5f01a4aa828, 0x1e38aeb6360b1af3, + 0xe2280b6c20dd5232, 0x25c6da63c38de1b0, + 0x8d590723948a535f, 0x579c487e5a38ad0e, + 0xb0af48ec79ace837, 0x2d835a9df0c6d851, + 0xdcdb1b2798182244, 0xf8e431456cf88e65, + 0x8a08f0f8bf0f156b, 0x1b8e9ecb641b58ff, + 0xac8b2d36eed2dac5, 0xe272467e3d222f3f, + 0xd7adf884aa879177, 0x5b0ed81dcc6abb0f, + 0x86ccbb52ea94baea, 0x98e947129fc2b4e9, + 0xa87fea27a539e9a5, 0x3f2398d747b36224, + 0xd29fe4b18e88640e, 0x8eec7f0d19a03aad, + 0x83a3eeeef9153e89, 0x1953cf68300424ac, + 0xa48ceaaab75a8e2b, 0x5fa8c3423c052dd7, + 0xcdb02555653131b6, 0x3792f412cb06794d, + 0x808e17555f3ebf11, 0xe2bbd88bbee40bd0, + 0xa0b19d2ab70e6ed6, 0x5b6aceaeae9d0ec4, + 0xc8de047564d20a8b, 0xf245825a5a445275, + 0xfb158592be068d2e, 0xeed6e2f0f0d56712, + 0x9ced737bb6c4183d, 0x55464dd69685606b, + 0xc428d05aa4751e4c, 0xaa97e14c3c26b886, + 0xf53304714d9265df, 0xd53dd99f4b3066a8, + 0x993fe2c6d07b7fab, 0xe546a8038efe4029, + 0xbf8fdb78849a5f96, 0xde98520472bdd033, + 0xef73d256a5c0f77c, 0x963e66858f6d4440, + 0x95a8637627989aad, 0xdde7001379a44aa8, + 0xbb127c53b17ec159, 0x5560c018580d5d52, + 0xe9d71b689dde71af, 0xaab8f01e6e10b4a6, + 0x9226712162ab070d, 0xcab3961304ca70e8, + 0xb6b00d69bb55c8d1, 0x3d607b97c5fd0d22, + 0xe45c10c42a2b3b05, 0x8cb89a7db77c506a, + 0x8eb98a7a9a5b04e3, 0x77f3608e92adb242, + 0xb267ed1940f1c61c, 0x55f038b237591ed3, + 0xdf01e85f912e37a3, 0x6b6c46dec52f6688, + 0x8b61313bbabce2c6, 0x2323ac4b3b3da015, + 0xae397d8aa96c1b77, 0xabec975e0a0d081a, + 0xd9c7dced53c72255, 0x96e7bd358c904a21, + 0x881cea14545c7575, 0x7e50d64177da2e54, + 0xaa242499697392d2, 0xdde50bd1d5d0b9e9, + 0xd4ad2dbfc3d07787, 0x955e4ec64b44e864, + 0x84ec3c97da624ab4, 0xbd5af13bef0b113e, + 0xa6274bbdd0fadd61, 0xecb1ad8aeacdd58e, + 0xcfb11ead453994ba, 0x67de18eda5814af2, + 0x81ceb32c4b43fcf4, 0x80eacf948770ced7, + 0xa2425ff75e14fc31, 0xa1258379a94d028d, + 0xcad2f7f5359a3b3e, 0x96ee45813a04330, + 0xfd87b5f28300ca0d, 0x8bca9d6e188853fc, + 0x9e74d1b791e07e48, 0x775ea264cf55347e, + 0xc612062576589dda, 0x95364afe032a819e, + 0xf79687aed3eec551, 0x3a83ddbd83f52205, + 0x9abe14cd44753b52, 0xc4926a9672793543, + 0xc16d9a0095928a27, 0x75b7053c0f178294, + 0xf1c90080baf72cb1, 0x5324c68b12dd6339, + 0x971da05074da7bee, 0xd3f6fc16ebca5e04, + 0xbce5086492111aea, 0x88f4bb1ca6bcf585, + 0xec1e4a7db69561a5, 0x2b31e9e3d06c32e6, + 0x9392ee8e921d5d07, 0x3aff322e62439fd0, + 0xb877aa3236a4b449, 0x9befeb9fad487c3, + 0xe69594bec44de15b, 0x4c2ebe687989a9b4, + 0x901d7cf73ab0acd9, 0xf9d37014bf60a11, + 0xb424dc35095cd80f, 0x538484c19ef38c95, + 0xe12e13424bb40e13, 0x2865a5f206b06fba, + 0x8cbccc096f5088cb, 0xf93f87b7442e45d4, + 0xafebff0bcb24aafe, 0xf78f69a51539d749, + 0xdbe6fecebdedd5be, 0xb573440e5a884d1c, + 0x89705f4136b4a597, 0x31680a88f8953031, + 0xabcc77118461cefc, 0xfdc20d2b36ba7c3e, + 0xd6bf94d5e57a42bc, 0x3d32907604691b4d, + 0x8637bd05af6c69b5, 0xa63f9a49c2c1b110, + 0xa7c5ac471b478423, 0xfcf80dc33721d54, + 0xd1b71758e219652b, 0xd3c36113404ea4a9, + 0x83126e978d4fdf3b, 0x645a1cac083126ea, + 0xa3d70a3d70a3d70a, 0x3d70a3d70a3d70a4, + 0xcccccccccccccccc, 0xcccccccccccccccd, + 0x8000000000000000, 0x0, + 0xa000000000000000, 0x0, + 0xc800000000000000, 0x0, + 0xfa00000000000000, 0x0, + 0x9c40000000000000, 0x0, + 0xc350000000000000, 0x0, + 0xf424000000000000, 0x0, + 0x9896800000000000, 0x0, + 0xbebc200000000000, 0x0, + 0xee6b280000000000, 0x0, + 0x9502f90000000000, 0x0, + 0xba43b74000000000, 0x0, + 0xe8d4a51000000000, 0x0, + 0x9184e72a00000000, 0x0, + 0xb5e620f480000000, 0x0, + 0xe35fa931a0000000, 0x0, + 0x8e1bc9bf04000000, 0x0, + 0xb1a2bc2ec5000000, 0x0, + 0xde0b6b3a76400000, 0x0, + 0x8ac7230489e80000, 0x0, + 0xad78ebc5ac620000, 0x0, + 0xd8d726b7177a8000, 0x0, + 0x878678326eac9000, 0x0, + 0xa968163f0a57b400, 0x0, + 0xd3c21bcecceda100, 0x0, + 0x84595161401484a0, 0x0, + 0xa56fa5b99019a5c8, 0x0, + 0xcecb8f27f4200f3a, 0x0, + 0x813f3978f8940984, 0x4000000000000000, + 0xa18f07d736b90be5, 0x5000000000000000, + 0xc9f2c9cd04674ede, 0xa400000000000000, + 0xfc6f7c4045812296, 0x4d00000000000000, + 0x9dc5ada82b70b59d, 0xf020000000000000, + 0xc5371912364ce305, 0x6c28000000000000, + 0xf684df56c3e01bc6, 0xc732000000000000, + 0x9a130b963a6c115c, 0x3c7f400000000000, + 0xc097ce7bc90715b3, 0x4b9f100000000000, + 0xf0bdc21abb48db20, 0x1e86d40000000000, + 0x96769950b50d88f4, 0x1314448000000000, + 0xbc143fa4e250eb31, 0x17d955a000000000, + 0xeb194f8e1ae525fd, 0x5dcfab0800000000, + 0x92efd1b8d0cf37be, 0x5aa1cae500000000, + 0xb7abc627050305ad, 0xf14a3d9e40000000, + 0xe596b7b0c643c719, 0x6d9ccd05d0000000, + 0x8f7e32ce7bea5c6f, 0xe4820023a2000000, + 0xb35dbf821ae4f38b, 0xdda2802c8a800000, + 0xe0352f62a19e306e, 0xd50b2037ad200000, + 0x8c213d9da502de45, 0x4526f422cc340000, + 0xaf298d050e4395d6, 0x9670b12b7f410000, + 0xdaf3f04651d47b4c, 0x3c0cdd765f114000, + 0x88d8762bf324cd0f, 0xa5880a69fb6ac800, + 0xab0e93b6efee0053, 0x8eea0d047a457a00, + 0xd5d238a4abe98068, 0x72a4904598d6d880, + 0x85a36366eb71f041, 0x47a6da2b7f864750, + 0xa70c3c40a64e6c51, 0x999090b65f67d924, + 0xd0cf4b50cfe20765, 0xfff4b4e3f741cf6d, + 0x82818f1281ed449f, 0xbff8f10e7a8921a4, + 0xa321f2d7226895c7, 0xaff72d52192b6a0d, + 0xcbea6f8ceb02bb39, 0x9bf4f8a69f764490, + 0xfee50b7025c36a08, 0x2f236d04753d5b4, + 0x9f4f2726179a2245, 0x1d762422c946590, + 0xc722f0ef9d80aad6, 0x424d3ad2b7b97ef5, + 0xf8ebad2b84e0d58b, 0xd2e0898765a7deb2, + 0x9b934c3b330c8577, 0x63cc55f49f88eb2f, + 0xc2781f49ffcfa6d5, 0x3cbf6b71c76b25fb, + 0xf316271c7fc3908a, 0x8bef464e3945ef7a, + 0x97edd871cfda3a56, 0x97758bf0e3cbb5ac, + 0xbde94e8e43d0c8ec, 0x3d52eeed1cbea317, + 0xed63a231d4c4fb27, 0x4ca7aaa863ee4bdd, + 0x945e455f24fb1cf8, 0x8fe8caa93e74ef6a, + 0xb975d6b6ee39e436, 0xb3e2fd538e122b44, + 0xe7d34c64a9c85d44, 0x60dbbca87196b616, + 0x90e40fbeea1d3a4a, 0xbc8955e946fe31cd, + 0xb51d13aea4a488dd, 0x6babab6398bdbe41, + 0xe264589a4dcdab14, 0xc696963c7eed2dd1, + 0x8d7eb76070a08aec, 0xfc1e1de5cf543ca2, + 0xb0de65388cc8ada8, 0x3b25a55f43294bcb, + 0xdd15fe86affad912, 0x49ef0eb713f39ebe, + 0x8a2dbf142dfcc7ab, 0x6e3569326c784337, + 0xacb92ed9397bf996, 0x49c2c37f07965404, + 0xd7e77a8f87daf7fb, 0xdc33745ec97be906, + 0x86f0ac99b4e8dafd, 0x69a028bb3ded71a3, + 0xa8acd7c0222311bc, 0xc40832ea0d68ce0c, + 0xd2d80db02aabd62b, 0xf50a3fa490c30190, + 0x83c7088e1aab65db, 0x792667c6da79e0fa, + 0xa4b8cab1a1563f52, 0x577001b891185938, + 0xcde6fd5e09abcf26, 0xed4c0226b55e6f86, + 0x80b05e5ac60b6178, 0x544f8158315b05b4, + 0xa0dc75f1778e39d6, 0x696361ae3db1c721, + 0xc913936dd571c84c, 0x3bc3a19cd1e38e9, + 0xfb5878494ace3a5f, 0x4ab48a04065c723, + 0x9d174b2dcec0e47b, 0x62eb0d64283f9c76, + 0xc45d1df942711d9a, 0x3ba5d0bd324f8394, + 0xf5746577930d6500, 0xca8f44ec7ee36479, + 0x9968bf6abbe85f20, 0x7e998b13cf4e1ecb, + 0xbfc2ef456ae276e8, 0x9e3fedd8c321a67e, + 0xefb3ab16c59b14a2, 0xc5cfe94ef3ea101e, + 0x95d04aee3b80ece5, 0xbba1f1d158724a12, + 0xbb445da9ca61281f, 0x2a8a6e45ae8edc97, + 0xea1575143cf97226, 0xf52d09d71a3293bd, + 0x924d692ca61be758, 0x593c2626705f9c56, + 0xb6e0c377cfa2e12e, 0x6f8b2fb00c77836c, + 0xe498f455c38b997a, 0xb6dfb9c0f956447, + 0x8edf98b59a373fec, 0x4724bd4189bd5eac, + 0xb2977ee300c50fe7, 0x58edec91ec2cb657, + 0xdf3d5e9bc0f653e1, 0x2f2967b66737e3ed, + 0x8b865b215899f46c, 0xbd79e0d20082ee74, + 0xae67f1e9aec07187, 0xecd8590680a3aa11, + 0xda01ee641a708de9, 0xe80e6f4820cc9495, + 0x884134fe908658b2, 0x3109058d147fdcdd, + 0xaa51823e34a7eede, 0xbd4b46f0599fd415, + 0xd4e5e2cdc1d1ea96, 0x6c9e18ac7007c91a, + 0x850fadc09923329e, 0x3e2cf6bc604ddb0, + 0xa6539930bf6bff45, 0x84db8346b786151c, + 0xcfe87f7cef46ff16, 0xe612641865679a63, + 0x81f14fae158c5f6e, 0x4fcb7e8f3f60c07e, + 0xa26da3999aef7749, 0xe3be5e330f38f09d, + 0xcb090c8001ab551c, 0x5cadf5bfd3072cc5, + 0xfdcb4fa002162a63, 0x73d9732fc7c8f7f6, + 0x9e9f11c4014dda7e, 0x2867e7fddcdd9afa, + 0xc646d63501a1511d, 0xb281e1fd541501b8, + 0xf7d88bc24209a565, 0x1f225a7ca91a4226, + 0x9ae757596946075f, 0x3375788de9b06958, + 0xc1a12d2fc3978937, 0x52d6b1641c83ae, + 0xf209787bb47d6b84, 0xc0678c5dbd23a49a, + 0x9745eb4d50ce6332, 0xf840b7ba963646e0, + 0xbd176620a501fbff, 0xb650e5a93bc3d898, + 0xec5d3fa8ce427aff, 0xa3e51f138ab4cebe, + 0x93ba47c980e98cdf, 0xc66f336c36b10137, + 0xb8a8d9bbe123f017, 0xb80b0047445d4184, + 0xe6d3102ad96cec1d, 0xa60dc059157491e5, + 0x9043ea1ac7e41392, 0x87c89837ad68db2f, + 0xb454e4a179dd1877, 0x29babe4598c311fb, + 0xe16a1dc9d8545e94, 0xf4296dd6fef3d67a, + 0x8ce2529e2734bb1d, 0x1899e4a65f58660c, + 0xb01ae745b101e9e4, 0x5ec05dcff72e7f8f, + 0xdc21a1171d42645d, 0x76707543f4fa1f73, + 0x899504ae72497eba, 0x6a06494a791c53a8, + 0xabfa45da0edbde69, 0x487db9d17636892, + 0xd6f8d7509292d603, 0x45a9d2845d3c42b6, + 0x865b86925b9bc5c2, 0xb8a2392ba45a9b2, + 0xa7f26836f282b732, 0x8e6cac7768d7141e, + 0xd1ef0244af2364ff, 0x3207d795430cd926, + 0x8335616aed761f1f, 0x7f44e6bd49e807b8, + 0xa402b9c5a8d3a6e7, 0x5f16206c9c6209a6, + 0xcd036837130890a1, 0x36dba887c37a8c0f, + 0x802221226be55a64, 0xc2494954da2c9789, + 0xa02aa96b06deb0fd, 0xf2db9baa10b7bd6c, + 0xc83553c5c8965d3d, 0x6f92829494e5acc7, + 0xfa42a8b73abbf48c, 0xcb772339ba1f17f9, + 0x9c69a97284b578d7, 0xff2a760414536efb, + 0xc38413cf25e2d70d, 0xfef5138519684aba, + 0xf46518c2ef5b8cd1, 0x7eb258665fc25d69, + 0x98bf2f79d5993802, 0xef2f773ffbd97a61, + 0xbeeefb584aff8603, 0xaafb550ffacfd8fa, + 0xeeaaba2e5dbf6784, 0x95ba2a53f983cf38, + 0x952ab45cfa97a0b2, 0xdd945a747bf26183, + 0xba756174393d88df, 0x94f971119aeef9e4, + 0xe912b9d1478ceb17, 0x7a37cd5601aab85d, + 0x91abb422ccb812ee, 0xac62e055c10ab33a, + 0xb616a12b7fe617aa, 0x577b986b314d6009, + 0xe39c49765fdf9d94, 0xed5a7e85fda0b80b, + 0x8e41ade9fbebc27d, 0x14588f13be847307, + 0xb1d219647ae6b31c, 0x596eb2d8ae258fc8, + 0xde469fbd99a05fe3, 0x6fca5f8ed9aef3bb, + 0x8aec23d680043bee, 0x25de7bb9480d5854, + 0xada72ccc20054ae9, 0xaf561aa79a10ae6a, + 0xd910f7ff28069da4, 0x1b2ba1518094da04, + 0x87aa9aff79042286, 0x90fb44d2f05d0842, + 0xa99541bf57452b28, 0x353a1607ac744a53, + 0xd3fa922f2d1675f2, 0x42889b8997915ce8, + 0x847c9b5d7c2e09b7, 0x69956135febada11, + 0xa59bc234db398c25, 0x43fab9837e699095, + 0xcf02b2c21207ef2e, 0x94f967e45e03f4bb, + 0x8161afb94b44f57d, 0x1d1be0eebac278f5, + 0xa1ba1ba79e1632dc, 0x6462d92a69731732, + 0xca28a291859bbf93, 0x7d7b8f7503cfdcfe, + 0xfcb2cb35e702af78, 0x5cda735244c3d43e, + 0x9defbf01b061adab, 0x3a0888136afa64a7, + 0xc56baec21c7a1916, 0x88aaa1845b8fdd0, + 0xf6c69a72a3989f5b, 0x8aad549e57273d45, + 0x9a3c2087a63f6399, 0x36ac54e2f678864b, + 0xc0cb28a98fcf3c7f, 0x84576a1bb416a7dd, + 0xf0fdf2d3f3c30b9f, 0x656d44a2a11c51d5, + 0x969eb7c47859e743, 0x9f644ae5a4b1b325, + 0xbc4665b596706114, 0x873d5d9f0dde1fee, + 0xeb57ff22fc0c7959, 0xa90cb506d155a7ea, + 0x9316ff75dd87cbd8, 0x9a7f12442d588f2, + 0xb7dcbf5354e9bece, 0xc11ed6d538aeb2f, + 0xe5d3ef282a242e81, 0x8f1668c8a86da5fa, + 0x8fa475791a569d10, 0xf96e017d694487bc, + 0xb38d92d760ec4455, 0x37c981dcc395a9ac, + 0xe070f78d3927556a, 0x85bbe253f47b1417, + 0x8c469ab843b89562, 0x93956d7478ccec8e, + 0xaf58416654a6babb, 0x387ac8d1970027b2, + 0xdb2e51bfe9d0696a, 0x6997b05fcc0319e, + 0x88fcf317f22241e2, 0x441fece3bdf81f03, + 0xab3c2fddeeaad25a, 0xd527e81cad7626c3, + 0xd60b3bd56a5586f1, 0x8a71e223d8d3b074, + 0x85c7056562757456, 0xf6872d5667844e49, + 0xa738c6bebb12d16c, 0xb428f8ac016561db, + 0xd106f86e69d785c7, 0xe13336d701beba52, + 0x82a45b450226b39c, 0xecc0024661173473, + 0xa34d721642b06084, 0x27f002d7f95d0190, + 0xcc20ce9bd35c78a5, 0x31ec038df7b441f4, + 0xff290242c83396ce, 0x7e67047175a15271, + 0x9f79a169bd203e41, 0xf0062c6e984d386, + 0xc75809c42c684dd1, 0x52c07b78a3e60868, + 0xf92e0c3537826145, 0xa7709a56ccdf8a82, + 0x9bbcc7a142b17ccb, 0x88a66076400bb691, + 0xc2abf989935ddbfe, 0x6acff893d00ea435, + 0xf356f7ebf83552fe, 0x583f6b8c4124d43, + 0x98165af37b2153de, 0xc3727a337a8b704a, + 0xbe1bf1b059e9a8d6, 0x744f18c0592e4c5c, + 0xeda2ee1c7064130c, 0x1162def06f79df73, + 0x9485d4d1c63e8be7, 0x8addcb5645ac2ba8, + 0xb9a74a0637ce2ee1, 0x6d953e2bd7173692, + 0xe8111c87c5c1ba99, 0xc8fa8db6ccdd0437, + 0x910ab1d4db9914a0, 0x1d9c9892400a22a2, + 0xb54d5e4a127f59c8, 0x2503beb6d00cab4b, + 0xe2a0b5dc971f303a, 0x2e44ae64840fd61d, + 0x8da471a9de737e24, 0x5ceaecfed289e5d2, + 0xb10d8e1456105dad, 0x7425a83e872c5f47, + 0xdd50f1996b947518, 0xd12f124e28f77719, + 0x8a5296ffe33cc92f, 0x82bd6b70d99aaa6f, + 0xace73cbfdc0bfb7b, 0x636cc64d1001550b, + 0xd8210befd30efa5a, 0x3c47f7e05401aa4e, + 0x8714a775e3e95c78, 0x65acfaec34810a71, + 0xa8d9d1535ce3b396, 0x7f1839a741a14d0d, + 0xd31045a8341ca07c, 0x1ede48111209a050, + 0x83ea2b892091e44d, 0x934aed0aab460432, + 0xa4e4b66b68b65d60, 0xf81da84d5617853f, + 0xce1de40642e3f4b9, 0x36251260ab9d668e, + 0x80d2ae83e9ce78f3, 0xc1d72b7c6b426019, + 0xa1075a24e4421730, 0xb24cf65b8612f81f, + 0xc94930ae1d529cfc, 0xdee033f26797b627, + 0xfb9b7cd9a4a7443c, 0x169840ef017da3b1, + 0x9d412e0806e88aa5, 0x8e1f289560ee864e, + 0xc491798a08a2ad4e, 0xf1a6f2bab92a27e2, + 0xf5b5d7ec8acb58a2, 0xae10af696774b1db, + 0x9991a6f3d6bf1765, 0xacca6da1e0a8ef29, + 0xbff610b0cc6edd3f, 0x17fd090a58d32af3, + 0xeff394dcff8a948e, 0xddfc4b4cef07f5b0, + 0x95f83d0a1fb69cd9, 0x4abdaf101564f98e, + 0xbb764c4ca7a4440f, 0x9d6d1ad41abe37f1, + 0xea53df5fd18d5513, 0x84c86189216dc5ed, + 0x92746b9be2f8552c, 0x32fd3cf5b4e49bb4, + 0xb7118682dbb66a77, 0x3fbc8c33221dc2a1, + 0xe4d5e82392a40515, 0xfabaf3feaa5334a, + 0x8f05b1163ba6832d, 0x29cb4d87f2a7400e, + 0xb2c71d5bca9023f8, 0x743e20e9ef511012, + 0xdf78e4b2bd342cf6, 0x914da9246b255416, + 0x8bab8eefb6409c1a, 0x1ad089b6c2f7548e, + 0xae9672aba3d0c320, 0xa184ac2473b529b1, + 0xda3c0f568cc4f3e8, 0xc9e5d72d90a2741e, + 0x8865899617fb1871, 0x7e2fa67c7a658892, + 0xaa7eebfb9df9de8d, 0xddbb901b98feeab7, + 0xd51ea6fa85785631, 0x552a74227f3ea565, + 0x8533285c936b35de, 0xd53a88958f87275f, + 0xa67ff273b8460356, 0x8a892abaf368f137, + 0xd01fef10a657842c, 0x2d2b7569b0432d85, + 0x8213f56a67f6b29b, 0x9c3b29620e29fc73, + 0xa298f2c501f45f42, 0x8349f3ba91b47b8f, + 0xcb3f2f7642717713, 0x241c70a936219a73, + 0xfe0efb53d30dd4d7, 0xed238cd383aa0110, + 0x9ec95d1463e8a506, 0xf4363804324a40aa, + 0xc67bb4597ce2ce48, 0xb143c6053edcd0d5, + 0xf81aa16fdc1b81da, 0xdd94b7868e94050a, + 0x9b10a4e5e9913128, 0xca7cf2b4191c8326, + 0xc1d4ce1f63f57d72, 0xfd1c2f611f63a3f0, + 0xf24a01a73cf2dccf, 0xbc633b39673c8cec, + 0x976e41088617ca01, 0xd5be0503e085d813, + 0xbd49d14aa79dbc82, 0x4b2d8644d8a74e18, + 0xec9c459d51852ba2, 0xddf8e7d60ed1219e, + 0x93e1ab8252f33b45, 0xcabb90e5c942b503, + 0xb8da1662e7b00a17, 0x3d6a751f3b936243, + 0xe7109bfba19c0c9d, 0xcc512670a783ad4, + 0x906a617d450187e2, 0x27fb2b80668b24c5, + 0xb484f9dc9641e9da, 0xb1f9f660802dedf6, + 0xe1a63853bbd26451, 0x5e7873f8a0396973, + 0x8d07e33455637eb2, 0xdb0b487b6423e1e8, + 0xb049dc016abc5e5f, 0x91ce1a9a3d2cda62, + 0xdc5c5301c56b75f7, 0x7641a140cc7810fb, + 0x89b9b3e11b6329ba, 0xa9e904c87fcb0a9d, + 0xac2820d9623bf429, 0x546345fa9fbdcd44, + 0xd732290fbacaf133, 0xa97c177947ad4095, + 0x867f59a9d4bed6c0, 0x49ed8eabcccc485d, + 0xa81f301449ee8c70, 0x5c68f256bfff5a74, + 0xd226fc195c6a2f8c, 0x73832eec6fff3111, + 0x83585d8fd9c25db7, 0xc831fd53c5ff7eab, + 0xa42e74f3d032f525, 0xba3e7ca8b77f5e55, + 0xcd3a1230c43fb26f, 0x28ce1bd2e55f35eb, + 0x80444b5e7aa7cf85, 0x7980d163cf5b81b3, + 0xa0555e361951c366, 0xd7e105bcc332621f, + 0xc86ab5c39fa63440, 0x8dd9472bf3fefaa7, + 0xfa856334878fc150, 0xb14f98f6f0feb951, + 0x9c935e00d4b9d8d2, 0x6ed1bf9a569f33d3, + 0xc3b8358109e84f07, 0xa862f80ec4700c8, + 0xf4a642e14c6262c8, 0xcd27bb612758c0fa, + 0x98e7e9cccfbd7dbd, 0x8038d51cb897789c, + 0xbf21e44003acdd2c, 0xe0470a63e6bd56c3, + 0xeeea5d5004981478, 0x1858ccfce06cac74, + 0x95527a5202df0ccb, 0xf37801e0c43ebc8, + 0xbaa718e68396cffd, 0xd30560258f54e6ba, + 0xe950df20247c83fd, 0x47c6b82ef32a2069, + 0x91d28b7416cdd27e, 0x4cdc331d57fa5441, + 0xb6472e511c81471d, 0xe0133fe4adf8e952, + 0xe3d8f9e563a198e5, 0x58180fddd97723a6, + 0x8e679c2f5e44ff8f, 0x570f09eaa7ea7648, + }; +}; + +#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE + +template +constexpr uint64_t + powers_template::power_of_five_128[number_of_entries]; + +#endif + +using powers = powers_template<>; + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_DECIMAL_TO_BINARY_H +#define FASTFLOAT_DECIMAL_TO_BINARY_H + +//included above: +//#include +#include +#include +//included above: +//#include +#include +//included above: +//#include + +namespace fast_float { + +// This will compute or rather approximate w * 5**q and return a pair of 64-bit +// words approximating the result, with the "high" part corresponding to the +// most significant bits and the low part corresponding to the least significant +// bits. +// +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 value128 +compute_product_approximation(int64_t q, uint64_t w) { + int const index = 2 * int(q - powers::smallest_power_of_five); + // For small values of q, e.g., q in [0,27], the answer is always exact + // because The line value128 firstproduct = full_multiplication(w, + // power_of_five_128[index]); gives the exact answer. + value128 firstproduct = + full_multiplication(w, powers::power_of_five_128[index]); + static_assert((bit_precision >= 0) && (bit_precision <= 64), + " precision should be in (0,64]"); + constexpr uint64_t precision_mask = + (bit_precision < 64) ? (uint64_t(0xFFFFFFFFFFFFFFFF) >> bit_precision) + : uint64_t(0xFFFFFFFFFFFFFFFF); + if ((firstproduct.high & precision_mask) == + precision_mask) { // could further guard with (lower + w < lower) + // regarding the second product, we only need secondproduct.high, but our + // expectation is that the compiler will optimize this extra work away if + // needed. + value128 secondproduct = + full_multiplication(w, powers::power_of_five_128[index + 1]); + firstproduct.low += secondproduct.high; + if (secondproduct.high > firstproduct.low) { + firstproduct.high++; + } + } + return firstproduct; +} + +namespace detail { +/** + * For q in (0,350), we have that + * f = (((152170 + 65536) * q ) >> 16); + * is equal to + * floor(p) + q + * where + * p = log(5**q)/log(2) = q * log(5)/log(2) + * + * For negative values of q in (-400,0), we have that + * f = (((152170 + 65536) * q ) >> 16); + * is equal to + * -ceil(p) + q + * where + * p = log(5**-q)/log(2) = -q * log(5)/log(2) + */ +constexpr fastfloat_really_inline int32_t power(int32_t q) noexcept { + return (((152170 + 65536) * q) >> 16) + 63; +} +} // namespace detail + +// create an adjusted mantissa, biased by the invalid power2 +// for significant digits already multiplied by 10 ** q. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 adjusted_mantissa +compute_error_scaled(int64_t q, uint64_t w, int lz) noexcept { + int hilz = int(w >> 63) ^ 1; + adjusted_mantissa answer; + answer.mantissa = w << hilz; + int bias = binary::mantissa_explicit_bits() - binary::minimum_exponent(); + answer.power2 = int32_t(detail::power(int32_t(q)) + bias - hilz - lz - 62 + + invalid_am_bias); + return answer; +} + +// w * 10 ** q, without rounding the representation up. +// the power2 in the exponent will be adjusted by invalid_am_bias. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa +compute_error(int64_t q, uint64_t w) noexcept { + int lz = leading_zeroes(w); + w <<= lz; + value128 product = + compute_product_approximation(q, w); + return compute_error_scaled(q, product.high, lz); +} + +// Computers w * 10 ** q. +// The returned value should be a valid number that simply needs to be +// packed. However, in some very rare cases, the computation will fail. In such +// cases, we return an adjusted_mantissa with a negative power of 2: the caller +// should recompute in such cases. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa +compute_float(int64_t q, uint64_t w) noexcept { + adjusted_mantissa answer; + if ((w == 0) || (q < binary::smallest_power_of_ten())) { + answer.power2 = 0; + answer.mantissa = 0; + // result should be zero + return answer; + } + if (q > binary::largest_power_of_ten()) { + // we want to get infinity: + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + return answer; + } + // At this point in time q is in [powers::smallest_power_of_five, + // powers::largest_power_of_five]. + + // We want the most significant bit of i to be 1. Shift if needed. + int lz = leading_zeroes(w); + w <<= lz; + + // The required precision is binary::mantissa_explicit_bits() + 3 because + // 1. We need the implicit bit + // 2. We need an extra bit for rounding purposes + // 3. We might lose a bit due to the "upperbit" routine (result too small, + // requiring a shift) + + value128 product = + compute_product_approximation(q, w); + // The computed 'product' is always sufficient. + // Mathematical proof: + // Noble Mushtak and Daniel Lemire, Fast Number Parsing Without Fallback (to + // appear) See script/mushtak_lemire.py + + // The "compute_product_approximation" function can be slightly slower than a + // branchless approach: value128 product = compute_product(q, w); but in + // practice, we can win big with the compute_product_approximation if its + // additional branch is easily predicted. Which is best is data specific. + int upperbit = int(product.high >> 63); + int shift = upperbit + 64 - binary::mantissa_explicit_bits() - 3; + + answer.mantissa = product.high >> shift; + + answer.power2 = int32_t(detail::power(int32_t(q)) + upperbit - lz - + binary::minimum_exponent()); + if (answer.power2 <= 0) { // we have a subnormal? + // Here have that answer.power2 <= 0 so -answer.power2 >= 0 + if (-answer.power2 + 1 >= + 64) { // if we have more than 64 bits below the minimum exponent, you + // have a zero for sure. + answer.power2 = 0; + answer.mantissa = 0; + // result should be zero + return answer; + } + // next line is safe because -answer.power2 + 1 < 64 + answer.mantissa >>= -answer.power2 + 1; + // Thankfully, we can't have both "round-to-even" and subnormals because + // "round-to-even" only occurs for powers close to 0 in the 32-bit and + // and 64-bit case (with no more than 19 digits). + answer.mantissa += (answer.mantissa & 1); // round up + answer.mantissa >>= 1; + // There is a weird scenario where we don't have a subnormal but just. + // Suppose we start with 2.2250738585072013e-308, we end up + // with 0x3fffffffffffff x 2^-1023-53 which is technically subnormal + // whereas 0x40000000000000 x 2^-1023-53 is normal. Now, we need to round + // up 0x3fffffffffffff x 2^-1023-53 and once we do, we are no longer + // subnormal, but we can only know this after rounding. + // So we only declare a subnormal if we are smaller than the threshold. + answer.power2 = + (answer.mantissa < (uint64_t(1) << binary::mantissa_explicit_bits())) + ? 0 + : 1; + return answer; + } + + // usually, we round *up*, but if we fall right in between and and we have an + // even basis, we need to round down + // We are only concerned with the cases where 5**q fits in single 64-bit word. + if ((product.low <= 1) && (q >= binary::min_exponent_round_to_even()) && + (q <= binary::max_exponent_round_to_even()) && + ((answer.mantissa & 3) == 1)) { // we may fall between two floats! + // To be in-between two floats we need that in doing + // answer.mantissa = product.high >> (upperbit + 64 - + // binary::mantissa_explicit_bits() - 3); + // ... we dropped out only zeroes. But if this happened, then we can go + // back!!! + if ((answer.mantissa << shift) == product.high) { + answer.mantissa &= ~uint64_t(1); // flip it so that we do not round up + } + } + + answer.mantissa += (answer.mantissa & 1); // round up + answer.mantissa >>= 1; + if (answer.mantissa >= (uint64_t(2) << binary::mantissa_explicit_bits())) { + answer.mantissa = (uint64_t(1) << binary::mantissa_explicit_bits()); + answer.power2++; // undo previous addition + } + + answer.mantissa &= ~(uint64_t(1) << binary::mantissa_explicit_bits()); + if (answer.power2 >= binary::infinite_power()) { // infinity + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + } + return answer; +} + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_BIGINT_H +#define FASTFLOAT_BIGINT_H + +#include +//included above: +//#include +//included above: +//#include +//included above: +//#include + + +namespace fast_float { + +// the limb width: we want efficient multiplication of double the bits in +// limb, or for 64-bit limbs, at least 64-bit multiplication where we can +// extract the high and low parts efficiently. this is every 64-bit +// architecture except for sparc, which emulates 128-bit multiplication. +// we might have platforms where `CHAR_BIT` is not 8, so let's avoid +// doing `8 * sizeof(limb)`. +#if defined(FASTFLOAT_64BIT) && !defined(__sparc) +#define FASTFLOAT_64BIT_LIMB 1 +typedef uint64_t limb; +constexpr size_t limb_bits = 64; +#else +#define FASTFLOAT_32BIT_LIMB +typedef uint32_t limb; +constexpr size_t limb_bits = 32; +#endif + +typedef span limb_span; + +// number of bits in a bigint. this needs to be at least the number +// of bits required to store the largest bigint, which is +// `log2(10**(digits + max_exp))`, or `log2(10**(767 + 342))`, or +// ~3600 bits, so we round to 4000. +constexpr size_t bigint_bits = 4000; +constexpr size_t bigint_limbs = bigint_bits / limb_bits; + +// vector-like type that is allocated on the stack. the entire +// buffer is pre-allocated, and only the length changes. +template struct stackvec { + limb data[size]; + // we never need more than 150 limbs + uint16_t length{0}; + + stackvec() = default; + stackvec(stackvec const &) = delete; + stackvec &operator=(stackvec const &) = delete; + stackvec(stackvec &&) = delete; + stackvec &operator=(stackvec &&other) = delete; + + // create stack vector from existing limb span. + FASTFLOAT_CONSTEXPR20 stackvec(limb_span s) { + FASTFLOAT_ASSERT(try_extend(s)); + } + + FASTFLOAT_CONSTEXPR14 limb &operator[](size_t index) noexcept { + FASTFLOAT_DEBUG_ASSERT(index < length); + return data[index]; + } + + FASTFLOAT_CONSTEXPR14 const limb &operator[](size_t index) const noexcept { + FASTFLOAT_DEBUG_ASSERT(index < length); + return data[index]; + } + + // index from the end of the container + FASTFLOAT_CONSTEXPR14 const limb &rindex(size_t index) const noexcept { + FASTFLOAT_DEBUG_ASSERT(index < length); + size_t rindex = length - index - 1; + return data[rindex]; + } + + // set the length, without bounds checking. + FASTFLOAT_CONSTEXPR14 void set_len(size_t len) noexcept { + length = uint16_t(len); + } + + constexpr size_t len() const noexcept { return length; } + + constexpr bool is_empty() const noexcept { return length == 0; } + + constexpr size_t capacity() const noexcept { return size; } + + // append item to vector, without bounds checking + FASTFLOAT_CONSTEXPR14 void push_unchecked(limb value) noexcept { + data[length] = value; + length++; + } + + // append item to vector, returning if item was added + FASTFLOAT_CONSTEXPR14 bool try_push(limb value) noexcept { + if (len() < capacity()) { + push_unchecked(value); + return true; + } else { + return false; + } + } + + // add items to the vector, from a span, without bounds checking + FASTFLOAT_CONSTEXPR20 void extend_unchecked(limb_span s) noexcept { + limb *ptr = data + length; + std::copy_n(s.ptr, s.len(), ptr); + set_len(len() + s.len()); + } + + // try to add items to the vector, returning if items were added + FASTFLOAT_CONSTEXPR20 bool try_extend(limb_span s) noexcept { + if (len() + s.len() <= capacity()) { + extend_unchecked(s); + return true; + } else { + return false; + } + } + + // resize the vector, without bounds checking + // if the new size is longer than the vector, assign value to each + // appended item. + FASTFLOAT_CONSTEXPR20 + void resize_unchecked(size_t new_len, limb value) noexcept { + if (new_len > len()) { + size_t count = new_len - len(); + limb *first = data + len(); + limb *last = first + count; + ::std::fill(first, last, value); + set_len(new_len); + } else { + set_len(new_len); + } + } + + // try to resize the vector, returning if the vector was resized. + FASTFLOAT_CONSTEXPR20 bool try_resize(size_t new_len, limb value) noexcept { + if (new_len > capacity()) { + return false; + } else { + resize_unchecked(new_len, value); + return true; + } + } + + // check if any limbs are non-zero after the given index. + // this needs to be done in reverse order, since the index + // is relative to the most significant limbs. + FASTFLOAT_CONSTEXPR14 bool nonzero(size_t index) const noexcept { + while (index < len()) { + if (rindex(index) != 0) { + return true; + } + index++; + } + return false; + } + + // normalize the big integer, so most-significant zero limbs are removed. + FASTFLOAT_CONSTEXPR14 void normalize() noexcept { + while (len() > 0 && rindex(0) == 0) { + length--; + } + } +}; + +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint64_t +empty_hi64(bool &truncated) noexcept { + truncated = false; + return 0; +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t +uint64_hi64(uint64_t r0, bool &truncated) noexcept { + truncated = false; + int shl = leading_zeroes(r0); + return r0 << shl; +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t +uint64_hi64(uint64_t r0, uint64_t r1, bool &truncated) noexcept { + int shl = leading_zeroes(r0); + if (shl == 0) { + truncated = r1 != 0; + return r0; + } else { + int shr = 64 - shl; + truncated = (r1 << shl) != 0; + return (r0 << shl) | (r1 >> shr); + } +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t +uint32_hi64(uint32_t r0, bool &truncated) noexcept { + return uint64_hi64(r0, truncated); +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t +uint32_hi64(uint32_t r0, uint32_t r1, bool &truncated) noexcept { + uint64_t x0 = r0; + uint64_t x1 = r1; + return uint64_hi64((x0 << 32) | x1, truncated); +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t +uint32_hi64(uint32_t r0, uint32_t r1, uint32_t r2, bool &truncated) noexcept { + uint64_t x0 = r0; + uint64_t x1 = r1; + uint64_t x2 = r2; + return uint64_hi64(x0, (x1 << 32) | x2, truncated); +} + +// add two small integers, checking for overflow. +// we want an efficient operation. for msvc, where +// we don't have built-in intrinsics, this is still +// pretty fast. +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 limb +scalar_add(limb x, limb y, bool &overflow) noexcept { + limb z; +// gcc and clang +#if defined(__has_builtin) +#if __has_builtin(__builtin_add_overflow) + if (!cpp20_and_in_constexpr()) { + overflow = __builtin_add_overflow(x, y, &z); + return z; + } +#endif +#endif + + // generic, this still optimizes correctly on MSVC. + z = x + y; + overflow = z < x; + return z; +} + +// multiply two small integers, getting both the high and low bits. +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 limb +scalar_mul(limb x, limb y, limb &carry) noexcept { +#ifdef FASTFLOAT_64BIT_LIMB +#if defined(__SIZEOF_INT128__) + // GCC and clang both define it as an extension. + __uint128_t z = __uint128_t(x) * __uint128_t(y) + __uint128_t(carry); + carry = limb(z >> limb_bits); + return limb(z); +#else + // fallback, no native 128-bit integer multiplication with carry. + // on msvc, this optimizes identically, somehow. + value128 z = full_multiplication(x, y); + bool overflow; + z.low = scalar_add(z.low, carry, overflow); + z.high += uint64_t(overflow); // cannot overflow + carry = z.high; + return z.low; +#endif +#else + uint64_t z = uint64_t(x) * uint64_t(y) + uint64_t(carry); + carry = limb(z >> limb_bits); + return limb(z); +#endif +} + +// add scalar value to bigint starting from offset. +// used in grade school multiplication +template +inline FASTFLOAT_CONSTEXPR20 bool small_add_from(stackvec &vec, limb y, + size_t start) noexcept { + size_t index = start; + limb carry = y; + bool overflow; + while (carry != 0 && index < vec.len()) { + vec[index] = scalar_add(vec[index], carry, overflow); + carry = limb(overflow); + index += 1; + } + if (carry != 0) { + FASTFLOAT_TRY(vec.try_push(carry)); + } + return true; +} + +// add scalar value to bigint. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool +small_add(stackvec &vec, limb y) noexcept { + return small_add_from(vec, y, 0); +} + +// multiply bigint by scalar value. +template +inline FASTFLOAT_CONSTEXPR20 bool small_mul(stackvec &vec, + limb y) noexcept { + limb carry = 0; + for (size_t index = 0; index < vec.len(); index++) { + vec[index] = scalar_mul(vec[index], y, carry); + } + if (carry != 0) { + FASTFLOAT_TRY(vec.try_push(carry)); + } + return true; +} + +// add bigint to bigint starting from index. +// used in grade school multiplication +template +FASTFLOAT_CONSTEXPR20 bool large_add_from(stackvec &x, limb_span y, + size_t start) noexcept { + // the effective x buffer is from `xstart..x.len()`, so exit early + // if we can't get that current range. + if (x.len() < start || y.len() > x.len() - start) { + FASTFLOAT_TRY(x.try_resize(y.len() + start, 0)); + } + + bool carry = false; + for (size_t index = 0; index < y.len(); index++) { + limb xi = x[index + start]; + limb yi = y[index]; + bool c1 = false; + bool c2 = false; + xi = scalar_add(xi, yi, c1); + if (carry) { + xi = scalar_add(xi, 1, c2); + } + x[index + start] = xi; + carry = c1 | c2; + } + + // handle overflow + if (carry) { + FASTFLOAT_TRY(small_add_from(x, 1, y.len() + start)); + } + return true; +} + +// add bigint to bigint. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool +large_add_from(stackvec &x, limb_span y) noexcept { + return large_add_from(x, y, 0); +} + +// grade-school multiplication algorithm +template +FASTFLOAT_CONSTEXPR20 bool long_mul(stackvec &x, limb_span y) noexcept { + limb_span xs = limb_span(x.data, x.len()); + stackvec z(xs); + limb_span zs = limb_span(z.data, z.len()); + + if (y.len() != 0) { + limb y0 = y[0]; + FASTFLOAT_TRY(small_mul(x, y0)); + for (size_t index = 1; index < y.len(); index++) { + limb yi = y[index]; + stackvec zi; + if (yi != 0) { + // re-use the same buffer throughout + zi.set_len(0); + FASTFLOAT_TRY(zi.try_extend(zs)); + FASTFLOAT_TRY(small_mul(zi, yi)); + limb_span zis = limb_span(zi.data, zi.len()); + FASTFLOAT_TRY(large_add_from(x, zis, index)); + } + } + } + + x.normalize(); + return true; +} + +// grade-school multiplication algorithm +template +FASTFLOAT_CONSTEXPR20 bool large_mul(stackvec &x, limb_span y) noexcept { + if (y.len() == 1) { + FASTFLOAT_TRY(small_mul(x, y[0])); + } else { + FASTFLOAT_TRY(long_mul(x, y)); + } + return true; +} + +template struct pow5_tables { + static constexpr uint32_t large_step = 135; + static constexpr uint64_t small_power_of_5[] = { + 1UL, + 5UL, + 25UL, + 125UL, + 625UL, + 3125UL, + 15625UL, + 78125UL, + 390625UL, + 1953125UL, + 9765625UL, + 48828125UL, + 244140625UL, + 1220703125UL, + 6103515625UL, + 30517578125UL, + 152587890625UL, + 762939453125UL, + 3814697265625UL, + 19073486328125UL, + 95367431640625UL, + 476837158203125UL, + 2384185791015625UL, + 11920928955078125UL, + 59604644775390625UL, + 298023223876953125UL, + 1490116119384765625UL, + 7450580596923828125UL, + }; +#ifdef FASTFLOAT_64BIT_LIMB + constexpr static limb large_power_of_5[] = { + 1414648277510068013UL, 9180637584431281687UL, 4539964771860779200UL, + 10482974169319127550UL, 198276706040285095UL}; +#else + constexpr static limb large_power_of_5[] = { + 4279965485U, 329373468U, 4020270615U, 2137533757U, 4287402176U, + 1057042919U, 1071430142U, 2440757623U, 381945767U, 46164893U}; +#endif +}; + +#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE + +template constexpr uint32_t pow5_tables::large_step; + +template constexpr uint64_t pow5_tables::small_power_of_5[]; + +template constexpr limb pow5_tables::large_power_of_5[]; + +#endif + +// big integer type. implements a small subset of big integer +// arithmetic, using simple algorithms since asymptotically +// faster algorithms are slower for a small number of limbs. +// all operations assume the big-integer is normalized. +struct bigint : pow5_tables<> { + // storage of the limbs, in little-endian order. + stackvec vec; + + FASTFLOAT_CONSTEXPR20 bigint() : vec() {} + + bigint(bigint const &) = delete; + bigint &operator=(bigint const &) = delete; + bigint(bigint &&) = delete; + bigint &operator=(bigint &&other) = delete; + + FASTFLOAT_CONSTEXPR20 bigint(uint64_t value) : vec() { +#ifdef FASTFLOAT_64BIT_LIMB + vec.push_unchecked(value); +#else + vec.push_unchecked(uint32_t(value)); + vec.push_unchecked(uint32_t(value >> 32)); +#endif + vec.normalize(); + } + + // get the high 64 bits from the vector, and if bits were truncated. + // this is to get the significant digits for the float. + FASTFLOAT_CONSTEXPR20 uint64_t hi64(bool &truncated) const noexcept { +#ifdef FASTFLOAT_64BIT_LIMB + if (vec.len() == 0) { + return empty_hi64(truncated); + } else if (vec.len() == 1) { + return uint64_hi64(vec.rindex(0), truncated); + } else { + uint64_t result = uint64_hi64(vec.rindex(0), vec.rindex(1), truncated); + truncated |= vec.nonzero(2); + return result; + } +#else + if (vec.len() == 0) { + return empty_hi64(truncated); + } else if (vec.len() == 1) { + return uint32_hi64(vec.rindex(0), truncated); + } else if (vec.len() == 2) { + return uint32_hi64(vec.rindex(0), vec.rindex(1), truncated); + } else { + uint64_t result = + uint32_hi64(vec.rindex(0), vec.rindex(1), vec.rindex(2), truncated); + truncated |= vec.nonzero(3); + return result; + } +#endif + } + + // compare two big integers, returning the large value. + // assumes both are normalized. if the return value is + // negative, other is larger, if the return value is + // positive, this is larger, otherwise they are equal. + // the limbs are stored in little-endian order, so we + // must compare the limbs in ever order. + FASTFLOAT_CONSTEXPR20 int compare(bigint const &other) const noexcept { + if (vec.len() > other.vec.len()) { + return 1; + } else if (vec.len() < other.vec.len()) { + return -1; + } else { + for (size_t index = vec.len(); index > 0; index--) { + limb xi = vec[index - 1]; + limb yi = other.vec[index - 1]; + if (xi > yi) { + return 1; + } else if (xi < yi) { + return -1; + } + } + return 0; + } + } + + // shift left each limb n bits, carrying over to the new limb + // returns true if we were able to shift all the digits. + FASTFLOAT_CONSTEXPR20 bool shl_bits(size_t n) noexcept { + // Internally, for each item, we shift left by n, and add the previous + // right shifted limb-bits. + // For example, we transform (for u8) shifted left 2, to: + // b10100100 b01000010 + // b10 b10010001 b00001000 + FASTFLOAT_DEBUG_ASSERT(n != 0); + FASTFLOAT_DEBUG_ASSERT(n < sizeof(limb) * 8); + + size_t shl = n; + size_t shr = limb_bits - shl; + limb prev = 0; + for (size_t index = 0; index < vec.len(); index++) { + limb xi = vec[index]; + vec[index] = (xi << shl) | (prev >> shr); + prev = xi; + } + + limb carry = prev >> shr; + if (carry != 0) { + return vec.try_push(carry); + } + return true; + } + + // move the limbs left by `n` limbs. + FASTFLOAT_CONSTEXPR20 bool shl_limbs(size_t n) noexcept { + FASTFLOAT_DEBUG_ASSERT(n != 0); + if (n + vec.len() > vec.capacity()) { + return false; + } else if (!vec.is_empty()) { + // move limbs + limb *dst = vec.data + n; + limb const *src = vec.data; + std::copy_backward(src, src + vec.len(), dst + vec.len()); + // fill in empty limbs + limb *first = vec.data; + limb *last = first + n; + ::std::fill(first, last, 0); + vec.set_len(n + vec.len()); + return true; + } else { + return true; + } + } + + // move the limbs left by `n` bits. + FASTFLOAT_CONSTEXPR20 bool shl(size_t n) noexcept { + size_t rem = n % limb_bits; + size_t div = n / limb_bits; + if (rem != 0) { + FASTFLOAT_TRY(shl_bits(rem)); + } + if (div != 0) { + FASTFLOAT_TRY(shl_limbs(div)); + } + return true; + } + + // get the number of leading zeros in the bigint. + FASTFLOAT_CONSTEXPR20 int ctlz() const noexcept { + if (vec.is_empty()) { + return 0; + } else { +#ifdef FASTFLOAT_64BIT_LIMB + return leading_zeroes(vec.rindex(0)); +#else + // no use defining a specialized leading_zeroes for a 32-bit type. + uint64_t r0 = vec.rindex(0); + return leading_zeroes(r0 << 32); +#endif + } + } + + // get the number of bits in the bigint. + FASTFLOAT_CONSTEXPR20 int bit_length() const noexcept { + int lz = ctlz(); + return int(limb_bits * vec.len()) - lz; + } + + FASTFLOAT_CONSTEXPR20 bool mul(limb y) noexcept { return small_mul(vec, y); } + + FASTFLOAT_CONSTEXPR20 bool add(limb y) noexcept { return small_add(vec, y); } + + // multiply as if by 2 raised to a power. + FASTFLOAT_CONSTEXPR20 bool pow2(uint32_t exp) noexcept { return shl(exp); } + + // multiply as if by 5 raised to a power. + FASTFLOAT_CONSTEXPR20 bool pow5(uint32_t exp) noexcept { + // multiply by a power of 5 + size_t large_length = sizeof(large_power_of_5) / sizeof(limb); + limb_span large = limb_span(large_power_of_5, large_length); + while (exp >= large_step) { + FASTFLOAT_TRY(large_mul(vec, large)); + exp -= large_step; + } +#ifdef FASTFLOAT_64BIT_LIMB + uint32_t small_step = 27; + limb max_native = 7450580596923828125UL; +#else + uint32_t small_step = 13; + limb max_native = 1220703125U; +#endif + while (exp >= small_step) { + FASTFLOAT_TRY(small_mul(vec, max_native)); + exp -= small_step; + } + if (exp != 0) { + // Work around clang bug https://godbolt.org/z/zedh7rrhc + // This is similar to https://github.com/llvm/llvm-project/issues/47746, + // except the workaround described there don't work here + FASTFLOAT_TRY(small_mul( + vec, limb(((void)small_power_of_5[0], small_power_of_5[exp])))); + } + + return true; + } + + // multiply as if by 10 raised to a power. + FASTFLOAT_CONSTEXPR20 bool pow10(uint32_t exp) noexcept { + FASTFLOAT_TRY(pow5(exp)); + return pow2(exp); + } +}; + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_DIGIT_COMPARISON_H +#define FASTFLOAT_DIGIT_COMPARISON_H + +//included above: +//#include +//included above: +//#include +//included above: +//#include +//included above: +//#include + + +namespace fast_float { + +// 1e0 to 1e19 +constexpr static uint64_t powers_of_ten_uint64[] = {1UL, + 10UL, + 100UL, + 1000UL, + 10000UL, + 100000UL, + 1000000UL, + 10000000UL, + 100000000UL, + 1000000000UL, + 10000000000UL, + 100000000000UL, + 1000000000000UL, + 10000000000000UL, + 100000000000000UL, + 1000000000000000UL, + 10000000000000000UL, + 100000000000000000UL, + 1000000000000000000UL, + 10000000000000000000UL}; + +// calculate the exponent, in scientific notation, of the number. +// this algorithm is not even close to optimized, but it has no practical +// effect on performance: in order to have a faster algorithm, we'd need +// to slow down performance for faster algorithms, and this is still fast. +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 int32_t +scientific_exponent(uint64_t mantissa, int32_t exponent) noexcept { + while (mantissa >= 10000) { + mantissa /= 10000; + exponent += 4; + } + while (mantissa >= 100) { + mantissa /= 100; + exponent += 2; + } + while (mantissa >= 10) { + mantissa /= 10; + exponent += 1; + } + return exponent; +} + +// this converts a native floating-point number to an extended-precision float. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa +to_extended(T value) noexcept { + using equiv_uint = equiv_uint_t; + constexpr equiv_uint exponent_mask = binary_format::exponent_mask(); + constexpr equiv_uint mantissa_mask = binary_format::mantissa_mask(); + constexpr equiv_uint hidden_bit_mask = binary_format::hidden_bit_mask(); + + adjusted_mantissa am; + int32_t bias = binary_format::mantissa_explicit_bits() - + binary_format::minimum_exponent(); + equiv_uint bits; +#if FASTFLOAT_HAS_BIT_CAST + bits = std::bit_cast(value); +#else + ::memcpy(&bits, &value, sizeof(T)); +#endif + if ((bits & exponent_mask) == 0) { + // denormal + am.power2 = 1 - bias; + am.mantissa = bits & mantissa_mask; + } else { + // normal + am.power2 = int32_t((bits & exponent_mask) >> + binary_format::mantissa_explicit_bits()); + am.power2 -= bias; + am.mantissa = (bits & mantissa_mask) | hidden_bit_mask; + } + + return am; +} + +// get the extended precision value of the halfway point between b and b+u. +// we are given a native float that represents b, so we need to adjust it +// halfway between b and b+u. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa +to_extended_halfway(T value) noexcept { + adjusted_mantissa am = to_extended(value); + am.mantissa <<= 1; + am.mantissa += 1; + am.power2 -= 1; + return am; +} + +// round an extended-precision float to the nearest machine float. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void round(adjusted_mantissa &am, + callback cb) noexcept { + int32_t mantissa_shift = 64 - binary_format::mantissa_explicit_bits() - 1; + if (-am.power2 >= mantissa_shift) { + // have a denormal float + int32_t shift = -am.power2 + 1; + cb(am, std::min(shift, 64)); + // check for round-up: if rounding-nearest carried us to the hidden bit. + am.power2 = (am.mantissa < + (uint64_t(1) << binary_format::mantissa_explicit_bits())) + ? 0 + : 1; + return; + } + + // have a normal float, use the default shift. + cb(am, mantissa_shift); + + // check for carry + if (am.mantissa >= + (uint64_t(2) << binary_format::mantissa_explicit_bits())) { + am.mantissa = (uint64_t(1) << binary_format::mantissa_explicit_bits()); + am.power2++; + } + + // check for infinite: we could have carried to an infinite power + am.mantissa &= ~(uint64_t(1) << binary_format::mantissa_explicit_bits()); + if (am.power2 >= binary_format::infinite_power()) { + am.power2 = binary_format::infinite_power(); + am.mantissa = 0; + } +} + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void +round_nearest_tie_even(adjusted_mantissa &am, int32_t shift, + callback cb) noexcept { + uint64_t const mask = (shift == 64) ? UINT64_MAX : (uint64_t(1) << shift) - 1; + uint64_t const halfway = (shift == 0) ? 0 : uint64_t(1) << (shift - 1); + uint64_t truncated_bits = am.mantissa & mask; + bool is_above = truncated_bits > halfway; + bool is_halfway = truncated_bits == halfway; + + // shift digits into position + if (shift == 64) { + am.mantissa = 0; + } else { + am.mantissa >>= shift; + } + am.power2 += shift; + + bool is_odd = (am.mantissa & 1) == 1; + am.mantissa += uint64_t(cb(is_odd, is_halfway, is_above)); +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void +round_down(adjusted_mantissa &am, int32_t shift) noexcept { + if (shift == 64) { + am.mantissa = 0; + } else { + am.mantissa >>= shift; + } + am.power2 += shift; +} + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void +skip_zeros(UC const *&first, UC const *last) noexcept { + uint64_t val; + while (!cpp20_and_in_constexpr() && + std::distance(first, last) >= int_cmp_len()) { + ::memcpy(&val, first, sizeof(uint64_t)); + if (val != int_cmp_zeros()) { + break; + } + first += int_cmp_len(); + } + while (first != last) { + if (*first != UC('0')) { + break; + } + first++; + } +} + +// determine if any non-zero digits were truncated. +// all characters must be valid digits. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool +is_truncated(UC const *first, UC const *last) noexcept { + // do 8-bit optimizations, can just compare to 8 literal 0s. + uint64_t val; + while (!cpp20_and_in_constexpr() && + std::distance(first, last) >= int_cmp_len()) { + ::memcpy(&val, first, sizeof(uint64_t)); + if (val != int_cmp_zeros()) { + return true; + } + first += int_cmp_len(); + } + while (first != last) { + if (*first != UC('0')) { + return true; + } + ++first; + } + return false; +} + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool +is_truncated(span s) noexcept { + return is_truncated(s.ptr, s.ptr + s.len()); +} + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void +parse_eight_digits(UC const *&p, limb &value, size_t &counter, + size_t &count) noexcept { + value = value * 100000000 + parse_eight_digits_unrolled(p); + p += 8; + counter += 8; + count += 8; +} + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void +parse_one_digit(UC const *&p, limb &value, size_t &counter, + size_t &count) noexcept { + value = value * 10 + limb(*p - UC('0')); + p++; + counter++; + count++; +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void +add_native(bigint &big, limb power, limb value) noexcept { + big.mul(power); + big.add(value); +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void +round_up_bigint(bigint &big, size_t &count) noexcept { + // need to round-up the digits, but need to avoid rounding + // ....9999 to ...10000, which could cause a false halfway point. + add_native(big, 10, 1); + count++; +} + +// parse the significant digits into a big integer +template +inline FASTFLOAT_CONSTEXPR20 void +parse_mantissa(bigint &result, parsed_number_string_t &num, + size_t max_digits, size_t &digits) noexcept { + // try to minimize the number of big integer and scalar multiplication. + // therefore, try to parse 8 digits at a time, and multiply by the largest + // scalar value (9 or 19 digits) for each step. + size_t counter = 0; + digits = 0; + limb value = 0; +#ifdef FASTFLOAT_64BIT_LIMB + size_t step = 19; +#else + size_t step = 9; +#endif + + // process all integer digits. + UC const *p = num.integer.ptr; + UC const *pend = p + num.integer.len(); + skip_zeros(p, pend); + // process all digits, in increments of step per loop + while (p != pend) { + while ((std::distance(p, pend) >= 8) && (step - counter >= 8) && + (max_digits - digits >= 8)) { + parse_eight_digits(p, value, counter, digits); + } + while (counter < step && p != pend && digits < max_digits) { + parse_one_digit(p, value, counter, digits); + } + if (digits == max_digits) { + // add the temporary value, then check if we've truncated any digits + add_native(result, limb(powers_of_ten_uint64[counter]), value); + bool truncated = is_truncated(p, pend); + if (num.fraction.ptr != nullptr) { + truncated |= is_truncated(num.fraction); + } + if (truncated) { + round_up_bigint(result, digits); + } + return; + } else { + add_native(result, limb(powers_of_ten_uint64[counter]), value); + counter = 0; + value = 0; + } + } + + // add our fraction digits, if they're available. + if (num.fraction.ptr != nullptr) { + p = num.fraction.ptr; + pend = p + num.fraction.len(); + if (digits == 0) { + skip_zeros(p, pend); + } + // process all digits, in increments of step per loop + while (p != pend) { + while ((std::distance(p, pend) >= 8) && (step - counter >= 8) && + (max_digits - digits >= 8)) { + parse_eight_digits(p, value, counter, digits); + } + while (counter < step && p != pend && digits < max_digits) { + parse_one_digit(p, value, counter, digits); + } + if (digits == max_digits) { + // add the temporary value, then check if we've truncated any digits + add_native(result, limb(powers_of_ten_uint64[counter]), value); + bool truncated = is_truncated(p, pend); + if (truncated) { + round_up_bigint(result, digits); + } + return; + } else { + add_native(result, limb(powers_of_ten_uint64[counter]), value); + counter = 0; + value = 0; + } + } + } + + if (counter != 0) { + add_native(result, limb(powers_of_ten_uint64[counter]), value); + } +} + +template +inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa +positive_digit_comp(bigint &bigmant, int32_t exponent) noexcept { + FASTFLOAT_ASSERT(bigmant.pow10(uint32_t(exponent))); + adjusted_mantissa answer; + bool truncated; + answer.mantissa = bigmant.hi64(truncated); + int bias = binary_format::mantissa_explicit_bits() - + binary_format::minimum_exponent(); + answer.power2 = bigmant.bit_length() - 64 + bias; + + round(answer, [truncated](adjusted_mantissa &a, int32_t shift) { + round_nearest_tie_even( + a, shift, + [truncated](bool is_odd, bool is_halfway, bool is_above) -> bool { + return is_above || (is_halfway && truncated) || + (is_odd && is_halfway); + }); + }); + + return answer; +} + +// the scaling here is quite simple: we have, for the real digits `m * 10^e`, +// and for the theoretical digits `n * 2^f`. Since `e` is always negative, +// to scale them identically, we do `n * 2^f * 5^-f`, so we now have `m * 2^e`. +// we then need to scale by `2^(f- e)`, and then the two significant digits +// are of the same magnitude. +template +inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa negative_digit_comp( + bigint &bigmant, adjusted_mantissa am, int32_t exponent) noexcept { + bigint &real_digits = bigmant; + int32_t real_exp = exponent; + + // get the value of `b`, rounded down, and get a bigint representation of b+h + adjusted_mantissa am_b = am; + // gcc7 buf: use a lambda to remove the noexcept qualifier bug with + // -Wnoexcept-type. + round(am_b, + [](adjusted_mantissa &a, int32_t shift) { round_down(a, shift); }); + T b; + to_float(false, am_b, b); + adjusted_mantissa theor = to_extended_halfway(b); + bigint theor_digits(theor.mantissa); + int32_t theor_exp = theor.power2; + + // scale real digits and theor digits to be same power. + int32_t pow2_exp = theor_exp - real_exp; + uint32_t pow5_exp = uint32_t(-real_exp); + if (pow5_exp != 0) { + FASTFLOAT_ASSERT(theor_digits.pow5(pow5_exp)); + } + if (pow2_exp > 0) { + FASTFLOAT_ASSERT(theor_digits.pow2(uint32_t(pow2_exp))); + } else if (pow2_exp < 0) { + FASTFLOAT_ASSERT(real_digits.pow2(uint32_t(-pow2_exp))); + } + + // compare digits, and use it to direct rounding + int ord = real_digits.compare(theor_digits); + adjusted_mantissa answer = am; + round(answer, [ord](adjusted_mantissa &a, int32_t shift) { + round_nearest_tie_even( + a, shift, [ord](bool is_odd, bool _, bool __) -> bool { + (void)_; // not needed, since we've done our comparison + (void)__; // not needed, since we've done our comparison + if (ord > 0) { + return true; + } else if (ord < 0) { + return false; + } else { + return is_odd; + } + }); + }); + + return answer; +} + +// parse the significant digits as a big integer to unambiguously round +// the significant digits. here, we are trying to determine how to round +// an extended float representation close to `b+h`, halfway between `b` +// (the float rounded-down) and `b+u`, the next positive float. this +// algorithm is always correct, and uses one of two approaches. when +// the exponent is positive relative to the significant digits (such as +// 1234), we create a big-integer representation, get the high 64-bits, +// determine if any lower bits are truncated, and use that to direct +// rounding. in case of a negative exponent relative to the significant +// digits (such as 1.2345), we create a theoretical representation of +// `b` as a big-integer type, scaled to the same binary exponent as +// the actual digits. we then compare the big integer representations +// of both, and use that to direct rounding. +template +inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa +digit_comp(parsed_number_string_t &num, adjusted_mantissa am) noexcept { + // remove the invalid exponent bias + am.power2 -= invalid_am_bias; + + int32_t sci_exp = + scientific_exponent(num.mantissa, static_cast(num.exponent)); + size_t max_digits = binary_format::max_digits(); + size_t digits = 0; + bigint bigmant; + parse_mantissa(bigmant, num, max_digits, digits); + // can't underflow, since digits is at most max_digits. + int32_t exponent = sci_exp + 1 - int32_t(digits); + if (exponent >= 0) { + return positive_digit_comp(bigmant, exponent); + } else { + return negative_digit_comp(bigmant, am, exponent); + } +} + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_PARSE_NUMBER_H +#define FASTFLOAT_PARSE_NUMBER_H + + +//included above: +//#include +//included above: +//#include +//included above: +//#include +//included above: +//#include + +namespace fast_float { + +namespace detail { +/** + * Special case +inf, -inf, nan, infinity, -infinity. + * The case comparisons could be made much faster given that we know that the + * strings a null-free and fixed. + **/ +template +from_chars_result_t + FASTFLOAT_CONSTEXPR14 parse_infnan(UC const *first, UC const *last, + T &value, chars_format fmt) noexcept { + from_chars_result_t answer{}; + answer.ptr = first; + answer.ec = std::errc(); // be optimistic + // assume first < last, so dereference without checks; + bool const minusSign = (*first == UC('-')); + // C++17 20.19.3.(7.1) explicitly forbids '+' sign here + if ((*first == UC('-')) || + (uint64_t(fmt & chars_format::allow_leading_plus) && + (*first == UC('+')))) { + ++first; + } + if (last - first >= 3) { + if (fastfloat_strncasecmp3(first, str_const_nan())) { + answer.ptr = (first += 3); + value = minusSign ? -std::numeric_limits::quiet_NaN() + : std::numeric_limits::quiet_NaN(); + // Check for possible nan(n-char-seq-opt), C++17 20.19.3.7, + // C11 7.20.1.3.3. At least MSVC produces nan(ind) and nan(snan). + if (first != last && *first == UC('(')) { + for (UC const *ptr = first + 1; ptr != last; ++ptr) { + if (*ptr == UC(')')) { + answer.ptr = ptr + 1; // valid nan(n-char-seq-opt) + break; + } else if (!((UC('a') <= *ptr && *ptr <= UC('z')) || + (UC('A') <= *ptr && *ptr <= UC('Z')) || + (UC('0') <= *ptr && *ptr <= UC('9')) || *ptr == UC('_'))) + break; // forbidden char, not nan(n-char-seq-opt) + } + } + return answer; + } + if (fastfloat_strncasecmp3(first, str_const_inf())) { + if ((last - first >= 8) && + fastfloat_strncasecmp5(first + 3, str_const_inf() + 3)) { + answer.ptr = first + 8; + } else { + answer.ptr = first + 3; + } + value = minusSign ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + return answer; + } + } + answer.ec = std::errc::invalid_argument; + return answer; +} + +/** + * Returns true if the floating-pointing rounding mode is to 'nearest'. + * It is the default on most system. This function is meant to be inexpensive. + * Credit : @mwalcott3 + */ +fastfloat_really_inline bool rounds_to_nearest() noexcept { + // https://lemire.me/blog/2020/06/26/gcc-not-nearest/ +#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) + return false; +#endif + // See + // A fast function to check your floating-point rounding mode + // https://lemire.me/blog/2022/11/16/a-fast-function-to-check-your-floating-point-rounding-mode/ + // + // This function is meant to be equivalent to : + // prior: #include + // return fegetround() == FE_TONEAREST; + // However, it is expected to be much faster than the fegetround() + // function call. + // + // The volatile keyword prevents the compiler from computing the function + // at compile-time. + // There might be other ways to prevent compile-time optimizations (e.g., + // asm). The value does not need to be std::numeric_limits::min(), any + // small value so that 1 + x should round to 1 would do (after accounting for + // excess precision, as in 387 instructions). + static float volatile fmin = std::numeric_limits::min(); + float fmini = fmin; // we copy it so that it gets loaded at most once. +// +// Explanation: +// Only when fegetround() == FE_TONEAREST do we have that +// fmin + 1.0f == 1.0f - fmin. +// +// FE_UPWARD: +// fmin + 1.0f > 1 +// 1.0f - fmin == 1 +// +// FE_DOWNWARD or FE_TOWARDZERO: +// fmin + 1.0f == 1 +// 1.0f - fmin < 1 +// +// Note: This may fail to be accurate if fast-math has been +// enabled, as rounding conventions may not apply. +#ifdef FASTFLOAT_VISUAL_STUDIO +#pragma warning(push) +// todo: is there a VS warning? +// see +// https://stackoverflow.com/questions/46079446/is-there-a-warning-for-floating-point-equality-checking-in-visual-studio-2013 +#elif defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wfloat-equal" +#elif defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + return (fmini + 1.0f == 1.0f - fmini); +#ifdef FASTFLOAT_VISUAL_STUDIO +#pragma warning(pop) +#elif defined(__clang__) +#pragma clang diagnostic pop +#elif defined(__GNUC__) +#pragma GCC diagnostic pop +#endif +} + +} // namespace detail + +template struct from_chars_caller { + template + FASTFLOAT_CONSTEXPR20 static from_chars_result_t + call(UC const *first, UC const *last, T &value, + parse_options_t options) noexcept { + return from_chars_advanced(first, last, value, options); + } +}; + +#ifdef __STDCPP_FLOAT32_T__ +template <> struct from_chars_caller { + template + FASTFLOAT_CONSTEXPR20 static from_chars_result_t + call(UC const *first, UC const *last, std::float32_t &value, + parse_options_t options) noexcept { + // if std::float32_t is defined, and we are in C++23 mode; macro set for + // float32; set value to float due to equivalence between float and + // float32_t + float val; + auto ret = from_chars_advanced(first, last, val, options); + value = val; + return ret; + } +}; +#endif + +#ifdef __STDCPP_FLOAT64_T__ +template <> struct from_chars_caller { + template + FASTFLOAT_CONSTEXPR20 static from_chars_result_t + call(UC const *first, UC const *last, std::float64_t &value, + parse_options_t options) noexcept { + // if std::float64_t is defined, and we are in C++23 mode; macro set for + // float64; set value as double due to equivalence between double and + // float64_t + double val; + auto ret = from_chars_advanced(first, last, val, options); + value = val; + return ret; + } +}; +#endif + +template +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars(UC const *first, UC const *last, T &value, + chars_format fmt /*= chars_format::general*/) noexcept { + return from_chars_caller::call(first, last, value, + parse_options_t(fmt)); +} + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool +clinger_fast_path_impl(uint64_t mantissa, int64_t exponent, bool is_negative, + T &value) noexcept { + // The implementation of the Clinger's fast path is convoluted because + // we want round-to-nearest in all cases, irrespective of the rounding mode + // selected on the thread. + // We proceed optimistically, assuming that detail::rounds_to_nearest() + // returns true. + if (binary_format::min_exponent_fast_path() <= exponent && + exponent <= binary_format::max_exponent_fast_path()) { + // Unfortunately, the conventional Clinger's fast path is only possible + // when the system rounds to the nearest float. + // + // We expect the next branch to almost always be selected. + // We could check it first (before the previous branch), but + // there might be performance advantages at having the check + // be last. + if (!cpp20_and_in_constexpr() && detail::rounds_to_nearest()) { + // We have that fegetround() == FE_TONEAREST. + // Next is Clinger's fast path. + if (mantissa <= binary_format::max_mantissa_fast_path()) { + value = T(mantissa); + if (exponent < 0) { + value = value / binary_format::exact_power_of_ten(-exponent); + } else { + value = value * binary_format::exact_power_of_ten(exponent); + } + if (is_negative) { + value = -value; + } + return true; + } + } else { + // We do not have that fegetround() == FE_TONEAREST. + // Next is a modified Clinger's fast path, inspired by Jakub Jelínek's + // proposal + if (exponent >= 0 && + mantissa <= binary_format::max_mantissa_fast_path(exponent)) { +#if defined(__clang__) || defined(FASTFLOAT_32BIT) + // Clang may map 0 to -0.0 when fegetround() == FE_DOWNWARD + if (mantissa == 0) { + value = is_negative ? T(-0.) : T(0.); + return true; + } +#endif + value = T(mantissa) * binary_format::exact_power_of_ten(exponent); + if (is_negative) { + value = -value; + } + return true; + } + } + } + return false; +} + +/** + * This function overload takes parsed_number_string_t structure that is created + * and populated either by from_chars_advanced function taking chars range and + * parsing options or other parsing custom function implemented by user. + */ +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars_advanced(parsed_number_string_t &pns, T &value) noexcept { + static_assert(is_supported_float_type::value, + "only some floating-point types are supported"); + static_assert(is_supported_char_type::value, + "only char, wchar_t, char16_t and char32_t are supported"); + + from_chars_result_t answer; + + answer.ec = std::errc(); // be optimistic + answer.ptr = pns.lastmatch; + + if (!pns.too_many_digits && + clinger_fast_path_impl(pns.mantissa, pns.exponent, pns.negative, value)) + return answer; + + adjusted_mantissa am = + compute_float>(pns.exponent, pns.mantissa); + if (pns.too_many_digits && am.power2 >= 0) { + if (am != compute_float>(pns.exponent, pns.mantissa + 1)) { + am = compute_error>(pns.exponent, pns.mantissa); + } + } + // If we called compute_float>(pns.exponent, pns.mantissa) + // and we have an invalid power (am.power2 < 0), then we need to go the long + // way around again. This is very uncommon. + if (am.power2 < 0) { + am = digit_comp(pns, am); + } + to_float(pns.negative, am, value); + // Test for over/underflow. + if ((pns.mantissa != 0 && am.mantissa == 0 && am.power2 == 0) || + am.power2 == binary_format::infinite_power()) { + answer.ec = std::errc::result_out_of_range; + } + return answer; +} + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars_float_advanced(UC const *first, UC const *last, T &value, + parse_options_t options) noexcept { + + static_assert(is_supported_float_type::value, + "only some floating-point types are supported"); + static_assert(is_supported_char_type::value, + "only char, wchar_t, char16_t and char32_t are supported"); + + chars_format const fmt = detail::adjust_for_feature_macros(options.format); + + from_chars_result_t answer; + if (uint64_t(fmt & chars_format::skip_white_space)) { + while ((first != last) && fast_float::is_space(*first)) { + first++; + } + } + if (first == last) { + answer.ec = std::errc::invalid_argument; + answer.ptr = first; + return answer; + } + parsed_number_string_t pns = + uint64_t(fmt & detail::basic_json_fmt) + ? parse_number_string(first, last, options) + : parse_number_string(first, last, options); + if (!pns.valid) { + if (uint64_t(fmt & chars_format::no_infnan)) { + answer.ec = std::errc::invalid_argument; + answer.ptr = first; + return answer; + } else { + return detail::parse_infnan(first, last, value, fmt); + } + } + + // call overload that takes parsed_number_string_t directly. + return from_chars_advanced(pns, value); +} + +template +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars(UC const *first, UC const *last, T &value, int base) noexcept { + + static_assert(is_supported_integer_type::value, + "only integer types are supported"); + static_assert(is_supported_char_type::value, + "only char, wchar_t, char16_t and char32_t are supported"); + + parse_options_t options; + options.base = base; + return from_chars_advanced(first, last, value, options); +} + +template +FASTFLOAT_CONSTEXPR20 + typename std::enable_if::value, T>::type + integer_times_pow10(uint64_t mantissa, int decimal_exponent) noexcept { + T value; + if (clinger_fast_path_impl(mantissa, decimal_exponent, false, value)) + return value; + + adjusted_mantissa am = + compute_float>(decimal_exponent, mantissa); + to_float(false, am, value); + return value; +} + +template +FASTFLOAT_CONSTEXPR20 + typename std::enable_if::value, T>::type + integer_times_pow10(int64_t mantissa, int decimal_exponent) noexcept { + const bool is_negative = mantissa < 0; + const uint64_t m = static_cast(is_negative ? -mantissa : mantissa); + + T value; + if (clinger_fast_path_impl(m, decimal_exponent, is_negative, value)) + return value; + + adjusted_mantissa am = compute_float>(decimal_exponent, m); + to_float(is_negative, am, value); + return value; +} + +FASTFLOAT_CONSTEXPR20 inline double +integer_times_pow10(uint64_t mantissa, int decimal_exponent) noexcept { + return integer_times_pow10(mantissa, decimal_exponent); +} + +FASTFLOAT_CONSTEXPR20 inline double +integer_times_pow10(int64_t mantissa, int decimal_exponent) noexcept { + return integer_times_pow10(mantissa, decimal_exponent); +} + +// the following overloads are here to avoid surprising ambiguity for int, +// unsigned, etc. +template +FASTFLOAT_CONSTEXPR20 + typename std::enable_if::value && + std::is_integral::value && + !std::is_signed::value, + T>::type + integer_times_pow10(Int mantissa, int decimal_exponent) noexcept { + return integer_times_pow10(static_cast(mantissa), + decimal_exponent); +} + +template +FASTFLOAT_CONSTEXPR20 + typename std::enable_if::value && + std::is_integral::value && + std::is_signed::value, + T>::type + integer_times_pow10(Int mantissa, int decimal_exponent) noexcept { + return integer_times_pow10(static_cast(mantissa), + decimal_exponent); +} + +template +FASTFLOAT_CONSTEXPR20 typename std::enable_if< + std::is_integral::value && !std::is_signed::value, double>::type +integer_times_pow10(Int mantissa, int decimal_exponent) noexcept { + return integer_times_pow10(static_cast(mantissa), decimal_exponent); +} + +template +FASTFLOAT_CONSTEXPR20 typename std::enable_if< + std::is_integral::value && std::is_signed::value, double>::type +integer_times_pow10(Int mantissa, int decimal_exponent) noexcept { + return integer_times_pow10(static_cast(mantissa), decimal_exponent); +} + +template +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars_int_advanced(UC const *first, UC const *last, T &value, + parse_options_t options) noexcept { + + static_assert(is_supported_integer_type::value, + "only integer types are supported"); + static_assert(is_supported_char_type::value, + "only char, wchar_t, char16_t and char32_t are supported"); + + chars_format const fmt = detail::adjust_for_feature_macros(options.format); + int const base = options.base; + + from_chars_result_t answer; + if (uint64_t(fmt & chars_format::skip_white_space)) { + while ((first != last) && fast_float::is_space(*first)) { + first++; + } + } + if (first == last || base < 2 || base > 36) { + answer.ec = std::errc::invalid_argument; + answer.ptr = first; + return answer; + } + + return parse_int_string(first, last, value, options); +} + +template struct from_chars_advanced_caller { + static_assert(TypeIx > 0, "unsupported type"); +}; + +template <> struct from_chars_advanced_caller<1> { + template + fastfloat_really_inline + FASTFLOAT_CONSTEXPR20 static from_chars_result_t + call(UC const *first, UC const *last, T &value, + parse_options_t options) noexcept { + return from_chars_float_advanced(first, last, value, options); + } +}; + +template <> struct from_chars_advanced_caller<2> { + template + fastfloat_really_inline FASTFLOAT_CONSTEXPR20 static from_chars_result_t + call(UC const *first, UC const *last, T &value, + parse_options_t options) noexcept { + return from_chars_int_advanced(first, last, value, options); + } +}; + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars_advanced(UC const *first, UC const *last, T &value, + parse_options_t options) noexcept { + return from_chars_advanced_caller< + size_t(is_supported_float_type::value) + + 2 * size_t(is_supported_integer_type::value)>::call(first, last, value, + options); +} + +} // namespace fast_float + +#endif + +#ifdef _MSC_VER +# pragma warning(pop) +#elif defined(__clang__) || defined(__APPLE_CC__) +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#endif + +#endif // _C4_EXT_FAST_FLOAT_HPP_ + + +// (end src/c4/ext/fast_float.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/std/vector_fwd.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_STD_VECTOR_FWD_HPP_ +#define _C4_STD_VECTOR_FWD_HPP_ + +/** @file vector_fwd.hpp Provides forward declaration of std::vector + * to enable order-independent includes for use with ref + * @ref c4::to_chars() and @ref c4::from_chars(). */ + +//included above: +//#include + +// NOLINTBEGIN(cert-dcl58-cpp) + +// forward declarations for std::vector +#if defined(__GLIBCXX__) || defined(__GLIBCPP__) || defined(_MSC_VER) +#if defined(_MSC_VER) +__pragma(warning(push)) +__pragma(warning(disable : 4643)) +#endif +namespace std { +template class allocator; // NOLINT +#ifdef _GLIBCXX_DEBUG +inline namespace __debug { +template class vector; // NOLINT +} +#else +template class vector; // NOLINT +#endif +} // namespace std +#if defined(_MSC_VER) +__pragma(warning(pop)) +#endif +#elif defined(_LIBCPP_ABI_NAMESPACE) +namespace std { +inline namespace _LIBCPP_ABI_NAMESPACE { +template class allocator; // NOLINT +template class vector; // NOLINT +} // namespace _LIBCPP_ABI_NAMESPACE +} // namespace std +#else +#error "unknown standard library" +#endif + +#ifndef C4CORE_SINGLE_HEADER +// amalgamate: removed include of +// c4/substr_fwd.hpp +//#include "c4/substr_fwd.hpp" +#if !defined(C4_SUBSTR_FWD_HPP_) && !defined(_C4_SUBSTR_FWD_HPP_) +#error "amalgamate: file c4/substr_fwd.hpp must have been included at this point" +#endif /* C4_SUBSTR_FWD_HPP_ */ + +#endif + +namespace c4 { + +template struct is_string; +template struct is_writeable_string; +template struct is_string>; +template struct is_string>; +template struct is_writeable_string>; + +template c4::substr to_substr(std::vector &vec); +template c4::csubstr to_csubstr(std::vector const& vec); + +template bool operator!= (c4::csubstr ss, std::vector const& s); +template bool operator== (c4::csubstr ss, std::vector const& s); +template bool operator>= (c4::csubstr ss, std::vector const& s); +template bool operator> (c4::csubstr ss, std::vector const& s); +template bool operator<= (c4::csubstr ss, std::vector const& s); +template bool operator< (c4::csubstr ss, std::vector const& s); + +template bool operator!= (std::vector const& s, c4::csubstr ss); +template bool operator== (std::vector const& s, c4::csubstr ss); +template bool operator>= (std::vector const& s, c4::csubstr ss); +template bool operator> (std::vector const& s, c4::csubstr ss); +template bool operator<= (std::vector const& s, c4::csubstr ss); +template bool operator< (std::vector const& s, c4::csubstr ss); + +template size_t to_chars(c4::substr buf, std::vector const& s); +template bool from_chars(c4::csubstr buf, std::vector * s); + +} // namespace c4 + +// NOLINTEND(cert-dcl58-cpp) + +#endif // _C4_STD_VECTOR_FWD_HPP_ + + +// (end src/c4/std/vector_fwd.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/std/span_fwd.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_STD_SPAN_FWD_HPP_ +#define _C4_STD_SPAN_FWD_HPP_ + +/** @file span_fwd.hpp Provides forward declaration of std::span + * to enable order-independent includes for use with ref @ref + * c4::to_chars() and @ref c4::from_chars(). */ + +#ifndef C4CORE_SINGLE_HEADER +// amalgamate: removed include of +// c4/language.hpp +//#include "c4/language.hpp" +#if !defined(C4_LANGUAGE_HPP_) && !defined(_C4_LANGUAGE_HPP_) +#error "amalgamate: file c4/language.hpp must have been included at this point" +#endif /* C4_LANGUAGE_HPP_ */ + +#endif + + +#if (C4_CPP >= 20) || defined(__DOXYGEN__) + +#ifndef C4CORE_SINGLE_HEADER +// amalgamate: removed include of +// c4/substr_fwd.hpp +//#include "c4/substr_fwd.hpp" +#if !defined(C4_SUBSTR_FWD_HPP_) && !defined(_C4_SUBSTR_FWD_HPP_) +#error "amalgamate: file c4/substr_fwd.hpp must have been included at this point" +#endif /* C4_SUBSTR_FWD_HPP_ */ + +#endif + +#include // AFAICT it's not possible to fwd-declare std::span + + +namespace c4 { + +template struct is_string; +template struct is_writeable_string; + +// mark std::span as a string type +template<> struct is_string>; +template<> struct is_string>; + +// mark std::span as a string type +template<> struct is_string>; +template<> struct is_string>; +template<> struct is_writeable_string>; +template<> struct is_writeable_string>; + + +//----------------------------------------------------------------------------- + +c4::csubstr to_csubstr(std::span s) noexcept; +c4::csubstr to_csubstr(std::span s) noexcept; +c4::substr to_substr(std::span s) noexcept; + + +//----------------------------------------------------------------------------- + +bool operator== (c4::csubstr ss, std::span s); +bool operator!= (c4::csubstr ss, std::span s); +bool operator>= (c4::csubstr ss, std::span s); +bool operator> (c4::csubstr ss, std::span s); +bool operator<= (c4::csubstr ss, std::span s); +bool operator< (c4::csubstr ss, std::span s); + +bool operator== (std::span s, c4::csubstr ss); +bool operator!= (std::span s, c4::csubstr ss); +bool operator<= (std::span s, c4::csubstr ss); +bool operator< (std::span s, c4::csubstr ss); +bool operator>= (std::span s, c4::csubstr ss); +bool operator> (std::span s, c4::csubstr ss); + + +bool operator== (c4::csubstr ss, std::span s); +bool operator!= (c4::csubstr ss, std::span s); +bool operator>= (c4::csubstr ss, std::span s); +bool operator> (c4::csubstr ss, std::span s); +bool operator<= (c4::csubstr ss, std::span s); +bool operator< (c4::csubstr ss, std::span s); + +bool operator== (std::span s, c4::csubstr ss); +bool operator!= (std::span s, c4::csubstr ss); +bool operator<= (std::span s, c4::csubstr ss); +bool operator< (std::span s, c4::csubstr ss); +bool operator>= (std::span s, c4::csubstr ss); +bool operator> (std::span s, c4::csubstr ss); + + +//----------------------------------------------------------------------------- + +size_t to_chars(c4::substr buf, std::span s); +size_t to_chars(c4::substr buf, std::span s); +bool from_chars(c4::csubstr buf, std::span * s); + +} // namespace c4 + +#endif // SPAN_AVAILABLE + +#endif // _C4_STD_SPAN_FWD_HPP_ + + +// (end src/c4/std/span_fwd.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/std/string_fwd.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_STD_STRING_FWD_HPP_ +#define _C4_STD_STRING_FWD_HPP_ + +/** @file string_fwd.hpp Provides forward declaration of std::string + * to enable order-independent includes for use with ref @ref + * c4::to_chars() and @ref c4::from_chars(). */ + +#ifndef DOXYGEN + +#ifndef C4CORE_SINGLE_HEADER +// amalgamate: removed include of +// c4/substr_fwd.hpp +//#include "c4/substr_fwd.hpp" +#if !defined(C4_SUBSTR_FWD_HPP_) && !defined(_C4_SUBSTR_FWD_HPP_) +#error "amalgamate: file c4/substr_fwd.hpp must have been included at this point" +#endif /* C4_SUBSTR_FWD_HPP_ */ + +#endif + +//included above: +//#include + +// forward declarations for std::string +#if defined(__GLIBCXX__) || defined(__GLIBCPP__) +#include // use the fwd header in glibcxx +#elif defined(_LIBCPP_VERSION) || defined(__APPLE_CC__) +#include // use the fwd header in stdlibc++ +#elif defined(_MSC_VER) +// amalgamate: removed include of +// c4/error.hpp +//#include "c4/error.hpp" +#if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) +#error "amalgamate: file c4/error.hpp must have been included at this point" +#endif /* C4_ERROR_HPP_ */ + +//! @todo is there a fwd header in msvc? +namespace std { +C4_SUPPRESS_WARNING_MSVC_WITH_PUSH(4643) // Forward declaring 'char_traits' in namespace std is not permitted by the C++ Standard. +template struct char_traits; +template class allocator; +template class basic_string; +using string = basic_string, allocator>; +C4_SUPPRESS_WARNING_MSVC_POP +} /* namespace std */ +#else +#error "unknown standard library" +#endif + +namespace c4 { + +// mark std::string as a string type +template struct is_string; +template<> struct is_string; +template<> struct is_string; + +// mark std::string as a writeable string type +template struct is_writeable_string; +template<> struct is_writeable_string; + +c4::substr to_substr(std::string &s) noexcept; +c4::csubstr to_csubstr(std::string const& s) noexcept; + +bool operator== (c4::csubstr ss, std::string const& s); +bool operator!= (c4::csubstr ss, std::string const& s); +bool operator>= (c4::csubstr ss, std::string const& s); +bool operator> (c4::csubstr ss, std::string const& s); +bool operator<= (c4::csubstr ss, std::string const& s); +bool operator< (c4::csubstr ss, std::string const& s); + +bool operator== (std::string const& s, c4::csubstr ss); +bool operator!= (std::string const& s, c4::csubstr ss); +bool operator>= (std::string const& s, c4::csubstr ss); +bool operator> (std::string const& s, c4::csubstr ss); +bool operator<= (std::string const& s, c4::csubstr ss); +bool operator< (std::string const& s, c4::csubstr ss); + +size_t to_chars(c4::substr buf, std::string const& s); +bool from_chars(c4::csubstr buf, std::string * s); + +} // namespace c4 + +#endif // DOXYGEN +#endif // _C4_STD_STRING_FWD_HPP_ + + +// (end src/c4/std/string_fwd.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/std/string_view_fwd.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_STD_STRING_VIEW_FWD_HPP_ +#define _C4_STD_STRING_VIEW_FWD_HPP_ + +/** @file string_view_fwd.hpp Provides forward declaration of std::string_view + * to enable order-independent includes for use with ref @ref + * c4::to_chars() and @ref c4::from_chars(). */ + +#ifndef C4CORE_SINGLE_HEADER +// amalgamate: removed include of +// c4/language.hpp +//#include "c4/language.hpp" +#if !defined(C4_LANGUAGE_HPP_) && !defined(_C4_LANGUAGE_HPP_) +#error "amalgamate: file c4/language.hpp must have been included at this point" +#endif /* C4_LANGUAGE_HPP_ */ + +#endif + + +#if (C4_CPP >= 17) || defined(__DOXYGEN__) + +#ifndef C4CORE_SINGLE_HEADER +// amalgamate: removed include of +// c4/substr_fwd.hpp +//#include "c4/substr_fwd.hpp" +#if !defined(C4_SUBSTR_FWD_HPP_) && !defined(_C4_SUBSTR_FWD_HPP_) +#error "amalgamate: file c4/substr_fwd.hpp must have been included at this point" +#endif /* C4_SUBSTR_FWD_HPP_ */ + +#endif + +#include // AFAICT it's not possible to fwd-declare std::string_view + + +namespace c4 { + +template struct is_string; + +// mark std::string_view as a string type +template<> struct is_string; +template<> struct is_string; + + +//----------------------------------------------------------------------------- + +c4::csubstr to_csubstr(std::string_view s) noexcept; + + +//----------------------------------------------------------------------------- + +bool operator== (c4::csubstr ss, std::string_view s); +bool operator!= (c4::csubstr ss, std::string_view s); +bool operator>= (c4::csubstr ss, std::string_view s); +bool operator> (c4::csubstr ss, std::string_view s); +bool operator<= (c4::csubstr ss, std::string_view s); +bool operator< (c4::csubstr ss, std::string_view s); + +bool operator== (std::string_view s, c4::csubstr ss); +bool operator!= (std::string_view s, c4::csubstr ss); +bool operator<= (std::string_view s, c4::csubstr ss); +bool operator< (std::string_view s, c4::csubstr ss); +bool operator>= (std::string_view s, c4::csubstr ss); +bool operator> (std::string_view s, c4::csubstr ss); + + +//----------------------------------------------------------------------------- + +size_t to_chars(c4::substr buf, std::string_view s); + +} // namespace c4 + +#endif // STRING_VIEW_AVAILABLE + +#endif // _C4_STD_STRING_VIEW_FWD_HPP_ + + +// (end src/c4/std/string_view_fwd.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/std/std_fwd.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_STD_STD_FWD_HPP_ +#define _C4_STD_STD_FWD_HPP_ + +/** @file std_fwd.hpp includes all c4-std interop fwd files + * to enable order-independent includes for use with ref @ref + * c4::to_chars() and @ref c4::from_chars(). */ + +// amalgamate: removed include of +// c4/std/vector_fwd.hpp +//#include "c4/std/vector_fwd.hpp" +#if !defined(C4_STD_VECTOR_FWD_HPP_) && !defined(_C4_STD_VECTOR_FWD_HPP_) +#error "amalgamate: file c4/std/vector_fwd.hpp must have been included at this point" +#endif /* C4_STD_VECTOR_FWD_HPP_ */ + +// amalgamate: removed include of +// c4/std/string_fwd.hpp +//#include "c4/std/string_fwd.hpp" +#if !defined(C4_STD_STRING_FWD_HPP_) && !defined(_C4_STD_STRING_FWD_HPP_) +#error "amalgamate: file c4/std/string_fwd.hpp must have been included at this point" +#endif /* C4_STD_STRING_FWD_HPP_ */ + +// amalgamate: removed include of +// c4/std/span_fwd.hpp +//#include "c4/std/span_fwd.hpp" +#if !defined(C4_STD_SPAN_FWD_HPP_) && !defined(_C4_STD_SPAN_FWD_HPP_) +#error "amalgamate: file c4/std/span_fwd.hpp must have been included at this point" +#endif /* C4_STD_SPAN_FWD_HPP_ */ + +// amalgamate: removed include of +// c4/std/string_view_fwd.hpp +//#include "c4/std/string_view_fwd.hpp" +#if !defined(C4_STD_STRING_VIEW_FWD_HPP_) && !defined(_C4_STD_STRING_VIEW_FWD_HPP_) +#error "amalgamate: file c4/std/string_view_fwd.hpp must have been included at this point" +#endif /* C4_STD_STRING_VIEW_FWD_HPP_ */ + +//#include "c4/std/tuple_fwd.hpp" + +#endif // _C4_STD_STD_FWD_HPP_ + + +// (end src/c4/std/std_fwd.hpp) + +// (amalgamate) this include is needed to work around +// conditional includes in charconv.hpp +#if (defined(_MSVC_LANG) && (_MSVC_LANG >= 201703L)) || (__cplusplus >= 201703L) +#include +#endif + + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/charconv.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_CHARCONV_HPP_ +#define _C4_CHARCONV_HPP_ + +/** @file charconv.hpp Lightweight generic type-safe wrappers for + * converting individual values to/from strings. + */ + +// amalgamate: removed include of +// c4/language.hpp +//#include "c4/language.hpp" +#if !defined(C4_LANGUAGE_HPP_) && !defined(_C4_LANGUAGE_HPP_) +#error "amalgamate: file c4/language.hpp must have been included at this point" +#endif /* C4_LANGUAGE_HPP_ */ + +//included above: +//#include +//included above: +//#include +//included above: +//#include +//included above: +//#include +//included above: +//#include + +// amalgamate: removed include of +// c4/config.hpp +//#include "c4/config.hpp" +#if !defined(C4_CONFIG_HPP_) && !defined(_C4_CONFIG_HPP_) +#error "amalgamate: file c4/config.hpp must have been included at this point" +#endif /* C4_CONFIG_HPP_ */ + +// amalgamate: removed include of +// c4/substr.hpp +//#include "c4/substr.hpp" +#if !defined(C4_SUBSTR_HPP_) && !defined(_C4_SUBSTR_HPP_) +#error "amalgamate: file c4/substr.hpp must have been included at this point" +#endif /* C4_SUBSTR_HPP_ */ + +// amalgamate: removed include of +// c4/std/std_fwd.hpp +//#include "c4/std/std_fwd.hpp" +#if !defined(C4_STD_STD_FWD_HPP_) && !defined(_C4_STD_STD_FWD_HPP_) +#error "amalgamate: file c4/std/std_fwd.hpp must have been included at this point" +#endif /* C4_STD_STD_FWD_HPP_ */ + +// amalgamate: removed include of +// c4/memory_util.hpp +//#include "c4/memory_util.hpp" +#if !defined(C4_MEMORY_UTIL_HPP_) && !defined(_C4_MEMORY_UTIL_HPP_) +#error "amalgamate: file c4/memory_util.hpp must have been included at this point" +#endif /* C4_MEMORY_UTIL_HPP_ */ + + +#ifndef C4CORE_NO_FAST_FLOAT +# if (C4_CPP >= 17) +# if defined(_MSC_VER) +# if (C4_MSVC_VERSION >= C4_MSVC_VERSION_2019) // VS2017 and lower do not have these macros +//included above: +//# include +# define C4CORE_HAVE_STD_TOCHARS 1 +# define C4CORE_HAVE_STD_FROMCHARS 0 // prefer fast_float with MSVC +# define C4CORE_HAVE_FAST_FLOAT 1 +# else +# define C4CORE_HAVE_STD_TOCHARS 0 +# define C4CORE_HAVE_STD_FROMCHARS 0 +# define C4CORE_HAVE_FAST_FLOAT 1 +# endif +# else +# if __has_include() +//included above: +//# include +# if defined(__cpp_lib_to_chars) +# define C4CORE_HAVE_STD_TOCHARS 1 +# define C4CORE_HAVE_STD_FROMCHARS 0 // glibc uses fast_float internally +# define C4CORE_HAVE_FAST_FLOAT 1 +# else +# define C4CORE_HAVE_STD_TOCHARS 0 +# define C4CORE_HAVE_STD_FROMCHARS 0 +# define C4CORE_HAVE_FAST_FLOAT 1 +# endif +# else +# define C4CORE_HAVE_STD_TOCHARS 0 +# define C4CORE_HAVE_STD_FROMCHARS 0 +# define C4CORE_HAVE_FAST_FLOAT 1 +# endif +# endif +# else +# define C4CORE_HAVE_STD_TOCHARS 0 +# define C4CORE_HAVE_STD_FROMCHARS 0 +# define C4CORE_HAVE_FAST_FLOAT 1 +# endif +# if C4CORE_HAVE_FAST_FLOAT +// amalgamate: removed include of +// c4/ext/fast_float.hpp +//# include "c4/ext/fast_float.hpp" +#if !defined(C4_EXT_FAST_FLOAT_HPP_) && !defined(_C4_EXT_FAST_FLOAT_HPP_) +#error "amalgamate: file c4/ext/fast_float.hpp must have been included at this point" +#endif /* C4_EXT_FAST_FLOAT_HPP_ */ + +# endif +#elif (C4_CPP >= 17) +# define C4CORE_HAVE_FAST_FLOAT 0 +# if defined(_MSC_VER) +# if (C4_MSVC_VERSION >= C4_MSVC_VERSION_2019) // VS2017 and lower do not have these macros +//included above: +//# include +# define C4CORE_HAVE_STD_TOCHARS 1 +# define C4CORE_HAVE_STD_FROMCHARS 1 +# else +# define C4CORE_HAVE_STD_TOCHARS 0 +# define C4CORE_HAVE_STD_FROMCHARS 0 +# endif +# else +# if __has_include() +//included above: +//# include +# if defined(__cpp_lib_to_chars) +# define C4CORE_HAVE_STD_TOCHARS 1 +# define C4CORE_HAVE_STD_FROMCHARS 1 // glibc uses fast_float internally +# else +# define C4CORE_HAVE_STD_TOCHARS 0 +# define C4CORE_HAVE_STD_FROMCHARS 0 +# endif +# else +# define C4CORE_HAVE_STD_TOCHARS 0 +# define C4CORE_HAVE_STD_FROMCHARS 0 +# endif +# endif +#else +# define C4CORE_HAVE_STD_TOCHARS 0 +# define C4CORE_HAVE_STD_FROMCHARS 0 +# define C4CORE_HAVE_FAST_FLOAT 0 +#endif + + +#if !C4CORE_HAVE_STD_FROMCHARS +#include +#endif + + +#if defined(_MSC_VER) && !defined(__clang__) +# pragma warning(push) +# pragma warning(disable: 4996) // snprintf/scanf: this function or variable may be unsafe +# if C4_MSVC_VERSION != C4_MSVC_VERSION_2017 +# pragma warning(disable: 4800) //'int': forcing value to bool 'true' or 'false' (performance warning) +# endif +#elif defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wtautological-constant-out-of-range-compare" +# pragma clang diagnostic ignored "-Wformat-nonliteral" +# pragma clang diagnostic ignored "-Wdouble-promotion" // implicit conversion increases floating-point precision +# pragma clang diagnostic ignored "-Wold-style-cast" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wformat-nonliteral" +# pragma GCC diagnostic ignored "-Wdouble-promotion" // implicit conversion increases floating-point precision +# pragma GCC diagnostic ignored "-Wuseless-cast" +# pragma GCC diagnostic ignored "-Wold-style-cast" +#endif + +#if defined(__clang__) +#define C4_NO_UBSAN_IOVRFLW __attribute__((no_sanitize("signed-integer-overflow"))) +#elif defined(__GNUC__) +#if __GNUC__ > 7 +#define C4_NO_UBSAN_IOVRFLW __attribute__((no_sanitize("signed-integer-overflow"))) +#else +#define C4_NO_UBSAN_IOVRFLW +#endif +#else +#define C4_NO_UBSAN_IOVRFLW +#endif + +// NOLINTBEGIN(hicpp-signed-bitwise) + +namespace c4 { + +/** @defgroup doc_charconv Charconv utilities + * + * Lightweight, very fast generic type-safe wrappers for converting + * individual values to/from strings. These are the main generic + * functions: + * - @ref doc_to_chars and its alias @ref xtoa(): implemented by calling @ref itoa() / @ref utoa() / @ref ftoa() / @ref dtoa() (or generically @ref xtoa()) + * - @ref doc_from_chars and its alias @ref atox(): implemented by calling @ref atoi() / @ref atou() / @ref atof() / @ref atod() (or generically @ref atox()) + * - @ref to_chars_sub() + * - @ref from_chars_first() + * - @ref xtoa() and @ref atox() are implemented in terms of @ref write_dec() / @ref read_dec() et al (see @ref doc_write / @ref doc_read()) + * + * And also some modest brag is in order: these functions are really + * fast: faster even than C++17 `std::to_chars()` and + * `std::to_chars()`, and many dozens of times faster than the + * iostream abominations. + * + * For example, here are some benchmark comparisons for @ref + * doc_from_chars (link leads to the main project README, where these + * results are shown more systematically). + * + * + * + *
atox,int64_t
g++12, linux Visual Studio 2019 + *
\image html linux-x86_64-gxx12.1-Release-c4core-bm-charconv-atox-mega_bytes_per_second-i64.png \image html windows-x86_64-vs2019-Release-c4core-bm-charconv-atox-mega_bytes_per_second-i64.png + *
+ * + * + * + *
xtoa,int64_t
g++12, linux Visual Studio 2019 + *
\image html linux-x86_64-gxx12.1-Release-c4core-bm-charconv-xtoa-mega_bytes_per_second-i64.png \image html windows-x86_64-vs2019-Release-c4core-bm-charconv-xtoa-mega_bytes_per_second-i64.png + *
+ * + * To parse floating point, c4core uses + * [fastfloat](https://github.com/fastfloat/fast_float), which is + * extremely fast, by an even larger factor: + * + * + * + *
atox,float
g++12, linux Visual Studio 2019 + *
\image html linux-x86_64-gxx12.1-Release-c4core-bm-charconv-atof-mega_bytes_per_second-float.png \image html windows-x86_64-vs2019-Release-c4core-bm-charconv-atof-mega_bytes_per_second-float.png + *
+ * + * @{ + */ + +#if C4CORE_HAVE_STD_TOCHARS +/** @warning Use only the symbol. Do not rely on the type or naked value of this enum. */ +typedef enum : std::underlying_type::type { + /** print the real number in floating point format (like %f) */ + FTOA_FLOAT = static_cast::type>(std::chars_format::fixed), + /** print the real number in scientific format (like %e) */ + FTOA_SCIENT = static_cast::type>(std::chars_format::scientific), + /** print the real number in flexible format (like %g) */ + FTOA_FLEX = static_cast::type>(std::chars_format::general), + /** print the real number in hexadecimal format (like %a) */ + FTOA_HEXA = static_cast::type>(std::chars_format::hex), +} RealFormat_e; +#else +/** @warning Use only the symbol. Do not rely on the type or naked value of this enum. */ +typedef enum : char { + /** print the real number in floating point format (like %f) */ + FTOA_FLOAT = 'f', + /** print the real number in scientific format (like %e) */ + FTOA_SCIENT = 'e', + /** print the real number in flexible format (like %g) */ + FTOA_FLEX = 'g', + /** print the real number in hexadecimal format (like %a) */ + FTOA_HEXA = 'a', +} RealFormat_e; +#endif + +/** @cond dev */ +/** in some platforms, int,unsigned int + * are not any of int8_t...int64_t and + * long,unsigned long are not any of uint8_t...uint64_t */ +template +struct is_fixed_length +{ + enum : bool { + /** true if T is one of the fixed length signed types */ + value_i = (std::is_integral::value + && (std::is_same::value + || std::is_same::value + || std::is_same::value + || std::is_same::value)), + /** true if T is one of the fixed length unsigned types */ + value_u = (std::is_integral::value + && (std::is_same::value + || std::is_same::value + || std::is_same::value + || std::is_same::value)), + /** true if T is one of the fixed length signed or unsigned types */ + value = value_i || value_u + }; +}; +/** @endcond */ + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +#if defined(_MSC_VER) && !defined(__clang__) +# pragma warning(push) +#elif defined(__clang__) +# pragma clang diagnostic push +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wconversion" +# if __GNUC__ >= 6 +# pragma GCC diagnostic ignored "-Wnull-dereference" +# endif +#endif + +/** @cond dev */ +namespace detail { + +/* python command to get the values below: +def dec(v): + return str(v) +for bits in (8, 16, 32, 64): + imin, imax, umax = (-(1 << (bits - 1))), (1 << (bits - 1)) - 1, (1 << bits) - 1 + for vname, v in (("imin", imin), ("imax", imax), ("umax", umax)): + for f in (bin, oct, dec, hex): + print(f"{bits}b: {vname}={v} {f.__name__}: len={len(f(v)):2d}: {v} {f(v)}") +*/ + +// do not use the type as the template argument because in some +// platforms long!=int32 and long!=int64. Just use the numbytes +// which is more generic and spares lengthy SFINAE code. +template struct charconv_digits_; +template using charconv_digits = charconv_digits_::value>; + +template<> struct charconv_digits_<1u, true> // int8_t +{ + enum : size_t { + maxdigits_bin = 1 + 2 + 8, // -128==-0b10000000 + maxdigits_oct = 1 + 2 + 3, // -128==-0o200 + maxdigits_dec = 1 + 3, // -128 + maxdigits_hex = 1 + 2 + 2, // -128==-0x80 + maxdigits_bin_nopfx = 8, // -128==-0b10000000 + maxdigits_oct_nopfx = 3, // -128==-0o200 + maxdigits_dec_nopfx = 3, // -128 + maxdigits_hex_nopfx = 2, // -128==-0x80 + }; + // min values without sign! + static constexpr csubstr min_value_dec() noexcept { return csubstr("128"); } + static constexpr csubstr min_value_hex() noexcept { return csubstr("80"); } + static constexpr csubstr min_value_oct() noexcept { return csubstr("200"); } + static constexpr csubstr min_value_bin() noexcept { return csubstr("10000000"); } + static constexpr csubstr max_value_dec() noexcept { return csubstr("127"); } + static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 3) || (str.len == 3 && str.str[0] <= '1')); } +}; +template<> struct charconv_digits_<1u, false> // uint8_t +{ + enum : size_t { + maxdigits_bin = 2 + 8, // 255 0b11111111 + maxdigits_oct = 2 + 3, // 255 0o377 + maxdigits_dec = 3, // 255 + maxdigits_hex = 2 + 2, // 255 0xff + maxdigits_bin_nopfx = 8, // 255 0b11111111 + maxdigits_oct_nopfx = 3, // 255 0o377 + maxdigits_dec_nopfx = 3, // 255 + maxdigits_hex_nopfx = 2, // 255 0xff + }; + static constexpr csubstr max_value_dec() noexcept { return csubstr("255"); } + static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 3) || (str.len == 3 && str.str[0] <= '3')); } +}; +template<> struct charconv_digits_<2u, true> // int16_t +{ + enum : size_t { + maxdigits_bin = 1 + 2 + 16, // -32768 -0b1000000000000000 + maxdigits_oct = 1 + 2 + 6, // -32768 -0o100000 + maxdigits_dec = 1 + 5, // -32768 -32768 + maxdigits_hex = 1 + 2 + 4, // -32768 -0x8000 + maxdigits_bin_nopfx = 16, // -32768 -0b1000000000000000 + maxdigits_oct_nopfx = 6, // -32768 -0o100000 + maxdigits_dec_nopfx = 5, // -32768 -32768 + maxdigits_hex_nopfx = 4, // -32768 -0x8000 + }; + // min values without sign! + static constexpr csubstr min_value_dec() noexcept { return csubstr("32768"); } + static constexpr csubstr min_value_hex() noexcept { return csubstr("8000"); } + static constexpr csubstr min_value_oct() noexcept { return csubstr("100000"); } + static constexpr csubstr min_value_bin() noexcept { return csubstr("1000000000000000"); } + static constexpr csubstr max_value_dec() noexcept { return csubstr("32767"); } + static constexpr bool is_oct_overflow(csubstr str) noexcept { return !(str.len < 6); } +}; +template<> struct charconv_digits_<2u, false> // uint16_t +{ + enum : size_t { + maxdigits_bin = 2 + 16, // 65535 0b1111111111111111 + maxdigits_oct = 2 + 6, // 65535 0o177777 + maxdigits_dec = 6, // 65535 65535 + maxdigits_hex = 2 + 4, // 65535 0xffff + maxdigits_bin_nopfx = 16, // 65535 0b1111111111111111 + maxdigits_oct_nopfx = 6, // 65535 0o177777 + maxdigits_dec_nopfx = 6, // 65535 65535 + maxdigits_hex_nopfx = 4, // 65535 0xffff + }; + static constexpr csubstr max_value_dec() noexcept { return csubstr("65535"); } + static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 6) || (str.len == 6 && str.str[0] <= '1')); } +}; +template<> struct charconv_digits_<4u, true> // int32_t +{ + enum : size_t { + maxdigits_bin = 1 + 2 + 32, // len=35: -2147483648 -0b10000000000000000000000000000000 + maxdigits_oct = 1 + 2 + 11, // len=14: -2147483648 -0o20000000000 + maxdigits_dec = 1 + 10, // len=11: -2147483648 -2147483648 + maxdigits_hex = 1 + 2 + 8, // len=11: -2147483648 -0x80000000 + maxdigits_bin_nopfx = 32, // len=35: -2147483648 -0b10000000000000000000000000000000 + maxdigits_oct_nopfx = 11, // len=14: -2147483648 -0o20000000000 + maxdigits_dec_nopfx = 10, // len=11: -2147483648 -2147483648 + maxdigits_hex_nopfx = 8, // len=11: -2147483648 -0x80000000 + }; + // min values without sign! + static constexpr csubstr min_value_dec() noexcept { return csubstr("2147483648"); } + static constexpr csubstr min_value_hex() noexcept { return csubstr("80000000"); } + static constexpr csubstr min_value_oct() noexcept { return csubstr("20000000000"); } + static constexpr csubstr min_value_bin() noexcept { return csubstr("10000000000000000000000000000000"); } + static constexpr csubstr max_value_dec() noexcept { return csubstr("2147483647"); } + static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 11) || (str.len == 11 && str.str[0] <= '1')); } +}; +template<> struct charconv_digits_<4u, false> // uint32_t +{ + enum : size_t { + maxdigits_bin = 2 + 32, // len=34: 4294967295 0b11111111111111111111111111111111 + maxdigits_oct = 2 + 11, // len=13: 4294967295 0o37777777777 + maxdigits_dec = 10, // len=10: 4294967295 4294967295 + maxdigits_hex = 2 + 8, // len=10: 4294967295 0xffffffff + maxdigits_bin_nopfx = 32, // len=34: 4294967295 0b11111111111111111111111111111111 + maxdigits_oct_nopfx = 11, // len=13: 4294967295 0o37777777777 + maxdigits_dec_nopfx = 10, // len=10: 4294967295 4294967295 + maxdigits_hex_nopfx = 8, // len=10: 4294967295 0xffffffff + }; + static constexpr csubstr max_value_dec() noexcept { return csubstr("4294967295"); } + static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 11) || (str.len == 11 && str.str[0] <= '3')); } +}; +template<> struct charconv_digits_<8u, true> // int64_t +{ + enum : size_t { + maxdigits_bin = 1 + 2 + 64, // len=67: -9223372036854775808 -0b1000000000000000000000000000000000000000000000000000000000000000 + maxdigits_oct = 1 + 2 + 22, // len=25: -9223372036854775808 -0o1000000000000000000000 + maxdigits_dec = 1 + 19, // len=20: -9223372036854775808 -9223372036854775808 + maxdigits_hex = 1 + 2 + 16, // len=19: -9223372036854775808 -0x8000000000000000 + maxdigits_bin_nopfx = 64, // len=67: -9223372036854775808 -0b1000000000000000000000000000000000000000000000000000000000000000 + maxdigits_oct_nopfx = 22, // len=25: -9223372036854775808 -0o1000000000000000000000 + maxdigits_dec_nopfx = 19, // len=20: -9223372036854775808 -9223372036854775808 + maxdigits_hex_nopfx = 16, // len=19: -9223372036854775808 -0x8000000000000000 + }; + static constexpr csubstr min_value_dec() noexcept { return csubstr("9223372036854775808"); } + static constexpr csubstr min_value_hex() noexcept { return csubstr("8000000000000000"); } + static constexpr csubstr min_value_oct() noexcept { return csubstr("1000000000000000000000"); } + static constexpr csubstr min_value_bin() noexcept { return csubstr("1000000000000000000000000000000000000000000000000000000000000000"); } + static constexpr csubstr max_value_dec() noexcept { return csubstr("9223372036854775807"); } + static constexpr bool is_oct_overflow(csubstr str) noexcept { return !(str.len < 22); } +}; +template<> struct charconv_digits_<8u, false> // uint64_t +{ + enum : size_t { + maxdigits_bin = 2 + 64, // len=66: 18446744073709551615 0b1111111111111111111111111111111111111111111111111111111111111111 + maxdigits_oct = 2 + 22, // len=24: 18446744073709551615 0o1777777777777777777777 + maxdigits_dec = 20, // len=20: 18446744073709551615 18446744073709551615 + maxdigits_hex = 2 + 16, // len=18: 18446744073709551615 0xffffffffffffffff + maxdigits_bin_nopfx = 64, // len=66: 18446744073709551615 0b1111111111111111111111111111111111111111111111111111111111111111 + maxdigits_oct_nopfx = 22, // len=24: 18446744073709551615 0o1777777777777777777777 + maxdigits_dec_nopfx = 20, // len=20: 18446744073709551615 18446744073709551615 + maxdigits_hex_nopfx = 16, // len=18: 18446744073709551615 0xffffffffffffffff + }; + static constexpr csubstr max_value_dec() noexcept { return csubstr("18446744073709551615"); } + static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 22) || (str.len == 22 && str.str[0] <= '1')); } +}; +} // namespace detail + +// Helper macros, undefined below +#define _c4append(c) { if(C4_LIKELY(pos < buf.len)) { buf.str[pos++] = static_cast(c); } else { ++pos; } } +#define _c4appendhex(i) { if(C4_LIKELY(pos < buf.len)) { buf.str[pos++] = hexchars[i]; } else { ++pos; } } + +/** @endcond */ + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + + +/** @defgroup doc_digits Get number of digits + * + * @note At first sight this code may look heavily branchy and + * therefore inefficient. However, measurements revealed this to be + * the fastest among the alternatives. + * + * @see https://github.com/biojppm/c4core/pull/77 + * + * @{ + */ + +/** decimal digits for 8 bit integers */ +template +C4_CONSTEXPR14 C4_ALWAYS_INLINE +auto digits_dec(T v) noexcept + -> typename std::enable_if::type +{ + C4_STATIC_ASSERT(std::is_integral::value); + C4_ASSERT(v >= 0); + return ((v >= 100) ? 3u : ((v >= 10) ? 2u : 1u)); +} + +/** decimal digits for 16 bit integers */ +template +C4_CONSTEXPR14 C4_ALWAYS_INLINE +auto digits_dec(T v) noexcept + -> typename std::enable_if::type +{ + C4_STATIC_ASSERT(std::is_integral::value); + C4_ASSERT(v >= 0); + return ((v >= 10000) ? 5u : (v >= 1000) ? 4u : (v >= 100) ? 3u : (v >= 10) ? 2u : 1u); +} + +/** decimal digits for 32 bit integers */ +template +C4_CONSTEXPR14 C4_ALWAYS_INLINE +auto digits_dec(T v) noexcept + -> typename std::enable_if::type +{ + C4_STATIC_ASSERT(std::is_integral::value); + C4_ASSERT(v >= 0); + return ((v >= 1000000000) ? 10u : (v >= 100000000) ? 9u : (v >= 10000000) ? 8u : + (v >= 1000000) ? 7u : (v >= 100000) ? 6u : (v >= 10000) ? 5u : + (v >= 1000) ? 4u : (v >= 100) ? 3u : (v >= 10) ? 2u : 1u); +} + +/** decimal digits for 64 bit integers */ +template +C4_CONSTEXPR14 C4_ALWAYS_INLINE +auto digits_dec(T v) noexcept + -> typename std::enable_if::type +{ + // thanks @fargies!!! + // https://github.com/biojppm/c4core/pull/77#issuecomment-1063753568 + C4_STATIC_ASSERT(std::is_integral::value); + C4_ASSERT(v >= 0); + if(v >= 1000000000) // 10 + { + if(v >= 100000000000000) // 15 [15-20] range + { + if(v >= 100000000000000000) // 18 (15 + (20 - 15) / 2) + { + if((typename std::make_unsigned::type)v >= 10000000000000000000u) // 20 + return 20u; + else + return (v >= 1000000000000000000) ? 19u : 18u; + } + else if(v >= 10000000000000000) // 17 + { + return 17u; + } + else + { + return(v >= 1000000000000000) ? 16u : 15u; + } + } + else if(v >= 1000000000000) // 13 + { + return (v >= 10000000000000) ? 14u : 13u; + } + else if(v >= 100000000000) // 12 + { + return 12; + } + else + { + return(v >= 10000000000) ? 11u : 10u; + } + } + else if(v >= 10000) // 5 [5-9] range + { + if(v >= 10000000) // 8 + return (v >= 100000000) ? 9u : 8u; + else if(v >= 1000000) // 7 + return 7; + else + return (v >= 100000) ? 6u : 5u; + } + else if(v >= 100) + { + return (v >= 1000) ? 4u : 3u; + } + else + { + return (v >= 10) ? 2u : 1u; + } +} + + +/** return the number of digits required to encode an hexadecimal number. */ +template +C4_CONSTEXPR14 C4_ALWAYS_INLINE unsigned digits_hex(T v) noexcept +{ + C4_STATIC_ASSERT(std::is_integral::value); + C4_ASSERT(v >= 0); + return v ? 1u + (msb((typename std::make_unsigned::type)v) >> 2u) : 1u; +} + +/** return the number of digits required to encode a binary number. */ +template +C4_CONSTEXPR14 C4_ALWAYS_INLINE unsigned digits_bin(T v) noexcept +{ + C4_STATIC_ASSERT(std::is_integral::value); + C4_ASSERT(v >= 0); + return v ? 1u + msb((typename std::make_unsigned::type)v) : 1u; +} + +/** return the number of digits required to encode an octal number. */ +template +C4_CONSTEXPR14 C4_ALWAYS_INLINE unsigned digits_oct(T v_) noexcept +{ + // TODO: is there a better way? + C4_STATIC_ASSERT(std::is_integral::value); + C4_ASSERT(v_ >= 0); + using U = typename std::conditional::type>::type; + U v = (U) v_; // safe because we require v_ >= 0 // NOLINT + uint32_t __n = 1; + enum : U { + __b2 = 64u, + __b3 = 64u * 8u, + __b4 = 64u * 8u * 8u, + }; + while(true) + { + if(v < 8u) + return __n; + else if(v < __b2) + return __n + 1; + else if(v < __b3) + return __n + 2; + else if(v < __b4) + return __n + 3; + v /= (U) __b4; + __n += 4; + } +} + +/** @} */ + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** @cond dev */ +namespace detail { +C4_INLINE_CONSTEXPR const char hexchars[] = "0123456789abcdef"; +C4_INLINE_CONSTEXPR const char digits0099[] = + "0001020304050607080910111213141516171819" + "2021222324252627282930313233343536373839" + "4041424344454647484950515253545556575859" + "6061626364656667686970717273747576777879" + "8081828384858687888990919293949596979899"; +} // namespace detail +/** @endcond */ + +C4_SUPPRESS_WARNING_GCC_PUSH +C4_SUPPRESS_WARNING_GCC("-Warray-bounds") // gcc has false positives here +#if (defined(__GNUC__) && (__GNUC__ >= 7)) +C4_SUPPRESS_WARNING_GCC("-Wstringop-overflow") // gcc has false positives here +#endif + +/** @defgroup doc_write_unchecked Write with known number of digits + * + * Writes a value without checking the buffer length with regards to + * the required number of digits to encode the value. It is the + * responsibility of the caller to ensure that the provided number of + * digits is enough to write the given value. Notwithstanding the + * name, assertions are liberally performed, so this code is safe. + * + * @{ */ + +template +C4_HOT C4_ALWAYS_INLINE +void write_dec_unchecked(substr buf, T v, unsigned digits_v) noexcept +{ + C4_STATIC_ASSERT(std::is_integral::value); + C4_ASSERT(v >= 0); + C4_ASSERT(buf.len >= digits_v); + C4_XASSERT(digits_v == digits_dec(v)); + // in bm_xtoa: checkoncelog_singlediv_write2 + while(v >= T(100)) + { + T quo = v; + quo /= T(100); + const auto num = (v - quo * T(100)) << 1u; // NOLINT + v = quo; + buf.str[--digits_v] = detail::digits0099[num + 1]; + buf.str[--digits_v] = detail::digits0099[num]; + } + if(v >= T(10)) + { + C4_ASSERT(digits_v == 2); + const auto num = v << 1u; + buf.str[1] = detail::digits0099[num + 1]; + buf.str[0] = detail::digits0099[num]; + } + else + { + C4_ASSERT(digits_v == 1); + buf.str[0] = (char)('0' + v); + } +} + + +template +C4_HOT C4_ALWAYS_INLINE +void write_hex_unchecked(substr buf, T v, unsigned digits_v) noexcept +{ + C4_STATIC_ASSERT(std::is_integral::value); + C4_ASSERT(v >= 0); + C4_ASSERT(buf.len >= digits_v); + C4_XASSERT(digits_v == digits_hex(v)); + do { + buf.str[--digits_v] = detail::hexchars[v & T(15)]; + v >>= 4; + } while(v); + C4_ASSERT(digits_v == 0); +} + + +template +C4_HOT C4_ALWAYS_INLINE +void write_oct_unchecked(substr buf, T v, unsigned digits_v) noexcept +{ + C4_STATIC_ASSERT(std::is_integral::value); + C4_ASSERT(v >= 0); + C4_ASSERT(buf.len >= digits_v); + C4_XASSERT(digits_v == digits_oct(v)); + do { + buf.str[--digits_v] = (char)('0' + (v & T(7))); + v >>= 3; + } while(v); + C4_ASSERT(digits_v == 0); +} + + +template +C4_HOT C4_ALWAYS_INLINE +void write_bin_unchecked(substr buf, T v, unsigned digits_v) noexcept +{ + C4_STATIC_ASSERT(std::is_integral::value); + C4_ASSERT(v >= 0); + C4_ASSERT(buf.len >= digits_v); + C4_XASSERT(digits_v == digits_bin(v)); + do { + buf.str[--digits_v] = (char)('0' + (v & T(1))); + v >>= 1; + } while(v); + C4_ASSERT(digits_v == 0); +} + +/** @} */ // write_unchecked + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** @defgroup doc_write Write a value + * + * Writes a value without checking the buffer length + * decimal number -- but asserting. + * + * @{ */ + +/** write an integer to a string in decimal format. This is the + * lowest level (and the fastest) function to do this task. + * @note does not accept negative numbers + * @note the resulting string is NOT zero-terminated. + * @note it is ok to call this with an empty or too-small buffer; + * no writes will occur, and the required size will be returned + * @return the number of characters required for the buffer. */ +template +C4_ALWAYS_INLINE size_t write_dec(substr buf, T v) noexcept +{ + C4_STATIC_ASSERT(std::is_integral::value); + C4_ASSERT(v >= 0); + unsigned digits = digits_dec(v); + if(C4_LIKELY(buf.len >= digits)) + write_dec_unchecked(buf, v, digits); + return digits; +} + +/** write an integer to a string in hexadecimal format. This is the + * lowest level (and the fastest) function to do this task. + * @note does not accept negative numbers + * @note does not prefix with 0x + * @note the resulting string is NOT zero-terminated. + * @note it is ok to call this with an empty or too-small buffer; + * no writes will occur, and the required size will be returned + * @return the number of characters required for the buffer. */ +template +C4_ALWAYS_INLINE size_t write_hex(substr buf, T v) noexcept +{ + C4_STATIC_ASSERT(std::is_integral::value); + C4_ASSERT(v >= 0); + unsigned digits = digits_hex(v); + if(C4_LIKELY(buf.len >= digits)) + write_hex_unchecked(buf, v, digits); + return digits; +} + +/** write an integer to a string in octal format. This is the + * lowest level (and the fastest) function to do this task. + * @note does not accept negative numbers + * @note does not prefix with 0o + * @note the resulting string is NOT zero-terminated. + * @note it is ok to call this with an empty or too-small buffer; + * no writes will occur, and the required size will be returned + * @return the number of characters required for the buffer. */ +template +C4_ALWAYS_INLINE size_t write_oct(substr buf, T v) noexcept +{ + C4_STATIC_ASSERT(std::is_integral::value); + C4_ASSERT(v >= 0); + unsigned digits = digits_oct(v); + if(C4_LIKELY(buf.len >= digits)) + write_oct_unchecked(buf, v, digits); + return digits; +} + +/** write an integer to a string in binary format. This is the + * lowest level (and the fastest) function to do this task. + * @note does not accept negative numbers + * @note does not prefix with 0b + * @note the resulting string is NOT zero-terminated. + * @note it is ok to call this with an empty or too-small buffer; + * no writes will occur, and the required size will be returned + * @return the number of characters required for the buffer. */ +template +C4_ALWAYS_INLINE size_t write_bin(substr buf, T v) noexcept +{ + C4_STATIC_ASSERT(std::is_integral::value); + C4_ASSERT(v >= 0); + unsigned digits = digits_bin(v); + C4_ASSERT(digits > 0); + if(C4_LIKELY(buf.len >= digits)) + write_bin_unchecked(buf, v, digits); + return digits; +} + + +/** @cond dev */ +namespace detail { +template using NumberWriter = size_t (*)(substr, U); +template writer> +size_t write_num_digits(substr buf, T v, size_t num_digits) noexcept +{ + C4_STATIC_ASSERT(std::is_integral::value); + const size_t ret = writer(buf, v); + if(ret >= num_digits) + return ret; + else if(ret >= buf.len || num_digits > buf.len) + return num_digits; + C4_ASSERT(num_digits >= ret); + const size_t delta = static_cast(num_digits - ret); // NOLINT + C4_ASSERT(ret + delta <= buf.len); + if(ret) + memmove(buf.str + delta, buf.str, ret); + if(delta) + memset(buf.str, '0', delta); + return num_digits; +} +} // namespace detail +/** @endcond */ + + +/** same as c4::write_dec(), but pad with zeroes on the left such that + * the resulting string is @p num_digits wide. If the given number + * requires more than num_digits, then the number prevails. */ +template +C4_ALWAYS_INLINE size_t write_dec(substr buf, T val, size_t num_digits) noexcept +{ + return detail::write_num_digits>(buf, val, num_digits); +} + +/** same as c4::write_hex(), but pad with zeroes on the left such that + * the resulting string is @p num_digits wide. If the given number + * requires more than num_digits, then the number prevails. */ +template +C4_ALWAYS_INLINE size_t write_hex(substr buf, T val, size_t num_digits) noexcept +{ + return detail::write_num_digits>(buf, val, num_digits); +} + +/** same as c4::write_bin(), but pad with zeroes on the left such that + * the resulting string is @p num_digits wide. If the given number + * requires more than num_digits, then the number prevails. */ +template +C4_ALWAYS_INLINE size_t write_bin(substr buf, T val, size_t num_digits) noexcept +{ + return detail::write_num_digits>(buf, val, num_digits); +} + +/** same as c4::write_oct(), but pad with zeroes on the left such that + * the resulting string is @p num_digits wide. If the given number + * requires more than num_digits, then the number prevails. */ +template +C4_ALWAYS_INLINE size_t write_oct(substr buf, T val, size_t num_digits) noexcept +{ + return detail::write_num_digits>(buf, val, num_digits); +} + +/** @} */ // write + +C4_SUPPRESS_WARNING_GCC_POP + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + + +C4_SUPPRESS_WARNING_MSVC_PUSH +C4_SUPPRESS_WARNING_MSVC(4365) // '=': conversion from 'int' to 'I', signed/unsigned mismatch + +/** @defgroup doc_read Read a value + * + * @{ */ + +/** read a decimal integer from a string. This is the + * lowest level (and the fastest) function to do this task. + * @note does not accept negative numbers + * @note The string must be trimmed. Whitespace is not accepted. + * @note the string must not be empty + * @note there is no check for overflow; the value wraps around + * in a way similar to the standard C/C++ overflow behavior. + * For example, `read_dec("128", &val)` returns true + * and val will be set to 0 because 127 is the max i8 value. + * @see overflows() to find out if a number string overflows a type range + * @return true if the conversion was successful (no overflow check) */ +template +C4_NO_UBSAN_IOVRFLW +C4_ALWAYS_INLINE bool read_dec(csubstr s, I *C4_RESTRICT v) noexcept +{ + C4_STATIC_ASSERT(std::is_integral::value); + C4_ASSERT(!s.empty()); + *v = 0; + for(char c : s) + { + if(C4_UNLIKELY(c < '0' || c > '9')) + return false; + *v = ((*v) * I(10)) + (I(c) - I('0')); + } + return true; +} + +/** read an hexadecimal integer from a string. This is the + * lowest level (and the fastest) function to do this task. + * @note does not accept negative numbers + * @note does not accept leading 0x or 0X + * @note the string must not be empty + * @note the string must be trimmed. Whitespace is not accepted. + * @note there is no check for overflow; the value wraps around + * in a way similar to the standard C/C++ overflow behavior. + * For example, `read_hex("80", &val)` returns true + * and val will be set to 0 because 7f is the max i8 value. + * @see overflows() to find out if a number string overflows a type range + * @return true if the conversion was successful (no overflow check) */ +template +C4_NO_UBSAN_IOVRFLW +C4_ALWAYS_INLINE bool read_hex(csubstr s, I *C4_RESTRICT v) noexcept +{ + C4_STATIC_ASSERT(std::is_integral::value); + C4_ASSERT(!s.empty()); + *v = 0; + for(char c : s) + { + I cv; + if(c >= '0' && c <= '9') + cv = I(c) - I('0'); + else if(c >= 'a' && c <= 'f') + cv = I(10) + (I(c) - I('a')); + else if(c >= 'A' && c <= 'F') + cv = I(10) + (I(c) - I('A')); + else + return false; + *v = ((*v) * I(16)) + cv; + } + return true; +} + +/** read a binary integer from a string. This is the + * lowest level (and the fastest) function to do this task. + * @note does not accept negative numbers + * @note does not accept leading 0b or 0B + * @note the string must not be empty + * @note the string must be trimmed. Whitespace is not accepted. + * @note there is no check for overflow; the value wraps around + * in a way similar to the standard C/C++ overflow behavior. + * For example, `read_bin("10000000", &val)` returns true + * and val will be set to 0 because 1111111 is the max i8 value. + * @see overflows() to find out if a number string overflows a type range + * @return true if the conversion was successful (no overflow check) */ +template +C4_NO_UBSAN_IOVRFLW +C4_ALWAYS_INLINE bool read_bin(csubstr s, I *C4_RESTRICT v) noexcept +{ + C4_STATIC_ASSERT(std::is_integral::value); + C4_ASSERT(!s.empty()); + *v = 0; + for(char c : s) + { + *v <<= 1; + if(c == '1') + *v |= 1; + else if(c != '0') + return false; + } + return true; +} + +/** read an octal integer from a string. This is the + * lowest level (and the fastest) function to do this task. + * @note does not accept negative numbers + * @note does not accept leading 0o or 0O + * @note the string must not be empty + * @note the string must be trimmed. Whitespace is not accepted. + * @note there is no check for overflow; the value wraps around + * in a way similar to the standard C/C++ overflow behavior. + * For example, `read_oct("200", &val)` returns true + * and val will be set to 0 because 177 is the max i8 value. + * @see overflows() to find out if a number string overflows a type range + * @return true if the conversion was successful (no overflow check) */ +template +C4_NO_UBSAN_IOVRFLW +C4_ALWAYS_INLINE bool read_oct(csubstr s, I *C4_RESTRICT v) noexcept +{ + C4_STATIC_ASSERT(std::is_integral::value); + C4_ASSERT(!s.empty()); + *v = 0; + for(char c : s) + { + if(C4_UNLIKELY(c < '0' || c > '7')) + return false; + *v = ((*v) * I(8)) + (I(c) - I('0')); + } + return true; +} + +/** @} */ + +C4_SUPPRESS_WARNING_MSVC_POP + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +C4_SUPPRESS_WARNING_GCC_WITH_PUSH("-Wswitch-default") + +/** @cond dev */ +namespace detail { +inline size_t _itoa2buf(substr buf, size_t pos, csubstr val) noexcept +{ + C4_ASSERT(pos < buf.len); + C4_ASSERT(pos + val.len <= buf.len); + C4_ASSERT(val.len > 0); + memcpy(buf.str + pos, val.str, val.len); + return pos + val.len; +} +inline size_t _itoa2bufwithdigits(substr buf, size_t pos, size_t num_digits, csubstr val) noexcept +{ + num_digits = num_digits > val.len ? num_digits - val.len : 0; + C4_ASSERT(num_digits + val.len <= buf.len); + for(size_t i = 0; i < num_digits; ++i) + _c4append('0'); + return detail::_itoa2buf(buf, pos, val); +} +template +C4_NO_INLINE size_t _itoadec2buf(substr buf) noexcept +{ + using digits_type = detail::charconv_digits; + if(C4_UNLIKELY(buf.len < digits_type::maxdigits_dec)) + return digits_type::maxdigits_dec; + buf.str[0] = '-'; + return detail::_itoa2buf(buf, 1, digits_type::min_value_dec()); +} +template +C4_NO_INLINE size_t _itoa2buf(substr buf, I radix) noexcept +{ + using digits_type = detail::charconv_digits; + size_t pos = 0; + if(C4_LIKELY(buf.len > 0)) + buf.str[pos++] = '-'; + switch(radix) // NOLINT(hicpp-multiway-paths-covered) + { + case I(10): + if(C4_UNLIKELY(buf.len < digits_type::maxdigits_dec)) + return digits_type::maxdigits_dec; + pos =_itoa2buf(buf, pos, digits_type::min_value_dec()); + break; + case I(16): + if(C4_UNLIKELY(buf.len < digits_type::maxdigits_hex)) + return digits_type::maxdigits_hex; + buf.str[pos++] = '0'; + buf.str[pos++] = 'x'; + pos = _itoa2buf(buf, pos, digits_type::min_value_hex()); + break; + case I( 2): + if(C4_UNLIKELY(buf.len < digits_type::maxdigits_bin)) + return digits_type::maxdigits_bin; + buf.str[pos++] = '0'; + buf.str[pos++] = 'b'; + pos = _itoa2buf(buf, pos, digits_type::min_value_bin()); + break; + case I( 8): + if(C4_UNLIKELY(buf.len < digits_type::maxdigits_oct)) + return digits_type::maxdigits_oct; + buf.str[pos++] = '0'; + buf.str[pos++] = 'o'; + pos = _itoa2buf(buf, pos, digits_type::min_value_oct()); + break; + } + return pos; +} +template +C4_NO_INLINE size_t _itoa2buf(substr buf, I radix, size_t num_digits) noexcept +{ + using digits_type = detail::charconv_digits; + size_t pos = 0; + size_t needed_digits = 0; + if(C4_LIKELY(buf.len > 0)) + buf.str[pos++] = '-'; + switch(radix) // NOLINT(hicpp-multiway-paths-covered) + { + case I(10): + // add 1 to account for - + needed_digits = num_digits+1 > digits_type::maxdigits_dec ? num_digits+1 : digits_type::maxdigits_dec; + if(C4_UNLIKELY(buf.len < needed_digits)) + return needed_digits; + pos = _itoa2bufwithdigits(buf, pos, num_digits, digits_type::min_value_dec()); + break; + case I(16): + // add 3 to account for -0x + needed_digits = num_digits+3 > digits_type::maxdigits_hex ? num_digits+3 : digits_type::maxdigits_hex; + if(C4_UNLIKELY(buf.len < needed_digits)) + return needed_digits; + buf.str[pos++] = '0'; + buf.str[pos++] = 'x'; + pos = _itoa2bufwithdigits(buf, pos, num_digits, digits_type::min_value_hex()); + break; + case I(2): + // add 3 to account for -0b + needed_digits = num_digits+3 > digits_type::maxdigits_bin ? num_digits+3 : digits_type::maxdigits_bin; + if(C4_UNLIKELY(buf.len < needed_digits)) + return needed_digits; + C4_ASSERT(buf.len >= digits_type::maxdigits_bin); + buf.str[pos++] = '0'; + buf.str[pos++] = 'b'; + pos = _itoa2bufwithdigits(buf, pos, num_digits, digits_type::min_value_bin()); + break; + case I(8): + // add 3 to account for -0o + needed_digits = num_digits+3 > digits_type::maxdigits_oct ? num_digits+3 : digits_type::maxdigits_oct; + if(C4_UNLIKELY(buf.len < needed_digits)) + return needed_digits; + C4_ASSERT(buf.len >= digits_type::maxdigits_oct); + buf.str[pos++] = '0'; + buf.str[pos++] = 'o'; + pos = _itoa2bufwithdigits(buf, pos, num_digits, digits_type::min_value_oct()); + break; + } + return pos; +} +} // namespace detail +/** @endcond */ + + +/** @defgroup doc_itoa itoa: signed to chars + * + * @{ */ + +/** convert an integral signed decimal to a string. + * @note the resulting string is NOT zero-terminated. + * @note it is ok to call this with an empty or too-small buffer; + * no writes will occur, and the needed size will be returned + * @return the number of characters required for the buffer. */ +template +C4_ALWAYS_INLINE size_t itoa(substr buf, T v) noexcept +{ + C4_STATIC_ASSERT(std::is_signed::value); + if(v >= T(0)) + { + // write_dec() checks the buffer size, so no need to check here + return write_dec(buf, v); + } + // when T is the min value (eg i8: -128), negating it + // will overflow, so treat the min as a special case + if(C4_LIKELY(v != std::numeric_limits::min())) + { + v = (T)-v; + unsigned digits = digits_dec(v); + if(C4_LIKELY(buf.len >= digits + 1u)) + { + buf.str[0] = '-'; + write_dec_unchecked(buf.sub(1), v, digits); + } + return digits + 1u; + } + return detail::_itoadec2buf(buf); +} + +/** convert an integral signed integer to a string, using a specific + * radix. The radix must be 2, 8, 10 or 16. + * + * @note the resulting string is NOT zero-terminated. + * @note it is ok to call this with an empty or too-small buffer; + * no writes will occur, and the needed size will be returned + * @return the number of characters required for the buffer. */ +template +C4_ALWAYS_INLINE size_t itoa(substr buf, T v, T radix) noexcept +{ + C4_STATIC_ASSERT(std::is_signed::value); + C4_ASSERT(radix == 2 || radix == 8 || radix == 10 || radix == 16); + C4_SUPPRESS_WARNING_GCC_PUSH + #if (defined(__GNUC__) && (__GNUC__ >= 7)) + C4_SUPPRESS_WARNING_GCC("-Wstringop-overflow") // gcc has a false positive here + #endif + // when T is the min value (eg i8: -128), negating it + // will overflow, so treat the min as a special case + if(C4_LIKELY(v != std::numeric_limits::min())) + { + unsigned pos = 0; + if(v < 0) + { + v = (T)-v; + if(C4_LIKELY(buf.len > 0)) + buf.str[pos] = '-'; + ++pos; + } + unsigned digits = 0; + switch(radix) // NOLINT(hicpp-multiway-paths-covered) + { + case T(10): + digits = digits_dec(v); + if(C4_LIKELY(buf.len >= pos + digits)) + write_dec_unchecked(buf.sub(pos), v, digits); + break; + case T(16): + digits = digits_hex(v); + if(C4_LIKELY(buf.len >= pos + 2u + digits)) + { + buf.str[pos + 0] = '0'; + buf.str[pos + 1] = 'x'; + write_hex_unchecked(buf.sub(pos + 2), v, digits); + } + digits += 2u; + break; + case T(2): + digits = digits_bin(v); + if(C4_LIKELY(buf.len >= pos + 2u + digits)) + { + buf.str[pos + 0] = '0'; + buf.str[pos + 1] = 'b'; + write_bin_unchecked(buf.sub(pos + 2), v, digits); + } + digits += 2u; + break; + case T(8): + digits = digits_oct(v); + if(C4_LIKELY(buf.len >= pos + 2u + digits)) + { + buf.str[pos + 0] = '0'; + buf.str[pos + 1] = 'o'; + write_oct_unchecked(buf.sub(pos + 2), v, digits); + } + digits += 2u; + break; + } + return pos + digits; + } + C4_SUPPRESS_WARNING_GCC_POP + // when T is the min value (eg i8: -128), negating it + // will overflow + return detail::_itoa2buf(buf, radix); +} + + +/** same as c4::itoa(), but pad with zeroes on the left such that the + * resulting string is @p num_digits wide, not accounting for radix + * prefix (0x,0o,0b). The @p radix must be 2, 8, 10 or 16. + * + * @note the resulting string is NOT zero-terminated. + * @note it is ok to call this with an empty or too-small buffer; + * no writes will occur, and the needed size will be returned + * @return the number of characters required for the buffer. */ +template +C4_ALWAYS_INLINE size_t itoa(substr buf, T v, T radix, size_t num_digits) noexcept +{ + C4_STATIC_ASSERT(std::is_signed::value); + C4_ASSERT(radix == 2 || radix == 8 || radix == 10 || radix == 16); + C4_SUPPRESS_WARNING_GCC_PUSH + #if (defined(__GNUC__) && (__GNUC__ >= 7)) + C4_SUPPRESS_WARNING_GCC("-Wstringop-overflow") // gcc has a false positive here + #endif + // when T is the min value (eg i8: -128), negating it + // will overflow, so treat the min as a special case + if(C4_LIKELY(v != std::numeric_limits::min())) + { + unsigned pos = 0; + if(v < 0) + { + v = (T)-v; + if(C4_LIKELY(buf.len > 0)) + buf.str[pos] = '-'; + ++pos; + } + unsigned total_digits = 0; + switch(radix) // NOLINT(hicpp-multiway-paths-covered) + { + case T(10): + total_digits = digits_dec(v); + total_digits = pos + (unsigned)(num_digits > total_digits ? num_digits : total_digits); + if(C4_LIKELY(buf.len >= total_digits)) + write_dec(buf.sub(pos), v, num_digits); + break; + case T(16): + total_digits = digits_hex(v); + total_digits = pos + 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits); + if(C4_LIKELY(buf.len >= total_digits)) + { + buf.str[pos + 0] = '0'; + buf.str[pos + 1] = 'x'; + write_hex(buf.sub(pos + 2), v, num_digits); + } + break; + case T(2): + total_digits = digits_bin(v); + total_digits = pos + 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits); + if(C4_LIKELY(buf.len >= total_digits)) + { + buf.str[pos + 0] = '0'; + buf.str[pos + 1] = 'b'; + write_bin(buf.sub(pos + 2), v, num_digits); + } + break; + case T(8): + total_digits = digits_oct(v); + total_digits = pos + 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits); + if(C4_LIKELY(buf.len >= total_digits)) + { + buf.str[pos + 0] = '0'; + buf.str[pos + 1] = 'o'; + write_oct(buf.sub(pos + 2), v, num_digits); + } + break; + } + return total_digits; + } + C4_SUPPRESS_WARNING_GCC_POP + // when T is the min value (eg i8: -128), negating it + // will overflow + return detail::_itoa2buf(buf, radix, num_digits); +} + +/** @} */ + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** @defgroup doc_utoa utoa: unsigned to chars + * + * @{ */ + +/** convert an integral unsigned decimal to a string. + * + * @note the resulting string is NOT zero-terminated. + * @note it is ok to call this with an empty or too-small buffer; + * no writes will occur, and the needed size will be returned + * @return the number of characters required for the buffer. */ +template +C4_ALWAYS_INLINE size_t utoa(substr buf, T v) noexcept +{ + C4_STATIC_ASSERT(std::is_unsigned::value); + // write_dec() does the buffer length check, so no need to check here + return write_dec(buf, v); +} + +/** convert an integral unsigned integer to a string, using a specific + * radix. The radix must be 2, 8, 10 or 16. + * + * @note the resulting string is NOT zero-terminated. + * @note it is ok to call this with an empty or too-small buffer; + * no writes will occur, and the needed size will be returned + * @return the number of characters required for the buffer. */ +template +C4_ALWAYS_INLINE size_t utoa(substr buf, T v, T radix) noexcept +{ + C4_STATIC_ASSERT(std::is_unsigned::value); + C4_ASSERT(radix == 10 || radix == 16 || radix == 2 || radix == 8); + unsigned digits = 0; + switch(radix) // NOLINT(hicpp-multiway-paths-covered) + { + case T(10): + digits = digits_dec(v); + if(C4_LIKELY(buf.len >= digits)) + write_dec_unchecked(buf, v, digits); + break; + case T(16): + digits = digits_hex(v); + if(C4_LIKELY(buf.len >= digits+2u)) + { + buf.str[0] = '0'; + buf.str[1] = 'x'; + write_hex_unchecked(buf.sub(2), v, digits); + } + digits += 2u; + break; + case T(2): + digits = digits_bin(v); + if(C4_LIKELY(buf.len >= digits+2u)) + { + buf.str[0] = '0'; + buf.str[1] = 'b'; + write_bin_unchecked(buf.sub(2), v, digits); + } + digits += 2u; + break; + case T(8): + digits = digits_oct(v); + if(C4_LIKELY(buf.len >= digits+2u)) + { + buf.str[0] = '0'; + buf.str[1] = 'o'; + write_oct_unchecked(buf.sub(2), v, digits); + } + digits += 2u; + break; + } + return digits; +} + +/** same as c4::utoa(), but pad with zeroes on the left such that the + * resulting string is @p num_digits wide. The @p radix must be 2, + * 8, 10 or 16. + * + * @note the resulting string is NOT zero-terminated. + * @note it is ok to call this with an empty or too-small buffer; + * no writes will occur, and the needed size will be returned + * @return the number of characters required for the buffer. */ +template +C4_ALWAYS_INLINE size_t utoa(substr buf, T v, T radix, size_t num_digits) noexcept +{ + C4_STATIC_ASSERT(std::is_unsigned::value); + C4_ASSERT(radix == 10 || radix == 16 || radix == 2 || radix == 8); + unsigned total_digits = 0; + switch(radix) // NOLINT(hicpp-multiway-paths-covered) + { + case T(10): + total_digits = digits_dec(v); + total_digits = (unsigned)(num_digits > total_digits ? num_digits : total_digits); + if(C4_LIKELY(buf.len >= total_digits)) + write_dec(buf, v, num_digits); + break; + case T(16): + total_digits = digits_hex(v); + total_digits = 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits); + if(C4_LIKELY(buf.len >= total_digits)) + { + buf.str[0] = '0'; + buf.str[1] = 'x'; + write_hex(buf.sub(2), v, num_digits); + } + break; + case T(2): + total_digits = digits_bin(v); + total_digits = 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits); + if(C4_LIKELY(buf.len >= total_digits)) + { + buf.str[0] = '0'; + buf.str[1] = 'b'; + write_bin(buf.sub(2), v, num_digits); + } + break; + case T(8): + total_digits = digits_oct(v); + total_digits = 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits); + if(C4_LIKELY(buf.len >= total_digits)) + { + buf.str[0] = '0'; + buf.str[1] = 'o'; + write_oct(buf.sub(2), v, num_digits); + } + break; + } + return total_digits; +} +C4_SUPPRESS_WARNING_GCC_POP + +/** @} */ + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** @defgroup doc_atoi atoi: chars to signed + * + * @{ */ + +/** Convert a trimmed string to a signed integral value. The input + * string can be formatted as decimal, binary (prefix 0b or 0B), octal + * (prefix 0o or 0O) or hexadecimal (prefix 0x or 0X). Strings with + * leading zeroes are considered as decimal and not octal (unlike the + * C/C++ convention). Every character in the input string is read for + * the conversion; the input string must not contain any leading or + * trailing whitespace. + * + * @return true if the conversion was successful. + * + * @note a positive sign is not accepted. ie, the string must not + * start with '+' + * + * @note overflow is not detected: the return status is true even if + * the conversion would return a value outside of the type's range, in + * which case the result will wrap around the type's range. This is + * similar to native behavior. See @ref doc_overflows and @ref + * doc_overflow_checked for overflow checking utilities. + * + * @see atoi_first() if the string is not trimmed to the value to read. */ +template +C4_NO_UBSAN_IOVRFLW +C4_ALWAYS_INLINE bool atoi(csubstr str, T * C4_RESTRICT v) noexcept +{ + C4_STATIC_ASSERT(std::is_integral::value); + C4_STATIC_ASSERT(std::is_signed::value); + + if(C4_UNLIKELY(str.len == 0)) + return false; + + C4_ASSERT(str.str[0] != '+'); + + T sign = 1; + size_t start = 0; + if(str.str[0] == '-') + { + if(C4_UNLIKELY(str.len == ++start)) + return false; + sign = -1; + } + + bool parsed_ok = true; + if(str.str[start] != '0') // this should be the common case, so put it first + { + parsed_ok = read_dec(str.sub(start), v); + } + else if(str.len > start + 1) + { + // starts with 0: is it 0x, 0o, 0b? + const char pfx = str.str[start + 1]; + if(pfx == 'x' || pfx == 'X') + parsed_ok = str.len > start + 2 && read_hex(str.sub(start + 2), v); + else if(pfx == 'b' || pfx == 'B') + parsed_ok = str.len > start + 2 && read_bin(str.sub(start + 2), v); + else if(pfx == 'o' || pfx == 'O') + parsed_ok = str.len > start + 2 && read_oct(str.sub(start + 2), v); + else + parsed_ok = read_dec(str.sub(start + 1), v); + } + else + { + parsed_ok = read_dec(str.sub(start), v); + } + if(C4_LIKELY(parsed_ok)) + *v *= sign; + return parsed_ok; +} + + +/** Select the next range of characters in the string that can be parsed + * as a signed integral value, and convert it using atoi(). Leading + * whitespace (space, newline, tabs) is skipped. + * @return the number of characters read for conversion, or csubstr::npos if the conversion failed + * @see atoi() if the string is already trimmed to the value to read. + * @see csubstr::first_int_span() */ +template +C4_ALWAYS_INLINE size_t atoi_first(csubstr str, T * C4_RESTRICT v) +{ + csubstr trimmed = str.first_int_span(); + if(trimmed.len && atoi(trimmed, v)) + return static_cast(trimmed.end() - str.begin()); + return csubstr::npos; +} + +/** @} */ + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** @defgroup doc_atou atou: chars to unsigned + * + * @{ */ + +/** Convert a trimmed string to an unsigned integral value. The string can be + * formatted as decimal, binary (prefix 0b or 0B), octal (prefix 0o or 0O) + * or hexadecimal (prefix 0x or 0X). Every character in the input string is read + * for the conversion; it must not contain any leading or trailing whitespace. + * + * @return true if the conversion was successful. + * + * @note overflow is not detected: the return status is true even if + * the conversion would return a value outside of the type's range, in + * which case the result will wrap around the type's range. See @ref + * doc_overflows and @ref doc_overflow_checked for overflow checking + * utilities. + * + * @note If the string has a minus character, the return status + * will be false. + * + * @see atou_first() if the string is not trimmed to the value to read. */ +template +bool atou(csubstr str, T * C4_RESTRICT v) noexcept +{ + C4_STATIC_ASSERT(std::is_integral::value); + + if(C4_UNLIKELY(str.len == 0 || str.front() == '-')) + return false; + + bool parsed_ok = true; + if(str.str[0] != '0') + { + parsed_ok = read_dec(str, v); + } + else + { + if(str.len > 1) + { + const char pfx = str.str[1]; + if(pfx == 'x' || pfx == 'X') + parsed_ok = str.len > 2 && read_hex(str.sub(2), v); + else if(pfx == 'b' || pfx == 'B') + parsed_ok = str.len > 2 && read_bin(str.sub(2), v); + else if(pfx == 'o' || pfx == 'O') + parsed_ok = str.len > 2 && read_oct(str.sub(2), v); + else + parsed_ok = read_dec(str, v); + } + else + { + *v = 0; // we know the first character is 0 + } + } + return parsed_ok; +} + + +/** Select the next range of characters in the string that can be parsed + * as an unsigned integral value, and convert it using atou(). Leading + * whitespace (space, newline, tabs) is skipped. + * @return the number of characters read for conversion, or csubstr::npos if the conversion faileds + * @see atou() if the string is already trimmed to the value to read. + * @see csubstr::first_uint_span() */ +template +C4_ALWAYS_INLINE size_t atou_first(csubstr str, T *v) +{ + csubstr trimmed = str.first_uint_span(); + if(trimmed.len && atou(trimmed, v)) + return static_cast(trimmed.end() - str.begin()); + return csubstr::npos; +} + + +/** @} */ + +#if defined(_MSC_VER) && !defined(__clang__) +# pragma warning(pop) +#elif defined(__clang__) +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#endif + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** @cond dev */ +namespace detail { +inline bool check_overflow(csubstr str, csubstr limit) noexcept +{ + if(str.len != limit.len) + return str.len > limit.len; + for(size_t i = 0; i < limit.len; ++i) + { + if(str.str[i] < limit.str[i]) + return false; + else if(str.str[i] > limit.str[i]) + return true; + } + return false; +} +} // namespace detail +/** @endcond */ + + +/** @defgroup doc_overflows overflows: does a number string overflow a type + * + * @{ */ + +/** Test if the following string would overflow when converted to + * associated integral types; this function is dispatched with SFINAE + * to handle differently signed and unsigned types. + * @return true if number will overflow, false if it fits (or doesn't parse) + * @see doc_overflow_checked for format specifiers to enforce no-overflow reads + */ +template +auto overflows(csubstr str) noexcept + -> typename std::enable_if::value, bool>::type +{ + C4_STATIC_ASSERT(std::is_integral::value); + + if(C4_UNLIKELY(str.len == 0)) + { + return false; + } + else if(str.str[0] == '0') + { + if (str.len == 1) + return false; + switch (str.str[1]) + { + case 'x': + case 'X': + { + size_t fno = str.first_not_of('0', 2); + if (fno == csubstr::npos) + return false; + return !(str.len <= fno + (sizeof(T) * 2)); + } + case 'b': + case 'B': + { + size_t fno = str.first_not_of('0', 2); + if (fno == csubstr::npos) + return false; + return !(str.len <= fno +(sizeof(T) * 8)); + } + case 'o': + case 'O': + { + size_t fno = str.first_not_of('0', 2); + if(fno == csubstr::npos) + return false; + return detail::charconv_digits::is_oct_overflow(str.sub(fno)); + } + default: + { + size_t fno = str.first_not_of('0', 1); + if(fno == csubstr::npos) + return false; + return detail::check_overflow(str.sub(fno), detail::charconv_digits::max_value_dec()); + } + } + } + else if(C4_UNLIKELY(str.str[0] == '-')) + { + return true; + } + else + { + return detail::check_overflow(str, detail::charconv_digits::max_value_dec()); + } +} + + +/** Test if the following string would overflow when converted to + * associated integral types; this function is dispatched with SFINAE + * to handle differently signed and unsigned types. + * + * @return true if number will overflow, false if it fits (or doesn't parse) + * @see doc_overflow_checked for format specifiers to enforce no-overflow reads + */ +template +auto overflows(csubstr str) noexcept + -> typename std::enable_if::value, bool>::type +{ + C4_STATIC_ASSERT(std::is_integral::value); + if(C4_UNLIKELY(str.len == 0)) + return false; + if(str.str[0] == '-') + { + if(str.str[1] == '0') + { + if(str.len == 2) + return false; + switch(str.str[2]) + { + case 'x': + case 'X': + { + size_t fno = str.first_not_of('0', 3); + if (fno == csubstr::npos) + return false; + return detail::check_overflow(str.sub(fno), detail::charconv_digits::min_value_hex()); + } + case 'b': + case 'B': + { + size_t fno = str.first_not_of('0', 3); + if (fno == csubstr::npos) + return false; + return detail::check_overflow(str.sub(fno), detail::charconv_digits::min_value_bin()); + } + case 'o': + case 'O': + { + size_t fno = str.first_not_of('0', 3); + if(fno == csubstr::npos) + return false; + return detail::check_overflow(str.sub(fno), detail::charconv_digits::min_value_oct()); + } + default: + { + size_t fno = str.first_not_of('0', 2); + if(fno == csubstr::npos) + return false; + return detail::check_overflow(str.sub(fno), detail::charconv_digits::min_value_dec()); + } + } + } + else + { + return detail::check_overflow(str.sub(1), detail::charconv_digits::min_value_dec()); + } + } + else if(str.str[0] == '0') + { + if (str.len == 1) + return false; + switch(str.str[1]) + { + case 'x': + case 'X': + { + size_t fno = str.first_not_of('0', 2); + if (fno == csubstr::npos) + return false; + const size_t len = str.len - fno; + return !((len < sizeof (T) * 2) || (len == sizeof(T) * 2 && str.str[fno] <= '7')); + } + case 'b': + case 'B': + { + size_t fno = str.first_not_of('0', 2); + if (fno == csubstr::npos) + return false; + return !(str.len <= fno + (sizeof(T) * 8 - 1)); + } + case 'o': + case 'O': + { + size_t fno = str.first_not_of('0', 2); + if(fno == csubstr::npos) + return false; + return detail::charconv_digits::is_oct_overflow(str.sub(fno)); + } + default: + { + size_t fno = str.first_not_of('0', 1); + if(fno == csubstr::npos) + return false; + return detail::check_overflow(str.sub(fno), detail::charconv_digits::max_value_dec()); + } + } + } + else + { + return detail::check_overflow(str, detail::charconv_digits::max_value_dec()); + } +} + +/** @} */ + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** @cond dev */ +namespace detail { + + +#if (!C4CORE_HAVE_STD_FROMCHARS) +/** @see http://www.exploringbinary.com/ for many good examples on float-str conversion */ +template +void get_real_format_str(char (& C4_RESTRICT fmt)[N], int precision, RealFormat_e formatting, const char* length_modifier="") +{ + int iret; + if(precision == -1) + iret = snprintf(fmt, sizeof(fmt), "%%%s%c", length_modifier, formatting); + else if(precision == 0) + iret = snprintf(fmt, sizeof(fmt), "%%.%s%c", length_modifier, formatting); + else + iret = snprintf(fmt, sizeof(fmt), "%%.%d%s%c", precision, length_modifier, formatting); + C4_ASSERT(iret >= 2 && size_t(iret) < sizeof(fmt)); + C4_UNUSED(iret); +} + + +/** @todo we're depending on snprintf()/sscanf() for converting to/from + * floating point numbers. Apparently, this increases the binary size + * by a considerable amount. There are some lightweight printf + * implementations: + * + * @see http://www.sparetimelabs.com/tinyprintf/tinyprintf.php (BSD) + * @see https://github.com/weiss/c99-snprintf + * @see https://github.com/nothings/stb/blob/master/stb_sprintf.h + * @see http://www.exploringbinary.com/ + * @see https://blog.benoitblanchon.fr/lightweight-float-to-string/ + * @see http://www.ryanjuckett.com/programming/printing-floating-point-numbers/ + */ +template +size_t print_one(substr str, const char* full_fmt, T v) +{ +#ifdef _MSC_VER + /** use _snprintf() to prevent early termination of the output + * for writing the null character at the last position + * @see https://msdn.microsoft.com/en-us/library/2ts7cx93.aspx */ + int iret = _snprintf(str.str, str.len, full_fmt, v); + if(iret < 0) + { + /* when buf.len is not enough, VS returns a negative value. + * so call it again with a negative value for getting an + * actual length of the string */ + iret = snprintf(nullptr, 0, full_fmt, v); + C4_ASSERT(iret > 0); + } + size_t ret = (size_t) iret; + return ret; +#else + int iret = snprintf(str.str, str.len, full_fmt, v); + C4_ASSERT(iret >= 0); + size_t ret = (size_t) iret; + if(ret >= str.len) + ++ret; /* snprintf() reserves the last character to write \0 */ + return ret; +#endif +} +#endif // (!C4CORE_HAVE_STD_FROMCHARS) + + +#if (!C4CORE_HAVE_STD_FROMCHARS) && (!C4CORE_HAVE_FAST_FLOAT) +/** scans a string using the given type format, while at the same time + * allowing non-null-terminated strings AND guaranteeing that the given + * string length is strictly respected, so that no buffer overflows + * might occur. */ +template +inline size_t scan_one(csubstr str, const char *type_fmt, T *v) +{ + /* snscanf() is absolutely needed here as we must be sure that + * str.len is strictly respected, because substr is + * generally not null-terminated. + * + * Alas, there is no snscanf(). + * + * So we fake it by using a dynamic format with an explicit + * field size set to the length of the given span. + * This trick is taken from: + * https://stackoverflow.com/a/18368910/5875572 */ + + /* this is the actual format we'll use for scanning */ + char fmt[16]; + + /* write the length into it. Eg "%12f". + * Also, get the number of characters read from the string. + * So the final format ends up as "%12f%n"*/ + int iret = std::snprintf(fmt, sizeof(fmt), "%%" "%zu" "%s" "%%n", str.len, type_fmt); + /* no nasty surprises, please! */ + C4_ASSERT(iret >= 0 && size_t(iret) < C4_COUNTOF(fmt)); + + /* now we scan with confidence that the span length is respected */ + int num_chars; + iret = std::sscanf(str.str, fmt, v, &num_chars); + /* scanf returns the number of successful conversions */ + if(iret != 1) return csubstr::npos; + C4_ASSERT(num_chars >= 0); + return (size_t)(num_chars); +} +#endif // (!C4CORE_HAVE_STD_FROMCHARS) && (!C4CORE_HAVE_FAST_FLOAT) + + +#if C4CORE_HAVE_STD_TOCHARS +template +C4_ALWAYS_INLINE size_t rtoa(substr buf, T v, int precision=-1, RealFormat_e formatting=FTOA_FLEX) noexcept +{ + std::to_chars_result result; + size_t pos = 0; + if(formatting == FTOA_HEXA) + { + if(buf.len > size_t(2)) + { + buf.str[0] = '0'; + buf.str[1] = 'x'; + } + pos += size_t(2); + } + if(precision == -1) + result = std::to_chars(buf.str + pos, buf.str + buf.len, v, (std::chars_format)formatting); + else + result = std::to_chars(buf.str + pos, buf.str + buf.len, v, (std::chars_format)formatting, precision); + if(result.ec == std::errc()) + { + // all good, no errors. + C4_ASSERT(result.ptr >= buf.str); + ptrdiff_t delta = result.ptr - buf.str; + return static_cast(delta); + } + C4_ASSERT(result.ec == std::errc::value_too_large); + // This is unfortunate. + // + // When the result can't fit in the given buffer, + // std::to_chars() returns the end pointer it was originally + // given, which is useless because here we would like to know + // _exactly_ how many characters the buffer must have to fit + // the result. + // + // So we take the pessimistic view, and assume as many digits + // as could ever be required: + size_t ret = static_cast(std::numeric_limits::max_digits10); + return ret > buf.len ? ret : buf.len + 1; +} +#endif // C4CORE_HAVE_STD_TOCHARS + + +#if C4CORE_HAVE_FAST_FLOAT +template +C4_ALWAYS_INLINE bool scan_rhex(csubstr s, T *C4_RESTRICT val) noexcept +{ + C4_ASSERT(s.len > 0); + C4_ASSERT(s.str[0] != '-'); + C4_ASSERT(s.str[0] != '+'); + C4_ASSERT(!s.begins_with("0x")); + C4_ASSERT(!s.begins_with("0X")); + size_t pos = 0; + // integer part + for( ; pos < s.len; ++pos) + { + const char c = s.str[pos]; + if(c >= '0' && c <= '9') + *val = (*val * T(16)) + T(c - '0'); + else if(c >= 'a' && c <= 'f') + *val = (*val * T(16)) + T(c - 'a'); + else if(c >= 'A' && c <= 'F') + *val = (*val * T(16)) + T(c - 'A'); + else if(c == '.') + { + ++pos; + break; // follow on to mantissa + } + else if(c == 'p' || c == 'P') + { + ++pos; + goto power; // no mantissa given, jump to power // NOLINT + } + else + { + return false; + } + } + // mantissa + { + // 0.0625 == 1/16 == value of first digit after the comma + for(T digit = T(0.0625); pos < s.len; ++pos, digit /= T(16)) // NOLINT + { + const char c = s.str[pos]; + if(c >= '0' && c <= '9') + *val += digit * T(c - '0'); + else if(c >= 'a' && c <= 'f') + *val += digit * T(c - 'a'); + else if(c >= 'A' && c <= 'F') + *val += digit * T(c - 'A'); + else if(c == 'p' || c == 'P') + { + ++pos; + goto power; // mantissa finished, jump to power // NOLINT + } + else + { + return false; + } + } + } + return true; +power: + if(C4_LIKELY(pos < s.len)) + { + if(s.str[pos] == '+') // atoi() cannot handle a leading '+' + ++pos; + if(C4_LIKELY(pos < s.len)) + { + int16_t powval = {}; + if(C4_LIKELY(atoi(s.sub(pos), &powval))) + { + *val *= ipow(powval); + return true; + } + } + } + return false; +} +#endif + +} // namespace detail +/** @endcond */ + + +#undef _c4appendhex +#undef _c4append + + +/** @defgroup doc_ftoa ftoa: float32 to chars + * + * @{ */ + +/** Convert a single-precision real number to string. The string will + * in general be NOT null-terminated. For FTOA_FLEX, \p precision is + * the number of significand digits. Otherwise \p precision is the + * number of decimals. It is safe to call this function with an empty + * or too-small buffer. + * + * @return the size of the buffer needed to write the number + */ +C4_ALWAYS_INLINE size_t ftoa(substr str, float v, int precision=-1, RealFormat_e formatting=FTOA_FLEX) noexcept +{ +#if C4CORE_HAVE_STD_TOCHARS + return detail::rtoa(str, v, precision, formatting); +#else + char fmt[16]; + detail::get_real_format_str(fmt, precision, formatting, /*length_modifier*/""); + return detail::print_one(str, fmt, v); +#endif +} + +/** @} */ + + +/** @defgroup doc_dtoa dtoa: float64 to chars + * + * @{ */ + +/** Convert a double-precision real number to string. The string will + * in general be NOT null-terminated. For FTOA_FLEX, \p precision is + * the number of significand digits. Otherwise \p precision is the + * number of decimals. It is safe to call this function with an empty + * or too-small buffer. + * + * @return the size of the buffer needed to write the number + */ +C4_ALWAYS_INLINE size_t dtoa(substr str, double v, int precision=-1, RealFormat_e formatting=FTOA_FLEX) noexcept +{ +#if C4CORE_HAVE_STD_TOCHARS + return detail::rtoa(str, v, precision, formatting); +#else + char fmt[16]; + detail::get_real_format_str(fmt, precision, formatting, /*length_modifier*/"l"); + return detail::print_one(str, fmt, v); +#endif +} + +/** @} */ + + +/** @defgroup doc_atof atof: chars to float32 + * + * @{ */ + +/** Convert a string to a single precision real number. + * The input string must be trimmed to the value, ie + * no leading or trailing whitespace can be present. + * @return true iff the conversion succeeded + * @see atof_first() if the string is not trimmed + */ +C4_ALWAYS_INLINE bool atof(csubstr str, float * C4_RESTRICT v) noexcept +{ + C4_ASSERT(str.len > 0); + C4_ASSERT(str.triml(" \r\t\n").len == str.len); +#if C4CORE_HAVE_FAST_FLOAT + // fastfloat cannot parse hexadecimal floats + bool isneg = (str.str[0] == '-'); + csubstr rem = str.sub(isneg || str.str[0] == '+'); + if(!(rem.len >= 2 && (rem.str[0] == '0' && (rem.str[1] == 'x' || rem.str[1] == 'X')))) + { + fast_float::from_chars_result result; + result = fast_float::from_chars(str.str, str.str + str.len, *v); + return result.ec == std::errc(); + } + else if(detail::scan_rhex(rem.sub(2), v)) + { + *v *= isneg ? -1.f : 1.f; + return true; + } + return false; +#elif C4CORE_HAVE_STD_FROMCHARS + std::from_chars_result result; + result = std::from_chars(str.str, str.str + str.len, *v); + return result.ec == std::errc(); +#else + csubstr rem = str.sub(str.str[0] == '-' || str.str[0] == '+'); + if(!(rem.len >= 2 && (rem.str[0] == '0' && (rem.str[1] == 'x' || rem.str[1] == 'X')))) + return detail::scan_one(str, "f", v) != csubstr::npos; + else + return detail::scan_one(str, "a", v) != csubstr::npos; +#endif +} + + +/** Convert a string to a single precision real number. + * Leading whitespace is skipped until valid characters are found. + * @return the number of characters read from the string, or npos if + * conversion was not successful or if the string was empty */ +inline size_t atof_first(csubstr str, float * C4_RESTRICT v) noexcept +{ + csubstr trimmed = str.first_real_span(); + if(trimmed.len == 0) + return csubstr::npos; + if(atof(trimmed, v)) + return static_cast(trimmed.end() - str.begin()); + return csubstr::npos; +} + +/** @} */ + + +/** @defgroup doc_atod atod: chars to float64 + * + * @{ */ + +/** Convert a string to a double precision real number. + * The input string must be trimmed to the value, ie + * no leading or trailing whitespace can be present. + * @return true iff the conversion succeeded + * @see atod_first() if the string is not trimmed + */ +C4_ALWAYS_INLINE bool atod(csubstr str, double * C4_RESTRICT v) noexcept +{ + C4_ASSERT(str.len > 0); + C4_ASSERT(str.triml(" \r\t\n").len == str.len); +#if C4CORE_HAVE_FAST_FLOAT + // fastfloat cannot parse hexadecimal floats + bool isneg = (str.str[0] == '-'); + csubstr rem = str.sub(isneg || str.str[0] == '+'); + if(!(rem.len >= 2 && (rem.str[0] == '0' && (rem.str[1] == 'x' || rem.str[1] == 'X')))) + { + fast_float::from_chars_result result; + #ifndef CLANG_TIDY // suppress a false-positive error (cannot be done with NOLINT: https://stackoverflow.com/questions/62838193/ ) + result = fast_float::from_chars(str.str, str.str + str.len, *v); + #else + result = {}; + #endif + return result.ec == std::errc(); + } + else if(detail::scan_rhex(rem.sub(2), v)) + { + *v *= isneg ? -1. : 1.; + return true; + } + return false; +#elif C4CORE_HAVE_STD_FROMCHARS + std::from_chars_result result; + result = std::from_chars(str.str, str.str + str.len, *v); + return result.ec == std::errc(); +#else + csubstr rem = str.sub(str.str[0] == '-' || str.str[0] == '+'); + if(!(rem.len >= 2 && (rem.str[0] == '0' && (rem.str[1] == 'x' || rem.str[1] == 'X')))) + return detail::scan_one(str, "lf", v) != csubstr::npos; + else + return detail::scan_one(str, "la", v) != csubstr::npos; +#endif +} + + +/** Convert a string to a double precision real number. + * Leading whitespace is skipped until valid characters are found. + * @return the number of characters read from the string, or npos if + * conversion was not successful or if the string was empty */ +inline size_t atod_first(csubstr str, double * C4_RESTRICT v) noexcept +{ + csubstr trimmed = str.first_real_span(); + if(trimmed.len == 0) + return csubstr::npos; + if(atod(trimmed, v)) + return static_cast(trimmed.end() - str.begin()); + return csubstr::npos; +} + +/** @} */ + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// generic versions + +/** @cond dev */ +// on some platforms, (unsigned) int and (unsigned) long +// are not any of the fixed length types above +#define _C4_IF_NOT_FIXED_LENGTH_I(T, ty) typename std::enable_if::value && !is_fixed_length::value_i, ty> +#define _C4_IF_NOT_FIXED_LENGTH_U(T, ty) typename std::enable_if::value && !is_fixed_length::value_u, ty> +/** @endcond*/ + + +/** @defgroup doc_xtoa xtoa: generic value to chars + * + * Dispatches to the most appropriate and efficient conversion + * function + * + * @{ */ +C4_ALWAYS_INLINE size_t xtoa(substr s, uint8_t v) noexcept { return write_dec(s, v); } +C4_ALWAYS_INLINE size_t xtoa(substr s, uint16_t v) noexcept { return write_dec(s, v); } +C4_ALWAYS_INLINE size_t xtoa(substr s, uint32_t v) noexcept { return write_dec(s, v); } +C4_ALWAYS_INLINE size_t xtoa(substr s, uint64_t v) noexcept { return write_dec(s, v); } +C4_ALWAYS_INLINE size_t xtoa(substr s, int8_t v) noexcept { return itoa(s, v); } +C4_ALWAYS_INLINE size_t xtoa(substr s, int16_t v) noexcept { return itoa(s, v); } +C4_ALWAYS_INLINE size_t xtoa(substr s, int32_t v) noexcept { return itoa(s, v); } +C4_ALWAYS_INLINE size_t xtoa(substr s, int64_t v) noexcept { return itoa(s, v); } +C4_ALWAYS_INLINE size_t xtoa(substr s, float v) noexcept { return ftoa(s, v); } +C4_ALWAYS_INLINE size_t xtoa(substr s, double v) noexcept { return dtoa(s, v); } + +C4_ALWAYS_INLINE size_t xtoa(substr s, uint8_t v, uint8_t radix) noexcept { return utoa(s, v, radix); } +C4_ALWAYS_INLINE size_t xtoa(substr s, uint16_t v, uint16_t radix) noexcept { return utoa(s, v, radix); } +C4_ALWAYS_INLINE size_t xtoa(substr s, uint32_t v, uint32_t radix) noexcept { return utoa(s, v, radix); } +C4_ALWAYS_INLINE size_t xtoa(substr s, uint64_t v, uint64_t radix) noexcept { return utoa(s, v, radix); } +C4_ALWAYS_INLINE size_t xtoa(substr s, int8_t v, int8_t radix) noexcept { return itoa(s, v, radix); } +C4_ALWAYS_INLINE size_t xtoa(substr s, int16_t v, int16_t radix) noexcept { return itoa(s, v, radix); } +C4_ALWAYS_INLINE size_t xtoa(substr s, int32_t v, int32_t radix) noexcept { return itoa(s, v, radix); } +C4_ALWAYS_INLINE size_t xtoa(substr s, int64_t v, int64_t radix) noexcept { return itoa(s, v, radix); } + +C4_ALWAYS_INLINE size_t xtoa(substr s, uint8_t v, uint8_t radix, size_t num_digits) noexcept { return utoa(s, v, radix, num_digits); } +C4_ALWAYS_INLINE size_t xtoa(substr s, uint16_t v, uint16_t radix, size_t num_digits) noexcept { return utoa(s, v, radix, num_digits); } +C4_ALWAYS_INLINE size_t xtoa(substr s, uint32_t v, uint32_t radix, size_t num_digits) noexcept { return utoa(s, v, radix, num_digits); } +C4_ALWAYS_INLINE size_t xtoa(substr s, uint64_t v, uint64_t radix, size_t num_digits) noexcept { return utoa(s, v, radix, num_digits); } +C4_ALWAYS_INLINE size_t xtoa(substr s, int8_t v, int8_t radix, size_t num_digits) noexcept { return itoa(s, v, radix, num_digits); } +C4_ALWAYS_INLINE size_t xtoa(substr s, int16_t v, int16_t radix, size_t num_digits) noexcept { return itoa(s, v, radix, num_digits); } +C4_ALWAYS_INLINE size_t xtoa(substr s, int32_t v, int32_t radix, size_t num_digits) noexcept { return itoa(s, v, radix, num_digits); } +C4_ALWAYS_INLINE size_t xtoa(substr s, int64_t v, int64_t radix, size_t num_digits) noexcept { return itoa(s, v, radix, num_digits); } + +C4_ALWAYS_INLINE size_t xtoa(substr s, float v, int precision, RealFormat_e formatting=FTOA_FLEX) noexcept { return ftoa(s, v, precision, formatting); } +C4_ALWAYS_INLINE size_t xtoa(substr s, double v, int precision, RealFormat_e formatting=FTOA_FLEX) noexcept { return dtoa(s, v, precision, formatting); } + +template C4_ALWAYS_INLINE auto xtoa(substr buf, T v) noexcept -> _C4_IF_NOT_FIXED_LENGTH_I(T, size_t)::type { return itoa(buf, v); } +template C4_ALWAYS_INLINE auto xtoa(substr buf, T v) noexcept -> _C4_IF_NOT_FIXED_LENGTH_U(T, size_t)::type { return write_dec(buf, v); } +template +C4_ALWAYS_INLINE size_t xtoa(substr s, T *v) noexcept { return itoa(s, (intptr_t)v, (intptr_t)16); } + +/** @} */ + +/** @defgroup doc_atox atox: generic chars to value + * + * Dispatches to the most appropriate and efficient conversion + * function + * + * @{ */ + +C4_ALWAYS_INLINE bool atox(csubstr s, uint8_t *C4_RESTRICT v) noexcept { return atou(s, v); } +C4_ALWAYS_INLINE bool atox(csubstr s, uint16_t *C4_RESTRICT v) noexcept { return atou(s, v); } +C4_ALWAYS_INLINE bool atox(csubstr s, uint32_t *C4_RESTRICT v) noexcept { return atou(s, v); } +C4_ALWAYS_INLINE bool atox(csubstr s, uint64_t *C4_RESTRICT v) noexcept { return atou(s, v); } +C4_ALWAYS_INLINE bool atox(csubstr s, int8_t *C4_RESTRICT v) noexcept { return atoi(s, v); } +C4_ALWAYS_INLINE bool atox(csubstr s, int16_t *C4_RESTRICT v) noexcept { return atoi(s, v); } +C4_ALWAYS_INLINE bool atox(csubstr s, int32_t *C4_RESTRICT v) noexcept { return atoi(s, v); } +C4_ALWAYS_INLINE bool atox(csubstr s, int64_t *C4_RESTRICT v) noexcept { return atoi(s, v); } +C4_ALWAYS_INLINE bool atox(csubstr s, float *C4_RESTRICT v) noexcept { return atof(s, v); } +C4_ALWAYS_INLINE bool atox(csubstr s, double *C4_RESTRICT v) noexcept { return atod(s, v); } + +template C4_ALWAYS_INLINE auto atox(csubstr buf, T *C4_RESTRICT v) noexcept -> _C4_IF_NOT_FIXED_LENGTH_I(T, bool)::type { return atoi(buf, v); } +template C4_ALWAYS_INLINE auto atox(csubstr buf, T *C4_RESTRICT v) noexcept -> _C4_IF_NOT_FIXED_LENGTH_U(T, bool)::type { return atou(buf, v); } +template +C4_ALWAYS_INLINE bool atox(csubstr s, T **v) noexcept { intptr_t tmp; bool ret = atox(s, &tmp); if(ret) { *v = (T*)tmp; } return ret; } + +/** @} */ + + +/** @defgroup doc_to_chars to_chars: generalized chars to value + * + * Convert the given value, writing into the string. The resulting + * string will NOT be null-terminated. Return the number of + * characters needed. This function is safe to call when the string + * is too small - no writes will occur beyond the string's last + * character. + * + * Dispatches to the most appropriate and efficient conversion + * function. + * + * @see write_dec, doc_utoa, doc_itoa, doc_ftoa, doc_dtoa + * + * @warning When serializing floating point values (float or double), + * be aware that because it uses defaults, to_chars() may cause a + * truncation of the precision. To enforce a particular precision, use + * for example @ref c4::fmt::real, or call directly @ref c4::ftoa or + * @ref c4::dtoa. + * + * @{ */ + +C4_ALWAYS_INLINE size_t to_chars(substr buf, uint8_t v) noexcept { return write_dec(buf, v); } +C4_ALWAYS_INLINE size_t to_chars(substr buf, uint16_t v) noexcept { return write_dec(buf, v); } +C4_ALWAYS_INLINE size_t to_chars(substr buf, uint32_t v) noexcept { return write_dec(buf, v); } +C4_ALWAYS_INLINE size_t to_chars(substr buf, uint64_t v) noexcept { return write_dec(buf, v); } +C4_ALWAYS_INLINE size_t to_chars(substr buf, int8_t v) noexcept { return itoa(buf, v); } +C4_ALWAYS_INLINE size_t to_chars(substr buf, int16_t v) noexcept { return itoa(buf, v); } +C4_ALWAYS_INLINE size_t to_chars(substr buf, int32_t v) noexcept { return itoa(buf, v); } +C4_ALWAYS_INLINE size_t to_chars(substr buf, int64_t v) noexcept { return itoa(buf, v); } +C4_ALWAYS_INLINE size_t to_chars(substr buf, float v) noexcept { return ftoa(buf, v); } +C4_ALWAYS_INLINE size_t to_chars(substr buf, double v) noexcept { return dtoa(buf, v); } + +template C4_ALWAYS_INLINE auto to_chars(substr buf, T v) noexcept -> _C4_IF_NOT_FIXED_LENGTH_I(T, size_t)::type { return itoa(buf, v); } +template C4_ALWAYS_INLINE auto to_chars(substr buf, T v) noexcept -> _C4_IF_NOT_FIXED_LENGTH_U(T, size_t)::type { return write_dec(buf, v); } +template +C4_ALWAYS_INLINE auto to_chars(substr s, T *v) noexcept + -> typename std::enable_if::value && + !std::is_same::value, + size_t>::type +{ + return itoa(s, (intptr_t)v, (intptr_t)16); +} + +/** @} */ + + +/** @defgroup doc_from_chars from_chars: generalized chars to value + * + * Read a value from the string, which must be trimmed to the value + * (ie, no leading/trailing whitespace). return true if the + * conversion succeeded. There is no check for overflow; the value + * wraps around in a way similar to the standard C/C++ overflow + * behavior. For example, from_chars("128", &val) returns true + * and val will be set tot 0. See @ref doc_overflows and @ref + * doc_overflow_checked for facilities enforcing no-overflow. + * + * Dispatches to the most appropriate and efficient conversion + * function + * + * @see doc_from_chars_first, atou, atoi, atof, atod + * @{ */ + +C4_ALWAYS_INLINE bool from_chars(csubstr buf, uint8_t *C4_RESTRICT v) noexcept { return atou(buf, v); } +C4_ALWAYS_INLINE bool from_chars(csubstr buf, uint16_t *C4_RESTRICT v) noexcept { return atou(buf, v); } +C4_ALWAYS_INLINE bool from_chars(csubstr buf, uint32_t *C4_RESTRICT v) noexcept { return atou(buf, v); } +C4_ALWAYS_INLINE bool from_chars(csubstr buf, uint64_t *C4_RESTRICT v) noexcept { return atou(buf, v); } +C4_ALWAYS_INLINE bool from_chars(csubstr buf, int8_t *C4_RESTRICT v) noexcept { return atoi(buf, v); } +C4_ALWAYS_INLINE bool from_chars(csubstr buf, int16_t *C4_RESTRICT v) noexcept { return atoi(buf, v); } +C4_ALWAYS_INLINE bool from_chars(csubstr buf, int32_t *C4_RESTRICT v) noexcept { return atoi(buf, v); } +C4_ALWAYS_INLINE bool from_chars(csubstr buf, int64_t *C4_RESTRICT v) noexcept { return atoi(buf, v); } +C4_ALWAYS_INLINE bool from_chars(csubstr buf, float *C4_RESTRICT v) noexcept { return atof(buf, v); } +C4_ALWAYS_INLINE bool from_chars(csubstr buf, double *C4_RESTRICT v) noexcept { return atod(buf, v); } + +template C4_ALWAYS_INLINE auto from_chars(csubstr buf, T *C4_RESTRICT v) noexcept -> _C4_IF_NOT_FIXED_LENGTH_I(T, bool)::type { return atoi(buf, v); } +template C4_ALWAYS_INLINE auto from_chars(csubstr buf, T *C4_RESTRICT v) noexcept -> _C4_IF_NOT_FIXED_LENGTH_U(T, bool)::type { return atou(buf, v); } +template +C4_ALWAYS_INLINE bool from_chars(csubstr buf, T **v) noexcept { intptr_t tmp; bool ret = from_chars(buf, &tmp); if(ret) { *v = (T*)tmp; } return ret; } + +/** @defgroup doc_from_chars_first from_chars_first: generalized chars to value + * + * Read the first valid sequence of characters from the string, + * skipping leading whitespace, and convert it using @ref doc_from_chars . + * Return the number of characters read for converting. + * + * Dispatches to the most appropriate and efficient conversion + * function. + * + * @see atou_first, atoi_first, atof_first, atod_first + * @{ */ + +C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, uint8_t *C4_RESTRICT v) noexcept { return atou_first(buf, v); } +C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, uint16_t *C4_RESTRICT v) noexcept { return atou_first(buf, v); } +C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, uint32_t *C4_RESTRICT v) noexcept { return atou_first(buf, v); } +C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, uint64_t *C4_RESTRICT v) noexcept { return atou_first(buf, v); } +C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, int8_t *C4_RESTRICT v) noexcept { return atoi_first(buf, v); } +C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, int16_t *C4_RESTRICT v) noexcept { return atoi_first(buf, v); } +C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, int32_t *C4_RESTRICT v) noexcept { return atoi_first(buf, v); } +C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, int64_t *C4_RESTRICT v) noexcept { return atoi_first(buf, v); } +C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, float *C4_RESTRICT v) noexcept { return atof_first(buf, v); } +C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, double *C4_RESTRICT v) noexcept { return atod_first(buf, v); } + +template C4_ALWAYS_INLINE auto from_chars_first(csubstr buf, T *C4_RESTRICT v) noexcept -> _C4_IF_NOT_FIXED_LENGTH_I(T, size_t)::type { return atoi_first(buf, v); } +template C4_ALWAYS_INLINE auto from_chars_first(csubstr buf, T *C4_RESTRICT v) noexcept -> _C4_IF_NOT_FIXED_LENGTH_U(T, size_t)::type { return atou_first(buf, v); } +template +C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, T **v) noexcept { intptr_t tmp; bool ret = from_chars_first(buf, &tmp); if(ret) { *v = (T*)tmp; } return ret; } + +/** @} */ + +/** @} */ + +#undef _C4_IF_NOT_FIXED_LENGTH_I +#undef _C4_IF_NOT_FIXED_LENGTH_U + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +/** call to_chars() and return a substr consisting of the + * written portion of the input buffer. Ie, same as to_chars(), + * but return a substr instead of a size_t. + * Convert the given value to a string using to_chars(), and + * return the resulting string, up to and including the last + * written character. + * @ingroup doc_to_chars + * @see to_chars() */ +template +C4_ALWAYS_INLINE substr to_chars_sub(substr buf, T const& C4_RESTRICT v) noexcept +{ + size_t sz = to_chars(buf, v); + return buf.left_of(sz <= buf.len ? sz : buf.len); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// bool implementation + +/** @ingroup doc_to_chars */ +C4_ALWAYS_INLINE size_t to_chars(substr buf, bool v) noexcept +{ + int val = v; + return to_chars(buf, val); +} + +/** @ingroup doc_from_chars */ +inline bool from_chars(csubstr buf, bool * C4_RESTRICT v) noexcept +{ + if(buf.len == 1) + { + if(buf.str[0] == '0') + { + *v = false; return true; + } + else if(buf.str[0] == '1') + { + *v = true; return true; + } + } + else if(buf.len == 4) + { + if(((buf.str[0] == 't') && (0 == memcmp(buf.str + 1, "rue", 3))) + || + ((buf.str[0] == 'T') && (0 == memcmp(buf.str + 1, "rue", 3) || + 0 == memcmp(buf.str + 1, "RUE", 3)))) + { + *v = true; return true; + } + } + else if(buf.len == 5) + { + if(((buf.str[0] == 'f') && (0 == memcmp(buf.str + 1, "alse", 4))) + || + ((buf.str[0] == 'F') && (0 == memcmp(buf.str + 1, "alse", 4) || + 0 == memcmp(buf.str + 1, "ALSE", 4)))) + { + *v = false; return true; + } + } + // fallback to c-style int bools + int val = 0; + bool ret = from_chars(buf, &val); + if(C4_LIKELY(ret)) + { + *v = (val != 0); + } + return ret; +} + +/** @ingroup doc_from_chars_first */ +inline size_t from_chars_first(csubstr buf, bool * C4_RESTRICT v) noexcept +{ + csubstr trimmed = buf.first_non_empty_span(); + if(trimmed.len == 0 || !from_chars(buf, v)) + return csubstr::npos; + return trimmed.len; +} + + +//----------------------------------------------------------------------------- +// single-char implementation + +/** @ingroup doc_to_chars */ +inline size_t to_chars(substr buf, char v) noexcept +{ + if(buf.len > 0) + { + C4_XASSERT(buf.str); + buf.str[0] = v; + } + return 1; +} + +/** extract a single character from a substring + * @note to extract a string instead and not just a single character, use the csubstr overload + * @ingroup doc_from_chars + * */ +inline bool from_chars(csubstr buf, char * C4_RESTRICT v) noexcept +{ + if(buf.len != 1) + return false; + C4_XASSERT(buf.str); + *v = buf.str[0]; + return true; +} + +/** @ingroup doc_from_chars_first */ +inline size_t from_chars_first(csubstr buf, char * C4_RESTRICT v) noexcept +{ + if(buf.len < 1) + return csubstr::npos; + *v = buf.str[0]; + return 1; +} + + +//----------------------------------------------------------------------------- +// csubstr implementation + +/** @ingroup doc_to_chars */ +inline size_t to_chars(substr buf, csubstr v) noexcept +{ + C4_ASSERT(!buf.overlaps(v)); + size_t len = buf.len < v.len ? buf.len : v.len; + // calling memcpy with null strings is undefined behavior + // and will wreak havoc in calling code's branches. + // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637 + if(len) + { + C4_ASSERT(buf.str != nullptr); + C4_ASSERT(v.str != nullptr); + memcpy(buf.str, v.str, len); + } + return v.len; +} + +/** @ingroup doc_from_chars */ +inline bool from_chars(csubstr buf, csubstr *C4_RESTRICT v) noexcept +{ + *v = buf; + return true; +} + +/** @ingroup doc_from_chars_first */ +inline size_t from_chars_first(csubstr buf, csubstr * C4_RESTRICT v) noexcept +{ + csubstr trimmed = buf.first_non_empty_span(); + if(trimmed.len == 0) + return csubstr::npos; + *v = trimmed; + return static_cast(trimmed.end() - buf.begin()); +} + + +//----------------------------------------------------------------------------- +// substr + +/** @ingroup doc_to_chars */ +inline size_t to_chars(substr buf, substr v) noexcept +{ + C4_ASSERT(!buf.overlaps(v)); + size_t len = buf.len < v.len ? buf.len : v.len; + // calling memcpy zero len is undefined behavior + // and will wreak havoc in calling code's branches. + // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637 + if(len) + { + C4_ASSERT(buf.str != nullptr); + C4_ASSERT(v.str != nullptr); + memcpy(buf.str, v.str, len); + } + return v.len; +} + +/** @ingroup doc_from_chars */ +inline bool from_chars(csubstr buf, substr * C4_RESTRICT v) noexcept +{ + C4_ASSERT(!buf.overlaps(*v)); + // is the destination buffer wide enough? + if(v->len >= buf.len) + { + // calling memcpy with zero len is undefined behavior + // and will wreak havoc in calling code's branches. + // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637 + if(buf.len) + { + C4_ASSERT(buf.str != nullptr); + C4_ASSERT(v->str != nullptr); + memcpy(v->str, buf.str, buf.len); + } + v->len = buf.len; + return true; + } + return false; +} + +/** @ingroup doc_from_chars_first */ +inline size_t from_chars_first(csubstr buf, substr * C4_RESTRICT v) noexcept +{ + csubstr trimmed = buf.first_non_empty_span(); + C4_ASSERT(!trimmed.overlaps(*v)); + if(C4_UNLIKELY(trimmed.len == 0)) + return csubstr::npos; + size_t len = trimmed.len > v->len ? v->len : trimmed.len; + // calling memcpy with zero len is undefined behavior + // and will wreak havoc in calling code's branches. + // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637 + if(len) + { + C4_ASSERT(buf.str != nullptr); + C4_ASSERT(v->str != nullptr); + memcpy(v->str, trimmed.str, len); + } + if(C4_UNLIKELY(trimmed.len > v->len)) + return csubstr::npos; + return static_cast(trimmed.end() - buf.begin()); +} + + +//----------------------------------------------------------------------------- + +/** @ingroup doc_to_chars + * (1) overload for `char(&)[N]` and `const char(&)[N]` */ +template +size_t to_chars(substr buf, const char (& C4_RESTRICT v)[N]) noexcept +{ + return to_chars(buf, csubstr{v, N-1}); +} + +/** @ingroup doc_to_chars + * (2) overload for `char*` and `const char*`. Must be zero-terminated! + * @warning will call strlen() + * @note this overload uses SFINAE to prevent it from overriding the array overload + * @see For a more detailed explanation on why the plain pointer overloads cannot + * coexist with the array overloads, see http://cplusplus.bordoon.com/specializeForCharacterArrays.html */ +template +auto to_chars(substr buf, CharPtr v) noexcept + -> typename std::enable_if::value + || + std::is_same::value, size_t>::type +{ + return v ? to_chars(buf, csubstr{v, strlen(v)}) : 0; +} + + +//----------------------------------------------------------------------------- +// nullptr implementation + +/** @ingroup doc_to_chars */ +C4_ALWAYS_INLINE size_t to_chars(substr, std::nullptr_t) noexcept +{ + return 0; +} + +// from_chars() and from_chars_sub() must not exist for nullptr + + +/** @} */ + +} // namespace c4 + +// NOLINTEND(hicpp-signed-bitwise) + +#if defined(_MSC_VER) && !defined(__clang__) +# pragma warning(pop) +#elif defined(__clang__) +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#endif + +#endif /* _C4_CHARCONV_HPP_ */ + + +// (end src/c4/charconv.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/utf.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef C4_UTF_HPP_ +#define C4_UTF_HPP_ + +// amalgamate: removed include of +// c4/language.hpp +//#include "c4/language.hpp" +#if !defined(C4_LANGUAGE_HPP_) && !defined(_C4_LANGUAGE_HPP_) +#error "amalgamate: file c4/language.hpp must have been included at this point" +#endif /* C4_LANGUAGE_HPP_ */ + +// amalgamate: removed include of +// c4/substr_fwd.hpp +//#include "c4/substr_fwd.hpp" +#if !defined(C4_SUBSTR_FWD_HPP_) && !defined(_C4_SUBSTR_FWD_HPP_) +#error "amalgamate: file c4/substr_fwd.hpp must have been included at this point" +#endif /* C4_SUBSTR_FWD_HPP_ */ + +// amalgamate: removed include of +// c4/export.hpp +//#include "c4/export.hpp" +#if !defined(C4_EXPORT_HPP_) && !defined(_C4_EXPORT_HPP_) +#error "amalgamate: file c4/export.hpp must have been included at this point" +#endif /* C4_EXPORT_HPP_ */ + +//included above: +//#include +//included above: +//#include + +/** @file utf.hpp utilities for UTF and Byte Order Mark */ + +namespace c4 { + +/** @defgroup doc_utf UTF utilities + * @{ */ + + +/** skip the Byte Order Mark, or get the full string if there is Byte Order Mark. + * @see Implements the Byte Order Marks as described in https://en.wikipedia.org/wiki/Byte_order_mark#Byte-order_marks_by_encoding */ +C4CORE_EXPORT substr skip_bom(substr s); +/** skip the Byte Order Mark, or get the full string if there is Byte Order Mark + * @see Implements the Byte Order Marks as described in https://en.wikipedia.org/wiki/Byte_order_mark#Byte-order_marks_by_encoding */ +C4CORE_EXPORT csubstr skip_bom(csubstr s); + + +/** get the Byte Order Mark, or an empty string if there is no Byte Order Mark + * @see Implements the Byte Order Marks as described in https://en.wikipedia.org/wiki/Byte_order_mark#Byte-order_marks_by_encoding */ +C4CORE_EXPORT substr get_bom(substr s); +/** get the Byte Order Mark, or an empty string if there is no Byte Order Mark + * @see Implements the Byte Order Marks as described in https://en.wikipedia.org/wiki/Byte_order_mark#Byte-order_marks_by_encoding */ +C4CORE_EXPORT csubstr get_bom(csubstr s); + + +/** return the position of the first character not belonging to the + * Byte Order Mark, or 0 if there is no Byte Order Mark. + * @see Implements the Byte Order Marks as described in https://en.wikipedia.org/wiki/Byte_order_mark#Byte-order_marks_by_encoding */ +C4CORE_EXPORT size_t first_non_bom(csubstr s); + + +/** decode the given @p code_point, writing into the output string in + * @p out. + * + * @param out the output string. must have at least 4 bytes (this is + * asserted), and must not have a null string. + * + * @param code_point: must have length in ]0,8], and must not begin + * with any of @verbatim `U+`,`\x`,`\u`,`\U`,`0` @endverbatim (asserted) + * + * @return the part of @p out that was written, which will always be + * at most 4 bytes. + */ +C4CORE_EXPORT substr decode_code_point(substr out, csubstr code_point); + +/** decode the given @p code point, writing into the output string @p + * buf, of size @p buflen + * + * @param buf the output string. must have at least 4 bytes (this is + * asserted), and must not be null + * + * @param buflen the length of the output string. must be at least 4 + * + * @param code: the code point must have length in ]0,8], and must not begin + * with any of @verbatim `U+`,`\x`,`\u`,`\U`,`0` @endverbatim (asserted) + * + * @return the number of written characters, which will always be + * at most 4 bytes. + */ +C4CORE_EXPORT size_t decode_code_point(uint8_t *C4_RESTRICT buf, size_t buflen, uint32_t code); + +/** @} */ + +} // namespace c4 + +#endif // C4_UTF_HPP_ + + +// (end src/c4/utf.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/format.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_FORMAT_HPP_ +#define _C4_FORMAT_HPP_ + +/** @file format.hpp provides type-safe facilities for formatting arguments + * to string buffers */ + +// amalgamate: removed include of +// c4/charconv.hpp +//#include "c4/charconv.hpp" +#if !defined(C4_CHARCONV_HPP_) && !defined(_C4_CHARCONV_HPP_) +#error "amalgamate: file c4/charconv.hpp must have been included at this point" +#endif /* C4_CHARCONV_HPP_ */ + +// amalgamate: removed include of +// c4/blob.hpp +//#include "c4/blob.hpp" +#if !defined(C4_BLOB_HPP_) && !defined(_C4_BLOB_HPP_) +#error "amalgamate: file c4/blob.hpp must have been included at this point" +#endif /* C4_BLOB_HPP_ */ + + + +#if defined(_MSC_VER) && !defined(__clang__) +# pragma warning(push) +# if C4_MSVC_VERSION != C4_MSVC_VERSION_2017 +# pragma warning(disable: 4800) // forcing value to bool 'true' or 'false' (performance warning) +# endif +# pragma warning(disable: 4996) // snprintf/scanf: this function or variable may be unsafe +#elif defined(__clang__) +# pragma clang diagnostic push +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wuseless-cast" +#endif +// NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast,*avoid-goto*) + +/** @defgroup doc_format_utils Format utilities + * + * @brief Provides generic and type-safe formatting/scanning utilities + * built on top of @ref doc_to_chars() and @ref doc_from_chars, + * forwarding the arguments to these functions, which in turn use the + * @ref doc_charconv utilities. Like @ref doc_charconv, the formatting + * facilities are very efficient and many times faster than printf(). + * + * @see [a formatting sample in rapidyaml's docs](https://rapidyaml.readthedocs.io/latest/doxygen/group__doc__quickstart.html#gac2425b515eb552589708cfff70c52b14) + * */ + +/** @defgroup doc_format_specifiers Format specifiers + * + * @brief Format specifiers are tag types and functions that are used + * together with @ref doc_to_chars and @ref doc_from_chars + * + * @see [a formatting sample in rapidyaml's docs](https://rapidyaml.readthedocs.io/latest/doxygen/group__doc__quickstart.html#gac2425b515eb552589708cfff70c52b14) + * @ingroup doc_format_utils */ + +namespace c4 { + +/** @addtogroup doc_format_utils + * @{ */ + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// formatting truthy types as booleans + +namespace fmt { + +/** @addtogroup doc_format_specifiers + * @{ */ + +/** @defgroup doc_boolean_specifiers boolean specifiers + * @{ */ + +struct boolalpha_ +{ + bool val; +}; + +/** tag function to mark a variable to be written as an alphabetic + * boolean, ie as either true or false */ +template +boolalpha_ boolalpha(T const& val=false) +{ + return boolalpha_{val ? true : false}; +} + +/** @} */ + +/** @} */ + +} // namespace fmt + +/** write a variable as an alphabetic boolean, ie as either true or + * false + * @ingroup doc_to_chars */ +inline size_t to_chars(substr buf, fmt::boolalpha_ fmt) +{ + return to_chars(buf, fmt.val ? "true" : "false"); +} + + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// formatting integral types + +namespace fmt { + +/** @addtogroup doc_format_specifiers + * @{ */ + +/** @defgroup doc_integer_specifiers Integer specifiers + * @{ */ + +/** format an integral type with a custom radix */ +template +struct integral_ +{ + C4_STATIC_ASSERT(std::is_integral::value); + T val; + T radix; + C4_ALWAYS_INLINE integral_(T val_, T radix_) : val(val_), radix(radix_) {} +}; + +/** format an integral type with a custom radix, and pad with zeroes on the left */ +template +struct integral_padded_ +{ + C4_STATIC_ASSERT(std::is_integral::value); + T val; + T radix; + size_t num_digits; + C4_ALWAYS_INLINE integral_padded_(T val_, T radix_, size_t nd) : val(val_), radix(radix_), num_digits(nd) {} +}; + + +/** format an integral type with a custom radix */ +template +C4_ALWAYS_INLINE integral_ integral(T val, T radix=10) +{ + return integral_(val, radix); +} +/** format an integral type with a custom radix */ +template +C4_ALWAYS_INLINE integral_ integral(T const* val, T radix=10) +{ + return integral_(reinterpret_cast(val), static_cast(radix)); +} +/** format an integral type with a custom radix */ +template +C4_ALWAYS_INLINE integral_ integral(std::nullptr_t, T radix=10) +{ + return integral_(intptr_t(0), static_cast(radix)); +} + + +/** format the pointer as an hexadecimal value */ +template +inline integral_ hex(T * v) +{ + return integral_(reinterpret_cast(v), intptr_t(16)); +} +/** format the pointer as an hexadecimal value */ +template +inline integral_ hex(T const* v) +{ + return integral_(reinterpret_cast(v), intptr_t(16)); +} +/** format null as an hexadecimal value + * @overload hex */ +inline integral_ hex(std::nullptr_t) +{ + return integral_(0, intptr_t(16)); +} +/** format the integral_ argument as an hexadecimal value + * @overload hex */ +template +inline integral_ hex(T v) +{ + return integral_(v, T(16)); +} + +/** format the pointer as an octal value */ +template +inline integral_ oct(T const* v) +{ + return integral_(reinterpret_cast(v), intptr_t(8)); +} +/** format the pointer as an octal value */ +template +inline integral_ oct(T * v) +{ + return integral_(reinterpret_cast(v), intptr_t(8)); +} +/** format null as an octal value */ +inline integral_ oct(std::nullptr_t) +{ + return integral_(intptr_t(0), intptr_t(8)); +} +/** format the integral_ argument as an octal value */ +template +inline integral_ oct(T v) +{ + return integral_(v, T(8)); +} + +/** format the pointer as a binary 0-1 value + * @see c4::raw() if you want to use a binary memcpy instead of 0-1 formatting */ +template +inline integral_ bin(T const* v) +{ + return integral_(reinterpret_cast(v), intptr_t(2)); +} +/** format the pointer as a binary 0-1 value + * @see c4::raw() if you want to use a binary memcpy instead of 0-1 formatting */ +template +inline integral_ bin(T * v) +{ + return integral_(reinterpret_cast(v), intptr_t(2)); +} +/** format null as a binary 0-1 value + * @see c4::raw() if you want to use a binary memcpy instead of 0-1 formatting */ +inline integral_ bin(std::nullptr_t) +{ + return integral_(intptr_t(0), intptr_t(2)); +} +/** format the integral_ argument as a binary 0-1 value + * @see c4::raw() if you want to use a raw memcpy-based binary dump instead of 0-1 formatting */ +template +inline integral_ bin(T v) +{ + return integral_(v, T(2)); +} + +/** @} */ // integer_specifiers + + +/** @defgroup doc_zpad Pad the number with zeroes on the left + * @{ */ + +/** pad the argument with zeroes on the left, with decimal radix */ +template +C4_ALWAYS_INLINE integral_padded_ zpad(T val, size_t num_digits) +{ + return integral_padded_(val, T(10), num_digits); +} +/** pad the argument with zeroes on the left */ +template +C4_ALWAYS_INLINE integral_padded_ zpad(integral_ val, size_t num_digits) +{ + return integral_padded_(val.val, val.radix, num_digits); +} +/** pad the argument with zeroes on the left */ +C4_ALWAYS_INLINE integral_padded_ zpad(std::nullptr_t, size_t num_digits) +{ + return integral_padded_(0, 16, num_digits); +} +/** pad the argument with zeroes on the left */ +template +C4_ALWAYS_INLINE integral_padded_ zpad(T const* val, size_t num_digits) +{ + return integral_padded_(reinterpret_cast(val), 16, num_digits); +} +template +C4_ALWAYS_INLINE integral_padded_ zpad(T * val, size_t num_digits) +{ + return integral_padded_(reinterpret_cast(val), 16, num_digits); +} + +/** @} */ // zpad + + +/** @defgroup doc_overflow_checked Check read for overflow + * @{ */ + +template +struct overflow_checked_ +{ + static_assert(std::is_integral::value, "range checking only for integral types"); + C4_ALWAYS_INLINE overflow_checked_(T &val_) : val(&val_) {} + T *val; +}; +template +C4_ALWAYS_INLINE overflow_checked_ overflow_checked(T &val) +{ + return overflow_checked_(val); +} + +/** @} */ // overflow_checked + +/** @} */ // format_specifiers + + +} // namespace fmt + +/** format an integer signed type + * @ingroup doc_to_chars */ +template +C4_ALWAYS_INLINE auto to_chars(substr buf, fmt::integral_ fmt) + -> typename std::enable_if::value, size_t>::type +{ + return itoa(buf, fmt.val, fmt.radix); +} +/** format an integer signed type, pad with zeroes + * @ingroup doc_to_chars */ +template +C4_ALWAYS_INLINE auto to_chars(substr buf, fmt::integral_padded_ fmt) + -> typename std::enable_if::value, size_t>::type +{ + return itoa(buf, fmt.val, fmt.radix, fmt.num_digits); +} + +/** format an integer unsigned type + * @ingroup doc_to_chars */ +template +C4_ALWAYS_INLINE auto to_chars(substr buf, fmt::integral_ fmt) + -> typename std::enable_if::value, size_t>::type +{ + return utoa(buf, fmt.val, fmt.radix); +} +/** format an integer unsigned type, pad with zeroes + * @ingroup doc_to_chars */ +template +C4_ALWAYS_INLINE auto to_chars(substr buf, fmt::integral_padded_ fmt) + -> typename std::enable_if::value, size_t>::type +{ + return utoa(buf, fmt.val, fmt.radix, fmt.num_digits); +} + +/** read an integer type, detecting overflow (returns false on overflow) + * @ingroup doc_from_chars */ +template +C4_ALWAYS_INLINE bool from_chars(csubstr s, fmt::overflow_checked_ wrapper) +{ + if(C4_LIKELY(!overflows(s))) + return atox(s, wrapper.val); + return false; +} +/** read an integer type, detecting overflow (returns false on overflow) + * @ingroup doc_from_chars */ +template +C4_ALWAYS_INLINE bool from_chars(csubstr s, fmt::overflow_checked_ *wrapper) +{ + if(C4_LIKELY(!overflows(s))) + return atox(s, wrapper->val); + return false; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// formatting real types + +namespace fmt { + +/** @addtogroup doc_format_specifiers + * @{ */ + +/** @defgroup doc_real_specifiers Real specifiers + * @{ */ + +template +struct real_ +{ + T val; + int precision; + RealFormat_e fmt; + real_(T v, int prec=-1, RealFormat_e f=FTOA_FLOAT) : val(v), precision(prec), fmt(f) {} +}; + +template +real_ real(T val, int precision, RealFormat_e fmt=FTOA_FLOAT) +{ + return real_(val, precision, fmt); +} + +/** @} */ // real_specifiers + +/** @} */ // format_specifiers + +} // namespace fmt + +/** @ingroup doc_to_chars */ +inline size_t to_chars(substr buf, fmt::real_< float> fmt) { return ftoa(buf, fmt.val, fmt.precision, fmt.fmt); } +/** @ingroup doc_to_chars */ +inline size_t to_chars(substr buf, fmt::real_ fmt) { return dtoa(buf, fmt.val, fmt.precision, fmt.fmt); } + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// writing raw binary data + +namespace fmt { + +/** @addtogroup doc_format_specifiers + * @{ */ + +/** @defgroup doc_raw_binary_specifiers Raw binary data + * @{ */ + +/** @see blob_ */ +template +struct raw_wrapper_ : public blob_ +{ + size_t alignment; + + C4_ALWAYS_INLINE raw_wrapper_(blob_ data, size_t alignment_) noexcept + : + blob_(data), + alignment(alignment_) + { + C4_ASSERT_MSG(alignment > 0 && (alignment & (alignment - 1)) == 0, "alignment must be a power of two"); + } +}; + +using const_raw_wrapper = raw_wrapper_; +using raw_wrapper = raw_wrapper_; + +/** mark a variable to be written in raw binary format, using memcpy + * @see blob_ */ +inline const_raw_wrapper craw(cblob data, size_t alignment=alignof(max_align_t)) +{ + return const_raw_wrapper(data, alignment); +} +/** mark a variable to be written in raw binary format, using memcpy + * @see blob_ */ +inline const_raw_wrapper raw(cblob data, size_t alignment=alignof(max_align_t)) +{ + return const_raw_wrapper(data, alignment); +} +/** mark a variable to be written in raw binary format, using memcpy + * @see blob_ */ +template +inline const_raw_wrapper craw(T const& C4_RESTRICT data, size_t alignment=alignof(T)) +{ + return const_raw_wrapper(cblob(data), alignment); +} +/** mark a variable to be written in raw binary format, using memcpy + * @see blob_ */ +template +inline const_raw_wrapper raw(T const& C4_RESTRICT data, size_t alignment=alignof(T)) +{ + return const_raw_wrapper(cblob(data), alignment); +} + +/** mark a variable to be read in raw binary format, using memcpy */ +inline raw_wrapper raw(blob data, size_t alignment=alignof(max_align_t)) +{ + return raw_wrapper(data, alignment); +} +/** mark a variable to be read in raw binary format, using memcpy */ +template +inline raw_wrapper raw(T & C4_RESTRICT data, size_t alignment=alignof(T)) +{ + return raw_wrapper(blob(data), alignment); +} + +/** @} */ // raw_binary_specifiers + +/** @} */ // format_specifiers + +} // namespace fmt + + +/** write a variable in raw binary format, using memcpy + * @ingroup doc_to_chars */ +C4CORE_EXPORT size_t to_chars(substr buf, fmt::const_raw_wrapper r); + +/** read a variable in raw binary format, using memcpy + * @ingroup doc_from_chars */ +C4CORE_EXPORT bool from_chars(csubstr buf, fmt::raw_wrapper *r); +/** read a variable in raw binary format, using memcpy + * @ingroup doc_from_chars */ +inline bool from_chars(csubstr buf, fmt::raw_wrapper r) +{ + return from_chars(buf, &r); +} + +/** read a variable in raw binary format, using memcpy + * @ingroup doc_from_chars_first */ +inline size_t from_chars_first(csubstr buf, fmt::raw_wrapper *r) +{ + return from_chars(buf, r); +} +/** read a variable in raw binary format, using memcpy + * @ingroup doc_from_chars_first */ +inline size_t from_chars_first(csubstr buf, fmt::raw_wrapper r) +{ + return from_chars(buf, &r); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// formatting aligned to left/right/center + +namespace fmt { + +/** @addtogroup doc_format_specifiers + * @{ */ + +/** @defgroup doc_alignment_specifiers Alignment specifiers + * @{ */ + +template struct left_ { T val; size_t width; char padchar; C4_ALWAYS_INLINE left_ (T v, size_t w, char c) noexcept : val(v), width(w), padchar(c) {} }; +template struct center_ { T val; size_t width; char padchar; C4_ALWAYS_INLINE center_(T v, size_t w, char c) noexcept : val(v), width(w), padchar(c) {} }; +template struct right_ { T val; size_t width; char padchar; C4_ALWAYS_INLINE right_ (T v, size_t w, char c) noexcept : val(v), width(w), padchar(c) {} }; + +/** tag type to mark an argument to be aligned left. + * @param val the argument to be aligned left + * @param width the length of the field + * @param padchar the padding character, defaults to ' ' + * + * @note This function (and the return structure) uses value semantics. To + * avoid copies on larger types, you can use std::cref(). For example: + * @code{.cpp} + * char buf[512]; + * std::string large_string = .....; + * size_t len = cat(buf, fmt::left(std::cref(large_string), 100)); + * @endcode + */ +template +left_ left(T val, size_t width, char padchar=' ') +{ + return left_(val, width, padchar); +} + +/** tag function to mark an argument to be aligned center + * @param val the argument to be aligned center + * @param width the length of the field + * @param padchar the padding character, defaults to ' ' + * + * @note This function (and the return value) uses value semantics. To + * avoid copies on larger types, you can use std::cref(). For example: + * @code{.cpp} + * char buf[512]; + * std::string large_string = .....; + * size_t len = cat(buf, fmt::center(std::cref(large_string), 100)); + * @endcode + */ +template +center_ center(T val, size_t width, char padchar=' ') +{ + return center_(val, width, padchar); +} + +/** tag function to mark an argument to be aligned right + * @param val the argument to be aligned right + * @param width the length of the field + * @param padchar the padding character, defaults to ' ' + * + * @note This function (and the return value) uses value semantics. To + * avoid copies on larger types, you can use std::cref(). For example: + * @code{.cpp} + * char buf[512]; + * std::string large_string = .....; + * size_t len = cat(buf, fmt::right(std::cref(large_string), 100)); + * @endcode + */ +template +right_ right(T val, size_t width, char padchar=' ') +{ + return right_(val, width, padchar); +} + +/** @} */ // alignment_specifiers + +/** @} */ // format_specifiers + +} // namespace fmt + + +/** @ingroup doc_to_chars */ +template +size_t to_chars(substr buf, fmt::left_ const& C4_RESTRICT align) +{ + size_t ret = to_chars(buf, align.val); + if(ret >= buf.len || ret >= align.width) + return ret > align.width ? ret : align.width; + buf.first(align.width).sub(ret).fill(align.padchar); + return align.width; +} + +/** @ingroup doc_to_chars */ +template +size_t to_chars(substr buf, fmt::right_ const& C4_RESTRICT align) +{ + size_t ret = to_chars(buf, align.val); + if(ret >= buf.len || ret >= align.width) + return ret > align.width ? ret : align.width; + size_t rem = align.width - ret; + if(ret) + memmove(buf.str + rem, buf.str, ret); + buf.first(rem).fill(align.padchar); + return align.width; +} + +/** @ingroup doc_to_chars */ +template +size_t to_chars(substr buf, fmt::center_ const& C4_RESTRICT align) +{ + size_t ret = to_chars(buf, align.val); + if(ret >= buf.len || ret >= align.width) + return ret > align.width ? ret : align.width; + size_t first = (align.width - ret) / 2u; + if(ret) + memmove(buf.str + first, buf.str, ret); + buf.first(first).fill(align.padchar); + buf.sub(first + ret).fill(align.padchar); + return align.width; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** @defgroup doc_cat cat: concatenate arguments to string + * @{ */ + + +/** @cond dev */ +// terminates the variadic recursion +inline size_t cat(substr /*buf*/) +{ + return 0; +} +/** @endcond */ + + +/** serialize the arguments, concatenating them to the given fixed-size buffer. + * The buffer size is strictly respected: no writes will occur beyond its end. + * @return the number of characters needed to write all the arguments into the buffer. + * @see @ref c4::catrs() if instead of a fixed-size buffer, a resizeable container is desired + * @see @ref c4::uncat() for the inverse function + * @see @ref c4::catsep() if a separator between each argument is to be used + * @see @ref c4::format() if a format string is desired + * + * @note The arguments to format are restricted (legal because they + * are rvalues). This may require a workaround when arguments of type + * char[]/const char[] are passed repeatedly to the function. For + * example, + * @code{.cpp} + * const char str[] = "Hi! "; + * cat(buf, str, str, str); // compile error: 'passing argument 2 to ‘restrict’-qualified parameter aliases with arguments 3, 4' + * @endcode + * It is possible to work around the problem by suppressing -Wrestrict + * or by using the decayed type char* or const char*, or even wrapping + * the argument in a csubstr(): + * @code{.cpp} + * const char str[] = "Hi! "; + * csubstr ss = to_csubstr(str); + * cat(buf, ss, ss, ss); // ok! compiles cleanly + * @endcode + */ +template +size_t cat(substr buf, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more) +{ + size_t num = to_chars(buf, a); + buf = buf.len >= num ? buf.sub(num) : substr{}; + num += cat(buf, more...); + return num; +} + +/** like @ref c4::cat() but return a substr instead of a size */ +template +substr cat_sub(substr buf, Args const& C4_RESTRICT ...args) +{ + size_t sz = cat(buf, args...); + C4_CHECK(sz <= buf.len); + return {buf.str, sz <= buf.len ? sz : buf.len}; +} + +/** @} */ + + +//----------------------------------------------------------------------------- + + +/** @defgroup doc_uncat uncat: read concatenated arguments from string + * @{ */ + +/** @cond dev */ +// terminates the variadic recursion +inline size_t uncat(csubstr /*buf*/) +{ + return 0; +} +/** @endcond */ + + +/** deserialize the arguments from the given buffer. + * + * @return the number of characters read from the buffer, or csubstr::npos + * if a conversion was not successful. + * @see @ref c4::cat(). @ref c4::uncat() is the inverse of @ref c4::cat(). */ +template +size_t uncat(csubstr buf, Arg & C4_RESTRICT a, Args & C4_RESTRICT ...more) +{ + size_t out = from_chars_first(buf, &a); + if(C4_UNLIKELY(out == csubstr::npos)) + return csubstr::npos; + buf = buf.len >= out ? buf.sub(out) : substr{}; + size_t num = uncat(buf, more...); + if(C4_UNLIKELY(num == csubstr::npos)) + return csubstr::npos; + return out + num; +} + +/** @} */ + + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + + +/** @defgroup doc_catsep catsep: cat arguments to string with separator + * @{ */ + +/** @cond dev */ +namespace detail { +template +C4_ALWAYS_INLINE size_t catsep_more(substr /*buf*/, Sep const& C4_RESTRICT /*sep*/) +{ + return 0; +} + +template +size_t catsep_more(substr buf, Sep const& C4_RESTRICT sep, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more) +{ + size_t ret = to_chars(buf, sep); + size_t num = ret; + buf = buf.len >= ret ? buf.sub(ret) : substr{}; + ret = to_chars(buf, a); + num += ret; + buf = buf.len >= ret ? buf.sub(ret) : substr{}; + ret = catsep_more(buf, sep, more...); + num += ret; + return num; +} +} // namespace detail + +// terminates the variadic recursion +template +size_t catsep(substr /*buf*/, Sep const& C4_RESTRICT /*sep*/) +{ + return 0; +} +/** @endcond */ + + +/** serialize the arguments, concatenating them to the given fixed-size + * buffer, using a separator between each argument. + * The buffer size is strictly respected: no writes will occur beyond its end. + * @return the number of characters needed to write all the arguments into the buffer. + * @see @ref c4::catseprs() if instead of a fixed-size buffer, a resizeable container is desired + * @see @ref c4::uncatsep() for the inverse function (ie, reading instead of writing) + * @see @ref c4::cat() if no separator is needed + * @see @ref c4::format() if a format string is desired + * + * + * @note The arguments to format are restricted (legal because they + * are rvalues). This may require a workaround when arguments of type + * char[]/const char[] are passed repeatedly to the function. For + * example, + * @code{.cpp} + * const char str[] = "Hi! "; + * cat(buf, str, str, str); // compile error: 'passing argument 2 to ‘restrict’-qualified parameter aliases with arguments 3, 4' + * @endcode + * It is possible to work around the problem by suppressing -Wrestrict + * or by using the decayed type char* or const char*, or even wrapping + * the argument in a csubstr(): + * @code{.cpp} + * const char str[] = "Hi! "; + * csubstr ss = to_csubstr(str); + * cat(buf, ss, ss, ss); // ok! compiles cleanly + * @endcode + */ +template +size_t catsep(substr buf, Sep const& C4_RESTRICT sep, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more) +{ + size_t num = to_chars(buf, a); + buf = buf.len >= num ? buf.sub(num) : substr{}; + num += detail::catsep_more(buf, sep, more...); + return num; +} + +/** like @ref c4::catsep() but return a substr instead of a size @see + * @ref c4::catsep(). @ref c4::uncatsep() is the inverse of @ref + * c4::catsep(). */ +template +substr catsep_sub(substr buf, Args && ...args) +{ + size_t sz = catsep(buf, std::forward(args)...); + C4_CHECK(sz <= buf.len); + return {buf.str, sz <= buf.len ? sz : buf.len}; +} + +/** @} */ + + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** @defgroup doc_uncatsep uncatsep: deserialize the separated arguments from a string + * @{ */ + +/** @cond dev */ +template +inline size_t uncatsep(csubstr buf, csubstr /*sep*/, Arg &C4_RESTRICT a) +{ + return from_chars(buf, &a) ? buf.len : csubstr::npos; +} +/** @endcond */ + +/** deserialize the arguments from the given buffer, using a separator. + * + * @return the number of characters read from the buffer, or csubstr::npos + * if a conversion was not successful + * + * @see c4::catsep(). @ref c4::uncatsep() is the inverse of @ref c4::catsep(). */ +template +size_t uncatsep(csubstr buf, csubstr sep, Arg & C4_RESTRICT a, Args & C4_RESTRICT ...more) +{ + if(C4_LIKELY(sep.len > 0)) + { + size_t pos = buf.find(sep); + if(C4_LIKELY(pos != csubstr::npos)) + { + if(C4_LIKELY(from_chars(buf.first(pos), &a))) + { + pos += sep.len; + size_t num = uncatsep(buf.sub(pos), sep, more...); + if(C4_LIKELY(num != csubstr::npos)) + return pos + num; + } + } + } + return csubstr::npos; +} + +/** @} */ + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** @defgroup doc_format format: formatted string interpolation + * @{ */ + +/// @cond dev +// terminates the variadic recursion +inline size_t format(substr buf, csubstr fmt) +{ + return to_chars(buf, fmt); +} +/// @endcond + + +/** Using a format string, serialize the arguments into the given + * fixed-size buffer. The buffer size is strictly respected: no writes + * will occur beyond its end. In the format string, each argument is + * marked with a compact curly-bracket pair "{}". This pair does not + * take any interior sequence numbers or extra formatting arguments + * inside it (contrary to eg the C++20 std::format implementation or + * the Python formatting facilities). To enable argument + * customization, use the formatting facilities in @ref + * doc_format_specifiers wrapping the arguments passed to this + * function. + * + * @return the number of bytes needed to write into the buffer. + * + * @see @ref c4::formatrs() if instead of a fixed-size buffer, a resizeable container is desired + * @see @ref c4::unformat() for the inverse function + * @see @ref c4::cat() if no format or separator is needed + * @see @ref c4::catsep() if no format is needed, but a separator must be used + * + * For example: + * @code{.cpp} + * c4::format(buf, "the {} drank {} {}", "partier", 5, "beers"); // the partier drank 5 beers + * c4::format(buf, "the {} drank {} {}", "programmer", 6, "coffees"); // the programmer drank 6 coffees + * @endcode + * + * Using @ref + * doc_format_specifiers enables control of the result. For example: + * @code{.cpp} + * c4::format(buf, "the {} drank {} {}", "partier", c4::fmt::real(5, 3), "beers"); // the partier drank 5.000 beers + * c4::format(buf, "the {} drank {} {}", "partier", c4::fmt::zpad(5, 3), "beers"); // the partier drank 005 beers + * c4::format(buf, "the {} drank {} {}", "partier", c4::fmt::bin(5), "beers"); // the partier drank 0b101 beers + * c4::format(buf, "the {} drank {} {}", "partier", c4::fmt::oct(5), "beers"); // the partier drank 0o6 beers + * c4::format(buf, "the {} drank {} {}", "partier", c4::fmt::hex(5), "beers"); // the partier drank 0x6 beers + * c4::format(buf, "the {} drank {} {}", "programmer", c4::fmt::real(6, 3), "coffees"); // the programmer drank 6.000 coffees + * c4::format(buf, "the {} drank {} {}", "programmer", c4::fmt::zpad(6, 3), "coffees"); // the programmer drank 006 coffees + * c4::format(buf, "the {} drank {} {}", "programmer", c4::fmt::bin(6), "coffees"); // the programmer drank 0b110 coffees + * c4::format(buf, "the {} drank {} {}", "programmer", c4::fmt::oct(6), "coffees"); // the programmer drank 0o6 coffees + * c4::format(buf, "the {} drank {} {}", "programmer", c4::fmt::hex(6), "coffees"); // the programmer drank 0x6 coffees + * @endcode + * + * @note Arguments beyond the last curly bracket pair are silently + * ignored. Curly bracket pairs without a corresponding argument are + * printed as part of the result. + * @code{.cpp} + * // note "and nothing else" being ignored + * c4::format(buf, "the {} drank {} {}", "partier", 5, "beers", "and nothing else"); // the partier drank 5 beers + * + * // note "this is ignored {}" being part of the result + * c4::format(buf, "the {} drank {} {} this is ignored: {}", "programmer", 6, "coffees"); // the programmer drank 6 coffees this is ignored: {} + * @endcode + * + * @note The curly bracket pair cannot be escaped, but can of course + * be placed into the result by passing an "{}" argument in its place, + * or if it is provided beyond the last argument passed to the + * function (see prior note). + * @code{.cpp} + * // as above: no argument given, so no substitution made: + * c4::format(buf, "let's show {} on the result"); // let's show {} on the result + * // or just pass "{}" as an argument to force the substitution: + * c4::format(buf, "let's show {} on the result and then {}", "{}", "this"); // let's show {} on the result and then this + * @endcode + * + * @note The arguments to format are restricted (legal because they + * are rvalues). This may require a workaround when arguments of type + * char[]/const char[] are passed repeatedly to the function. For + * example, + * @code{.cpp} + * const char str[] = "Hi! "; + * cat(buf, str, str, str); // compile error: 'passing argument 2 to ‘restrict’-qualified parameter aliases with arguments 3, 4' + * @endcode + * It is possible to work around the problem by suppressing -Wrestrict + * or by using the decayed type char* or const char*, or even wrapping + * the argument in a csubstr(): + * @code{.cpp} + * const char str[] = "Hi! "; + * csubstr ss = to_csubstr(str); + * cat(buf, ss, ss, ss); // ok! compiles cleanly + * @endcode + */ +template +size_t format(substr buf, csubstr fmt, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more) +{ + size_t pos = fmt.find("{}"); + if(C4_UNLIKELY(pos == csubstr::npos)) + return to_chars(buf, fmt); + size_t num = to_chars(buf, fmt.first(pos)); + size_t out = num; + buf = buf.len >= num ? buf.sub(num) : substr{}; + num = to_chars(buf, a); + out += num; + buf = buf.len >= num ? buf.sub(num) : substr{}; + num = format(buf, fmt.sub(pos + 2), more...); + out += num; + return out; +} + +/** like @ref c4::format() but return a substr instead of a size + * @see c4::format() + * @see c4::catsep(). @ref c4::uncatsep() is the inverse of @ref c4::catsep(). */ +template +substr format_sub(substr buf, csubstr fmt, Args const& C4_RESTRICT ...args) +{ + size_t sz = c4::format(buf, fmt, args...); + C4_CHECK(sz <= buf.len); + return {buf.str, sz <= buf.len ? sz : buf.len}; +} + +/** @} */ + + +//----------------------------------------------------------------------------- + +/** @defgroup doc_unformat unformat: formatted read from string + * @{ */ + +/// @cond dev +// terminates the variadic recursion +inline size_t unformat(csubstr /*buf*/, csubstr fmt) +{ + return fmt.len; +} +/// @endcond + + +/** using a format string, deserialize the arguments from the given + * buffer. This is the inverse function to @ref c4::format(). + * + * @return the number of characters read from the buffer, or npos if a conversion failed. + * + * @see @ref c4::format(). */ +template +size_t unformat(csubstr buf, csubstr fmt, Arg & C4_RESTRICT a, Args & C4_RESTRICT ...more) +{ + const size_t pos = fmt.find("{}"); + if(C4_UNLIKELY(pos == csubstr::npos)) + return unformat(buf, fmt); + size_t num = pos; + size_t out = num; + buf = buf.len >= num ? buf.sub(num) : substr{}; + num = from_chars_first(buf, &a); + if(C4_UNLIKELY(num == csubstr::npos)) + return csubstr::npos; + out += num; + buf = buf.len >= num ? buf.sub(num) : substr{}; + num = unformat(buf, fmt.sub(pos + 2), more...); + if(C4_UNLIKELY(num == csubstr::npos)) + return csubstr::npos; + out += num; + return out; +} + +/** @} */ + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** cat+resize: like @ref c4::cat(), but receives a container, and + * resizes it as needed to contain the result. The container is + * overwritten. To append to it, use @ref c4::catrs_append(). + * + * @see @ref c4::cat() + * @see @ref c4::catrs_append() + * @ingroup doc_cat + * + * @note The arguments to format are restricted (legal because they + * are rvalues). This may require a workaround when arguments of type + * char[]/const char[] are passed repeatedly to the function. For + * example, + * @code{.cpp} + * const char str[] = "Hi! "; + * cat(buf, str, str, str); // compile error: 'passing argument 2 to ‘restrict’-qualified parameter aliases with arguments 3, 4' + * @endcode + * It is possible to work around the problem by suppressing -Wrestrict + * or by using the decayed type char* or const char*, or even wrapping + * the argument in a csubstr(): + * @code{.cpp} + * const char str[] = "Hi! "; + * csubstr ss = to_csubstr(str); + * cat(buf, ss, ss, ss); // ok! compiles cleanly + * @endcode + */ +template +inline void catrs(CharOwningContainer * C4_RESTRICT cont, Args const& C4_RESTRICT ...args) +{ + cont->resize(cont->capacity()); // improve the odds of fitting in the original buffer +retry: + substr buf = to_substr(*cont); + size_t ret = cat(buf, args...); + cont->resize(ret); + if(ret > buf.len) + goto retry; +} + +/** cat+resize: like @ref c4::cat(), but creates and returns a new + * container sized as needed to contain the result. + * + * @see @ref c4::cat() + * @ingroup doc_cat + * + * @note The arguments to format are restricted (legal because they + * are rvalues). This may require a workaround when arguments of type + * char[]/const char[] are passed repeatedly to the function. For + * example, + * @code{.cpp} + * const char str[] = "Hi! "; + * cat(buf, str, str, str); // compile error: 'passing argument 2 to ‘restrict’-qualified parameter aliases with arguments 3, 4' + * @endcode + * It is possible to work around the problem by suppressing -Wrestrict + * or by using the decayed type char* or const char*, or even wrapping + * the argument in a csubstr(): + * @code{.cpp} + * const char str[] = "Hi! "; + * csubstr ss = to_csubstr(str); + * cat(buf, ss, ss, ss); // ok! compiles cleanly + * @endcode + */ +template +inline CharOwningContainer catrs(Args const& C4_RESTRICT ...args) +{ + CharOwningContainer cont; + catrs(&cont, args...); + return cont; +} + +/** cat+resize+append: like @ref c4::cat(), but receives a container, + * and appends to it instead of overwriting it. The container is + * resized as needed to contain the result. + * + * @return the region newly appended to the original container + * @see @ref c4::cat() + * @see @ref c4::catrs() + * @ingroup doc_cat + * + * @note The arguments to format are restricted (legal because they + * are rvalues). This may require a workaround when arguments of type + * char[]/const char[] are passed repeatedly to the function. For + * example, + * @code{.cpp} + * const char str[] = "Hi! "; + * cat(buf, str, str, str); // compile error: 'passing argument 2 to ‘restrict’-qualified parameter aliases with arguments 3, 4' + * @endcode + * It is possible to work around the problem by suppressing -Wrestrict + * or by using the decayed type char* or const char*, or even wrapping + * the argument in a csubstr(): + * @code{.cpp} + * const char str[] = "Hi! "; + * csubstr ss = to_csubstr(str); + * cat(buf, ss, ss, ss); // ok! compiles cleanly + * @endcode + */ +template +inline csubstr catrs_append(CharOwningContainer * C4_RESTRICT cont, Args const& C4_RESTRICT ...args) +{ + const size_t pos = cont->size(); + cont->resize(cont->capacity()); // improve the odds of fitting in the original buffer +retry: + substr buf = to_substr(*cont).sub(pos); + size_t ret = cat(buf, args...); + cont->resize(pos + ret); + if(ret > buf.len) + goto retry; + return to_csubstr(*cont).range(pos, cont->size()); +} + + +//----------------------------------------------------------------------------- + +/** catsep+resize: like @ref c4::catsep(), but receives a container, + * and resizes it as needed to contain the result. The container is + * overwritten. To append to the container use @ref + * c4::catseprs_append(). + * + * @see @ref c4::catsep() + * @see @ref c4::catseprs_append() + * @ingroup doc_catsep + * + * @note The arguments to format are restricted (legal because they + * are rvalues). This may require a workaround when arguments of type + * char[]/const char[] are passed repeatedly to the function. For + * example, + * @code{.cpp} + * const char str[] = "Hi! "; + * cat(buf, str, str, str); // compile error: 'passing argument 2 to ‘restrict’-qualified parameter aliases with arguments 3, 4' + * @endcode + * It is possible to work around the problem by suppressing -Wrestrict + * or by using the decayed type char* or const char*, or even wrapping + * the argument in a csubstr(): + * @code{.cpp} + * const char str[] = "Hi! "; + * csubstr ss = to_csubstr(str); + * cat(buf, ss, ss, ss); // ok! compiles cleanly + * @endcode + */ +template +inline void catseprs(CharOwningContainer * C4_RESTRICT cont, Sep const& C4_RESTRICT sep, Args const& C4_RESTRICT ...args) +{ + cont->resize(cont->capacity()); // improve the odds of fitting in the original buffer +retry: + substr buf = to_substr(*cont); + size_t ret = catsep(buf, sep, args...); + cont->resize(ret); + if(ret > buf.len) + goto retry; +} + +/** catsep+resize: like @ref c4::catsep(), but create a new container + * with the result. + * + * @return the requested container + * @ingroup doc_catsep + * + * @note The arguments to format are restricted (legal because they + * are rvalues). This may require a workaround when arguments of type + * char[]/const char[] are passed repeatedly to the function. For + * example, + * @code{.cpp} + * const char str[] = "Hi! "; + * cat(buf, str, str, str); // compile error: 'passing argument 2 to ‘restrict’-qualified parameter aliases with arguments 3, 4' + * @endcode + * It is possible to work around the problem by suppressing -Wrestrict + * or by using the decayed type char* or const char*, or even wrapping + * the argument in a csubstr(): + * @code{.cpp} + * const char str[] = "Hi! "; + * csubstr ss = to_csubstr(str); + * cat(buf, ss, ss, ss); // ok! compiles cleanly + * @endcode + */ +template +inline CharOwningContainer catseprs(Sep const& C4_RESTRICT sep, Args const& C4_RESTRICT ...args) +{ + CharOwningContainer cont; + catseprs(&cont, sep, args...); + return cont; +} + + +/** catsep+resize+append: like @ref c4::catsep(), but receives a + * container, and appends the arguments, resizing the container as + * needed to contain the result. The buffer is appended to. + * + * @return a csubstr of the appended part + * @ingroup doc_catsep + * + * @note The arguments to format are restricted (legal because they + * are rvalues). This may require a workaround when arguments of type + * char[]/const char[] are passed repeatedly to the function. For + * example, + * @code{.cpp} + * const char str[] = "Hi! "; + * cat(buf, str, str, str); // compile error: 'passing argument 2 to ‘restrict’-qualified parameter aliases with arguments 3, 4' + * @endcode + * It is possible to work around the problem by suppressing -Wrestrict + * or by using the decayed type char* or const char*, or even wrapping + * the argument in a csubstr(): + * @code{.cpp} + * const char str[] = "Hi! "; + * csubstr ss = to_csubstr(str); + * cat(buf, ss, ss, ss); // ok! compiles cleanly + * @endcode + */ +template +inline csubstr catseprs_append(CharOwningContainer * C4_RESTRICT cont, Sep const& C4_RESTRICT sep, Args const& C4_RESTRICT ...args) +{ + const size_t pos = cont->size(); + cont->resize(cont->capacity()); // improve the odds of fitting in the original buffer +retry: + substr buf = to_substr(*cont).sub(pos); + size_t ret = catsep(buf, sep, args...); + cont->resize(pos + ret); + if(ret > buf.len) + goto retry; + return to_csubstr(*cont).range(pos, cont->size()); +} + + +//----------------------------------------------------------------------------- + +/** format+resize: like @ref c4::format(), but receives a container, + * and resizes it as needed to contain the result. The container is + * overwritten. To append to the container use @ref + * c4::formatrs_append(). + * + * @see @ref c4::format() + * @see @ref c4::formatrs_append() + * @ingroup doc_format + * + * @note The arguments to format are restricted (legal because they + * are rvalues). This may require a workaround when arguments of type + * char[]/const char[] are passed repeatedly to the function. For + * example, + * @code{.cpp} + * const char str[] = "Hi! "; + * cat(buf, str, str, str); // compile error: 'passing argument 2 to ‘restrict’-qualified parameter aliases with arguments 3, 4' + * @endcode + * It is possible to work around the problem by suppressing -Wrestrict + * or by using the decayed type char* or const char*, or even wrapping + * the argument in a csubstr(): + * @code{.cpp} + * const char str[] = "Hi! "; + * csubstr ss = to_csubstr(str); + * cat(buf, ss, ss, ss); // ok! compiles cleanly + * @endcode + */ +template +inline void formatrs(CharOwningContainer * C4_RESTRICT cont, csubstr fmt, Args const& C4_RESTRICT ...args) +{ + cont->resize(cont->capacity()); // improve the odds of fitting in the original buffer +retry: + substr buf = to_substr(*cont); + size_t ret = format(buf, fmt, args...); + cont->resize(ret); + if(ret > buf.len) + goto retry; +} + +/** format+resize: like @ref c4::format(), but create a new container + * with the result. + * + * @return the requested container + * @ingroup doc_format + * + * @note The arguments to format are restricted (legal because they + * are rvalues). This may require a workaround when arguments of type + * char[]/const char[] are passed repeatedly to the function. For + * example, + * @code{.cpp} + * const char str[] = "Hi! "; + * cat(buf, str, str, str); // compile error: 'passing argument 2 to ‘restrict’-qualified parameter aliases with arguments 3, 4' + * @endcode + * It is possible to work around the problem by suppressing -Wrestrict + * or by using the decayed type char* or const char*, or even wrapping + * the argument in a csubstr(): + * @code{.cpp} + * const char str[] = "Hi! "; + * csubstr ss = to_csubstr(str); + * cat(buf, ss, ss, ss); // ok! compiles cleanly + * @endcode + */ +template +inline CharOwningContainer formatrs(csubstr fmt, Args const& C4_RESTRICT ...args) +{ + CharOwningContainer cont; + formatrs(&cont, fmt, args...); + return cont; +} + +/** format+resize+append: like @ref c4::format(), but receives a + * container, and appends the arguments, resizing the container as + * needed to contain the result. The buffer is appended to. + * + * @return the region newly appended to the original container + * @ingroup doc_format + * + * @note The arguments to format are restricted (legal because they + * are rvalues). This may require a workaround when arguments of type + * char[]/const char[] are passed repeatedly to the function. For + * example, + * @code{.cpp} + * const char str[] = "Hi! "; + * cat(buf, str, str, str); // compile error: 'passing argument 2 to ‘restrict’-qualified parameter aliases with arguments 3, 4' + * @endcode + * It is possible to work around the problem by suppressing -Wrestrict + * or by using the decayed type char* or const char*, or even wrapping + * the argument in a csubstr(): + * @code{.cpp} + * const char str[] = "Hi! "; + * csubstr ss = to_csubstr(str); + * cat(buf, ss, ss, ss); // ok! compiles cleanly + * @endcode + */ +template +inline csubstr formatrs_append(CharOwningContainer * C4_RESTRICT cont, csubstr fmt, Args const& C4_RESTRICT ...args) +{ + const size_t pos = cont->size(); + C4_SUPPRESS_WARNING_GCC_WITH_PUSH("-Warray-bounds") + cont->resize(cont->capacity()); // improve the odds of fitting in the original buffer + C4_SUPPRESS_WARNING_GCC_POP +retry: + substr buf = to_substr(*cont).sub(pos); + size_t ret = format(buf, fmt, args...); + cont->resize(pos + ret); + if(ret > buf.len) + goto retry; + return to_csubstr(*cont).range(pos, cont->size()); +} + +/** @} */ + +} // namespace c4 + +// NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast,*avoid-goto*) +#ifdef _MSC_VER +# pragma warning(pop) +#elif defined(__clang__) +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#endif + +#endif /* _C4_FORMAT_HPP_ */ + + +// (end src/c4/format.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/dump.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef C4_DUMP_HPP_ +#define C4_DUMP_HPP_ + +// amalgamate: removed include of +// c4/substr.hpp +//#include +#if !defined(C4_SUBSTR_HPP_) && !defined(_C4_SUBSTR_HPP_) +#error "amalgamate: file c4/substr.hpp must have been included at this point" +#endif /* C4_SUBSTR_HPP_ */ + +//included above: +//#include // for std::forward + + +/** @file dump.hpp This file provides functions to dump several + * arguments as strings to a user-provided function sink, for example + * to implement a type-safe printf()-like function (where the sink + * would just be a plain call to putchars()). The function sink can be + * passed either by dynamic dispatching or by static dispatching (as a + * template argument). There are analogs to @ref c4::cat() (@ref + * c4::cat_dump() and @ref c4::cat_dump_resume()), @ref c4::catsep() + * (@ref c4::catsep_dump() and @ref c4::catsep_dump_resume()) and @ref + * c4::format() (@ref c4::format_dump() and @ref + * c4::format_dump_resume()). The analogs have two types: immediate + * and resuming. An analog of immediate type cannot be retried when + * the work buffer is too small; this means that successful dumps in + * the first (successful) arguments will be dumped again in the + * subsequent attempt to call. An analog of resuming type will only + * ever dump as-yet-undumped arguments, through the use of @ref + * c4::DumpResults return type. */ + +namespace c4 { + +C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wold-style-cast") + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** @defgroup dump_building_blocks Basic building blocks for dumping. + * + * The basic building block: given an argument and a + * buffer, serialize the argument to the buffer using @ref + * c4::to_chars(), and dump the buffer to the provided sink + * function. When the argument is a string, no serialization is + * performed, and the argument is dumped directly to the sink. + * + * @{ */ + + +/** Type of the function to be used as the sink. This function + * receives as its argument the string with characters to send to the + * sink. + * + * @warning the string passed to the sink may have zero length. If the + * user sink uses memcpy(), the call to memcpy() should be defended + * with a check for zero length (calling memcpy with zero length is + * undefined behavior). + * */ +using SinkPfn = void (*)(csubstr str); + + +/** @cond dev */ +namespace detail { +// std::remove_cvref appeared in c++20 +template +struct _remove_cvref +{ + using type = typename std::remove_cv::type>::type; +}; +} // namespace detail +/** @endcond */ + +template struct is_string; // fwd-decl + +/** a traits class used by @ref c4::dump() to decide whether a type is + * treated as a string type (which is dumped directly to the sink via + * to_csubstr()), or if the type is treated as a value, which is first + * serialized to the dump buffer using to_chars() prior to dumping it + * to the sink. This type defaults to @ref c4::is_string, but can be + * overriden independently. */ +template struct dump_directly : public is_string::type> {}; + + +#if (C4_CPP >= 17) || defined(__DOXYGEN__) +/** Dump a serializable object to the (statically dispatched) + * sink. Before dumping, the object may be serialized to a string if + * @ref c4::dump_directly is a false type (the default if + * dump_directly does not have a specialization). Otherwise the object + * is considered a string object and is therefore dumped directly, + * without any intermediate serialization. + * + * @return the number of bytes needed to serialize the string-type + * object, which may be 0 when there is no serialization + * + * @note the argument is considered a value when @ref + * c4::dump_directly is a false type, which is the default. To enable + * the argument to be treated as a string type, which is dumped + * directly to the sink without intermediate serialization, define + * @ref c4::dump_directly to a true type. + * + * @warning the string passed to the sink may have zero length. If the + * user sink uses memcpy(), the call to memcpy() should be defended + * with a check for zero length (calling memcpy with zero length is + * undefined behavior). + * + * @see @ref c4::dump_directly + */ +template +size_t dump(substr buf, Arg const& a) +{ + if constexpr (dump_directly::value) + { + C4_UNUSED(buf); + csubstr sa = to_csubstr(a); + C4_ASSERT(!buf.overlaps(sa)); + // dump directly, no need to serialize to the buffer + sinkfn(sa); + return 0; // no space was used in the buffer + } + else + { + // serialize to the buffer + const size_t sz = to_chars(buf, a); + // dump the buffer to the sink + if(C4_LIKELY(sz <= buf.len)) + { + // NOTE: don't do this: + //sinkfn(buf.first(sz)); + // ... but do this instead: + sinkfn({buf.str, sz}); + // ... this is needed because Release builds for armv5 and + // armv6 were failing for the first call, with the wrong + // buffer being passed into the function (!) + } + return sz; + } +} + +/** Dump a serializable object to the (dynamically dispatched) + * sink. Before dumping, the object may be serialized to a string if + * @ref c4::dump_directly is a false type (the default if + * dump_directly does not have a specialization). Otherwise the object + * is considered a string object is dumped directly, without any + * intermediate serialization. + * + * @return the number of bytes needed to serialize the string-type + * object, which may be 0 when there is no serialization + * + * @note the argument is considered a value when @ref + * c4::dump_directly is a false type, which is the default. To enable + * the argument to be treated as a string type, which is dumped + * directly to the sink without intermediate serialization, define + * @ref c4::dump_directly to a true type. + * + * @warning the string passed to the sink may have zero length. If the + * user sink uses memcpy(), the call to memcpy() should be defended + * with a check for zero length (calling memcpy with zero length is + * undefined behavior). + * + * @see @ref c4::dump_directly + */ +template +size_t dump(SinkFn &&sinkfn, substr buf, Arg const& a) +{ + if constexpr (dump_directly::value) + { + C4_UNUSED(buf); + csubstr sa = to_csubstr(a); + C4_ASSERT(!buf.overlaps(sa)); + // dump directly, no need to serialize to the buffer + std::forward(sinkfn)(sa); + return 0; // no space was used in the buffer + } + else + { + // serialize to the buffer + const size_t sz = to_chars(buf, a); + // dump the buffer to the sink + if(C4_LIKELY(sz <= buf.len)) + { + // NOTE: don't do this: + //std::forward(sinkfn)(buf.first(sz)); + // ... but do this instead: + std::forward(sinkfn)({buf.str, sz}); + // ... this is needed because Release builds for armv5 and + // armv6 were failing for the first call, with the wrong + // buffer being passed into the function (!) + } + return sz; + } +} + +#else // C4_CPP < 17 + +template +inline auto dump(substr buf, Arg const& a) + -> typename std::enable_if::value, size_t>::type +{ + C4_UNUSED(buf); + csubstr sa = to_csubstr(a); + C4_ASSERT(!buf.overlaps(sa)); + // dump directly, no need to serialize to the buffer + sinkfn(sa); + return 0; // no space was used in the buffer +} +template +inline auto dump(SinkFn &&sinkfn, substr buf, Arg const& a) + -> typename std::enable_if::value, size_t>::type +{ + C4_UNUSED(buf); + csubstr sa = to_csubstr(a); + C4_ASSERT(!buf.overlaps(sa)); + // dump directly, no need to serialize to the buffer + std::forward(sinkfn)(sa); + return 0; // no space was used in the buffer +} + + +template +inline auto dump(substr buf, Arg const& a) + -> typename std::enable_if::value, size_t>::type +{ + // serialize to the buffer + const size_t sz = to_chars(buf, a); + // dump the buffer to the sink + if(C4_LIKELY(sz <= buf.len)) + { + // NOTE: don't do this: + //sinkfn(buf.first(sz)); + // ... but do this instead: + sinkfn({buf.str, sz}); + // ... this is needed because Release builds for armv5 and + // armv6 were failing for the first call, with the wrong + // buffer being passed into the function (!) + } + return sz; +} +template +inline auto dump(SinkFn &&sinkfn, substr buf, Arg const& a) + -> typename std::enable_if::value, size_t>::type +{ + // serialize to the buffer + const size_t sz = to_chars(buf, a); + // dump the buffer to the sink + if(C4_LIKELY(sz <= buf.len)) + { + // NOTE: don't do this: + //std::forward(sinkfn)(buf.first(sz)); + // ... but do this instead: + std::forward(sinkfn)({buf.str, sz}); + // ... this is needed because Release builds for armv5 and + // armv6 were failing for the first call, with the wrong + // buffer being passed into the function (!) + } + return sz; +} +#endif // C4_CPP < 17 + + +/** An opaque type used by resumeable dump functions like @ref + * c4::cat_dump_resume(), @ref c4::catsep_dump_resume() or @ref + * c4::format_dump_resume(). */ +struct DumpResults +{ + enum : size_t { noarg = (size_t)-1 }; + size_t bufsize = 0; + size_t lastok = noarg; + bool success_until(size_t expected) const { return lastok == noarg ? false : lastok >= expected; } + bool write_arg(size_t arg) const { return lastok == noarg || arg > lastok; } + size_t argfail() const { return lastok + 1; } +}; + +/** @} */ + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + + +/** @defgroup cat_dump Dump several arguments to a sink, + * concatenated. This is the analog to @ref c4::cat(), with the + * significant difference that each argument is immediately sent to + * the sink (resulting in multiple calls to the sink function, once + * per argument), whereas equivalent usage of c4::cat() would first + * serialize all the arguments to the buffer, and then call the sink + * once at the end. As a consequence, the size needed for the buffer + * is only the maximum of the size needed for the arguments, whereas + * with c4::cat(), the size needed for the buffer would be the sum of + * the size needed for the arguments. When the size of dump + * + * @{ */ + +/// @cond dev +// terminates the variadic recursion +template +size_t cat_dump(SinkFn &&, substr) // NOLINT +{ + return 0; +} + +// terminates the variadic recursion +template +size_t cat_dump(substr) // NOLINT +{ + return 0; +} +/// @endcond + + +/** Dump several arguments to the (dynamically dispatched) sink + * function, as if through c4::cat(). For each argument, @ref dump() + * is called with the buffer and sink. If any of the arguments is too + * large for the buffer, no subsequent argument is sent to the sink, + * (but all the arguments are still processed to compute the size + * required for the buffer). This function can be safely called with an + * empty buffer. + * + * @return the size required for the buffer, which is the maximum size + * across all arguments + * + * @note subsequent calls with the same set of arguments will dump + * again the first successful arguments. If each argument must only be + * sent once to the sink (for example with printf-like behavior), use + * instead @ref cat_dump_resume(). */ +template +size_t cat_dump(SinkFn &&sinkfn, substr buf, Arg const& a, Args const& ...more) +{ + const size_t size_for_a = dump(std::forward(sinkfn), buf, a); + if(C4_UNLIKELY(size_for_a > buf.len)) + buf.len = 0; // ensure no more calls to the sink + const size_t size_for_more = cat_dump(std::forward(sinkfn), buf, more...); + return size_for_more > size_for_a ? size_for_more : size_for_a; +} + + +/** Dump several arguments to the (statically dispatched) sink + * function, as if through c4::cat(). For each argument, @ref dump() + * is called with the buffer and sink. If any of the arguments is too + * large for the buffer, no subsequent argument is sent to the sink, + * (but all the arguments are still processed to compute the size + * required for the buffer). This function can be safely called with an + * empty buffer. + * + * @return the size required for the buffer, which is the maximum size + * across all arguments + * + * @note subsequent calls with the same set of arguments will dump + * again the first successful arguments. If each argument must only be + * sent once to the sink (for example with printf-like behavior), use + * instead @ref cat_dump_resume(). */ +template +size_t cat_dump(substr buf, Arg const& a, Args const& ...more) +{ + const size_t size_for_a = dump(buf, a); + if(C4_UNLIKELY(size_for_a > buf.len)) + buf.len = 0; // ensure no more calls to the sink + const size_t size_for_more = cat_dump(buf, more...); + return size_for_more > size_for_a ? size_for_more : size_for_a; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/// @cond dev +namespace detail { + +// terminates the variadic recursion +template +C4_ALWAYS_INLINE DumpResults cat_dump_resume(size_t, DumpResults results, substr) +{ + return results; +} + +// terminates the variadic recursion +template +C4_ALWAYS_INLINE DumpResults cat_dump_resume(size_t, SinkFn &&, DumpResults results, substr) // NOLINT +{ + return results; +} + +template +DumpResults cat_dump_resume(size_t currarg, DumpResults results, substr buf, Arg const& C4_RESTRICT a, Args const& ...more) +{ + if(C4_LIKELY(results.write_arg(currarg))) + { + size_t sz = dump(buf, a); // yield to the specialized function + if(currarg == results.lastok + 1 && sz <= buf.len) + results.lastok = currarg; + results.bufsize = sz > results.bufsize ? sz : results.bufsize; + } + return detail::cat_dump_resume(currarg + 1u, results, buf, more...); +} + +template +DumpResults cat_dump_resume(size_t currarg, SinkFn &&sinkfn, DumpResults results, substr buf, Arg const& C4_RESTRICT a, Args const& ...more) +{ + if(C4_LIKELY(results.write_arg(currarg))) + { + size_t sz = dump(std::forward(sinkfn), buf, a); // yield to the specialized function + if(currarg == results.lastok + 1 && sz <= buf.len) + results.lastok = currarg; + results.bufsize = sz > results.bufsize ? sz : results.bufsize; + } + return detail::cat_dump_resume(currarg + 1u, std::forward(sinkfn), results, buf, more...); +} +} // namespace detail +/// @endcond + + +template +C4_ALWAYS_INLINE DumpResults cat_dump_resume(substr buf, Arg const& C4_RESTRICT a, Args const& ...more) +{ + return detail::cat_dump_resume(0u, DumpResults{}, buf, a, more...); +} + +template +C4_ALWAYS_INLINE DumpResults cat_dump_resume(SinkFn &&sinkfn, substr buf, Arg const& C4_RESTRICT a, Args const& ...more) +{ + return detail::cat_dump_resume(0u, std::forward(sinkfn), DumpResults{}, buf, a, more...); +} + + +template +C4_ALWAYS_INLINE DumpResults cat_dump_resume(DumpResults results, substr buf, Arg const& C4_RESTRICT a, Args const& ...more) +{ + if(results.bufsize > buf.len) + return results; + return detail::cat_dump_resume(0u, results, buf, a, more...); +} + +template +C4_ALWAYS_INLINE DumpResults cat_dump_resume(SinkFn &&sinkfn, DumpResults results, substr buf, Arg const& C4_RESTRICT a, Args const& ...more) +{ + if(results.bufsize > buf.len) + return results; + return detail::cat_dump_resume(0u, std::forward(sinkfn), results, buf, a, more...); +} + +/** @} */ + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/// @cond dev +// terminate the recursion +template +size_t catsep_dump(SinkFn &&, substr, Sep const& C4_RESTRICT) // NOLINT +{ + return 0; +} + +// terminate the recursion +template +size_t catsep_dump(substr, Sep const& C4_RESTRICT) // NOLINT +{ + return 0; +} +/// @endcond + +/** take the function pointer as a function argument */ +template +size_t catsep_dump(SinkFn &&sinkfn, substr buf, Sep const& sep, Arg const& a, Args const& ...more) +{ + size_t sz = dump(std::forward(sinkfn), buf, a); + if(C4_UNLIKELY(sz > buf.len)) + buf.len = 0; // ensure no more calls + if C4_IF_CONSTEXPR (sizeof...(more) > 0) + { + size_t szsep = dump(std::forward(sinkfn), buf, sep); + if(C4_UNLIKELY(szsep > buf.len)) + buf.len = 0; // ensure no more calls + sz = sz > szsep ? sz : szsep; + } + size_t size_for_more = catsep_dump(std::forward(sinkfn), buf, sep, more...); + return size_for_more > sz ? size_for_more : sz; +} + +/** take the function pointer as a template argument */ +template +size_t catsep_dump(substr buf, Sep const& sep, Arg const& a, Args const& ...more) +{ + size_t sz = dump(buf, a); + if(C4_UNLIKELY(sz > buf.len)) + buf.len = 0; // ensure no more calls + if C4_IF_CONSTEXPR (sizeof...(more) > 0) + { + size_t szsep = dump(buf, sep); + if(C4_UNLIKELY(szsep > buf.len)) + buf.len = 0; // ensure no more calls + sz = sz > szsep ? sz : szsep; + } + size_t size_for_more = catsep_dump(buf, sep, more...); + return size_for_more > sz ? size_for_more : sz; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/// @cond dev +namespace detail { +template +void catsep_dump_resume_(size_t currarg, DumpResults *C4_RESTRICT results, substr *buf, Arg const& a) +{ + if(C4_LIKELY(results->write_arg(currarg))) + { + size_t sz = dump(*buf, a); + results->bufsize = sz > results->bufsize ? sz : results->bufsize; + if(C4_LIKELY(sz <= buf->len)) + results->lastok = currarg; + else + buf->len = 0; + } +} + +template +void catsep_dump_resume_(size_t currarg, SinkFn &&sinkfn, DumpResults *C4_RESTRICT results, substr *C4_RESTRICT buf, Arg const& C4_RESTRICT a) +{ + if(C4_LIKELY(results->write_arg(currarg))) + { + size_t sz = dump(std::forward(sinkfn), *buf, a); + results->bufsize = sz > results->bufsize ? sz : results->bufsize; + if(C4_LIKELY(sz <= buf->len)) + results->lastok = currarg; + else + buf->len = 0; + } +} + +template +C4_ALWAYS_INLINE void catsep_dump_resume(size_t currarg, DumpResults *C4_RESTRICT results, substr *C4_RESTRICT buf, Sep const&, Arg const& a) +{ + detail::catsep_dump_resume_(currarg, results, buf, a); +} + +template +C4_ALWAYS_INLINE void catsep_dump_resume(size_t currarg, SinkFn &&sinkfn, DumpResults *C4_RESTRICT results, substr *C4_RESTRICT buf, Sep const&, Arg const& a) +{ + detail::catsep_dump_resume_(currarg, std::forward(sinkfn), results, buf, a); +} + +template +C4_ALWAYS_INLINE void catsep_dump_resume(size_t currarg, DumpResults *C4_RESTRICT results, substr *C4_RESTRICT buf, Sep const& sep, Arg const& a, Args const& ...more) +{ + detail::catsep_dump_resume_(currarg , results, buf, a); + detail::catsep_dump_resume_(currarg + 1u, results, buf, sep); + detail::catsep_dump_resume (currarg + 2u, results, buf, sep, more...); +} + +template +C4_ALWAYS_INLINE void catsep_dump_resume(size_t currarg, SinkFn &&sinkfn, DumpResults *C4_RESTRICT results, substr *C4_RESTRICT buf, Sep const& sep, Arg const& a, Args const& ...more) +{ + detail::catsep_dump_resume_(currarg , std::forward(sinkfn), results, buf, a); + detail::catsep_dump_resume_(currarg + 1u, std::forward(sinkfn), results, buf, sep); + detail::catsep_dump_resume (currarg + 2u, std::forward(sinkfn), results, buf, sep, more...); +} +} // namespace detail +/// @endcond + + +template +C4_ALWAYS_INLINE DumpResults catsep_dump_resume(substr buf, Sep const& sep, Args const& ...args) +{ + DumpResults results; + detail::catsep_dump_resume(0u, &results, &buf, sep, args...); + return results; +} + +template +C4_ALWAYS_INLINE DumpResults catsep_dump_resume(SinkFn &&sinkfn, substr buf, Sep const& sep, Args const& ...args) +{ + DumpResults results; + detail::catsep_dump_resume(0u, std::forward(sinkfn), &results, &buf, sep, args...); + return results; +} + + +template +C4_ALWAYS_INLINE DumpResults catsep_dump_resume(DumpResults results, substr buf, Sep const& sep, Args const& ...args) +{ + detail::catsep_dump_resume(0u, &results, &buf, sep, args...); + return results; +} + +template +C4_ALWAYS_INLINE DumpResults catsep_dump_resume(SinkFn &&sinkfn, DumpResults results, substr buf, Sep const& sep, Args const& ...args) +{ + detail::catsep_dump_resume(0u, std::forward(sinkfn), &results, &buf, sep, args...); + return results; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/// @cond dev +namespace detail { +// terminate the recursion +C4_ALWAYS_INLINE size_t _format_dump_compute_size() +{ + return 0u; +} +template +C4_ALWAYS_INLINE auto _format_dump_compute_size(T const&) + -> typename std::enable_if::value, size_t>::type +{ + return 0u; // no buffer needed +} +template +C4_ALWAYS_INLINE auto _format_dump_compute_size(T const& v) + -> typename std::enable_if::value, size_t>::type +{ + return to_chars(substr{}, v); +} +template +size_t _format_dump_compute_size(Arg const& a, Args const& ...more) +{ + const size_t sz = _format_dump_compute_size(a); // don't call to_chars() directly + const size_t rest = _format_dump_compute_size(more...); + return sz > rest ? sz : rest; +} +} // namespace detail + +// terminate the recursion +template +C4_ALWAYS_INLINE size_t format_dump(SinkFn &&sinkfn, substr, csubstr fmt) +{ + // we can dump without using buf, so no need to check it + std::forward(sinkfn)(fmt); + return 0u; +} +// terminate the recursion +/** take the function pointer as a template argument */ +template +C4_ALWAYS_INLINE size_t format_dump(substr, csubstr fmt) +{ + // we can dump without using buf, so no need to check it + sinkfn(fmt); + return 0u; +} +/// @endcond + + +/** take the function pointer as a function argument */ +template +C4_NO_INLINE size_t format_dump(SinkFn &&sinkfn, substr buf, csubstr fmt, Arg const& a, Args const& ...more) +{ + // we can dump without using buf + // but we'll only dump if the buffer is ok + size_t pos = fmt.find("{}"); // @todo use _find_fmt() + if(C4_UNLIKELY(pos == csubstr::npos)) + { + std::forward(sinkfn)(fmt); + return 0u; + } + std::forward(sinkfn)(fmt.first(pos)); // we can dump without using buf + fmt = fmt.sub(pos + 2); // skip {} do this before assigning to pos again + pos = dump(std::forward(sinkfn), buf, a); // reuse pos to get needed_size + // dump no more if the buffer was exhausted + size_t size_for_more; + if(C4_LIKELY(pos <= buf.len)) + size_for_more = format_dump(std::forward(sinkfn), buf, fmt, more...); + else + size_for_more = detail::_format_dump_compute_size(more...); + return size_for_more > pos ? size_for_more : pos; +} + +/** take the function pointer as a template argument */ +template +C4_NO_INLINE size_t format_dump(substr buf, csubstr fmt, Arg const& C4_RESTRICT a, Args const& ...more) +{ + // we can dump without using buf + // but we'll only dump if the buffer is ok + size_t pos = fmt.find("{}"); // @todo use _find_fmt() + if(C4_UNLIKELY(pos == csubstr::npos)) + { + sinkfn(fmt); + return 0u; + } + sinkfn(fmt.first(pos)); // we can dump without using buf + fmt = fmt.sub(pos + 2); // skip {} do this before assigning to pos again + pos = dump(buf, a); // reuse pos to get needed_size + // dump no more if the buffer was exhausted + size_t size_for_more; + if(C4_LIKELY(pos <= buf.len)) + size_for_more = format_dump(buf, fmt, more...); + else + size_for_more = detail::_format_dump_compute_size(more...); + return size_for_more > pos ? size_for_more : pos; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/// @cond dev +namespace detail { +// terminate the recursion +template +DumpResults format_dump_resume(size_t currarg, DumpResults results, substr, csubstr fmt) +{ + if(C4_LIKELY(results.write_arg(currarg))) + { + // we can dump without using buf + sinkfn(fmt); + results.lastok = currarg; + } + return results; +} + +// terminate the recursion +template +DumpResults format_dump_resume(size_t currarg, SinkFn &&sinkfn, DumpResults results, substr, csubstr fmt) +{ + if(C4_LIKELY(results.write_arg(currarg))) + { + // we can dump without using buf + std::forward(sinkfn)(fmt); + results.lastok = currarg; + } + return results; +} + +template +DumpResults format_dump_resume(size_t currarg, DumpResults results, substr buf, csubstr fmt, Arg const& a, Args const& ...more) +{ + // we need to process the format even if we're not + // going to print the first arguments because we're resuming + const size_t pos = fmt.find("{}"); // @todo use _find_fmt() + if(C4_LIKELY(pos != csubstr::npos)) + { + if(C4_LIKELY(results.write_arg(currarg))) + { + sinkfn(fmt.first(pos)); + results.lastok = currarg; + } + if(C4_LIKELY(results.write_arg(currarg + 1u))) + { + const size_t len = dump(buf, a); + results.bufsize = len > results.bufsize ? len : results.bufsize; + if(C4_LIKELY(len <= buf.len)) + { + results.lastok = currarg + 1u; + } + else + { + const size_t rest = _format_dump_compute_size(more...); + results.bufsize = rest > results.bufsize ? rest : results.bufsize; + return results; + } + } + } + else + { + if(C4_LIKELY(results.write_arg(currarg))) + { + sinkfn(fmt); + results.lastok = currarg; + } + return results; + } + // NOTE: sparc64 had trouble with reassignment to fmt, and + // was passing the original fmt to the recursion: + //fmt = fmt.sub(pos + 2); // DONT! + return detail::format_dump_resume(currarg + 2u, results, buf, fmt.sub(pos + 2), more...); +} + + +template +DumpResults format_dump_resume(size_t currarg, SinkFn &&sinkfn, DumpResults results, substr buf, csubstr fmt, Arg const& a, Args const& ...more) +{ + // we need to process the format even if we're not + // going to print the first arguments because we're resuming + const size_t pos = fmt.find("{}"); // @todo use _find_fmt() + if(C4_LIKELY(pos != csubstr::npos)) + { + if(C4_LIKELY(results.write_arg(currarg))) + { + std::forward(sinkfn)(fmt.first(pos)); + results.lastok = currarg; + } + if(C4_LIKELY(results.write_arg(currarg + 1u))) + { + const size_t len = dump(std::forward(sinkfn), buf, a); + results.bufsize = len > results.bufsize ? len : results.bufsize; + if(C4_LIKELY(len <= buf.len)) + { + results.lastok = currarg + 1u; + } + else + { + const size_t rest = _format_dump_compute_size(more...); + results.bufsize = rest > results.bufsize ? rest : results.bufsize; + return results; + } + } + } + else + { + if(C4_LIKELY(results.write_arg(currarg))) + { + std::forward(sinkfn)(fmt); + results.lastok = currarg; + } + return results; + } + // NOTE: sparc64 had trouble with reassignment to fmt, and + // was passing the original fmt to the recursion: + //fmt = fmt.sub(pos + 2); // DONT! + return detail::format_dump_resume(currarg + 2u, std::forward(sinkfn), results, buf, fmt.sub(pos + 2), more...); +} +} // namespace detail +/// @endcond + + +template +C4_ALWAYS_INLINE DumpResults format_dump_resume(substr buf, csubstr fmt, Args const& ...args) +{ + return detail::format_dump_resume(0u, DumpResults{}, buf, fmt, args...); +} + +template +C4_ALWAYS_INLINE DumpResults format_dump_resume(SinkFn &&sinkfn, substr buf, csubstr fmt, Args const& ...args) +{ + return detail::format_dump_resume(0u, std::forward(sinkfn), DumpResults{}, buf, fmt, args...); +} + + +template +C4_ALWAYS_INLINE DumpResults format_dump_resume(DumpResults results, substr buf, csubstr fmt, Args const& ...args) +{ + return detail::format_dump_resume(0u, results, buf, fmt, args...); +} + +template +C4_ALWAYS_INLINE DumpResults format_dump_resume(SinkFn &&sinkfn, DumpResults results, substr buf, csubstr fmt, Args const& ...args) +{ + return detail::format_dump_resume(0u, std::forward(sinkfn), results, buf, fmt, args...); +} + +C4_SUPPRESS_WARNING_GCC_CLANG_POP + +} // namespace c4 + + +#endif /* C4_DUMP_HPP_ */ + + +// (end src/c4/dump.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/enum.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_ENUM_HPP_ +#define _C4_ENUM_HPP_ + +// amalgamate: removed include of +// c4/error.hpp +//#include "c4/error.hpp" +#if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) +#error "amalgamate: file c4/error.hpp must have been included at this point" +#endif /* C4_ERROR_HPP_ */ + + +//included above: +//#include +//included above: +//#include +//included above: +//#include + +/** @file enum.hpp utilities for enums: convert to/from string + */ + + +namespace c4 { + +C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wold-style-cast") + +//! taken from http://stackoverflow.com/questions/15586163/c11-type-trait-to-differentiate-between-enum-class-and-regular-enum +template +using is_scoped_enum = std::integral_constant::value && !std::is_convertible::value>; + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +typedef enum { // NOLINT(cert-int09-c,readability-enum-initial-value) + EOFFS_NONE = 0, ///< no offset + EOFFS_CLS = 1, ///< get the enum offset for the class name. @see eoffs_cls() + EOFFS_PFX = 2, ///< get the enum offset for the enum prefix. @see eoffs_pfx() + _EOFFS_LAST ///< reserved +} EnumOffsetType; + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +/** A simple (proxy) container for the value-name pairs of an enum type. + * Uses linear search for finds; this could be improved for time-critical + * code. */ +template +class EnumSymbols +{ +public: + + struct Sym + { + Enum value; + const char *name; + + bool cmp(const char *s) const; + bool cmp(const char *s, size_t len) const; + + const char *name_offs(EnumOffsetType t) const; + }; + + using const_iterator = Sym const*; + +public: + + template + EnumSymbols(Sym const (&p)[N]) : m_symbols(p), m_num(N) {} + EnumSymbols(Sym const *p, size_t N) : m_symbols(p), m_num(N) {} + + size_t size() const { return m_num; } + bool empty() const { return m_num == 0; } + + Sym const* get(Enum v) const { auto p = find(v); C4_CHECK_MSG(p != nullptr, "could not find symbol=%zd", (std::ptrdiff_t)v); return p; } + Sym const* get(const char *s) const { auto p = find(s); C4_CHECK_MSG(p != nullptr, "could not find symbol \"%s\"", s); return p; } + Sym const* get(const char *s, size_t len) const { auto p = find(s, len); C4_CHECK_MSG(p != nullptr, "could not find symbol \"%.*s\"", len, s); return p; } + + Sym const* find(Enum v) const; + Sym const* find(const char *s) const; + Sym const* find(const char *s, size_t len) const; + + Sym const& operator[] (size_t i) const { C4_CHECK(i < m_num); return m_symbols[i]; } + + Sym const* begin() const { return m_symbols; } + Sym const* end () const { return m_symbols + m_num; } + +private: + + Sym const* m_symbols; + size_t const m_num; // NOLINT(*avoid-const*) + +}; + +//----------------------------------------------------------------------------- +/** return an EnumSymbols object for the enum type T + * + * @warning SPECIALIZE! This needs to be specialized for each enum + * type. Failure to provide a specialization will cause a linker + * error. */ +template +EnumSymbols esyms(); + + +/** return the offset for an enum symbol class. For example, + * eoffs_cls() would be 13=strlen("MyEnumClass::"). + * + * With this function you can announce that the full prefix (including + * an eventual enclosing class or C++11 enum class) is of a certain + * length. + * + * @warning Needs to be specialized for each enum class type that + * wants to use this. When no specialization is given, will return + * 0. */ +template +size_t eoffs_cls() +{ + return 0; +} + + +/** return the offset for an enum symbol prefix. This includes + * eoffs_cls(). With this function you can announce that the full + * prefix (including an eventual enclosing class or C++11 enum class + * plus the string prefix) is of a certain length. + * + * @warning Needs to be specialized for each enum class type that + * wants to use this. When no specialization is given, will return + * 0. */ +template +size_t eoffs_pfx() +{ + return 0; +} + + +template +size_t eoffs(EnumOffsetType which) +{ + switch(which) + { + case EOFFS_NONE: + return 0; + case EOFFS_CLS: + return eoffs_cls(); + case EOFFS_PFX: + { + size_t pfx = eoffs_pfx(); + return pfx > 0 ? pfx : eoffs_cls(); + } + default: + C4_ERROR("unknown offset type %d", (int)which); + } +} + + +//----------------------------------------------------------------------------- +/** get the enum value corresponding to a c-string */ + +#ifdef __clang__ +# pragma clang diagnostic push +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# if __GNUC__ >= 6 +# pragma GCC diagnostic ignored "-Wnull-dereference" +# endif +#endif + +template +Enum str2e(const char* str) +{ + auto pairs = esyms(); + auto *p = pairs.get(str); + C4_CHECK_MSG(p != nullptr, "no valid enum pair name for '%s'", str); + return p->value; +} + +/** get the c-string corresponding to an enum value */ +template +const char* e2str(Enum e) +{ + auto es = esyms(); + auto *p = es.get(e); + C4_CHECK_MSG(p != nullptr, "no valid enum pair name"); + return p->name; +} + +/** like e2str(), but add an offset. */ +template +const char* e2stroffs(Enum e, EnumOffsetType ot=EOFFS_PFX) +{ + const char *s = e2str(e) + eoffs(ot); + return s; +} + +#ifdef __clang__ +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#endif + +//----------------------------------------------------------------------------- +/** Find a symbol by value. Returns nullptr when none is found */ +template +typename EnumSymbols::Sym const* EnumSymbols::find(Enum v) const +{ + for(Sym const* p = this->m_symbols, *e = p+this->m_num; p < e; ++p) + if(p->value == v) + return p; + return nullptr; +} + +/** Find a symbol by name. Returns nullptr when none is found */ +template +typename EnumSymbols::Sym const* EnumSymbols::find(const char *s) const +{ + for(Sym const* p = this->m_symbols, *e = p+this->m_num; p < e; ++p) + if(p->cmp(s)) + return p; + return nullptr; +} + +/** Find a symbol by name. Returns nullptr when none is found */ +template +typename EnumSymbols::Sym const* EnumSymbols::find(const char *s, size_t len) const +{ + for(Sym const* p = this->m_symbols, *e = p+this->m_num; p < e; ++p) + if(p->cmp(s, len)) + return p; + return nullptr; +} + +//----------------------------------------------------------------------------- +template +bool EnumSymbols::Sym::cmp(const char *s) const +{ + if(strcmp(name, s) == 0) + return true; + + for(int i = 1; i < _EOFFS_LAST; ++i) + { + auto o = eoffs((EnumOffsetType)i); + if(o > 0) + if(strcmp(name + o, s) == 0) + return true; + } + + return false; +} + +template +bool EnumSymbols::Sym::cmp(const char *s, size_t len) const +{ + if(strncmp(name, s, len) == 0) + return true; + + size_t nlen = 0; + for(int i = 1; i <_EOFFS_LAST; ++i) + { + auto o = eoffs((EnumOffsetType)i); + if(o > 0) + { + if(!nlen) + { + nlen = strlen(name); + } + C4_ASSERT(o < nlen); + size_t rem = nlen - o; + auto m = len > rem ? len : rem; + if(len >= m && strncmp(name + o, s, m) == 0) + return true; + } + } + + return false; +} + +//----------------------------------------------------------------------------- +template +const char* EnumSymbols::Sym::name_offs(EnumOffsetType t) const +{ + C4_ASSERT(eoffs(t) < strlen(name)); + return name + eoffs(t); +} + +C4_SUPPRESS_WARNING_GCC_CLANG_POP + +} // namespace c4 + +#endif // _C4_ENUM_HPP_ + + +// (end src/c4/enum.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/bitmask.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_BITMASK_HPP_ +#define _C4_BITMASK_HPP_ + +/** @file bitmask.hpp bitmask utilities */ + +//included above: +//#include +//included above: +//#include + +// amalgamate: removed include of +// c4/enum.hpp +//#include "c4/enum.hpp" +#if !defined(C4_ENUM_HPP_) && !defined(_C4_ENUM_HPP_) +#error "amalgamate: file c4/enum.hpp must have been included at this point" +#endif /* C4_ENUM_HPP_ */ + +// amalgamate: removed include of +// c4/format.hpp +//#include "c4/format.hpp" +#if !defined(C4_FORMAT_HPP_) && !defined(_C4_FORMAT_HPP_) +#error "amalgamate: file c4/format.hpp must have been included at this point" +#endif /* C4_FORMAT_HPP_ */ + + +#if defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 4996) // 'strncpy', fopen, etc: This function or variable may be unsafe +#endif + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wold-style-cast" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wold-style-cast" +# if __GNUC__ >= 8 +# pragma GCC diagnostic ignored "-Wstringop-truncation" +# pragma GCC diagnostic ignored "-Wstringop-overflow" +# pragma GCC diagnostic ignored "-Warray-bounds" +# endif +#endif + +namespace c4 { + +//----------------------------------------------------------------------------- +/** write a bitmask to a stream, formatted as a string */ + +template +Stream& bm2stream(Stream &s, typename std::underlying_type::type bits, EnumOffsetType offst=EOFFS_PFX) +{ + using I = typename std::underlying_type::type; + bool written = false; + + auto const& pairs = esyms(); + + // write non null value + if(bits) + { + // do reverse iteration to give preference to composite enum symbols, + // which are likely to appear at the end of the enum sequence + for(size_t i = pairs.size() - 1; i != size_t(-1); --i) + { + auto p = pairs[i]; + I b(static_cast(p.value)); + if(b && (bits & b) == b) + { + if(written) s << '|'; // append bit-or character + written = true; + s << p.name_offs(offst); // append bit string + bits &= ~b; + } + } + return s; + } + else + { + // write a null value + for(size_t i = pairs.size() - 1; i != size_t(-1); --i) + { + auto p = pairs[i]; + I b(static_cast(p.value)); + if(b == 0) + { + s << p.name_offs(offst); + written = true; + break; + } + } + } + if(!written) + { + s << '0'; + } + return s; +} + +template +typename std::enable_if::value, Stream&>::type +bm2stream(Stream &s, Enum value, EnumOffsetType offst=EOFFS_PFX) +{ + using I = typename std::underlying_type::type; + return bm2stream(s, static_cast(value), offst); +} + + +//----------------------------------------------------------------------------- + +// some utility macros, undefed below + +/// @cond dev + +/* Execute `code` if the `num` of characters is available in the str + * buffer. This macro simplifies the code for bm2str(). + * @todo improve performance by writing from the end and moving only once. */ +#define _c4prependchars(code, num) \ + if(str && (pos + (num) <= sz)) \ + { \ + /* move the current string to the right */ \ + memmove(str + (num), str, pos); \ + /* now write in the beginning of the string */ \ + code; \ + } \ + else if(str && sz) \ + { \ + C4_ERROR("cannot write to string pos=%d num=%d sz=%d", \ + (int)pos, (int)(num), (int)sz); \ + } \ + pos += num + +/* Execute `code` if the `num` of characters is available in the str + * buffer. This macro simplifies the code for bm2str(). */ +#define _c4appendchars(code, num) \ + if(str && (pos + (num) <= sz)) \ + { \ + code; \ + } \ + else if(str && sz) \ + { \ + C4_ERROR("cannot write to string pos=%d num=%d sz=%d", \ + (int)pos, (int)(num), (int)sz); \ + } \ + pos += num + +/// @endcond + + +/** convert a bitmask to string. + * return the number of characters written. To find the needed size, + * call first with str=nullptr and sz=0 */ +template +size_t bm2str +( + typename std::underlying_type::type bits, + char *str=nullptr, + size_t sz=0, + EnumOffsetType offst=EOFFS_PFX +) +{ + using I = typename std::underlying_type::type; + C4_ASSERT((str == nullptr) == (sz == 0)); + + auto syms = esyms(); + size_t pos = 0; + typename EnumSymbols::Sym const* C4_RESTRICT zero = nullptr; + + // do reverse iteration to give preference to composite enum symbols, + // which are likely to appear later in the enum sequence + for(size_t i = syms.size()-1; i != size_t(-1); --i) + { + auto const &C4_RESTRICT p = syms[i]; // do not copy, we are assigning to `zero` + I b = static_cast(p.value); + if(b == 0) + { + zero = &p; // save this symbol for later + } + else if((bits & b) == b) + { + bits &= ~b; + // append bit-or character + if(pos > 0) + { + _c4prependchars(*str = '|', 1); + } + // append bit string + const char *pname = p.name_offs(offst); + size_t len = strlen(pname); + _c4prependchars(strncpy(str, pname, len), len); + } + } + + C4_CHECK_MSG(bits == 0, "could not find all bits"); + if(pos == 0) // make sure at least something is written + { + if(zero) // if we have a zero symbol, use that + { + const char *pname = zero->name_offs(offst); + size_t len = strlen(pname); + _c4prependchars(strncpy(str, pname, len), len); + } + else // otherwise just write an integer zero + { + _c4prependchars(*str = '0', 1); + } + } + _c4appendchars(str[pos] = '\0', 1); + + return pos; +} + + +// cleanup! +#undef _c4appendchars +#undef _c4prependchars + + +/** scoped enums do not convert automatically to their underlying type, + * so this SFINAE overload will accept scoped enum symbols and cast them + * to the underlying type */ +template +typename std::enable_if::value, size_t>::type +bm2str +( + Enum bits, + char *str=nullptr, + size_t sz=0, + EnumOffsetType offst=EOFFS_PFX +) +{ + using I = typename std::underlying_type::type; + return bm2str(static_cast(bits), str, sz, offst); +} + + +//----------------------------------------------------------------------------- + +namespace detail { + +#ifdef __clang__ +# pragma clang diagnostic push +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# if __GNUC__ >= 6 +# pragma GCC diagnostic ignored "-Wnull-dereference" +# endif +#endif + +template +typename std::underlying_type::type str2bm_read_one(const char *str, size_t sz, bool alnum) +{ + using I = typename std::underlying_type::type; + auto pairs = esyms(); + if(alnum) + { + auto *p = pairs.find(str, sz); + C4_CHECK_MSG(p != nullptr, "no valid enum pair name for '%.*s'", (int)sz, str); + return static_cast(p->value); + } + I tmp{0}; + size_t len = uncat(csubstr(str, sz), tmp); + C4_CHECK_MSG(len != csubstr::npos, "could not read string as an integral type: '%.*s'", (int)sz, str); + return tmp; +} + +#ifdef __clang__ +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#endif +} // namespace detail + +/** convert a string to a bitmask */ +template +typename std::underlying_type::type str2bm(const char *str, size_t sz) +{ + using I = typename std::underlying_type::type; + + I val = 0; + bool started = false; + bool alnum = false, num = false; + const char *f = nullptr, *pc = str; + for( ; pc < str+sz; ++pc) + { + const char c = *pc; + if((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_') + { + C4_CHECK(( ! num) || ((pc - f) == 1 && (c == 'x' || c == 'X'))); // accept hexadecimal numbers + if( ! started) + { + f = pc; + alnum = started = true; + } + } + else if(c >= '0' && c <= '9') + { + C4_CHECK( ! alnum); + if(!started) + { + f = pc; + num = started = true; + } + } + else if(c == ':' || c == ' ') + { + // skip this char + } + else if(c == '|' || c == '\0') + { + C4_ASSERT(num != alnum); + C4_ASSERT(pc >= f); + val |= detail::str2bm_read_one(f, static_cast(pc-f), alnum); + started = num = alnum = false; + if(c == '\0') + { + return val; + } + } + else + { + C4_ERROR("bad character '%c' in bitmask string", c); + } + } + + if(f) + { + C4_ASSERT(num != alnum); + C4_ASSERT(pc >= f); + val |= detail::str2bm_read_one(f, static_cast(pc-f), alnum); + } + + return val; +} + +/** convert a string to a bitmask */ +template +typename std::underlying_type::type str2bm(const char *str) +{ + return str2bm(str, strlen(str)); +} + +} // namespace c4 + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +#if defined(__clang__) +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#endif + +#endif // _C4_BITMASK_HPP_ + + +// (end src/c4/bitmask.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/span.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_SPAN_HPP_ +#define _C4_SPAN_HPP_ + +/** @file span.hpp Provides span classes. */ + +// amalgamate: removed include of +// c4/config.hpp +//#include "c4/config.hpp" +#if !defined(C4_CONFIG_HPP_) && !defined(_C4_CONFIG_HPP_) +#error "amalgamate: file c4/config.hpp must have been included at this point" +#endif /* C4_CONFIG_HPP_ */ + +// amalgamate: removed include of +// c4/types.hpp +//#include "c4/types.hpp" +#if !defined(C4_TYPES_HPP_) && !defined(_C4_TYPES_HPP_) +#error "amalgamate: file c4/types.hpp must have been included at this point" +#endif /* C4_TYPES_HPP_ */ + +// amalgamate: removed include of +// c4/error.hpp +//#include "c4/error.hpp" +#if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) +#error "amalgamate: file c4/error.hpp must have been included at this point" +#endif /* C4_ERROR_HPP_ */ + +// amalgamate: removed include of +// c4/szconv.hpp +//#include "c4/szconv.hpp" +#if !defined(C4_SZCONV_HPP_) && !defined(_C4_SZCONV_HPP_) +#error "amalgamate: file c4/szconv.hpp must have been included at this point" +#endif /* C4_SZCONV_HPP_ */ + + +//included above: +//#include + +namespace c4 { + +C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wold-style-cast") +// NOLINTBEGIN(misc-confusable-identifiers) + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +/** a crtp base for implementing span classes + * + * A span is a non-owning range of elements contiguously stored in memory. + * Unlike STL's array_view, the span allows write-access to its members. + * + * To obtain subspans from a span, the following const member functions + * are available: + * - subspan(first, num) + * - range(first, last) + * - first(num) + * - last(num) + * + * A span can also be resized via the following non-const member functions: + * - resize(sz) + * - ltrim(num) + * - rtrim(num) + * + * @see span + * @see cspan + * @see spanrs + * @see cspanrs + * @see spanrsl + * @see cspanrsl + */ +template +class span_crtp // NOLINT(*crtp-constructor-accessibility) +{ +// some utility defines, undefined at the end of this class +#define _c4this ((SpanImpl *)this) +#define _c4cthis ((SpanImpl const*)this) +#define _c4ptr ((SpanImpl *)this)->m_ptr +#define _c4cptr ((SpanImpl const*)this)->m_ptr +#define _c4sz ((SpanImpl *)this)->m_size +#define _c4csz ((SpanImpl const*)this)->m_size + +public: + + _c4_DEFINE_ARRAY_TYPES(T, I); + +public: + + C4_ALWAYS_INLINE constexpr I value_size() const noexcept { return sizeof(T); } + C4_ALWAYS_INLINE constexpr I elm_size () const noexcept { return sizeof(T); } + C4_ALWAYS_INLINE constexpr I type_size () const noexcept { return sizeof(T); } + C4_ALWAYS_INLINE I byte_size () const noexcept { return _c4csz*sizeof(T); } + + C4_ALWAYS_INLINE bool empty() const noexcept { return _c4csz == 0; } + C4_ALWAYS_INLINE I size() const noexcept { return _c4csz; } + //C4_ALWAYS_INLINE I capacity() const noexcept { return _c4sz; } // this must be defined by impl classes + + C4_ALWAYS_INLINE void clear() noexcept { _c4sz = 0; } + + C4_ALWAYS_INLINE T * data() noexcept { return _c4ptr; } + C4_ALWAYS_INLINE T const* data() const noexcept { return _c4cptr; } + + C4_ALWAYS_INLINE iterator begin() noexcept { return _c4ptr; } + C4_ALWAYS_INLINE const_iterator begin() const noexcept { return _c4cptr; } + C4_ALWAYS_INLINE const_iterator cbegin() const noexcept { return _c4cptr; } + + C4_ALWAYS_INLINE iterator end() noexcept { return _c4ptr + _c4sz; } + C4_ALWAYS_INLINE const_iterator end() const noexcept { return _c4cptr + _c4csz; } + C4_ALWAYS_INLINE const_iterator cend() const noexcept { return _c4cptr + _c4csz; } + + C4_ALWAYS_INLINE reverse_iterator rbegin() noexcept { return reverse_iterator(_c4ptr + _c4sz); } + C4_ALWAYS_INLINE const_reverse_iterator rbegin() const noexcept { return reverse_iterator(_c4cptr + _c4csz); } + C4_ALWAYS_INLINE const_reverse_iterator crbegin() const noexcept { return reverse_iterator(_c4cptr + _c4csz); } + + C4_ALWAYS_INLINE reverse_iterator rend() noexcept { return const_reverse_iterator(_c4ptr); } + C4_ALWAYS_INLINE const_reverse_iterator rend() const noexcept { return const_reverse_iterator(_c4cptr); } + C4_ALWAYS_INLINE const_reverse_iterator crend() const noexcept { return const_reverse_iterator(_c4cptr); } + + C4_ALWAYS_INLINE T & front() C4_NOEXCEPT_X { C4_XASSERT(!empty()); return _c4ptr [0]; } + C4_ALWAYS_INLINE T const& front() const C4_NOEXCEPT_X { C4_XASSERT(!empty()); return _c4cptr[0]; } + + C4_ALWAYS_INLINE T & back() C4_NOEXCEPT_X { C4_XASSERT(!empty()); return _c4ptr [_c4sz - 1]; } + C4_ALWAYS_INLINE T const& back() const C4_NOEXCEPT_X { C4_XASSERT(!empty()); return _c4cptr[_c4csz - 1]; } + + C4_ALWAYS_INLINE T & operator[] (I i) C4_NOEXCEPT_X { C4_XASSERT(i >= 0 && i < _c4sz ); return _c4ptr [i]; } + C4_ALWAYS_INLINE T const& operator[] (I i) const C4_NOEXCEPT_X { C4_XASSERT(i >= 0 && i < _c4csz); return _c4cptr[i]; } + + C4_ALWAYS_INLINE SpanImpl subspan(I first, I num) const C4_NOEXCEPT_X + { + C4_XASSERT((first >= 0 && first < _c4csz) || (first == _c4csz && num == 0)); + C4_XASSERT((first + num >= 0) && (first + num <= _c4csz)); + return _c4cthis->_select(_c4cptr + first, num); + } + C4_ALWAYS_INLINE SpanImpl subspan(I first) const C4_NOEXCEPT_X ///< goes up until the end of the span + { + C4_XASSERT(first >= 0 && first <= _c4csz); + return _c4cthis->_select(_c4cptr + first, _c4csz - first); + } + + C4_ALWAYS_INLINE SpanImpl range(I first, I last) const C4_NOEXCEPT_X ///< last element is NOT included + { + C4_XASSERT(((first >= 0) && (first < _c4csz)) || (first == _c4csz && first == last)); + C4_XASSERT((last >= 0) && (last <= _c4csz)); + C4_XASSERT(last >= first); + return _c4cthis->_select(_c4cptr + first, last - first); + } + C4_ALWAYS_INLINE SpanImpl range(I first) const C4_NOEXCEPT_X ///< goes up until the end of the span + { + C4_XASSERT(((first >= 0) && (first <= _c4csz))); + return _c4cthis->_select(_c4cptr + first, _c4csz - first); + } + + C4_ALWAYS_INLINE SpanImpl first(I num) const C4_NOEXCEPT_X ///< get the first num elements, starting at 0 + { + C4_XASSERT((num >= 0) && (num <= _c4csz)); + return _c4cthis->_select(_c4cptr, num); + } + C4_ALWAYS_INLINE SpanImpl last(I num) const C4_NOEXCEPT_X ///< get the last num elements, starting at size()-num + { + C4_XASSERT((num >= 0) && (num <= _c4csz)); + return _c4cthis->_select(_c4cptr + _c4csz - num, num); + } + + bool is_subspan(span_crtp const& ss) const noexcept + { + if(_c4cptr == nullptr) return false; + auto *b = begin(), *e = end(); + auto *ssb = ss.begin(), *sse = ss.end(); + if(ssb >= b && sse <= e) + { + return true; + } + else + { + return false; + } + } + + /** COMPLement Left: return the complement to the left of the beginning of the given subspan. + * If ss does not begin inside this, returns an empty substring. */ + SpanImpl compll(span_crtp const& ss) const C4_NOEXCEPT_X + { + auto ssb = ss.begin(); + auto b = begin(); + auto e = end(); + if(ssb >= b && ssb <= e) + { + return subspan(0, static_cast(ssb - b)); + } + else + { + return subspan(0, 0); + } + } + + /** COMPLement Right: return the complement to the right of the end of the given subspan. + * If ss does not end inside this, returns an empty substring. */ + SpanImpl complr(span_crtp const& ss) const C4_NOEXCEPT_X + { + auto sse = ss.end(); + auto b = begin(); + auto e = end(); + if(sse >= b && sse <= e) + { + return subspan(static_cast(sse - b), static_cast(e - sse)); + } + else + { + return subspan(0, 0); + } + } + + C4_ALWAYS_INLINE bool same_span(span_crtp const& that) const noexcept + { + return size() == that.size() && data() == that.data(); + } + template + C4_ALWAYS_INLINE bool same_span(span_crtp const& that) const C4_NOEXCEPT_X + { + I tsz = szconv(that.size()); // x-asserts that the size does not overflow + return size() == tsz && data() == that.data(); + } + +#undef _c4this +#undef _c4cthis +#undef _c4ptr +#undef _c4cptr +#undef _c4sz +#undef _c4csz +}; + + +//----------------------------------------------------------------------------- +// NOLINTBEGIN(*-redundant-inline*) + +template +inline constexpr bool operator== +( + span_crtp const& l, + span_crtp const& r +) +{ +#if C4_CPP >= 14 + return std::equal(l.begin(), l.end(), r.begin(), r.end()); +#else + return l.same_span(r) || std::equal(l.begin(), l.end(), r.begin()); +#endif +} + +template +inline constexpr bool operator!= +( + span_crtp const& l, + span_crtp const& r +) +{ + return ! (l == r); +} + +//----------------------------------------------------------------------------- +template +inline constexpr bool operator< +( + span_crtp const& l, + span_crtp const& r +) +{ + return std::lexicographical_compare(l.begin(), l.end(), r.begin(), r.end()); +} + +template +inline constexpr bool operator<= +( + span_crtp const& l, + span_crtp const& r +) +{ + return ! (l > r); +} + +//----------------------------------------------------------------------------- +template +inline constexpr bool operator> +( + span_crtp const& l, + span_crtp const& r +) +{ + return r < l; +} + +//----------------------------------------------------------------------------- +template +inline constexpr bool operator>= +( + span_crtp const& l, + span_crtp const& r +) +{ + return ! (l < r); +} + +// NOLINTEND(*-redundant-inline*) + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +/** A non-owning span of elements contiguously stored in memory. */ +template +class span : public span_crtp> // NOLINT(*-special-member-functions) +{ + friend class span_crtp>; + + T * C4_RESTRICT m_ptr; + I m_size; + + C4_ALWAYS_INLINE span _select(T *p, I sz) const { return span(p, sz); } + +public: + + _c4_DEFINE_ARRAY_TYPES(T, I); + using NCT = typename std::remove_const::type; //!< NCT=non const type + using CT = typename std::add_const::type; //!< CT=const type + using const_type = span; + + /// convert automatically to span of const T + operator span () const { span s(m_ptr, m_size); return s; } + +public: + + C4_ALWAYS_INLINE C4_CONSTEXPR14 span() noexcept : m_ptr{nullptr}, m_size{0} {} + + span(span const&) = default; + span(span &&) = default; + + span& operator= (span const&) = default; + span& operator= (span &&) = default; + +public: + + /** @name Construction and assignment from same type */ + /** @{ */ + + template C4_ALWAYS_INLINE C4_CONSTEXPR14 span (T (&arr)[N]) noexcept : m_ptr{arr}, m_size{N} {} + template C4_ALWAYS_INLINE C4_CONSTEXPR14 void assign(T (&arr)[N]) noexcept { m_ptr = arr; m_size = N; } + + C4_ALWAYS_INLINE C4_CONSTEXPR14 span(T *p, I sz) noexcept : m_ptr{p}, m_size{sz} {} + C4_ALWAYS_INLINE C4_CONSTEXPR14 void assign(T *p, I sz) noexcept { m_ptr = p; m_size = sz; } + + C4_ALWAYS_INLINE C4_CONSTEXPR14 span (c4::aggregate_t, std::initializer_list il) noexcept : m_ptr{&*il.begin()}, m_size{il.size()} {} + C4_ALWAYS_INLINE C4_CONSTEXPR14 void assign(c4::aggregate_t, std::initializer_list il) noexcept { m_ptr = &*il.begin(); m_size = il.size(); } + + /** @} */ + +public: + + C4_ALWAYS_INLINE I capacity() const noexcept { return m_size; } + + C4_ALWAYS_INLINE void resize(I sz) C4_NOEXCEPT_A { C4_ASSERT(sz <= m_size); m_size = sz; } + C4_ALWAYS_INLINE void rtrim (I n ) C4_NOEXCEPT_A { C4_ASSERT(n >= 0 && n < m_size); m_size -= n; } + C4_ALWAYS_INLINE void ltrim (I n ) C4_NOEXCEPT_A { C4_ASSERT(n >= 0 && n < m_size); m_size -= n; m_ptr += n; } + +}; +template using cspan = span; + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +/** A non-owning span resizeable up to a capacity. Subselection or resizing + * will keep the original provided it starts at begin(). If subselection or + * resizing change the pointer, then the original capacity information will + * be lost. + * + * Thus, resizing via resize() and ltrim() and subselecting via first() + * or any of subspan() or range() when starting from the beginning will keep + * the original capacity. OTOH, using last(), or any of subspan() or range() + * with an offset from the start will remove from capacity (shifting the + * pointer) by the corresponding offset. If this is undesired, then consider + * using spanrsl. + * + * @see spanrs for a span resizeable on the right + * @see spanrsl for a span resizeable on the right and left + */ + +template +class spanrs : public span_crtp> // NOLINT(*-special-member-functions) +{ + friend class span_crtp>; + + T * C4_RESTRICT m_ptr; + I m_size; + I m_capacity; + + C4_ALWAYS_INLINE spanrs _select(T *p, I sz) const noexcept + { + C4_ASSERT(p >= m_ptr); + size_t delta = static_cast(p - m_ptr); + C4_ASSERT(m_capacity >= delta); + return spanrs(p, sz, static_cast(m_capacity - delta)); + } + +public: + + _c4_DEFINE_ARRAY_TYPES(T, I); + using NCT = typename std::remove_const::type; //!< NCT=non const type + using CT = typename std::add_const::type; //!< CT=const type + using const_type = spanrs; + + /// convert automatically to span of T + C4_ALWAYS_INLINE operator span () const noexcept { return span(m_ptr, m_size); } + /// convert automatically to span of const T + //C4_ALWAYS_INLINE operator span () const noexcept { span s(m_ptr, m_size); return s; } + /// convert automatically to spanrs of const T + C4_ALWAYS_INLINE operator spanrs () const noexcept { spanrs s(m_ptr, m_size, m_capacity); return s; } + +public: + + C4_ALWAYS_INLINE spanrs() noexcept : m_ptr{nullptr}, m_size{0}, m_capacity{0} {} + + spanrs(spanrs const&) = default; + spanrs(spanrs &&) = default; + + spanrs& operator= (spanrs const&) = default; + spanrs& operator= (spanrs &&) = default; + +public: + + /** @name Construction and assignment from same type */ + /** @{ */ + + C4_ALWAYS_INLINE spanrs(T *p, I sz) noexcept : m_ptr{p}, m_size{sz}, m_capacity{sz} {} + /** @warning will reset the capacity to sz */ + C4_ALWAYS_INLINE void assign(T *p, I sz) noexcept { m_ptr = p; m_size = sz; m_capacity = sz; } + + C4_ALWAYS_INLINE spanrs(T *p, I sz, I cap) noexcept : m_ptr{p}, m_size{sz}, m_capacity{cap} {} + C4_ALWAYS_INLINE void assign(T *p, I sz, I cap) noexcept { m_ptr = p; m_size = sz; m_capacity = cap; } + + template C4_ALWAYS_INLINE spanrs(T (&arr)[N]) noexcept : m_ptr{arr}, m_size{N}, m_capacity{N} {} + template C4_ALWAYS_INLINE void assign(T (&arr)[N]) noexcept { m_ptr = arr; m_size = N; m_capacity = N; } + + C4_ALWAYS_INLINE spanrs(c4::aggregate_t, std::initializer_list il) noexcept : m_ptr{il.begin()}, m_size{il.size()}, m_capacity{il.size()} {} + C4_ALWAYS_INLINE void assign(c4::aggregate_t, std::initializer_list il) noexcept { m_ptr = il.begin(); m_size = il.size(); m_capacity = il.size(); } + + /** @} */ + +public: + + C4_ALWAYS_INLINE I capacity() const noexcept { return m_capacity; } + + C4_ALWAYS_INLINE void resize(I sz) C4_NOEXCEPT_A { C4_ASSERT(sz <= m_capacity); m_size = sz; } + C4_ALWAYS_INLINE void rtrim (I n ) C4_NOEXCEPT_A { C4_ASSERT(n >= 0 && n < m_size); m_size -= n; } + C4_ALWAYS_INLINE void ltrim (I n ) C4_NOEXCEPT_A { C4_ASSERT(n >= 0 && n < m_size); m_size -= n; m_ptr += n; m_capacity -= n; } + +}; +template using cspanrs = spanrs; + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +/** A non-owning span which always retains the capacity of the original + * range it was taken from (though it may loose its original size). + * The resizing methods resize(), ltrim(), rtrim() as well + * as the subselection methods subspan(), range(), first() and last() can be + * used at will without loosing the original capacity; the full capacity span + * can always be recovered by calling original(). + */ +template +class spanrsl : public span_crtp> // NOLINT(*-special-member-functions) +{ + friend class span_crtp>; + + T *C4_RESTRICT m_ptr; ///< the current ptr. the original ptr is (m_ptr - m_offset). + I m_size; ///< the current size. the original size is unrecoverable. + I m_capacity; ///< the current capacity. the original capacity is (m_capacity + m_offset). + I m_offset; ///< the offset of the current m_ptr to the start of the original memory block. + + C4_ALWAYS_INLINE spanrsl _select(T *p, I sz) const noexcept + { + C4_ASSERT(p >= m_ptr); + I delta = static_cast(p - m_ptr); + C4_ASSERT(m_capacity >= delta); + return spanrsl(p, sz, static_cast(m_capacity - delta), m_offset + delta); + } + +public: + + _c4_DEFINE_ARRAY_TYPES(T, I); + using NCT = typename std::remove_const::type; //!< NCT=non const type + using CT = typename std::add_const::type; //!< CT=const type + using const_type = spanrsl; + + C4_ALWAYS_INLINE operator span () const noexcept { return span(m_ptr, m_size); } + C4_ALWAYS_INLINE operator spanrs () const noexcept { return spanrs(m_ptr, m_size, m_capacity); } + C4_ALWAYS_INLINE operator spanrsl () const noexcept { return spanrsl(m_ptr, m_size, m_capacity, m_offset); } + +public: + + C4_ALWAYS_INLINE spanrsl() noexcept : m_ptr{nullptr}, m_size{0}, m_capacity{0}, m_offset{0} {} + + spanrsl(spanrsl const&) = default; + spanrsl(spanrsl &&) = default; + + spanrsl& operator= (spanrsl const&) = default; + spanrsl& operator= (spanrsl &&) = default; + +public: + + C4_ALWAYS_INLINE spanrsl(T *p, I sz) noexcept : m_ptr{p}, m_size{sz}, m_capacity{sz}, m_offset{0} {} + C4_ALWAYS_INLINE void assign(T *p, I sz) noexcept { m_ptr = p; m_size = sz; m_capacity = sz; m_offset = 0; } + + C4_ALWAYS_INLINE spanrsl(T *p, I sz, I cap) noexcept : m_ptr{p}, m_size{sz}, m_capacity{cap}, m_offset{0} {} + C4_ALWAYS_INLINE void assign(T *p, I sz, I cap) noexcept { m_ptr = p; m_size = sz; m_capacity = cap; m_offset = 0; } + + C4_ALWAYS_INLINE spanrsl(T *p, I sz, I cap, I offs) noexcept : m_ptr{p}, m_size{sz}, m_capacity{cap}, m_offset{offs} {} + C4_ALWAYS_INLINE void assign(T *p, I sz, I cap, I offs) noexcept { m_ptr = p; m_size = sz; m_capacity = cap; m_offset = offs; } + + template C4_ALWAYS_INLINE spanrsl(T (&arr)[N]) noexcept : m_ptr{arr}, m_size{N}, m_capacity{N}, m_offset{0} {} + template C4_ALWAYS_INLINE void assign(T (&arr)[N]) noexcept { m_ptr = arr; m_size = N; m_capacity = N; m_offset = 0; } + + C4_ALWAYS_INLINE spanrsl(c4::aggregate_t, std::initializer_list il) noexcept : m_ptr{il.begin()}, m_size{il.size()}, m_capacity{il.size()}, m_offset{0} {} + C4_ALWAYS_INLINE void assign (c4::aggregate_t, std::initializer_list il) noexcept { m_ptr = il.begin(); m_size = il.size(); m_capacity = il.size(); m_offset = 0; } + +public: + + C4_ALWAYS_INLINE I offset() const noexcept { return m_offset; } + C4_ALWAYS_INLINE I capacity() const noexcept { return m_capacity; } + + C4_ALWAYS_INLINE void resize(I sz) C4_NOEXCEPT_A { C4_ASSERT(sz <= m_capacity); m_size = sz; } + C4_ALWAYS_INLINE void rtrim (I n ) C4_NOEXCEPT_A { C4_ASSERT(n >= 0 && n < m_size); m_size -= n; } + C4_ALWAYS_INLINE void ltrim (I n ) C4_NOEXCEPT_A { C4_ASSERT(n >= 0 && n < m_size); m_size -= n; m_ptr += n; m_offset += n; m_capacity -= n; } + + /** recover the original span as an spanrsl */ + C4_ALWAYS_INLINE spanrsl original() const + { + return spanrsl(m_ptr - m_offset, m_capacity + m_offset, m_capacity + m_offset, 0); + } + /** recover the original span as a different span type. Example: spanrs<...> orig = s.original(); */ + template class OtherSpanType> + C4_ALWAYS_INLINE OtherSpanType original() + { + return OtherSpanType(m_ptr - m_offset, m_capacity + m_offset); + } +}; +template using cspanrsl = spanrsl; + +// NOLINTEND(misc-confusable-identifiers) + +C4_SUPPRESS_WARNING_GCC_CLANG_POP + +} // namespace c4 + + +#endif /* _C4_SPAN_HPP_ */ + + +// (end src/c4/span.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/type_name.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_TYPENAME_HPP_ +#define _C4_TYPENAME_HPP_ + +/** @file type_name.hpp compile-time type name */ + +//included above: +//#include +// amalgamate: removed include of +// c4/language.hpp +//#include "c4/language.hpp" +#if !defined(C4_LANGUAGE_HPP_) && !defined(_C4_LANGUAGE_HPP_) +#error "amalgamate: file c4/language.hpp must have been included at this point" +#endif /* C4_LANGUAGE_HPP_ */ + + +/// @cond dev +// this is an abbreviated way of getting the type name, +// hence the terse and non-namespaced names +struct c4t_ +{ + const char *str; + size_t sz; + template C4_ALWAYS_INLINE constexpr + c4t_(const char (&s)[N]) noexcept : str(s), sz(N-1) {} // take off the \0 +}; +template +#if !defined(__GNUC__) || (__GNUC__ >= 5) || (C4_CPP >= 14) +constexpr +#endif +C4_ALWAYS_INLINE c4t_ c4tn_() +{ + return c4t_(C4_PRETTY_FUNC); +} +/// @endcond + + +namespace c4 { + +/** A non-zero terminated typename string. We're not using csubstr or + * span to spare the heavy include. */ +struct C4CORE_EXPORT TypeNameStr +{ + const char* str; + size_t len; +}; + +/** compile-time type name + * @see http://stackoverflow.com/a/20170989/5875572 */ +template +C4_CONSTEXPR14 TypeNameStr type_name() noexcept +{ + const c4t_ p(c4tn_()); + +#if (0) // enable this to debug and find the offsets + for(size_t index = 0; index < p.sz; ++index) + printf(" %2c", p.str[index]); + printf("\n"); + for(size_t index = 0; index < p.sz; ++index) + printf(" %2zu", index); + printf("\n"); + for(size_t index = 0; index < p.sz; ++index) + printf(" %2zu", p.sz - index); + printf("\n"); +#endif + +#if defined(_MSC_VER) +# if defined(__clang__) // Visual Studio has the clang toolset +# if (_MSC_VER >= 1930) // do not use this: defined(C4_MSVC_2022) + // ..............................xxx. + // c4t_ __cdecl c4tn_(void) [T = int] + enum : size_t { tstart = 30, tend = 1}; +# else + // example: + // ..........................xxx. + // c4t_ __cdecl c4tn_() [T = int] + enum : size_t { tstart = 26, tend = 1}; +# endif +# elif (_MSC_VER >= 1900) + // Note: subtract 7 at the end because the function terminates with ">(void)" in VS2015+ + size_t tstart = 26, tend = 7; + + const char *C4_RESTRICT s = p.str + tstart; // look at the start + size_t sz = p.sz - tstart; + + // we're not using strcmp() or memcmp() to spare the #include + + // does it start with 'class '? + if(sz > 6 && s[0] == 'c' && s[1] == 'l' && s[2] == 'a' && s[3] == 's' && s[4] == 's' && s[5] == ' ') + { + tstart += 6; + } + // does it start with 'struct '? + else if(sz > 7 && s[0] == 's' && s[1] == 't' && s[2] == 'r' && s[3] == 'u' && s[4] == 'c' && s[5] == 't' && s[6] == ' ') + { + tstart += 7; + } + +# else + #error unknown visual studio +# endif + +#elif defined(__ICC) + // example: + // ........................xxx. + // "c4t_ c4tn_() [with T = int]" + enum : size_t { tstart = 23, tend = 1}; + +#elif defined(__clang__) + // example: + // ...................xxx. + // "c4t_ c4tn_() [T = int]" + enum : size_t { tstart = 18, tend = 1}; + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wuninitialized" + +#elif defined(__GNUC__) + #if (__GNUC__ >= 5) || (C4_CPP >= 14) + // example: + // ..................................xxx. + // "constexpr _c4t _c4tn() [with T = int]" + enum : size_t { tstart = 33, tend = 1 }; + #else + // example: + // ........................xxx. + // "_c4t _c4tn() [with T = int]" + enum : size_t { tstart = 23, tend = 1 }; + #endif + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wuninitialized" +#else + #error not implemented +#endif + + TypeNameStr o{p.str + tstart, p.sz - tstart - tend}; + +#if defined(_MSC_VER) +#elif defined(__clang__) + #pragma clang diagnostic pop +#elif defined(__GNUC__) + #pragma GCC diagnostic pop +#endif + + return o; +} + +/** compile-time type name + * @overload */ +template +C4_CONSTEXPR14 C4_ALWAYS_INLINE TypeNameStr type_name(T const&) noexcept +{ + return type_name(); +} + +} // namespace c4 + +#endif //_C4_TYPENAME_HPP_ + + +// (end src/c4/type_name.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/base64.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_BASE64_HPP_ +#define _C4_BASE64_HPP_ + +/** @file base64.hpp encoding/decoding for base64. + * @see https://en.wikipedia.org/wiki/Base64 + * @see https://www.base64encode.org/ + * */ + +#ifndef _C4_EXPORT_HPP_ +// amalgamate: removed include of +// c4/export.hpp +//#include "c4/export.hpp" +#if !defined(C4_EXPORT_HPP_) && !defined(_C4_EXPORT_HPP_) +#error "amalgamate: file c4/export.hpp must have been included at this point" +#endif /* C4_EXPORT_HPP_ */ + +#endif +//included above: +//#include + +namespace c4 { + +/** @defgroup doc_base64 Base64 encoding/decoding + * @see https://en.wikipedia.org/wiki/Base64 + * @see https://www.base64encode.org/ + * @{ */ + + +/** check that the given buffer is a valid base64 encoding + * @see https://en.wikipedia.org/wiki/Base64 */ +C4CORE_EXPORT bool base64_valid(const char* encoded, size_t encoded_sz); + + +/** base64-encode binary data. This is a plain implementation with a + * focus on simplicity and small footprint, such that it runs + * reasonably well in constrained platforms. On larger platforms it is + * reasonably fast (reaching 3GB/s and over), but it is not the + * fastest. If ultimate base64 speed in x64 platforms is your + * objective, there are faster implementations available. One + * recommendation is https://github.com/aklomp/base64, which uses a + * larger Look-Up Table (4096B as compared with 64B in c4core), making + * it between 1.5x~2x faster than c4core for larger payloads (but also + * slower for small payloads), and much faster when using AVX2 or + * AVX512 processing. But this speed comes at a cost in constrained + * platforms: eg c4core encodes ~2.5x faster in armv4 and armv5. + * + * @param encoded [out] output buffer for encoded data + * + * @param encoded_sz [in] size of the output buffer for encoded data + * + * @param data [in] the input buffer with the binary data + * + * @param data_sz [in] size of the input buffer with the binary data + * + * @return the number of bytes required for the output buffer. No + * writes occur beyond the end of the output buffer, so it is + * safe to do a speculative call where the encoded buffer is + * empty, or maybe too small. The caller should ensure that + * the returned size is smaller than the size of the encoded + * buffer. + * + * @note the result depends on endianness. If transfer between + * little/big endian systems is desired, the caller should + * normalize @p data before encoding. + * + * @see https://en.wikipedia.org/wiki/Base64 */ +C4CORE_EXPORT size_t base64_encode(char *encoded, size_t encoded_sz, + void const* data, size_t data_sz); + + +/** decode the base64 encoding in the given buffer. This is a plain + * implementation with a focus on simplicity and small footprint, such + * that it runs reasonably well in constrained platforms. On larger + * platforms it is reasonably fast, but it is not the fastest. If + * ultimate base64 speed in x64 platforms is your objective, there are + * faster implementations available. One recommendation is + * https://github.com/aklomp/base64, which uses up to 16x larger + * Look-Up Tables, making it between 1.5x~2x faster than c4core (but + * also slower for small payloads), and much faster when using AVX2 or + * AVX512 processing. But this x64 speed comes at a cost in + * constrained platforms: eg c4core decodes ~4x faster in armv4 and + * armv5. + * + * @param encoded [in] the encoded base64 + * + * @param encoded_sz [in] the size of the encoded buffer + * + * @param data [out] the output decoded buffer + * + * @param data_sz [in] the size of the output decoded buffer + * + * @param data_sz_required [out] the size required for the output + * decoded buffer, ie, the number of bytes needed to return the + * output (ie the required size for @p data). No writes occur + * beyond the end of the output buffer, so it is safe to do a + * speculative call where the data buffer is empty, or maybe + * too small. The caller should ensure that this value + * is smaller than data_sz. + * + * @return false if the encoding was invalid or the data size was + * too small, and true otherwise. + * + * @note the result depends on endianness. If transfer between + * little/big endian systems is desired, the caller should + * normalize @p data after decoding. + * + * @see https://en.wikipedia.org/wiki/Base64 */ +C4CORE_EXPORT bool base64_decode(char const* encoded, size_t encoded_sz, + void * data, size_t data_sz, + size_t *data_sz_required); + +/** @} */ // base64 + +} // namespace c4 + +#endif /* _C4_BASE64_HPP_ */ + + +// (end src/c4/base64.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/format_base64.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_FORMAT_BASE64_HPP_ +#define _C4_FORMAT_BASE64_HPP_ + +/** @file format_base64.hpp Utilities for formatting data as base64 */ + +#ifndef _C4_SUBSTR_HPP_ +// amalgamate: removed include of +// c4/substr.hpp +//#include "c4/substr.hpp" +#if !defined(C4_SUBSTR_HPP_) && !defined(_C4_SUBSTR_HPP_) +#error "amalgamate: file c4/substr.hpp must have been included at this point" +#endif /* C4_SUBSTR_HPP_ */ + +#endif +#ifndef _C4_BLOB_HPP_ +// amalgamate: removed include of +// c4/blob.hpp +//#include "c4/blob.hpp" +#if !defined(C4_BLOB_HPP_) && !defined(_C4_BLOB_HPP_) +#error "amalgamate: file c4/blob.hpp must have been included at this point" +#endif /* C4_BLOB_HPP_ */ + +#endif +#ifndef _C4_BASE64_HPP_ +// amalgamate: removed include of +// c4/base64.hpp +//#include "c4/base64.hpp" +#if !defined(C4_BASE64_HPP_) && !defined(_C4_BASE64_HPP_) +#error "amalgamate: file c4/base64.hpp must have been included at this point" +#endif /* C4_BASE64_HPP_ */ + +#endif + + +namespace c4 { +namespace fmt { + +/** @defgroup doc_base64_fmt Base64 format specifiers + * + * ```c++ + * // given these variables: + * T var = {}; + * T &ref = var; + * std::vector vec = ...; + * substr buf = ...; + * + * // there are different approaches to encode/decode base64: + * size_t numchars = to_chars(buf, base64(var)); // encode var as base64 + * size_t numchars = to_chars(buf, base64(ref)); // same as above + * size_t numchars = to_chars(buf, base64(&var, 1)); // same as above + * size_t numchars = to_chars(buf, base64(vec.data(), vec.size())); // for the container, but same call form as above + * size_t numchars = to_chars(buf, base64(vec)); // same effect as prev call + * + * // using cbase64() (the `c` prefix is for const) is equivalent + * // for encoding, but quicker to compile: + * size_t numchars = to_chars(buf, cbase64(var)); // encode var as base64 + * size_t numchars = to_chars(buf, cbase64(ref)); // same as above + * size_t numchars = to_chars(buf, cbase64(&var, 1)); // same as above + * size_t numchars = to_chars(buf, cbase64(vec.data(), vec.size())); // for the container, but same call as above + * size_t numchars = to_chars(buf, cbase64(vec)); // same effect as prev call + * + * // to decode: + * csubstr buf = ...; + * size_t reqbytes = 0; // number of bytes of decoded data + * bool ok = from_chars(buf, base64(var)); // decode base64 to var + * bool ok = from_chars(buf, base64(var, &reqbytes)); // same. reqbytes will be sizeof(var) + * bool ok = from_chars(buf, base64(ref)); // same as above + * bool ok = from_chars(buf, base64(ref, &reqbytes)); // same. reqbytes will be sizeof(var) + * bool ok = from_chars(buf, base64(&var, 1, &reqbytes)); // same + * bool ok = from_chars(buf, base64(vec.data(), vec.size(), &reqbytes)); // for the container, but same call as above + * bool ok = from_chars(buf, base64(vec, &reqbytes)); // same effect as prev call + * ``` + * @ingroup doc_format_specifiers + * @ingroup doc_base64 + * @{ */ + +/** @cond dev */ +namespace detail { +template struct sfinae_true : std::true_type{}; +template static auto test_resize(int) -> sfinae_true().resize(std::declval()))>; +template static auto test_resize(int64_t) -> std::false_type; +/// a traits class to report when a type as a member method named resize +/// @see https://stackoverflow.com/a/9154394 +template +struct has_resize : decltype(detail::test_resize::type>::type>::type, Arg>(0)){}; + +template +struct base64_wrapper_ +{ + blob_ data; + size_t *required_size; + base64_wrapper_(blob_ blob, size_t *len_=nullptr) noexcept + : data(blob) + , required_size(len_) + {} +}; + +template +struct base64_container_wrapper_ +{ + using value_type = typename Container::value_type; + Container *container; + size_t *required_size; + base64_container_wrapper_(Container *c, size_t *len_=nullptr) noexcept + : container(c) + , required_size(len_) + {} + blob_ data() const noexcept + { + size_t sz = container->size(); + CharOrConstChar *first = sz ? reinterpret_cast(&(*container)[0]) : nullptr; // NOLINT + return blob_(first, sizeof(value_type) * sz); + } +}; +} // namespace detail +/** @endcond */ + + + +/** a tag type to mark a payload to be encoded as base64 */ +using const_base64_wrapper = detail::base64_wrapper_; +/** a tag type to mark a payload to be decoded as base64 */ +using base64_wrapper = detail::base64_wrapper_; + + +/** a tag type to mark a payload as base64-encoded */ +template +using const_base64_container_wrapper = detail::base64_container_wrapper_; +/** a tag type to mark a payload to be encoded as base64 */ +template +using base64_container_wrapper = detail::base64_container_wrapper_; + + +/** a tag function to mark a csubstr payload to be encoded in base64 format */ +C4_ALWAYS_INLINE const_base64_wrapper cbase64(csubstr s, size_t *reqsize=nullptr) +{ + return const_base64_wrapper(cblob(s.str, s.len), reqsize); +} +/** a tag function to mark a csubstr payload to be encoded in base64 format */ +C4_ALWAYS_INLINE const_base64_wrapper base64(csubstr s, size_t *reqsize=nullptr) +{ + return const_base64_wrapper(cblob(s.str, s.len), reqsize); +} +/** a tag function to mark a variable to be decoded from base64 */ +C4_ALWAYS_INLINE base64_wrapper base64(substr s, size_t *reqsize=nullptr) +{ + return base64_wrapper(blob(s.str, s.len), reqsize); +} + + +/** a tag function to mark a payload to be encoded in base64 format */ +template +C4_ALWAYS_INLINE const_base64_wrapper cbase64(T const* arg, size_t sz, size_t *reqsize=nullptr) // NOLINT +{ + return const_base64_wrapper(cblob(arg, sz), reqsize); +} +/** a tag function to mark a payload to be encoded in base64 format */ +template +C4_ALWAYS_INLINE auto base64(T * arg, size_t sz, size_t *reqsize=nullptr) // NOLINT + -> typename std::conditional::type>::type>::value, + const_base64_wrapper, + base64_wrapper>::type +{ + using U = typename std::remove_reference::type>::type; + using ret_type = typename std::conditional::value, + const_base64_wrapper, + base64_wrapper>::type; + using blob_type = typename std::conditional::value, + cblob, + blob>::type; + return ret_type(blob_type(arg, sz), reqsize); +} + + +/** a tag function to mark a payload to be encoded in base64 format */ +template +C4_ALWAYS_INLINE auto cbase64(T const& arg, size_t *reqsize=nullptr) // NOLINT + -> typename std::enable_if< ! detail::has_resize::value, const_base64_wrapper>::type +{ + return const_base64_wrapper(cblob(arg), reqsize); +} +/** a tag function to mark a payload to be encoded or decoded in base64 format */ +template +C4_ALWAYS_INLINE auto base64(T & arg, size_t *reqsize=nullptr) // NOLINT + -> typename std::enable_if< ! detail::has_resize::value, + typename std::conditional::type>::type>::value, + const_base64_wrapper, + base64_wrapper>::type>::type +{ + using U = typename std::remove_reference::type>::type; + using ret_type = typename std::conditional::value, + const_base64_wrapper, + base64_wrapper>::type; + using blob_type = typename std::conditional::value, + cblob, + blob>::type; + return ret_type(blob_type(arg), reqsize); +} + +/** a tag function to mark a container (payload with a .resize() + * method) to be encoded in base64 format. */ +template +C4_ALWAYS_INLINE auto cbase64(T const& arg, size_t *reqsize=nullptr) // NOLINT + -> typename std::enable_if::value, const_base64_container_wrapper>::type +{ + return const_base64_container_wrapper(&arg, reqsize); +} +/** a tag function to mark a container (payload with a .resize() + * method) to be encoded or decoded in base64 format. Subsequently + * when decoding, from_chars() will resize the container to fit the + * decoded data. */ +template +C4_ALWAYS_INLINE auto base64(T & arg, size_t *reqsize=nullptr) // NOLINT + -> typename std::enable_if::value, + typename std::conditional::type>::type>::value, + const_base64_container_wrapper, + base64_container_wrapper>::type>::type +{ + using ret_type = typename std::conditional::type>::type>::value, + const_base64_container_wrapper, + base64_container_wrapper>::type; + return ret_type(&arg, reqsize); +} + +/** @} */ // base64_fmt + +} // namespace fmt + + +//----------------------------------------------------------------------------- + +/** @addtogroup doc_base64 + * @{ */ + +/** (1) read a variable in base64 format + * @ingroup doc_from_chars */ +inline bool from_chars(csubstr buf, fmt::base64_wrapper const& b) +{ + size_t reqsize = 0; + bool ok = base64_decode(buf.str, buf.len, b.data.buf, b.data.len, &reqsize); + if(b.required_size) + *b.required_size = reqsize; + return ok; +} +/** (2) read a variable in base64 format + * @ingroup doc_from_chars */ +inline bool from_chars(csubstr buf, fmt::base64_wrapper *b) +{ + return from_chars(buf, *b); +} + + +/** write a variable or buffer in base64 format + * @ingroup doc_to_chars */ +template +inline size_t to_chars(substr buf, fmt::detail::base64_wrapper_ const& b) +{ + size_t reqsize = base64_encode(buf.str, buf.len, b.data.buf, b.data.len); + if(b.required_size) + *b.required_size = reqsize; + return reqsize; +} +/** write a container in base64 format + * @ingroup doc_to_chars */ +template +size_t to_chars(substr buf, fmt::detail::base64_container_wrapper_ const& b) +{ + cblob data = b.data(); + size_t reqsize = base64_encode(buf.str, buf.len, data.buf, data.len); + if(b.required_size) + *b.required_size = reqsize; + return reqsize; +} + +/** read a container in base64 format, resizing it as needed to + * accomodate the result + * @ingroup doc_from_chars */ +template +bool from_chars(csubstr buf, fmt::base64_container_wrapper const& b) +{ + enum : size_t { elm_sz = sizeof(typename fmt::base64_container_wrapper::value_type) }; // NOLINT + blob data = b.data(); + size_t required_size = 0; + bool ok = base64_decode(buf.str, buf.len, data.buf, data.len, &required_size); + if(b.required_size) + *b.required_size = required_size; + if(!required_size) + return ok; + else if(!ok && ((required_size < data.len) || (required_size % elm_sz))) + return false; + size_t num_elms = required_size / elm_sz; + b.container->resize(num_elms); + if(required_size > data.len) + { + data = b.data(); + ok = base64_decode(buf.str, buf.len, data.buf, data.len, &required_size); + if(b.required_size) + *b.required_size = required_size; + } + return ok; +} +/** read a container in base64 format, resizing it as needed to + * accomodate the result + * @ingroup doc_from_chars */ +template +bool from_chars(csubstr buf, fmt::base64_container_wrapper const* b) +{ + return from_chars(buf, *b); +} + +/** @} */ // base64 + +} // namespace c4 + +#endif /* _C4_FORMAT_BASE64_HPP_ */ + + +// (end src/c4/format_base64.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/std/span.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_STD_SPAN_HPP_ +#define _C4_STD_SPAN_HPP_ + +/** @file span.hpp */ + +#ifndef C4CORE_SINGLE_HEADER +// amalgamate: removed include of +// c4/language.hpp +//#include "c4/language.hpp" +#if !defined(C4_LANGUAGE_HPP_) && !defined(_C4_LANGUAGE_HPP_) +#error "amalgamate: file c4/language.hpp must have been included at this point" +#endif /* C4_LANGUAGE_HPP_ */ + +#endif + + +#if (C4_CPP >= 20) || defined(__DOXYGEN__) + +#ifndef C4CORE_SINGLE_HEADER +// amalgamate: removed include of +// c4/substr.hpp +//#include "c4/substr.hpp" +#if !defined(C4_SUBSTR_HPP_) && !defined(_C4_SUBSTR_HPP_) +#error "amalgamate: file c4/substr.hpp must have been included at this point" +#endif /* C4_SUBSTR_HPP_ */ + +#endif + +//included above: +//#include + + +namespace c4 { + +template struct is_string; +template struct is_writeable_string; + +// mark std::span as a string type +template<> struct is_string> : public std::true_type {}; +template<> struct is_string> : public std::true_type {}; + +// mark std::span as a string type +template<> struct is_string> : public std::true_type {}; +template<> struct is_string> : public std::true_type {}; +template<> struct is_writeable_string> : public std::true_type {}; +template<> struct is_writeable_string> : public std::true_type {}; + + +//----------------------------------------------------------------------------- + +/** create a csubstr from an existing std::span */ +C4_ALWAYS_INLINE c4::csubstr to_csubstr(std::span s) noexcept +{ + return c4::csubstr(s.data(), s.size()); +} + +/** create a csubstr from an existing std::span */ +C4_ALWAYS_INLINE c4::csubstr to_csubstr(std::span s) noexcept +{ + return c4::csubstr(s.data(), s.size()); +} +/** create a substr from an existing std::span */ +C4_ALWAYS_INLINE c4::substr to_substr(std::span s) noexcept +{ + return c4::substr(s.data(), s.size()); +} + + +//----------------------------------------------------------------------------- + +C4_ALWAYS_INLINE bool operator== (c4::csubstr ss, std::span s) { return ss.compare(s.data(), s.size()) == 0; } +C4_ALWAYS_INLINE bool operator!= (c4::csubstr ss, std::span s) { return ss.compare(s.data(), s.size()) != 0; } +C4_ALWAYS_INLINE bool operator>= (c4::csubstr ss, std::span s) { return ss.compare(s.data(), s.size()) >= 0; } +C4_ALWAYS_INLINE bool operator> (c4::csubstr ss, std::span s) { return ss.compare(s.data(), s.size()) > 0; } +C4_ALWAYS_INLINE bool operator<= (c4::csubstr ss, std::span s) { return ss.compare(s.data(), s.size()) <= 0; } +C4_ALWAYS_INLINE bool operator< (c4::csubstr ss, std::span s) { return ss.compare(s.data(), s.size()) < 0; } + +C4_ALWAYS_INLINE bool operator== (std::span s, c4::csubstr ss) { return ss.compare(s.data(), s.size()) == 0; } +C4_ALWAYS_INLINE bool operator!= (std::span s, c4::csubstr ss) { return ss.compare(s.data(), s.size()) != 0; } +C4_ALWAYS_INLINE bool operator<= (std::span s, c4::csubstr ss) { return ss.compare(s.data(), s.size()) >= 0; } +C4_ALWAYS_INLINE bool operator< (std::span s, c4::csubstr ss) { return ss.compare(s.data(), s.size()) > 0; } +C4_ALWAYS_INLINE bool operator>= (std::span s, c4::csubstr ss) { return ss.compare(s.data(), s.size()) <= 0; } +C4_ALWAYS_INLINE bool operator> (std::span s, c4::csubstr ss) { return ss.compare(s.data(), s.size()) < 0; } + + +C4_ALWAYS_INLINE bool operator== (c4::csubstr ss, std::span s) { return ss.compare(s.data(), s.size()) == 0; } +C4_ALWAYS_INLINE bool operator!= (c4::csubstr ss, std::span s) { return ss.compare(s.data(), s.size()) != 0; } +C4_ALWAYS_INLINE bool operator>= (c4::csubstr ss, std::span s) { return ss.compare(s.data(), s.size()) >= 0; } +C4_ALWAYS_INLINE bool operator> (c4::csubstr ss, std::span s) { return ss.compare(s.data(), s.size()) > 0; } +C4_ALWAYS_INLINE bool operator<= (c4::csubstr ss, std::span s) { return ss.compare(s.data(), s.size()) <= 0; } +C4_ALWAYS_INLINE bool operator< (c4::csubstr ss, std::span s) { return ss.compare(s.data(), s.size()) < 0; } + +C4_ALWAYS_INLINE bool operator== (std::span s, c4::csubstr ss) { return ss.compare(s.data(), s.size()) == 0; } +C4_ALWAYS_INLINE bool operator!= (std::span s, c4::csubstr ss) { return ss.compare(s.data(), s.size()) != 0; } +C4_ALWAYS_INLINE bool operator<= (std::span s, c4::csubstr ss) { return ss.compare(s.data(), s.size()) >= 0; } +C4_ALWAYS_INLINE bool operator< (std::span s, c4::csubstr ss) { return ss.compare(s.data(), s.size()) > 0; } +C4_ALWAYS_INLINE bool operator>= (std::span s, c4::csubstr ss) { return ss.compare(s.data(), s.size()) <= 0; } +C4_ALWAYS_INLINE bool operator> (std::span s, c4::csubstr ss) { return ss.compare(s.data(), s.size()) < 0; } + + +//----------------------------------------------------------------------------- + +/** copy a std::span to a writeable substr */ +C4_ALWAYS_INLINE size_t to_chars(c4::substr buf, std::span s) +{ + size_t sz = s.size(); + size_t len = buf.len < sz ? buf.len : sz; + buf.copy_from(csubstr(s.data(), len)); // copy only available chars + return sz; // return the number of needed chars +} + +/** copy a std::span to a writeable substr */ +inline size_t to_chars(c4::substr buf, std::span s) +{ + size_t sz = s.size(); + size_t len = buf.len < sz ? buf.len : sz; + buf.copy_from(csubstr(s.data(), len)); // copy only available chars + return sz; // return the number of needed chars +} + +/** copy a csubstr to an existing std::span */ +inline bool from_chars(c4::csubstr buf, std::span * s) +{ + if(buf.len <= s->size()) + { + substr(s->data(), buf.len).copy_from(buf); + *s = s->first(buf.len); + return true; + } + return false; +} + +} // namespace c4 + +#endif // SPAN_AVAILABLE + +#endif // _C4_STD_SPAN_HPP_ + + +// (end src/c4/std/span.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/std/string.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_STD_STRING_HPP_ +#define _C4_STD_STRING_HPP_ + +/** @file string.hpp */ + +#ifndef C4CORE_SINGLE_HEADER +// amalgamate: removed include of +// c4/substr.hpp +//#include "c4/substr.hpp" +#if !defined(C4_SUBSTR_HPP_) && !defined(_C4_SUBSTR_HPP_) +#error "amalgamate: file c4/substr.hpp must have been included at this point" +#endif /* C4_SUBSTR_HPP_ */ + +#endif + +//included above: +//#include + +namespace c4 { + +// mark std::string as a string type +template struct is_string; +template<> struct is_string : public std::true_type {}; +template<> struct is_string : public std::true_type {}; + +// mark std::string as a writeable string type +template struct is_writeable_string; +template<> struct is_writeable_string : public std::true_type {}; + + +//----------------------------------------------------------------------------- + +/** get a writeable view to an existing std::string. + * When the string is empty, the returned view will be pointing + * at the character with value '\0', but the size will be zero. + * @see https://en.cppreference.com/w/cpp/string/basic_string/operator_at + */ +C4_ALWAYS_INLINE c4::substr to_substr(std::string &s) noexcept +{ + #if C4_CPP < 11 + #error this function requires c++11 + #endif + // since c++11 it is legal to call s[s.size()]. + return c4::substr(&s[0], s.size()); // NOLINT +} + +/** get a readonly view to an existing std::string. + * When the string is empty, the returned view will be pointing + * at the character with value '\0', but the size will be zero. + * @see https://en.cppreference.com/w/cpp/string/basic_string/operator_at + */ +C4_ALWAYS_INLINE c4::csubstr to_csubstr(std::string const& s) noexcept +{ + #if C4_CPP < 11 + #error this function requires c++11 + #endif + // since c++11 it is legal to call s[s.size()]. + return c4::csubstr(&s[0], s.size()); // NOLINT +} + + +//----------------------------------------------------------------------------- + +C4_ALWAYS_INLINE bool operator== (c4::csubstr ss, std::string const& s) { return ss.compare(to_csubstr(s)) == 0; } +C4_ALWAYS_INLINE bool operator!= (c4::csubstr ss, std::string const& s) { return ss.compare(to_csubstr(s)) != 0; } +C4_ALWAYS_INLINE bool operator>= (c4::csubstr ss, std::string const& s) { return ss.compare(to_csubstr(s)) >= 0; } +C4_ALWAYS_INLINE bool operator> (c4::csubstr ss, std::string const& s) { return ss.compare(to_csubstr(s)) > 0; } +C4_ALWAYS_INLINE bool operator<= (c4::csubstr ss, std::string const& s) { return ss.compare(to_csubstr(s)) <= 0; } +C4_ALWAYS_INLINE bool operator< (c4::csubstr ss, std::string const& s) { return ss.compare(to_csubstr(s)) < 0; } + +C4_ALWAYS_INLINE bool operator== (std::string const& s, c4::csubstr ss) { return ss.compare(to_csubstr(s)) == 0; } +C4_ALWAYS_INLINE bool operator!= (std::string const& s, c4::csubstr ss) { return ss.compare(to_csubstr(s)) != 0; } +C4_ALWAYS_INLINE bool operator>= (std::string const& s, c4::csubstr ss) { return ss.compare(to_csubstr(s)) <= 0; } +C4_ALWAYS_INLINE bool operator> (std::string const& s, c4::csubstr ss) { return ss.compare(to_csubstr(s)) < 0; } +C4_ALWAYS_INLINE bool operator<= (std::string const& s, c4::csubstr ss) { return ss.compare(to_csubstr(s)) >= 0; } +C4_ALWAYS_INLINE bool operator< (std::string const& s, c4::csubstr ss) { return ss.compare(to_csubstr(s)) > 0; } + + +//----------------------------------------------------------------------------- + +/** copy a std::string to a writeable substr */ +inline size_t to_chars(c4::substr buf, std::string const& s) +{ + size_t sz = s.size(); + size_t len = buf.len < sz ? buf.len : sz; + buf.copy_from(csubstr(s.data(), len)); // copy only available chars + return sz; // return the number of needed chars +} + +/** copy a csubstr to an existing std::string */ +inline bool from_chars(c4::csubstr buf, std::string * s) +{ + s->resize(buf.len); + substr(&(*s)[0], buf.len).copy_from(buf); // NOLINT + return true; +} + +} // namespace c4 + +#endif // _C4_STD_STRING_HPP_ + + +// (end src/c4/std/string.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/std/string_view.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_STD_STRING_VIEW_HPP_ +#define _C4_STD_STRING_VIEW_HPP_ + +/** @file string_view.hpp */ + +#ifndef C4CORE_SINGLE_HEADER +#ifndef _C4_LANGUAGE_HPP_ +// amalgamate: removed include of +// c4/language.hpp +//#include "c4/language.hpp" +#if !defined(C4_LANGUAGE_HPP_) && !defined(_C4_LANGUAGE_HPP_) +#error "amalgamate: file c4/language.hpp must have been included at this point" +#endif /* C4_LANGUAGE_HPP_ */ + +#endif +#endif + +#if (C4_CPP >= 17 && defined(__cpp_lib_string_view)) || defined(__DOXYGEN__) + +#ifndef C4CORE_SINGLE_HEADER +#ifndef _C4_SUBSTR_HPP_ +// amalgamate: removed include of +// c4/substr.hpp +//#include "c4/substr.hpp" +#if !defined(C4_SUBSTR_HPP_) && !defined(_C4_SUBSTR_HPP_) +#error "amalgamate: file c4/substr.hpp must have been included at this point" +#endif /* C4_SUBSTR_HPP_ */ + +#endif +#endif + +//included above: +//#include + + +namespace c4 { + +// mark std::string_view as a string type +template struct is_string; +template<> struct is_string : public std::true_type {}; +template<> struct is_string : public std::true_type {}; + + +//----------------------------------------------------------------------------- + +/** create a csubstr from an existing std::string_view. */ +C4_ALWAYS_INLINE c4::csubstr to_csubstr(std::string_view s) noexcept +{ + return c4::csubstr(s.data(), s.size()); +} + + +//----------------------------------------------------------------------------- + +C4_ALWAYS_INLINE bool operator== (c4::csubstr ss, std::string_view s) { return ss.compare(s.data(), s.size()) == 0; } +C4_ALWAYS_INLINE bool operator!= (c4::csubstr ss, std::string_view s) { return ss.compare(s.data(), s.size()) != 0; } +C4_ALWAYS_INLINE bool operator>= (c4::csubstr ss, std::string_view s) { return ss.compare(s.data(), s.size()) >= 0; } +C4_ALWAYS_INLINE bool operator> (c4::csubstr ss, std::string_view s) { return ss.compare(s.data(), s.size()) > 0; } +C4_ALWAYS_INLINE bool operator<= (c4::csubstr ss, std::string_view s) { return ss.compare(s.data(), s.size()) <= 0; } +C4_ALWAYS_INLINE bool operator< (c4::csubstr ss, std::string_view s) { return ss.compare(s.data(), s.size()) < 0; } + +C4_ALWAYS_INLINE bool operator== (std::string_view s, c4::csubstr ss) { return ss.compare(s.data(), s.size()) == 0; } +C4_ALWAYS_INLINE bool operator!= (std::string_view s, c4::csubstr ss) { return ss.compare(s.data(), s.size()) != 0; } +C4_ALWAYS_INLINE bool operator<= (std::string_view s, c4::csubstr ss) { return ss.compare(s.data(), s.size()) >= 0; } +C4_ALWAYS_INLINE bool operator< (std::string_view s, c4::csubstr ss) { return ss.compare(s.data(), s.size()) > 0; } +C4_ALWAYS_INLINE bool operator>= (std::string_view s, c4::csubstr ss) { return ss.compare(s.data(), s.size()) <= 0; } +C4_ALWAYS_INLINE bool operator> (std::string_view s, c4::csubstr ss) { return ss.compare(s.data(), s.size()) < 0; } + + +//----------------------------------------------------------------------------- + +/** copy an std::string_view to a writeable substr */ +inline size_t to_chars(c4::substr buf, std::string_view s) +{ + size_t sz = s.size(); + size_t len = buf.len < sz ? buf.len : sz; + buf.copy_from(csubstr(s.data(), len)); // copy only available chars + return sz; // return the number of needed chars +} + +} // namespace c4 + +#endif // STRING_VIEW_AVAILABLE + +#endif // _C4_STD_STRING_VIEW_HPP_ + + +// (end src/c4/std/string_view.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/std/span.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_STD_SPAN_HPP_ +#define _C4_STD_SPAN_HPP_ + +/** @file span.hpp */ + +#ifndef C4CORE_SINGLE_HEADER +// amalgamate: removed include of +// c4/language.hpp +//#include "c4/language.hpp" +#if !defined(C4_LANGUAGE_HPP_) && !defined(_C4_LANGUAGE_HPP_) +#error "amalgamate: file c4/language.hpp must have been included at this point" +#endif /* C4_LANGUAGE_HPP_ */ + +#endif + + +#if (C4_CPP >= 20) || defined(__DOXYGEN__) + +#ifndef C4CORE_SINGLE_HEADER +// amalgamate: removed include of +// c4/substr.hpp +//#include "c4/substr.hpp" +#if !defined(C4_SUBSTR_HPP_) && !defined(_C4_SUBSTR_HPP_) +#error "amalgamate: file c4/substr.hpp must have been included at this point" +#endif /* C4_SUBSTR_HPP_ */ + +#endif + +//included above: +//#include + + +namespace c4 { + +template struct is_string; +template struct is_writeable_string; + +// mark std::span as a string type +template<> struct is_string> : public std::true_type {}; +template<> struct is_string> : public std::true_type {}; + +// mark std::span as a string type +template<> struct is_string> : public std::true_type {}; +template<> struct is_string> : public std::true_type {}; +template<> struct is_writeable_string> : public std::true_type {}; +template<> struct is_writeable_string> : public std::true_type {}; + + +//----------------------------------------------------------------------------- + +/** create a csubstr from an existing std::span */ +C4_ALWAYS_INLINE c4::csubstr to_csubstr(std::span s) noexcept +{ + return c4::csubstr(s.data(), s.size()); +} + +/** create a csubstr from an existing std::span */ +C4_ALWAYS_INLINE c4::csubstr to_csubstr(std::span s) noexcept +{ + return c4::csubstr(s.data(), s.size()); +} +/** create a substr from an existing std::span */ +C4_ALWAYS_INLINE c4::substr to_substr(std::span s) noexcept +{ + return c4::substr(s.data(), s.size()); +} + + +//----------------------------------------------------------------------------- + +C4_ALWAYS_INLINE bool operator== (c4::csubstr ss, std::span s) { return ss.compare(s.data(), s.size()) == 0; } +C4_ALWAYS_INLINE bool operator!= (c4::csubstr ss, std::span s) { return ss.compare(s.data(), s.size()) != 0; } +C4_ALWAYS_INLINE bool operator>= (c4::csubstr ss, std::span s) { return ss.compare(s.data(), s.size()) >= 0; } +C4_ALWAYS_INLINE bool operator> (c4::csubstr ss, std::span s) { return ss.compare(s.data(), s.size()) > 0; } +C4_ALWAYS_INLINE bool operator<= (c4::csubstr ss, std::span s) { return ss.compare(s.data(), s.size()) <= 0; } +C4_ALWAYS_INLINE bool operator< (c4::csubstr ss, std::span s) { return ss.compare(s.data(), s.size()) < 0; } + +C4_ALWAYS_INLINE bool operator== (std::span s, c4::csubstr ss) { return ss.compare(s.data(), s.size()) == 0; } +C4_ALWAYS_INLINE bool operator!= (std::span s, c4::csubstr ss) { return ss.compare(s.data(), s.size()) != 0; } +C4_ALWAYS_INLINE bool operator<= (std::span s, c4::csubstr ss) { return ss.compare(s.data(), s.size()) >= 0; } +C4_ALWAYS_INLINE bool operator< (std::span s, c4::csubstr ss) { return ss.compare(s.data(), s.size()) > 0; } +C4_ALWAYS_INLINE bool operator>= (std::span s, c4::csubstr ss) { return ss.compare(s.data(), s.size()) <= 0; } +C4_ALWAYS_INLINE bool operator> (std::span s, c4::csubstr ss) { return ss.compare(s.data(), s.size()) < 0; } + + +C4_ALWAYS_INLINE bool operator== (c4::csubstr ss, std::span s) { return ss.compare(s.data(), s.size()) == 0; } +C4_ALWAYS_INLINE bool operator!= (c4::csubstr ss, std::span s) { return ss.compare(s.data(), s.size()) != 0; } +C4_ALWAYS_INLINE bool operator>= (c4::csubstr ss, std::span s) { return ss.compare(s.data(), s.size()) >= 0; } +C4_ALWAYS_INLINE bool operator> (c4::csubstr ss, std::span s) { return ss.compare(s.data(), s.size()) > 0; } +C4_ALWAYS_INLINE bool operator<= (c4::csubstr ss, std::span s) { return ss.compare(s.data(), s.size()) <= 0; } +C4_ALWAYS_INLINE bool operator< (c4::csubstr ss, std::span s) { return ss.compare(s.data(), s.size()) < 0; } + +C4_ALWAYS_INLINE bool operator== (std::span s, c4::csubstr ss) { return ss.compare(s.data(), s.size()) == 0; } +C4_ALWAYS_INLINE bool operator!= (std::span s, c4::csubstr ss) { return ss.compare(s.data(), s.size()) != 0; } +C4_ALWAYS_INLINE bool operator<= (std::span s, c4::csubstr ss) { return ss.compare(s.data(), s.size()) >= 0; } +C4_ALWAYS_INLINE bool operator< (std::span s, c4::csubstr ss) { return ss.compare(s.data(), s.size()) > 0; } +C4_ALWAYS_INLINE bool operator>= (std::span s, c4::csubstr ss) { return ss.compare(s.data(), s.size()) <= 0; } +C4_ALWAYS_INLINE bool operator> (std::span s, c4::csubstr ss) { return ss.compare(s.data(), s.size()) < 0; } + + +//----------------------------------------------------------------------------- + +/** copy a std::span to a writeable substr */ +C4_ALWAYS_INLINE size_t to_chars(c4::substr buf, std::span s) +{ + size_t sz = s.size(); + size_t len = buf.len < sz ? buf.len : sz; + buf.copy_from(csubstr(s.data(), len)); // copy only available chars + return sz; // return the number of needed chars +} + +/** copy a std::span to a writeable substr */ +inline size_t to_chars(c4::substr buf, std::span s) +{ + size_t sz = s.size(); + size_t len = buf.len < sz ? buf.len : sz; + buf.copy_from(csubstr(s.data(), len)); // copy only available chars + return sz; // return the number of needed chars +} + +/** copy a csubstr to an existing std::span */ +inline bool from_chars(c4::csubstr buf, std::span * s) +{ + if(buf.len <= s->size()) + { + substr(s->data(), buf.len).copy_from(buf); + *s = s->first(buf.len); + return true; + } + return false; +} + +} // namespace c4 + +#endif // SPAN_AVAILABLE + +#endif // _C4_STD_SPAN_HPP_ + + +// (end src/c4/std/span.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/std/vector.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_STD_VECTOR_HPP_ +#define _C4_STD_VECTOR_HPP_ + +/** @file vector.hpp provides conversion and comparison facilities + * from/between std::vector to c4::substr and c4::csubstr. + * @todo add to_span() and friends + */ + +#ifndef C4CORE_SINGLE_HEADER +// amalgamate: removed include of +// c4/substr.hpp +//#include "c4/substr.hpp" +#if !defined(C4_SUBSTR_HPP_) && !defined(_C4_SUBSTR_HPP_) +#error "amalgamate: file c4/substr.hpp must have been included at this point" +#endif /* C4_SUBSTR_HPP_ */ + +#endif + +#if defined(__GNUC__) && (__GNUC__ >= 6) +C4_SUPPRESS_WARNING_GCC_WITH_PUSH("-Wnull-dereference") +#endif +#include +#if defined(__GNUC__) && (__GNUC__ >= 6) +C4_SUPPRESS_WARNING_GCC_POP +#endif + +namespace c4 { + +// mark std::string as a string type +template struct is_string; +template struct is_string> : public std::true_type {}; +template struct is_string> : public std::true_type {}; + +// mark std::string as a writeable string type +template struct is_writeable_string; +template struct is_writeable_string> : public std::true_type {}; + + +//----------------------------------------------------------------------------- + +/** get a substr (writeable string view) of an existing std::vector */ +template +c4::substr to_substr(std::vector &vec) +{ + char *data = vec.empty() ? nullptr : vec.data(); // data() may or may not return a null pointer. + return c4::substr(data, vec.size()); +} + +/** get a csubstr (read-only string) view of an existing std::vector */ +template +c4::csubstr to_csubstr(std::vector const& vec) +{ + const char *data = vec.empty() ? nullptr : vec.data(); // data() may or may not return a null pointer. + return c4::csubstr(data, vec.size()); +} + + +//----------------------------------------------------------------------------- +// comparisons between substrings and std::vector + +template C4_ALWAYS_INLINE bool operator!= (c4::csubstr ss, std::vector const& s) { return ss != to_csubstr(s); } +template C4_ALWAYS_INLINE bool operator== (c4::csubstr ss, std::vector const& s) { return ss == to_csubstr(s); } +template C4_ALWAYS_INLINE bool operator>= (c4::csubstr ss, std::vector const& s) { return ss >= to_csubstr(s); } +template C4_ALWAYS_INLINE bool operator> (c4::csubstr ss, std::vector const& s) { return ss > to_csubstr(s); } +template C4_ALWAYS_INLINE bool operator<= (c4::csubstr ss, std::vector const& s) { return ss <= to_csubstr(s); } +template C4_ALWAYS_INLINE bool operator< (c4::csubstr ss, std::vector const& s) { return ss < to_csubstr(s); } + +template C4_ALWAYS_INLINE bool operator!= (std::vector const& s, c4::csubstr ss) { return ss != to_csubstr(s); } +template C4_ALWAYS_INLINE bool operator== (std::vector const& s, c4::csubstr ss) { return ss == to_csubstr(s); } +template C4_ALWAYS_INLINE bool operator>= (std::vector const& s, c4::csubstr ss) { return ss <= to_csubstr(s); } +template C4_ALWAYS_INLINE bool operator> (std::vector const& s, c4::csubstr ss) { return ss < to_csubstr(s); } +template C4_ALWAYS_INLINE bool operator<= (std::vector const& s, c4::csubstr ss) { return ss >= to_csubstr(s); } +template C4_ALWAYS_INLINE bool operator< (std::vector const& s, c4::csubstr ss) { return ss > to_csubstr(s); } + + +//----------------------------------------------------------------------------- + +/** copy a std::vector to a substr */ +template +inline size_t to_chars(c4::substr buf, std::vector const& s) +{ + size_t sz = s.size(); + size_t len = buf.len < sz ? buf.len : sz; + buf.copy_from(csubstr(s.data(), len)); // copy only available chars + return sz; // return the number of needed chars +} + +/** copy a csubstr to an existing std::vector */ +template +inline bool from_chars(c4::csubstr buf, std::vector * s) +{ + s->resize(buf.len); + substr(s->data(), buf.len).copy_from(buf); + return true; +} + +} // namespace c4 + +#endif // _C4_STD_VECTOR_HPP_ + + +// (end src/c4/std/vector.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/std/tuple.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_STD_TUPLE_HPP_ +#define _C4_STD_TUPLE_HPP_ + +/** @file tuple.hpp */ + +#ifndef C4CORE_SINGLE_HEADER +// amalgamate: removed include of +// c4/format.hpp +//#include "c4/format.hpp" +#if !defined(C4_FORMAT_HPP_) && !defined(_C4_FORMAT_HPP_) +#error "amalgamate: file c4/format.hpp must have been included at this point" +#endif /* C4_FORMAT_HPP_ */ + +#endif + +#include + +/** this is a work in progress */ +#undef C4_TUPLE_TO_CHARS + +namespace c4 { + +#ifdef C4_TUPLE_TO_CHARS +namespace detail { + +template< size_t Curr, class... Types > +struct tuple_helper +{ + static size_t do_cat(substr buf, std::tuple< Types... > const& tp) + { + size_t num = to_chars(buf, std::get(tp)); + buf = buf.len >= num ? buf.sub(num) : substr{}; + num += tuple_helper< Curr+1, Types... >::do_cat(buf, tp); + return num; + } + + static size_t do_uncat(csubstr buf, std::tuple< Types... > & tp) + { + size_t num = from_str_trim(buf, &std::get(tp)); + if(num == csubstr::npos) return csubstr::npos; + buf = buf.len >= num ? buf.sub(num) : substr{}; + num += tuple_helper< Curr+1, Types... >::do_uncat(buf, tp); + return num; + } + + template< class Sep > + static size_t do_catsep_more(substr buf, Sep const& sep, std::tuple< Types... > const& tp) + { + size_t ret = to_chars(buf, sep), num = ret; + buf = buf.len >= ret ? buf.sub(ret) : substr{}; + ret = to_chars(buf, std::get(tp)); + num += ret; + buf = buf.len >= ret ? buf.sub(ret) : substr{}; + ret = tuple_helper< Curr+1, Types... >::do_catsep_more(buf, sep, tp); + num += ret; + return num; + } + + template< class Sep > + static size_t do_uncatsep_more(csubstr buf, Sep & sep, std::tuple< Types... > & tp) + { + size_t ret = from_str_trim(buf, &sep), num = ret; + if(ret == csubstr::npos) return csubstr::npos; + buf = buf.len >= ret ? buf.sub(ret) : substr{}; + ret = from_str_trim(buf, &std::get(tp)); + if(ret == csubstr::npos) return csubstr::npos; + num += ret; + buf = buf.len >= ret ? buf.sub(ret) : substr{}; + ret = tuple_helper< Curr+1, Types... >::do_uncatsep_more(buf, sep, tp); + if(ret == csubstr::npos) return csubstr::npos; + num += ret; + return num; + } + + static size_t do_format(substr buf, csubstr fmt, std::tuple< Types... > const& tp) + { + auto pos = fmt.find("{}"); + if(pos != csubstr::npos) + { + size_t num = to_chars(buf, fmt.sub(0, pos)); + size_t out = num; + buf = buf.len >= num ? buf.sub(num) : substr{}; + num = to_chars(buf, std::get(tp)); + out += num; + buf = buf.len >= num ? buf.sub(num) : substr{}; + num = tuple_helper< Curr+1, Types... >::do_format(buf, fmt.sub(pos + 2), tp); + out += num; + return out; + } + else + { + return format(buf, fmt); + } + } + + static size_t do_unformat(csubstr buf, csubstr fmt, std::tuple< Types... > & tp) + { + auto pos = fmt.find("{}"); + if(pos != csubstr::npos) + { + size_t num = pos; + size_t out = num; + buf = buf.len >= num ? buf.sub(num) : substr{}; + num = from_str_trim(buf, &std::get(tp)); + out += num; + buf = buf.len >= num ? buf.sub(num) : substr{}; + num = tuple_helper< Curr+1, Types... >::do_unformat(buf, fmt.sub(pos + 2), tp); + out += num; + return out; + } + else + { + return tuple_helper< sizeof...(Types), Types... >::do_unformat(buf, fmt, tp); + } + } + +}; + +/** @todo VS compilation fails for this class */ +template< class... Types > +struct tuple_helper< sizeof...(Types), Types... > +{ + static size_t do_cat(substr /*buf*/, std::tuple const& /*tp*/) { return 0; } + static size_t do_uncat(csubstr /*buf*/, std::tuple & /*tp*/) { return 0; } + + template< class Sep > static size_t do_catsep_more(substr /*buf*/, Sep const& /*sep*/, std::tuple const& /*tp*/) { return 0; } + template< class Sep > static size_t do_uncatsep_more(csubstr /*buf*/, Sep & /*sep*/, std::tuple & /*tp*/) { return 0; } + + static size_t do_format(substr buf, csubstr fmt, std::tuple const& /*tp*/) + { + return to_chars(buf, fmt); + } + + static size_t do_unformat(csubstr buf, csubstr fmt, std::tuple const& /*tp*/) + { + return 0; + } +}; + +} // namespace detail + +template< class... Types > +inline size_t cat(substr buf, std::tuple< Types... > const& tp) +{ + return detail::tuple_helper< 0, Types... >::do_cat(buf, tp); +} + +template< class... Types > +inline size_t uncat(csubstr buf, std::tuple< Types... > & tp) +{ + return detail::tuple_helper< 0, Types... >::do_uncat(buf, tp); +} + +template< class Sep, class... Types > +inline size_t catsep(substr buf, Sep const& sep, std::tuple< Types... > const& tp) +{ + size_t num = to_chars(buf, std::cref(std::get<0>(tp))); + buf = buf.len >= num ? buf.sub(num) : substr{}; + num += detail::tuple_helper< 1, Types... >::do_catsep_more(buf, sep, tp); + return num; +} + +template< class Sep, class... Types > +inline size_t uncatsep(csubstr buf, Sep & sep, std::tuple< Types... > & tp) +{ + size_t ret = from_str_trim(buf, &std::get<0>(tp)), num = ret; + if(ret == csubstr::npos) return csubstr::npos; + buf = buf.len >= ret ? buf.sub(ret) : substr{}; + ret = detail::tuple_helper< 1, Types... >::do_uncatsep_more(buf, sep, tp); + if(ret == csubstr::npos) return csubstr::npos; + num += ret; + return num; +} + +template< class... Types > +inline size_t format(substr buf, csubstr fmt, std::tuple< Types... > const& tp) +{ + return detail::tuple_helper< 0, Types... >::do_format(buf, fmt, tp); +} + +template< class... Types > +inline size_t unformat(csubstr buf, csubstr fmt, std::tuple< Types... > & tp) +{ + return detail::tuple_helper< 0, Types... >::do_unformat(buf, fmt, tp); +} +#endif // C4_TUPLE_TO_CHARS + +} // namespace c4 + +#endif /* _C4_STD_TUPLE_HPP_ */ + + +// (end src/c4/std/tuple.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/std/std.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_STD_STD_HPP_ +#define _C4_STD_STD_HPP_ + +/** @file std.hpp includes all c4-std interop files */ + +// amalgamate: removed include of +// c4/std/vector.hpp +//#include "c4/std/vector.hpp" +#if !defined(C4_STD_VECTOR_HPP_) && !defined(_C4_STD_VECTOR_HPP_) +#error "amalgamate: file c4/std/vector.hpp must have been included at this point" +#endif /* C4_STD_VECTOR_HPP_ */ + +// amalgamate: removed include of +// c4/std/string.hpp +//#include "c4/std/string.hpp" +#if !defined(C4_STD_STRING_HPP_) && !defined(_C4_STD_STRING_HPP_) +#error "amalgamate: file c4/std/string.hpp must have been included at this point" +#endif /* C4_STD_STRING_HPP_ */ + +// amalgamate: removed include of +// c4/std/span.hpp +//#include "c4/std/span.hpp" +#if !defined(C4_STD_SPAN_HPP_) && !defined(_C4_STD_SPAN_HPP_) +#error "amalgamate: file c4/std/span.hpp must have been included at this point" +#endif /* C4_STD_SPAN_HPP_ */ + +// amalgamate: removed include of +// c4/std/string_view.hpp +//#include "c4/std/string_view.hpp" +#if !defined(C4_STD_STRING_VIEW_HPP_) && !defined(_C4_STD_STRING_VIEW_HPP_) +#error "amalgamate: file c4/std/string_view.hpp must have been included at this point" +#endif /* C4_STD_STRING_VIEW_HPP_ */ + +// amalgamate: removed include of +// c4/std/tuple.hpp +//#include "c4/std/tuple.hpp" +#if !defined(C4_STD_TUPLE_HPP_) && !defined(_C4_STD_TUPLE_HPP_) +#error "amalgamate: file c4/std/tuple.hpp must have been included at this point" +#endif /* C4_STD_TUPLE_HPP_ */ + + +#endif // _C4_STD_STD_HPP_ + + +// (end src/c4/std/std.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/ext/rng/rng.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +/* Copyright (c) 2018 Arvid Gerstmann. + * + * https://arvid.io/2018/07/02/better-cxx-prng/ + * + * This code is licensed under MIT license. */ +#ifndef AG_RANDOM_H +#define AG_RANDOM_H + +//included above: +//#include +#include + + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wold-style-cast" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wold-style-cast" +#endif + +namespace c4 { +namespace rng { + +class splitmix +{ +public: + using result_type = uint32_t; + static constexpr result_type (min)() { return 0; } + static constexpr result_type (max)() { return UINT32_MAX; } + friend bool operator==(splitmix const &, splitmix const &); + friend bool operator!=(splitmix const &, splitmix const &); + + splitmix() : m_seed(1) {} + explicit splitmix(uint64_t s) : m_seed(s) {} + explicit splitmix(std::random_device &rd) + { + seed(rd); + } + + void seed(uint64_t s) { m_seed = s; } + void seed(std::random_device &rd) + { + m_seed = uint64_t(rd()) << 31 | uint64_t(rd()); + } + + result_type operator()() + { + uint64_t z = (m_seed += UINT64_C(0x9E3779B97F4A7C15)); + z = (z ^ (z >> 30)) * UINT64_C(0xBF58476D1CE4E5B9); + z = (z ^ (z >> 27)) * UINT64_C(0x94D049BB133111EB); + return result_type((z ^ (z >> 31)) >> 31); + } + + void discard(unsigned long long n) + { + for (unsigned long long i = 0; i < n; ++i) + operator()(); + } + +private: + uint64_t m_seed; +}; + +inline bool operator==(splitmix const &lhs, splitmix const &rhs) +{ + return lhs.m_seed == rhs.m_seed; +} +inline bool operator!=(splitmix const &lhs, splitmix const &rhs) +{ + return lhs.m_seed != rhs.m_seed; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +class xorshift +{ +public: + using result_type = uint32_t; + static constexpr result_type (min)() { return 0; } + static constexpr result_type (max)() { return UINT32_MAX; } + friend bool operator==(xorshift const &, xorshift const &); + friend bool operator!=(xorshift const &, xorshift const &); + + xorshift() : m_seed(0xc1f651c67c62c6e0ull) {} + explicit xorshift(std::random_device &rd) + { + seed(rd); + } + + void seed(uint64_t s) { m_seed = s; } + void seed(std::random_device &rd) + { + m_seed = uint64_t(rd()) << 31 | uint64_t(rd()); + } + + result_type operator()() + { + uint64_t result = m_seed * 0xd989bcacc137dcd5ull; + m_seed ^= m_seed >> 11; + m_seed ^= m_seed << 31; + m_seed ^= m_seed >> 18; + return uint32_t(result >> 32ull); + } + + void discard(unsigned long long n) + { + for (unsigned long long i = 0; i < n; ++i) + operator()(); + } + +private: + uint64_t m_seed; +}; + +inline bool operator==(xorshift const &lhs, xorshift const &rhs) +{ + return lhs.m_seed == rhs.m_seed; +} +inline bool operator!=(xorshift const &lhs, xorshift const &rhs) +{ + return lhs.m_seed != rhs.m_seed; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +class pcg +{ +public: + using result_type = uint32_t; + static constexpr result_type (min)() { return 0; } + static constexpr result_type (max)() { return UINT32_MAX; } + friend bool operator==(pcg const &, pcg const &); + friend bool operator!=(pcg const &, pcg const &); + + pcg() + : m_state(0x853c49e6748fea9bULL) + , m_inc(0xda3e39cb94b95bdbULL) + {} + explicit pcg(uint64_t s) { m_state = s; m_inc = m_state << 1; } + explicit pcg(std::random_device &rd) + { + seed(rd); + } + + void seed(uint64_t s) { m_state = s; } + void seed(std::random_device &rd) + { + uint64_t s0 = uint64_t(rd()) << 31 | uint64_t(rd()); + uint64_t s1 = uint64_t(rd()) << 31 | uint64_t(rd()); + + m_state = 0; + m_inc = (s1 << 1) | 1; + (void)operator()(); + m_state += s0; + (void)operator()(); + } + + result_type operator()() + { + uint64_t oldstate = m_state; + m_state = oldstate * 6364136223846793005ULL + m_inc; + uint32_t xorshifted = uint32_t(((oldstate >> 18u) ^ oldstate) >> 27u); + //int rot = oldstate >> 59u; // the original. error? + int64_t rot = (int64_t)oldstate >> 59u; // error? + return (xorshifted >> rot) | (xorshifted << ((uint64_t)(-rot) & 31)); + } + + void discard(unsigned long long n) + { + for (unsigned long long i = 0; i < n; ++i) + operator()(); + } + +private: + uint64_t m_state; + uint64_t m_inc; +}; + +inline bool operator==(pcg const &lhs, pcg const &rhs) +{ + return lhs.m_state == rhs.m_state + && lhs.m_inc == rhs.m_inc; +} +inline bool operator!=(pcg const &lhs, pcg const &rhs) +{ + return lhs.m_state != rhs.m_state + || lhs.m_inc != rhs.m_inc; +} + +} // namespace rng +} // namespace c4 + +#ifdef __clang__ +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#endif + +#endif /* AG_RANDOM_H */ + + +// (end src/c4/ext/rng/rng.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/ext/sg14/inplace_function.h +//-------------------------------------------------------------------------------- +//******************************************************************************** + +/* + * 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. + */ +#ifndef _C4_EXT_SG14_INPLACE_FUNCTION_H_ +#define _C4_EXT_SG14_INPLACE_FUNCTION_H_ + +//included above: +//#include +//included above: +//#include +#include +//included above: +//#include + +namespace stdext { + +namespace inplace_function_detail { + +static constexpr size_t InplaceFunctionDefaultCapacity = 32; + +#if defined(__GLIBCXX__) // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61458 +template +union aligned_storage_helper { + struct double1 { double a; }; + struct double4 { double a[4]; }; + template using maybe = typename std::conditional<(Cap >= sizeof(T)), T, char>::type; + char real_data[Cap]; + maybe a; + maybe b; + maybe c; + maybe d; + maybe e; + maybe f; + maybe g; + maybe h; +}; + +template>::value> +struct aligned_storage { + using type = typename std::aligned_storage::type; +}; +#else +using std::aligned_storage; +#endif + +template struct wrapper +{ + using type = T; +}; + +template struct vtable +{ + using storage_ptr_t = void*; + + using invoke_ptr_t = R(*)(storage_ptr_t, Args&&...); + using process_ptr_t = void(*)(storage_ptr_t, storage_ptr_t); + using destructor_ptr_t = void(*)(storage_ptr_t); + + const invoke_ptr_t invoke_ptr; + const process_ptr_t copy_ptr; + const process_ptr_t move_ptr; + const destructor_ptr_t destructor_ptr; + + explicit constexpr vtable() noexcept : + invoke_ptr{ [](storage_ptr_t, Args&&...) -> R + { + #if (defined(_MSC_VER) && (defined(_CPPUNWIND) && (__CPPUNWIND == 1))) \ + || \ + (defined(__EXCEPTIONS) || defined(__cpp_exceptions)) + throw std::bad_function_call(); + #else + std::abort(); + #endif + } + }, + copy_ptr{ [](storage_ptr_t, storage_ptr_t) noexcept -> void {} }, + move_ptr{ [](storage_ptr_t, storage_ptr_t) noexcept -> void {} }, + destructor_ptr{ [](storage_ptr_t) noexcept -> void {} } + {} + + template explicit constexpr vtable(wrapper) noexcept : + invoke_ptr{ [](storage_ptr_t storage_ptr, Args&&... args) + noexcept(noexcept(std::declval()(args...))) -> R + { return (*static_cast(storage_ptr))( + std::forward(args)... + ); } + }, + copy_ptr{ [](storage_ptr_t dst_ptr, storage_ptr_t src_ptr) + noexcept(std::is_nothrow_copy_constructible::value) -> void + { new (dst_ptr) C{ (*static_cast(src_ptr)) }; } + }, + move_ptr{ [](storage_ptr_t dst_ptr, storage_ptr_t src_ptr) + noexcept(std::is_nothrow_move_constructible::value) -> void + { new (dst_ptr) C{ std::move(*static_cast(src_ptr)) }; } + }, + destructor_ptr{ [](storage_ptr_t storage_ptr) + noexcept -> void + { static_cast(storage_ptr)->~C(); } + } + {} + + vtable(const vtable&) = delete; + vtable(vtable&&) = delete; + + vtable& operator= (const vtable&) = delete; + vtable& operator= (vtable&&) = delete; + + ~vtable() = default; +}; + +template +struct is_valid_inplace_dst : std::true_type +{ + static_assert(DstCap >= SrcCap, + "Can't squeeze larger inplace_function into a smaller one" + ); + + static_assert(DstAlign % SrcAlign == 0, + "Incompatible inplace_function alignments" + ); +}; + +} // namespace inplace_function_detail + +template< + typename Signature, + size_t Capacity = inplace_function_detail::InplaceFunctionDefaultCapacity, + size_t Alignment = std::alignment_of::type>::value +> +class inplace_function; // unspecified + +template< + typename R, + typename... Args, + size_t Capacity, + size_t Alignment +> +class inplace_function +{ + static const constexpr inplace_function_detail::vtable empty_vtable{}; +public: + using capacity = std::integral_constant; + using alignment = std::integral_constant; + + using storage_t = typename inplace_function_detail::aligned_storage::type; + using vtable_t = inplace_function_detail::vtable; + using vtable_ptr_t = const vtable_t*; + + template friend class inplace_function; + + inplace_function() noexcept : + vtable_ptr_{std::addressof(empty_vtable)} + {} + + template< + typename T, + typename C = typename std::decay::type, + typename = typename std::enable_if< + !(std::is_same::value + || std::is_convertible::value) + >::type + > + inplace_function(T&& closure) + { +#if __cplusplus >= 201703L + static_assert(std::is_invocable_r::value, + "inplace_function cannot be constructed from non-callable type" + ); +#endif + static_assert(std::is_copy_constructible::value, + "inplace_function cannot be constructed from non-copyable type" + ); + + static_assert(sizeof(C) <= Capacity, + "inplace_function cannot be constructed from object with this (large) size" + ); + + static_assert(Alignment % std::alignment_of::value == 0, + "inplace_function cannot be constructed from object with this (large) alignment" + ); + + static const vtable_t vt{inplace_function_detail::wrapper{}}; + vtable_ptr_ = std::addressof(vt); + + new (std::addressof(storage_)) C{std::forward(closure)}; + } + + inplace_function(std::nullptr_t) noexcept : + vtable_ptr_{std::addressof(empty_vtable)} + {} + + inplace_function(const inplace_function& other) : + vtable_ptr_{other.vtable_ptr_} + { + vtable_ptr_->copy_ptr( + std::addressof(storage_), + std::addressof(other.storage_) + ); + } + + inplace_function(inplace_function&& other) : + vtable_ptr_{other.vtable_ptr_} + { + vtable_ptr_->move_ptr( + std::addressof(storage_), + std::addressof(other.storage_) + ); + } + + inplace_function& operator= (std::nullptr_t) noexcept + { + vtable_ptr_->destructor_ptr(std::addressof(storage_)); + vtable_ptr_ = std::addressof(empty_vtable); + return *this; + } + + inplace_function& operator= (const inplace_function& other) + { + if(this != std::addressof(other)) + { + vtable_ptr_->destructor_ptr(std::addressof(storage_)); + + vtable_ptr_ = other.vtable_ptr_; + vtable_ptr_->copy_ptr( + std::addressof(storage_), + std::addressof(other.storage_) + ); + } + return *this; + } + + inplace_function& operator= (inplace_function&& other) + { + if(this != std::addressof(other)) + { + vtable_ptr_->destructor_ptr(std::addressof(storage_)); + + vtable_ptr_ = other.vtable_ptr_; + vtable_ptr_->move_ptr( + std::addressof(storage_), + std::addressof(other.storage_) + ); + } + return *this; + } + + ~inplace_function() + { + vtable_ptr_->destructor_ptr(std::addressof(storage_)); + } + + R operator() (Args... args) const + { + return vtable_ptr_->invoke_ptr( + std::addressof(storage_), + std::forward(args)... + ); + } + + constexpr bool operator== (std::nullptr_t) const noexcept + { + return !operator bool(); + } + + constexpr bool operator!= (std::nullptr_t) const noexcept + { + return operator bool(); + } + + explicit constexpr operator bool() const noexcept + { + return vtable_ptr_ != std::addressof(empty_vtable); + } + + template + operator inplace_function() const& + { + static_assert(inplace_function_detail::is_valid_inplace_dst< + Cap, Align, Capacity, Alignment + >::value, "conversion not allowed"); + + return {vtable_ptr_, vtable_ptr_->copy_ptr, std::addressof(storage_)}; + } + + template + operator inplace_function() && + { + static_assert(inplace_function_detail::is_valid_inplace_dst< + Cap, Align, Capacity, Alignment + >::value, "conversion not allowed"); + + return {vtable_ptr_, vtable_ptr_->move_ptr, std::addressof(storage_)}; + } + + void swap(inplace_function& other) + { + if (this == std::addressof(other)) return; + + storage_t tmp; + vtable_ptr_->move_ptr( + std::addressof(tmp), + std::addressof(storage_) + ); + vtable_ptr_->destructor_ptr(std::addressof(storage_)); + + other.vtable_ptr_->move_ptr( + std::addressof(storage_), + std::addressof(other.storage_) + ); + other.vtable_ptr_->destructor_ptr(std::addressof(other.storage_)); + + vtable_ptr_->move_ptr( + std::addressof(other.storage_), + std::addressof(tmp) + ); + vtable_ptr_->destructor_ptr(std::addressof(tmp)); + + std::swap(vtable_ptr_, other.vtable_ptr_); + } + +private: + vtable_ptr_t vtable_ptr_; + mutable storage_t storage_; + + inplace_function( + vtable_ptr_t vtable_ptr, + typename vtable_t::process_ptr_t process_ptr, + typename vtable_t::storage_ptr_t storage_ptr + ) : vtable_ptr_{vtable_ptr} + { + process_ptr(std::addressof(storage_), storage_ptr); + } +}; + +} // namespace stdext + +#endif /* _C4_EXT_SG14_INPLACE_FUNCTION_H_ */ + + +// (end src/c4/ext/sg14/inplace_function.h) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/version.cpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifdef C4CORE_SINGLE_HDR_DEFINE_NOW +// amalgamate: removed include of +// c4/version.hpp +//#include "c4/version.hpp" +#if !defined(C4_VERSION_HPP_) && !defined(_C4_VERSION_HPP_) +#error "amalgamate: file c4/version.hpp must have been included at this point" +#endif /* C4_VERSION_HPP_ */ + + +namespace c4 { + +const char* version() +{ + return C4CORE_VERSION; +} + +int version_major() +{ + return C4CORE_VERSION_MAJOR; +} + +int version_minor() +{ + return C4CORE_VERSION_MINOR; +} + +int version_patch() +{ + return C4CORE_VERSION_PATCH; +} + +} // namespace c4 + +#endif /* C4CORE_SINGLE_HDR_DEFINE_NOW */ + + +// (end src/c4/version.cpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/language.cpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifdef C4CORE_SINGLE_HDR_DEFINE_NOW +// amalgamate: removed include of +// c4/language.hpp +//#include "c4/language.hpp" +#if !defined(C4_LANGUAGE_HPP_) && !defined(_C4_LANGUAGE_HPP_) +#error "amalgamate: file c4/language.hpp must have been included at this point" +#endif /* C4_LANGUAGE_HPP_ */ + + +namespace c4 { +namespace detail { + +#ifndef __GNUC__ +void use_char_pointer(char const volatile* v) +{ + C4_UNUSED(v); +} +#else +// to avoid empty file warning from the linker +C4_MAYBE_UNUSED void foo() {} // NOLINT(misc-use-internal-linkage) +#endif + +} // namespace detail +} // namespace c4 + +#endif /* C4CORE_SINGLE_HDR_DEFINE_NOW */ + + +// (end src/c4/language.cpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/format.cpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifdef C4CORE_SINGLE_HDR_DEFINE_NOW +// amalgamate: removed include of +// c4/format.hpp +//#include "c4/format.hpp" +#if !defined(C4_FORMAT_HPP_) && !defined(_C4_FORMAT_HPP_) +#error "amalgamate: file c4/format.hpp must have been included at this point" +#endif /* C4_FORMAT_HPP_ */ + + +//included above: +//#include // for std::align + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wformat-nonliteral" +# pragma clang diagnostic ignored "-Wold-style-cast" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wformat-nonliteral" +# pragma GCC diagnostic ignored "-Wold-style-cast" +#endif + +namespace c4 { + + +size_t to_chars(substr buf, fmt::const_raw_wrapper r) +{ + void * vptr = buf.str; + size_t space = buf.len; + char * ptr = (char*) std::align(r.alignment, r.len, vptr, space); + if(ptr == nullptr) + { + // if it was not possible to align, return a conservative estimate + // of the required space + return r.alignment + r.len; + } + C4_CHECK(ptr >= buf.begin() && ptr <= buf.end()); + size_t sz = static_cast(ptr - buf.str) + r.len; + if(sz <= buf.len) + { + memcpy(ptr, r.buf, r.len); + } + return sz; +} + + +bool from_chars(csubstr buf, fmt::raw_wrapper *r) +{ + C4_SUPPRESS_WARNING_GCC_WITH_PUSH("-Wcast-qual") + void * vptr = (void*)buf.str; + C4_SUPPRESS_WARNING_GCC_POP + size_t space = buf.len; + char * ptr = (char*) std::align(r->alignment, r->len, vptr, space); + C4_CHECK(ptr != nullptr); + C4_CHECK(ptr >= buf.begin() && ptr <= buf.end()); + C4_SUPPRESS_WARNING_GCC_PUSH + #if defined(__GNUC__) && __GNUC__ > 9 + C4_SUPPRESS_WARNING_GCC("-Wanalyzer-null-argument") + #endif + memcpy(r->buf, ptr, r->len); + C4_SUPPRESS_WARNING_GCC_POP + return true; +} + + +} // namespace c4 + +#ifdef __clang__ +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#endif + +#endif /* C4CORE_SINGLE_HDR_DEFINE_NOW */ + + +// (end src/c4/format.cpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/memory_util.cpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifdef C4CORE_SINGLE_HDR_DEFINE_NOW +// amalgamate: removed include of +// c4/memory_util.hpp +//#include "c4/memory_util.hpp" +#if !defined(C4_MEMORY_UTIL_HPP_) && !defined(_C4_MEMORY_UTIL_HPP_) +#error "amalgamate: file c4/memory_util.hpp must have been included at this point" +#endif /* C4_MEMORY_UTIL_HPP_ */ + +// amalgamate: removed include of +// c4/error.hpp +//#include "c4/error.hpp" +#if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) +#error "amalgamate: file c4/error.hpp must have been included at this point" +#endif /* C4_ERROR_HPP_ */ + + +namespace c4 { + + +/** Fills 'dest' with the first 'pattern_size' bytes at 'pattern', 'num_times'. */ +void mem_repeat(void* dest, void const* pattern, size_t pattern_size, size_t num_times) +{ + C4_ASSERT( ! mem_overlaps(dest, pattern, num_times*pattern_size, pattern_size)); + if(C4_UNLIKELY(num_times == 0)) + return; + char *C4_RESTRICT begin = static_cast(dest); + char *C4_RESTRICT 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; + size_t n2 = n * 2; + while(begin + n2 < end) + { + ::memcpy(begin + n, begin, n); + n = n2; + n2 *= 2u; + } + // copy the missing part + if(begin + n < end) + { + ::memcpy(begin + n, begin, static_cast(end - (begin + n))); + } +} + + +} // namespace c4 + +#endif /* C4CORE_SINGLE_HDR_DEFINE_NOW */ + + +// (end src/c4/memory_util.cpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/char_traits.cpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifdef C4CORE_SINGLE_HDR_DEFINE_NOW +// amalgamate: removed include of +// c4/char_traits.hpp +//#include "c4/char_traits.hpp" +#if !defined(C4_CHAR_TRAITS_HPP_) && !defined(_C4_CHAR_TRAITS_HPP_) +#error "amalgamate: file c4/char_traits.hpp must have been included at this point" +#endif /* C4_CHAR_TRAITS_HPP_ */ + + +namespace c4 { + +constexpr const char char_traits< char >::whitespace_chars[]; +constexpr const size_t char_traits< char >::num_whitespace_chars; +constexpr const wchar_t char_traits< wchar_t >::whitespace_chars[]; +constexpr const size_t char_traits< wchar_t >::num_whitespace_chars; + +} // namespace c4 + +#endif /* C4CORE_SINGLE_HDR_DEFINE_NOW */ + + +// (end src/c4/char_traits.cpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/alloc.cpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifdef C4CORE_SINGLE_HDR_DEFINE_NOW +// amalgamate: removed include of +// c4/alloc.hpp +//#include "c4/alloc.hpp" +#if !defined(C4_ALLOC_HPP_) && !defined(_C4_ALLOC_HPP_) +#error "amalgamate: file c4/alloc.hpp must have been included at this point" +#endif /* C4_ALLOC_HPP_ */ + +// amalgamate: removed include of +// c4/memory_util.hpp +//#include "c4/memory_util.hpp" +#if !defined(C4_MEMORY_UTIL_HPP_) && !defined(_C4_MEMORY_UTIL_HPP_) +#error "amalgamate: file c4/memory_util.hpp must have been included at this point" +#endif /* C4_MEMORY_UTIL_HPP_ */ + + +//included above: +//#include +//included above: +//#include +#if defined(C4_POSIX) || defined(C4_IOS) || defined(C4_MACOS) || defined(C4_ARM) +# include +#endif +#if defined(C4_ARM) +# include +#endif + +namespace c4 { + +C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wold-style-cast") + +namespace detail { + + +#ifdef C4_NO_ALLOC_DEFAULTS +aalloc_pfn s_aalloc = nullptr; +free_pfn s_afree = nullptr; +arealloc_pfn s_arealloc = nullptr; +#else + + +void afree_impl(void *ptr) // NOLINT(misc-use-internal-linkage) +{ +#if defined(C4_WIN) || defined(C4_XBOX) + ::_aligned_free(ptr); +#else + ::free(ptr); +#endif +} + + +void* aalloc_impl(size_t size, size_t alignment) // NOLINT(misc-use-internal-linkage) +{ + // alignment must be nonzero and a power of 2 + C4_CHECK(alignment > 0 && (alignment & (alignment - 1u)) == 0); + // NOTE: alignment needs to be sized in multiples of sizeof(void*) + if(C4_UNLIKELY(alignment < sizeof(void*))) + alignment = sizeof(void*); + static_assert((sizeof(void*) & (sizeof(void*)-1u)) == 0, "sizeof(void*) must be a power of 2"); + C4_CHECK(((alignment & (sizeof(void*) - 1u))) == 0u); + void *mem; +#if defined(C4_WIN) || defined(C4_XBOX) + mem = ::_aligned_malloc(size, alignment); + C4_CHECK(mem != nullptr || size == 0); +#elif defined(C4_POSIX) || defined(C4_IOS) || defined(C4_MACOS) + int ret = ::posix_memalign(&mem, alignment, size); + if(C4_UNLIKELY(ret)) + { + C4_ASSERT(ret != EINVAL); // this was already handled above + if(ret == ENOMEM) + { + C4_ERROR("There was insufficient memory to fulfill the " + "allocation request of %zu bytes (alignment=%lu)", size, size); + } + return nullptr; // LCOV_EXCL_LINE + } +#elif defined(C4_ARM) || defined(C4_ANDROID) + // https://stackoverflow.com/questions/53614538/undefined-reference-to-posix-memalign-in-arm-gcc + // https://electronics.stackexchange.com/questions/467382/e2-studio-undefined-reference-to-posix-memalign/467753 + mem = memalign(alignment, size); + C4_CHECK(mem != nullptr || size == 0); +#else + (void)size; + (void)alignment; + mem = nullptr; + C4_NOT_IMPLEMENTED_MSG("need to implement an aligned allocation for this platform"); +#endif + C4_ASSERT_MSG((uintptr_t(mem) & (alignment-1)) == 0, "address %p is not aligned to %zu boundary", mem, alignment); + return mem; +} + + +C4CORE_EXPORT void* arealloc_impl(void* ptr, size_t oldsz, size_t newsz, size_t alignment) // NOLINT(misc-use-internal-linkage) +{ + /** @todo make this more efficient + * @see https://stackoverflow.com/questions/9078259/does-realloc-keep-the-memory-alignment-of-posix-memalign + * @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; + if(mem_overlaps(ptr, tmp, oldsz, newsz)) + { + ::memmove(tmp, ptr, min); // LCOV_EXCL_LINE + } + else + { + ::memcpy(tmp, ptr, min); + } + afree(ptr); + return tmp; +} + +aalloc_pfn s_aalloc = aalloc_impl; // NOLINT(misc-use-internal-linkage) +afree_pfn s_afree = afree_impl; // NOLINT(misc-use-internal-linkage) +arealloc_pfn s_arealloc = arealloc_impl; // NOLINT(misc-use-internal-linkage) + +#endif // C4_NO_ALLOC_DEFAULTS + +} // namespace detail + + +aalloc_pfn get_aalloc() +{ + return detail::s_aalloc; +} +void set_aalloc(aalloc_pfn fn) +{ + detail::s_aalloc = fn; +} + +afree_pfn get_afree() +{ + return detail::s_afree; +} +void set_afree(afree_pfn fn) +{ + detail::s_afree = fn; +} + +arealloc_pfn get_arealloc() +{ + return detail::s_arealloc; +} +void set_arealloc(arealloc_pfn fn) +{ + detail::s_arealloc = fn; +} + + +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; +} + +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); +} + +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_SUPPRESS_WARNING_GCC_CLANG_POP + +} // namespace c4 + +#endif /* C4CORE_SINGLE_HDR_DEFINE_NOW */ + + +// (end src/c4/alloc.cpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/memory_resource.cpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifdef C4CORE_SINGLE_HDR_DEFINE_NOW +// amalgamate: removed include of +// c4/memory_resource.hpp +//#include "c4/memory_resource.hpp" +#if !defined(C4_MEMORY_RESOURCE_HPP_) && !defined(_C4_MEMORY_RESOURCE_HPP_) +#error "amalgamate: file c4/memory_resource.hpp must have been included at this point" +#endif /* C4_MEMORY_RESOURCE_HPP_ */ + +// amalgamate: removed include of +// c4/memory_util.hpp +//#include "c4/memory_util.hpp" +#if !defined(C4_MEMORY_UTIL_HPP_) && !defined(_C4_MEMORY_UTIL_HPP_) +#error "amalgamate: file c4/memory_util.hpp must have been included at this point" +#endif /* C4_MEMORY_UTIL_HPP_ */ + + +//included above: +//#include +//included above: +//#include +#if defined(C4_POSIX) || defined(C4_IOS) || defined(C4_MACOS) || defined(C4_ARM) +//included above: +//# include +#endif +#if defined(C4_ARM) +//included above: +//# include +#endif + +//included above: +//#include + +namespace c4 { + +C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wold-style-cast") + +MemoryResourceMalloc s_mrmalloc; // NOLINT +thread_local static MemoryResource* s_mrcurr = nullptr; // NOLINT + +void set_memory_resource(MemoryResource* mr) +{ + C4_ASSERT(mr != nullptr); + s_mrcurr = mr; +} + +MemoryResource* get_memory_resource() +{ + if(!s_mrcurr) + s_mrcurr = &s_mrmalloc; + return s_mrcurr; +} + +MemoryResourceMalloc* get_memory_resource_malloc() +{ + return &s_mrmalloc; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +void detail::_MemoryResourceSingleChunk::release() +{ + if(m_mem && m_owner) + { + impl_type::deallocate(m_mem, m_size); + } + m_mem = nullptr; + m_size = 0; + m_owner = false; + m_pos = 0; +} + +void detail::_MemoryResourceSingleChunk::acquire(size_t sz) +{ + clear(); + m_owner = true; + m_mem = (char*) impl_type::allocate(sz, alignof(max_align_t)); + m_size = sz; + m_pos = 0; +} + +void detail::_MemoryResourceSingleChunk::acquire(void *mem, size_t sz) +{ + clear(); + m_owner = false; + m_mem = (char*) mem; + m_size = sz; + m_pos = 0; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +void* MemoryResourceLinear::do_allocate(size_t sz, size_t alignment, void *hint) +{ + C4_UNUSED(hint); + if(sz == 0) return nullptr; + // make sure there's enough room to allocate + if(m_pos + sz > m_size) + { + C4_ERROR("out of memory"); + } + void *mem = m_mem + m_pos; + size_t space = m_size - m_pos; + if(std::align(alignment, sz, mem, space)) + { + C4_ASSERT(m_pos <= m_size); + C4_ASSERT(m_size - m_pos >= space); + m_pos += (m_size - m_pos) - space; + m_pos += sz; + C4_ASSERT(m_pos <= m_size); + } + else + { + C4_ERROR("could not align memory"); + } + return mem; +} + +void MemoryResourceLinear::do_deallocate(void* ptr, size_t sz, size_t alignment) +{ + C4_UNUSED(ptr); + C4_UNUSED(sz); + C4_UNUSED(alignment); + // nothing to do!! +} + +void* MemoryResourceLinear::do_reallocate(void* ptr, size_t oldsz, size_t newsz, size_t alignment) +{ + if(newsz == oldsz) return ptr; + // is ptr the most recently allocated (MRA) block? + char *cptr = (char*)ptr; + bool same_pos = (m_mem + m_pos == cptr + oldsz); + // no need to get more memory when shrinking + if(newsz < oldsz) + { + // if this is the MRA, we can safely shrink the position + if(same_pos) + { + m_pos -= oldsz - newsz; + } + return ptr; + } + // we're growing the block, and it fits in size + else if(same_pos && cptr + newsz <= m_mem + m_size) + { + // if this is the MRA, we can safely shrink the position + m_pos += newsz - oldsz; + return ptr; + } + // we're growing the block or it doesn't fit - + // delegate any of these situations to do_deallocate() + return do_allocate(newsz, alignment, ptr); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** @todo add a free list allocator. A good candidate because of its + * small size is TLSF. + * + * @see https://github.com/mattconte/tlsf + * + * Comparisons: + * + * @see https://www.researchgate.net/publication/262375150_A_Comparative_Study_on_Memory_Allocators_in_Multicore_and_Multithreaded_Applications_-_SBESC_2011_-_Presentation_Slides + * @see http://webkit.sed.hu/blog/20100324/war-allocators-tlsf-action + * @see https://github.com/emeryberger/Malloc-Implementations/tree/master/allocators + * + * */ + +C4_SUPPRESS_WARNING_GCC_CLANG_POP + +} // namespace c4 + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +#ifdef C4_REDEFINE_CPPNEW +#include +void* operator new(size_t size) +{ + auto *mr = ::c4::get_memory_resource(); + return mr->allocate(size); +} +void operator delete(void *p) noexcept +{ + C4_NEVER_REACH(); +} +void operator delete(void *p, size_t size) +{ + auto *mr = ::c4::get_memory_resource(); + mr->deallocate(p, size); +} +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 size) +{ + operator delete(p, size); +} +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 size, std::nothrow_t) +{ + operator delete(p, size); +} +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, size); +} +#endif // C4_REDEFINE_CPPNEW + +#endif /* C4CORE_SINGLE_HDR_DEFINE_NOW */ + + +// (end src/c4/memory_resource.cpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/utf.cpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifdef C4CORE_SINGLE_HDR_DEFINE_NOW +// amalgamate: removed include of +// c4/utf.hpp +//#include "c4/utf.hpp" +#if !defined(C4_UTF_HPP_) && !defined(_C4_UTF_HPP_) +#error "amalgamate: file c4/utf.hpp must have been included at this point" +#endif /* C4_UTF_HPP_ */ + +// amalgamate: removed include of +// c4/charconv.hpp +//#include "c4/charconv.hpp" +#if !defined(C4_CHARCONV_HPP_) && !defined(_C4_CHARCONV_HPP_) +#error "amalgamate: file c4/charconv.hpp must have been included at this point" +#endif /* C4_CHARCONV_HPP_ */ + + +namespace c4 { + +C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wold-style-cast") + +size_t decode_code_point(uint8_t *C4_RESTRICT buf, size_t buflen, const uint32_t code) +{ + C4_ASSERT(buf); + C4_ASSERT(buflen >= 4); + C4_UNUSED(buflen); + if (code <= UINT32_C(0x7f)) + { + buf[0] = (uint8_t)code; + return 1u; + } + else if(code <= UINT32_C(0x7ff)) + { + buf[0] = (uint8_t)(UINT32_C(0xc0) | (code >> 6u)); /* 110xxxxx */ + buf[1] = (uint8_t)(UINT32_C(0x80) | (code & UINT32_C(0x3f))); /* 10xxxxxx */ + return 2u; + } + else if(code <= UINT32_C(0xffff)) + { + buf[0] = (uint8_t)(UINT32_C(0xe0) | ((code >> 12u))); /* 1110xxxx */ // NOLINT + buf[1] = (uint8_t)(UINT32_C(0x80) | ((code >> 6u) & UINT32_C(0x3f))); /* 10xxxxxx */ + buf[2] = (uint8_t)(UINT32_C(0x80) | ((code ) & UINT32_C(0x3f))); /* 10xxxxxx */ // NOLINT + return 3u; + } + else if(code <= UINT32_C(0x10ffff)) + { + buf[0] = (uint8_t)(UINT32_C(0xf0) | ((code >> 18u))); /* 11110xxx */ // NOLINT + buf[1] = (uint8_t)(UINT32_C(0x80) | ((code >> 12u) & UINT32_C(0x3f))); /* 10xxxxxx */ + buf[2] = (uint8_t)(UINT32_C(0x80) | ((code >> 6u) & UINT32_C(0x3f))); /* 10xxxxxx */ + buf[3] = (uint8_t)(UINT32_C(0x80) | ((code ) & UINT32_C(0x3f))); /* 10xxxxxx */ // NOLINT + return 4u; + } + return 0; +} + +substr decode_code_point(substr out, csubstr code_point) +{ + C4_ASSERT(out.len >= 4); + C4_ASSERT(!code_point.begins_with("U+")); + C4_ASSERT(!code_point.begins_with("\\x")); + C4_ASSERT(!code_point.begins_with("\\u")); + C4_ASSERT(!code_point.begins_with("\\U")); + C4_ASSERT(!code_point.begins_with('0')); + C4_ASSERT(code_point.len <= 8); + C4_ASSERT(code_point.len > 0); + uint32_t code_point_val; + C4_CHECK(read_hex(code_point, &code_point_val)); + size_t ret = decode_code_point((uint8_t*)out.str, out.len, code_point_val); + C4_ASSERT(ret <= 4); + return out.first(ret); +} + +size_t first_non_bom(csubstr s) +{ + #define c4check2_(s, c0, c1) ((s).len >= 2) && (((s).str[0] == (c0)) && ((s).str[1] == (c1))) + #define c4check3_(s, c0, c1, c2) ((s).len >= 3) && (((s).str[0] == (c0)) && ((s).str[1] == (c1)) && ((s).str[2] == (c2))) + #define c4check4_(s, c0, c1, c2, c3) ((s).len >= 4) && (((s).str[0] == (c0)) && ((s).str[1] == (c1)) && ((s).str[2] == (c2)) && ((s).str[3] == (c3))) + // see https://en.wikipedia.org/wiki/Byte_order_mark#Byte-order_marks_by_encoding + if(s.len < 2u) + return false; + else if(c4check3_(s, '\xef', '\xbb', '\xbf')) // UTF-8 + return 3u; + else if(c4check4_(s, '\x00', '\x00', '\xfe', '\xff')) // UTF-32BE + return 4u; + else if(c4check4_(s, '\xff', '\xfe', '\x00', '\x00')) // UTF-32LE + return 4u; + else if(c4check2_(s, '\xfe', '\xff')) // UTF-16BE + return 2u; + else if(c4check2_(s, '\xff', '\xfe')) // UTF-16BE + return 2u; + else if(c4check3_(s, '\x2b', '\x2f', '\x76')) // UTF-7 + return 3u; + else if(c4check3_(s, '\xf7', '\x64', '\x4c')) // UTF-1 + return 3u; + else if(c4check4_(s, '\xdd', '\x73', '\x66', '\x73')) // UTF-EBCDIC + return 4u; + else if(c4check3_(s, '\x0e', '\xfe', '\xff')) // SCSU + return 3u; + else if(c4check3_(s, '\xfb', '\xee', '\x28')) // BOCU-1 + return 3u; + else if(c4check4_(s, '\x84', '\x31', '\x95', '\x33')) // GB18030 + return 4u; + return 0u; + #undef c4check2_ + #undef c4check3_ + #undef c4check4_ +} + +substr get_bom(substr s) +{ + return s.first(first_non_bom(s)); +} +csubstr get_bom(csubstr s) +{ + return s.first(first_non_bom(s)); +} +substr skip_bom(substr s) +{ + return s.sub(first_non_bom(s)); +} +csubstr skip_bom(csubstr s) +{ + return s.sub(first_non_bom(s)); +} + +C4_SUPPRESS_WARNING_GCC_CLANG_POP + +} // namespace c4 + +#endif /* C4CORE_SINGLE_HDR_DEFINE_NOW */ + + +// (end src/c4/utf.cpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/base64.cpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifdef C4CORE_SINGLE_HDR_DEFINE_NOW +#ifndef _C4_BASE64_HPP_ +// amalgamate: removed include of +// c4/base64.hpp +//#include "c4/base64.hpp" +#if !defined(C4_BASE64_HPP_) && !defined(_C4_BASE64_HPP_) +#error "amalgamate: file c4/base64.hpp must have been included at this point" +#endif /* C4_BASE64_HPP_ */ + +#endif +#ifndef _C4_ERROR_HPP_ +// amalgamate: removed include of +// c4/error.hpp +//#include "c4/error.hpp" +#if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) +#error "amalgamate: file c4/error.hpp must have been included at this point" +#endif /* C4_ERROR_HPP_ */ + +#endif + +//included above: +//#include +//included above: +//#include +//included above: +//#include + +#define C4_PREFER_BSWAP + +#if defined(C4_PREFER_BSWAP) && C4_LITTLE_ENDIAN && defined(_MSC_VER) +//included above: +//#include +#endif + +C4_SUPPRESS_WARNING_PUSH +C4_SUPPRESS_WARNING_GCC("-Wtype-limits") +C4_SUPPRESS_WARNING_GCC("-Wuseless-cast") +C4_SUPPRESS_WARNING_GCC_CLANG("-Wold-style-cast") +C4_SUPPRESS_WARNING_GCC_CLANG("-Wchar-subscripts") + + +// NOLINTBEGIN(bugprone-signed-char-misuse,cert-str34-c,hicpp-signed-bitwise) + +namespace c4 { + +namespace { + +const char base64_sextet_to_char_[64] = { + /* 0/ 65*/ 'A', /* 1/ 66*/ 'B', /* 2/ 67*/ 'C', /* 3/ 68*/ 'D', + /* 4/ 69*/ 'E', /* 5/ 70*/ 'F', /* 6/ 71*/ 'G', /* 7/ 72*/ 'H', + /* 8/ 73*/ 'I', /* 9/ 74*/ 'J', /*10/ 75*/ 'K', /*11/ 74*/ 'L', + /*12/ 77*/ 'M', /*13/ 78*/ 'N', /*14/ 79*/ 'O', /*15/ 78*/ 'P', + /*16/ 81*/ 'Q', /*17/ 82*/ 'R', /*18/ 83*/ 'S', /*19/ 82*/ 'T', + /*20/ 85*/ 'U', /*21/ 86*/ 'V', /*22/ 87*/ 'W', /*23/ 88*/ 'X', + /*24/ 89*/ 'Y', /*25/ 90*/ 'Z', /*26/ 97*/ 'a', /*27/ 98*/ 'b', + /*28/ 99*/ 'c', /*29/100*/ 'd', /*30/101*/ 'e', /*31/102*/ 'f', + /*32/103*/ 'g', /*33/104*/ 'h', /*34/105*/ 'i', /*35/106*/ 'j', + /*36/107*/ 'k', /*37/108*/ 'l', /*38/109*/ 'm', /*39/110*/ 'n', + /*40/111*/ 'o', /*41/112*/ 'p', /*42/113*/ 'q', /*43/114*/ 'r', + /*44/115*/ 's', /*45/116*/ 't', /*46/117*/ 'u', /*47/118*/ 'v', + /*48/119*/ 'w', /*49/120*/ 'x', /*50/121*/ 'y', /*51/122*/ 'z', + /*52/ 48*/ '0', /*53/ 49*/ '1', /*54/ 50*/ '2', /*55/ 51*/ '3', + /*56/ 52*/ '4', /*57/ 53*/ '5', /*58/ 54*/ '6', /*59/ 55*/ '7', + /*60/ 56*/ '8', /*61/ 57*/ '9', /*62/ 43*/ '+', /*63/ 47*/ '/', +}; + +using dectype = uint8_t; + +#define __ dectype(-1) // undefined below + +// https://www.cs.cmu.edu/~pattis/15-1XX/common/handouts/ascii.html +const dectype base64_char_to_sextet_[128] = { + /* 0 NUL*/ __, /* 1 SOH*/ __, /* 2 STX*/ __, /* 3 ETX*/ __, + /* 4 EOT*/ __, /* 5 ENQ*/ __, /* 6 ACK*/ __, /* 7 BEL*/ __, + /* 8 BS */ __, /* 9 TAB*/ __, /* 10 LF */ __, /* 11 VT */ __, + /* 12 FF */ __, /* 13 CR */ __, /* 14 SO */ __, /* 15 SI */ __, + /* 16 DLE*/ __, /* 17 DC1*/ __, /* 18 DC2*/ __, /* 19 DC3*/ __, + /* 20 DC4*/ __, /* 21 NAK*/ __, /* 22 SYN*/ __, /* 23 ETB*/ __, + /* 24 CAN*/ __, /* 25 EM */ __, /* 26 SUB*/ __, /* 27 ESC*/ __, + /* 28 FS */ __, /* 29 GS */ __, /* 30 RS */ __, /* 31 US */ __, + /* 32 SPC*/ __, /* 33 ! */ __, /* 34 " */ __, /* 35 # */ __, + /* 36 $ */ __, /* 37 % */ __, /* 38 & */ __, /* 39 ' */ __, + /* 40 ( */ __, /* 41 ) */ __, /* 42 * */ __, /* 43 + */ 62, + /* 44 , */ __, /* 45 - */ __, /* 46 . */ __, /* 47 / */ 63, + /* 48 0 */ 52, /* 49 1 */ 53, /* 50 2 */ 54, /* 51 3 */ 55, + /* 52 4 */ 56, /* 53 5 */ 57, /* 54 6 */ 58, /* 55 7 */ 59, + /* 56 8 */ 60, /* 57 9 */ 61, /* 58 : */ __, /* 59 ; */ __, + /* 60 < */ __, /* 61 = */ __, /* 62 > */ __, /* 63 ? */ __, + /* 64 @ */ __, /* 65 A */ 0, /* 66 B */ 1, /* 67 C */ 2, + /* 68 D */ 3, /* 69 E */ 4, /* 70 F */ 5, /* 71 G */ 6, + /* 72 H */ 7, /* 73 I */ 8, /* 74 J */ 9, /* 75 K */ 10, + /* 76 L */ 11, /* 77 M */ 12, /* 78 N */ 13, /* 79 O */ 14, + /* 80 P */ 15, /* 81 Q */ 16, /* 82 R */ 17, /* 83 S */ 18, + /* 84 T */ 19, /* 85 U */ 20, /* 86 V */ 21, /* 87 W */ 22, + /* 88 X */ 23, /* 89 Y */ 24, /* 90 Z */ 25, /* 91 [ */ __, + /* 92 \ */ __, /* 93 ] */ __, /* 94 ^ */ __, /* 95 _ */ __, + /* 96 ` */ __, /* 97 a */ 26, /* 98 b */ 27, /* 99 c */ 28, + /*100 d */ 29, /*101 e */ 30, /*102 f */ 31, /*103 g */ 32, + /*104 h */ 33, /*105 i */ 34, /*106 j */ 35, /*107 k */ 36, + /*108 l */ 37, /*109 m */ 38, /*110 n */ 39, /*111 o */ 40, + /*112 p */ 41, /*113 q */ 42, /*114 r */ 43, /*115 s */ 44, + /*116 t */ 45, /*117 u */ 46, /*118 v */ 47, /*119 w */ 48, + /*120 x */ 49, /*121 y */ 50, /*122 z */ 51, /*123 { */ __, + /*124 | */ __, /*125 } */ __, /*126 ~ */ __, /*127 DEL*/ __, +}; +} // namespace + +#ifndef NDEBUG +namespace detail { +C4CORE_EXPORT void base64_test_tables() // NOLINT(*use-internal-linkage*) +{ + for(size_t i = 0; i < C4_COUNTOF(base64_sextet_to_char_); ++i) + { + char s2c = base64_sextet_to_char_[i]; + dectype c2s = base64_char_to_sextet_[(unsigned)s2c]; + C4_CHECK((size_t)c2s == i); + } + for(size_t i = 0; i < C4_COUNTOF(base64_char_to_sextet_); ++i) + { + dectype c2s = base64_char_to_sextet_[i]; + if(c2s == __) + continue; + char s2c = base64_sextet_to_char_[(unsigned)c2s]; + C4_CHECK((size_t)s2c == i); + } +} +} // namespace detail +#endif + + +//----------------------------------------------------------------------------- + +namespace { +#if C4_CPP >= 17 +C4_HOT C4_ALWAYS_INLINE bool is_valid_encoded_char_(char c) noexcept +{ + if constexpr (std::is_unsigned_v) + return ((c < 128) && (base64_char_to_sextet_[c] != __)); + else + return ((c >= 0) && (base64_char_to_sextet_[c] != __)); +} +#else // pre c++-17 implementation requires SFINAE +template +C4_HOT C4_ALWAYS_INLINE auto is_valid_encoded_char_(Char c) noexcept + -> typename std::enable_if::value, bool>::type +{ + return ((c < 128) && (base64_char_to_sextet_[c] != __)); +} +template +C4_HOT C4_ALWAYS_INLINE auto is_valid_encoded_char_(Char c) noexcept + -> typename std::enable_if< ! std::is_unsigned::value, bool>::type +{ + return ((c >= 0) && (base64_char_to_sextet_[c] != __)); +} +#endif + +#undef __ + + +C4_HOT C4_ALWAYS_INLINE bool is_valid_encoded_group4_(const char *C4_RESTRICT c) noexcept +{ + return is_valid_encoded_char_(c[0]) + && is_valid_encoded_char_(c[1]) + && is_valid_encoded_char_(c[2]) + && is_valid_encoded_char_(c[3]); +} +C4_HOT C4_ALWAYS_INLINE bool is_valid_encoded_group8_(const char *C4_RESTRICT c) noexcept +{ + return is_valid_encoded_char_(c[0]) + && is_valid_encoded_char_(c[1]) + && is_valid_encoded_char_(c[2]) + && is_valid_encoded_char_(c[3]) + && is_valid_encoded_char_(c[4]) + && is_valid_encoded_char_(c[5]) + && is_valid_encoded_char_(c[6]) + && is_valid_encoded_char_(c[7]); +} +#if (C4_WORDSIZE >= 8) +C4_HOT C4_ALWAYS_INLINE bool is_valid_encoded_group16_(const char *C4_RESTRICT c, size_t num) noexcept +{ + C4_ASSERT(num >= 16); + C4_ASSERT(!(num & 15)); // must be multiple of 16 + size_t rem = num; + for( ; rem >= 16; rem -= 16, c += 16) + if(C4_UNLIKELY(!is_valid_encoded_group8_(c) + || !is_valid_encoded_group8_(c + 8))) + return false; + return true; +} +#endif + + +#ifdef C4_PREFER_BSWAP +# if C4_BIG_ENDIAN || (C4_MIXED_ENDIAN \ + && defined(__BYTE_ORDER__) \ + && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) +# define _BSWAP_TO_BIG_ENDIAN64(x) +# define _BSWAP_TO_BIG_ENDIAN32(x) +# elif C4_LITTLE_ENDIAN || (C4_MIXED_ENDIAN \ + && defined(__BYTE_ORDER__) \ + && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) +# ifdef _MSC_VER +# define _BSWAP_TO_BIG_ENDIAN64(x) (x) = _byteswap_uint64(x) +# define _BSWAP_TO_BIG_ENDIAN32(x) (x) = _byteswap_ulong(x) +# else +# define _BSWAP_TO_BIG_ENDIAN64(x) (x) = __builtin_bswap64(x) +# define _BSWAP_TO_BIG_ENDIAN32(x) (x) = __builtin_bswap32(x) +# endif +# else +# error not implemented +# endif +#endif + + +enum : uint32_t { mask32 = uint32_t((1 << 6u) - 1u) }; // NOLINT +#if (C4_WORDSIZE >= 8) +enum : uint64_t { mask64 = uint64_t((1 << 6u) - 1u) }; // NOLINT +C4_HOT C4_ALWAYS_INLINE void base64_encode_block64_(const uint8_t *C4_RESTRICT const data, char *C4_RESTRICT const encoded) noexcept +{ + #if defined(C4_PREFER_BSWAP) + uint64_t val; // MSB -> LSB + memcpy(&val, data, sizeof(val)); // |.|.|5|4|3|2|1|0| + _BSWAP_TO_BIG_ENDIAN64(val); // |0|1|2|3|4|5|.|.| + encoded[0] = base64_sextet_to_char_[(val >> 58) & mask64]; + encoded[1] = base64_sextet_to_char_[(val >> 52) & mask64]; + encoded[2] = base64_sextet_to_char_[(val >> 46) & mask64]; + encoded[3] = base64_sextet_to_char_[(val >> 40) & mask64]; + encoded[4] = base64_sextet_to_char_[(val >> 34) & mask64]; + encoded[5] = base64_sextet_to_char_[(val >> 28) & mask64]; + encoded[6] = base64_sextet_to_char_[(val >> 22) & mask64]; + encoded[7] = base64_sextet_to_char_[(val >> 16) & mask64]; + #else + const uint64_t val = ((uint64_t(data[0]) << 40) | // |.|.|0|1|2|3|4|5| + (uint64_t(data[1]) << 32) | + (uint64_t(data[2]) << 24) | + (uint64_t(data[3]) << 16) | + (uint64_t(data[4]) << 8) | + (uint64_t(data[5]))); + encoded[0] = base64_sextet_to_char_[(val >> 42) & mask64]; + encoded[1] = base64_sextet_to_char_[(val >> 36) & mask64]; + encoded[2] = base64_sextet_to_char_[(val >> 30) & mask64]; + encoded[3] = base64_sextet_to_char_[(val >> 24) & mask64]; + encoded[4] = base64_sextet_to_char_[(val >> 18) & mask64]; + encoded[5] = base64_sextet_to_char_[(val >> 12) & mask64]; + encoded[6] = base64_sextet_to_char_[(val >> 6) & mask64]; + encoded[7] = base64_sextet_to_char_[(val ) & mask64]; + #endif +} +#endif + +C4_HOT void base64_encode_block32_(const uint8_t *C4_RESTRICT const data, char *C4_RESTRICT const encoded) noexcept +{ + #if defined(C4_PREFER_BSWAP) + uint32_t val = 0; + memcpy(&val, data, sizeof(val)); // MSB: |.|2|1|0| :LSB + _BSWAP_TO_BIG_ENDIAN32(val); // MSB: |0|1|2|.| :LSB + encoded[0] = base64_sextet_to_char_[(val >> 26) & mask32]; + encoded[1] = base64_sextet_to_char_[(val >> 20) & mask32]; + encoded[2] = base64_sextet_to_char_[(val >> 14) & mask32]; + encoded[3] = base64_sextet_to_char_[(val >> 8) & mask32]; + #else + // MSB: |.|0|1|2| :LSB + const uint32_t val = ((uint32_t(data[0]) << 16) | (uint32_t(data[1]) << 8) | (uint32_t(data[2]))); + encoded[0] = base64_sextet_to_char_[(val >> 18) & mask32]; + encoded[1] = base64_sextet_to_char_[(val >> 12) & mask32]; + encoded[2] = base64_sextet_to_char_[(val >> 6) & mask32]; + encoded[3] = base64_sextet_to_char_[(val ) & mask32]; + #endif +} +void base64_encode_block32_term2_(const uint8_t *C4_RESTRICT data, char *C4_RESTRICT encoded) noexcept +{ + // MSB: |.|.|0|1| :LSB + const uint32_t val = ((uint32_t(data[0]) << 16) | (uint32_t(data[1]) << 8)); + encoded[0] = base64_sextet_to_char_[(val >> 18) & mask32]; + encoded[1] = base64_sextet_to_char_[(val >> 12) & mask32]; + encoded[2] = base64_sextet_to_char_[(val >> 6) & mask32]; + encoded[3] = '='; +} +void base64_encode_block32_term1_(const uint8_t *C4_RESTRICT data, char *C4_RESTRICT encoded) noexcept +{ + // MSB: |.|.|.|0| :LSB + const uint32_t val = ((uint32_t(data[0]) << 16)); + encoded[0] = base64_sextet_to_char_[(val >> 18) & mask32]; + encoded[1] = base64_sextet_to_char_[(val >> 12) & mask32]; + encoded[2] = '='; + encoded[3] = '='; +} + + +//----------------------------------------------------------------------------- + +enum : uint32_t { dmask32 = 0xff }; // NOLINT +#if (C4_WORDSIZE >= 8) +enum : uint64_t { dmask64 = 0xff }; // NOLINT +void base64_decode_block64_(const char *C4_RESTRICT encoded, dectype *C4_RESTRICT data) noexcept +{ + uint64_t val = + (((uint64_t)base64_char_to_sextet_[encoded[0]]) << 42) + | (((uint64_t)base64_char_to_sextet_[encoded[1]]) << 36) + | (((uint64_t)base64_char_to_sextet_[encoded[2]]) << 30) + | (((uint64_t)base64_char_to_sextet_[encoded[3]]) << 24) + | (((uint64_t)base64_char_to_sextet_[encoded[4]]) << 18) + | (((uint64_t)base64_char_to_sextet_[encoded[5]]) << 12) + | (((uint64_t)base64_char_to_sextet_[encoded[6]]) << 6) + | (((uint64_t)base64_char_to_sextet_[encoded[7]]) ); + data[0] = (dectype)((val >> 40) & dmask64); + data[1] = (dectype)((val >> 32) & dmask64); + data[2] = (dectype)((val >> 24) & dmask64); + data[3] = (dectype)((val >> 16) & dmask64); + data[4] = (dectype)((val >> 8) & dmask64); + data[5] = (dectype)((val ) & dmask64); +} +#endif +C4_HOT void base64_decode_block32_(const char *C4_RESTRICT encoded, dectype *C4_RESTRICT data) noexcept +{ + const uint32_t val = + (((uint32_t)base64_char_to_sextet_[encoded[0]]) << 18) + | (((uint32_t)base64_char_to_sextet_[encoded[1]]) << 12) + | (((uint32_t)base64_char_to_sextet_[encoded[2]]) << 6) + | (((uint32_t)base64_char_to_sextet_[encoded[3]]) ); + data[0] = (dectype)((val >> 16) & dmask32); + data[1] = (dectype)((val >> 8) & dmask32); + data[2] = (dectype)((val ) & dmask32); +} +void base64_decode_block32_term1_(const char *C4_RESTRICT encoded, dectype *C4_RESTRICT data) noexcept +{ + const uint32_t val = + (((uint32_t)base64_char_to_sextet_[encoded[0]]) << 18) + | (((uint32_t)base64_char_to_sextet_[encoded[1]]) << 12); + data[0] = (dectype)((val >> 16) & dmask32); +} +void base64_decode_block32_term2_(const char *C4_RESTRICT encoded, dectype *C4_RESTRICT data) noexcept +{ + const uint32_t val = + (((uint32_t)base64_char_to_sextet_[encoded[0]]) << 18) + | (((uint32_t)base64_char_to_sextet_[encoded[1]]) << 12) + | (((uint32_t)base64_char_to_sextet_[encoded[2]]) << 6); + data[0] = (dectype)((val >> 16) & dmask32); + data[1] = (dectype)((val >> 8) & dmask32); +} + +} // namespace + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +bool base64_valid(const char *encoded_, size_t encoded_sz) +{ + if(!encoded_sz) + return true; + if((encoded_sz & size_t(3u))) // is it not a multiple of 4? + return false; + const char *C4_RESTRICT encoded = encoded_; + size_t i = 0; + #if C4_WORDSIZE >= 8 + for( ; i + 8 < encoded_sz; i += 8) + if(!is_valid_encoded_group8_(encoded + i)) + return false; + #endif + for( ; i + 4 < encoded_sz; i += 4) + if(!is_valid_encoded_group4_(encoded + i)) + return false; + if(!is_valid_encoded_char_(encoded[i]) + || !is_valid_encoded_char_(encoded[i + 1])) + return false; + if(!is_valid_encoded_char_(encoded[i + 2])) + return (encoded[i + 2] == '=' && encoded[i + 3] == '='); + if(!is_valid_encoded_char_(encoded[i + 3])) + return (encoded[i + 3] == '='); + return true; +} + + +//----------------------------------------------------------------------------- + +size_t base64_encode(char *encoded_, size_t encoded_sz, const void *data_, size_t data_sz) +{ + C4_ASSERT(encoded_ != nullptr || encoded_sz == 0); + C4_ASSERT(data_ != nullptr || data_sz == 0); + // ....................... how many groups of 3 bytes to read + // .... each group results in 4 bytes written + size_t required_sz = ((data_sz + 3 - 1) / 3) * 4; + if(encoded_sz < required_sz) + return required_sz; + size_t rem = data_sz; + char *C4_RESTRICT encoded = encoded_; + const uint8_t *C4_RESTRICT data = (const uint8_t *) data_; // cast to unsigned to avoid wrapping high-bits +#if (C4_WORDSIZE >= 8) + for( ; rem >= 15; rem -= 12) // leave 3 at the end (15=12+3) + { + base64_encode_block64_(data, encoded); data += 6; encoded += 8; + base64_encode_block64_(data, encoded); data += 6; encoded += 8; + } + for( ; rem >= 9; rem -= 6) // leave 3 at the end (9=6+3) + { + base64_encode_block64_(data, encoded); data += 6; encoded += 8; + } +#else + for( ; rem >= 15; rem -= 12) // leave 3 at the end (15=12+3) + { + base64_encode_block32_(data, encoded); data += 3; encoded += 4; + base64_encode_block32_(data, encoded); data += 3; encoded += 4; + base64_encode_block32_(data, encoded); data += 3; encoded += 4; + base64_encode_block32_(data, encoded); data += 3; encoded += 4; + } + for( ; rem >= 9; rem -= 6) // leave 3 at the end (9=6+3) + { + base64_encode_block32_(data, encoded); data += 3; encoded += 4; + base64_encode_block32_(data, encoded); data += 3; encoded += 4; + } +#endif + for( ; rem >= 3; rem -= 3) + { + base64_encode_block32_(data, encoded); data += 3; encoded += 4; + } + C4_ASSERT(rem < 3); + if(rem == 2) + base64_encode_block32_term2_(data, encoded); + else if(rem == 1) + base64_encode_block32_term1_(data, encoded); + return required_sz; +} + + +//----------------------------------------------------------------------------- + +bool base64_decode(char const* encoded_, size_t encoded_sz, + void * data_, size_t data_sz, + size_t *data_sz_required) +{ + C4_ASSERT(encoded_ != nullptr || encoded_sz == 0); + C4_ASSERT(data_ != nullptr || data_sz == 0); + C4_ASSERT(data_sz_required != nullptr); + if(!encoded_sz) + { + *data_sz_required = 0; + return true; + } + else if(encoded_sz & 3u) // is encoded_sz not a multiple of 4? + { + return false; + } + // compute the required size for the decoded buffer: + // ................ how many 4-byte groups of encoded data to decode + // .... each group results in 3 decoded bytes + *data_sz_required = (encoded_sz / 4) * 3; + const char *C4_RESTRICT encoded = encoded_; + // account for padded bytes at the end + C4_ASSERT(encoded_sz >= 4); + if(encoded[encoded_sz - 1] == '=') + { + C4_ASSERT(*data_sz_required >= 3); + if(encoded[encoded_sz - 2] == '=') + *data_sz_required -= 2; + else + *data_sz_required -= 1; + } + if(data_sz < *data_sz_required) + return false; + // we have enough room + size_t rem = *data_sz_required; // numbytes remaining to write + dectype *C4_RESTRICT data = (dectype *)data_; + C4_STATIC_ASSERT(sizeof(dectype) == 1); +#if (C4_WORDSIZE >= 8) + for( ; rem >= 15; rem -= 12) + { + if(C4_UNLIKELY(!is_valid_encoded_group16_(encoded, 16))) + return false; + base64_decode_block64_(encoded, data); encoded += 8; data += 6; + base64_decode_block64_(encoded, data); encoded += 8; data += 6; + } + for( ; rem >= 9; rem -= 6) + { + if(C4_UNLIKELY(!is_valid_encoded_group8_(encoded))) + return false; + base64_decode_block64_(encoded, data); encoded += 8; data += 6; + } +#else + for( ; rem >= 9; rem -= 6) + { + if(C4_UNLIKELY(!is_valid_encoded_group8_(encoded))) + return false; + base64_decode_block32_(encoded, data); encoded += 4; data += 3; + base64_decode_block32_(encoded, data); encoded += 4; data += 3; + } +#endif + for( ; rem >= 3; rem -= 3) + { + if(C4_UNLIKELY(!is_valid_encoded_group4_(encoded))) + return false; + base64_decode_block32_(encoded, data); encoded += 4; data += 3; + } + C4_ASSERT(rem < 3); + // the last quartet requires dealing with padded chars + if(rem == 1) // 1 remaining byte, 2 padding chars + { + if(!is_valid_encoded_char_(encoded[0]) + || !is_valid_encoded_char_(encoded[1]) + || encoded[2] != '=' + || encoded[3] != '=') + return false; + base64_decode_block32_term1_(encoded, data); + } + else if(rem == 2) // 2 remaining bytes, 1 padding char + { + if(!is_valid_encoded_char_(encoded[0]) + || !is_valid_encoded_char_(encoded[1]) + || !is_valid_encoded_char_(encoded[2]) + || encoded[3] != '=') + return false; + base64_decode_block32_term2_(encoded, data); + } + return true; +} + +} // namespace c4 + +// NOLINTEND(bugprone-signed-char-misuse,cert-str34-c,hicpp-signed-bitwise) + +C4_SUPPRESS_WARNING_POP + +#endif /* C4CORE_SINGLE_HDR_DEFINE_NOW */ + + +// (end src/c4/base64.cpp) + +#define C4_WINDOWS_POP_HPP_ + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/windows_push.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_WINDOWS_PUSH_HPP_ +#define _C4_WINDOWS_PUSH_HPP_ + +/** @file windows_push.hpp sets up macros to include windows header files + * without pulling in all of + * + * @see windows_pop.hpp to undefine these macros + * + * @see https://aras-p.info/blog/2018/01/12/Minimizing-windows.h/ */ + + +#if defined(_WIN64) || defined(_WIN32) + +#if defined(_M_AMD64) +# ifndef _AMD64_ +# define _c4_AMD64_ +# define _AMD64_ +# endif +#elif defined(_M_IX86) +# ifndef _X86_ +# define _c4_X86_ +# define _X86_ +# endif +#elif defined(_M_ARM64) +# ifndef _ARM64_ +# define _c4_ARM64_ +# define _ARM64_ +# endif +#elif defined(_M_ARM) +# ifndef _ARM_ +# define _c4_ARM_ +# define _ARM_ +# endif +#endif + +#ifndef NOMINMAX +# define _c4_NOMINMAX +# define NOMINMAX +#endif + +#ifndef NOGDI +# define _c4_NOGDI +# define NOGDI +#endif + +#ifndef VC_EXTRALEAN +# define _c4_VC_EXTRALEAN +# define VC_EXTRALEAN +#endif + +#ifndef WIN32_LEAN_AND_MEAN +# define _c4_WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif + +/* If defined, the following flags inhibit definition + * of the indicated items. + * + * NOGDICAPMASKS - CC_*, LC_*, PC_*, CP_*, TC_*, RC_ + * NOVIRTUALKEYCODES - VK_* + * NOWINMESSAGES - WM_*, EM_*, LB_*, CB_* + * NOWINSTYLES - WS_*, CS_*, ES_*, LBS_*, SBS_*, CBS_* + * NOSYSMETRICS - SM_* + * NOMENUS - MF_* + * NOICONS - IDI_* + * NOKEYSTATES - MK_* + * NOSYSCOMMANDS - SC_* + * NORASTEROPS - Binary and Tertiary raster ops + * NOSHOWWINDOW - SW_* + * OEMRESOURCE - OEM Resource values + * NOATOM - Atom Manager routines + * NOCLIPBOARD - Clipboard routines + * NOCOLOR - Screen colors + * NOCTLMGR - Control and Dialog routines + * NODRAWTEXT - DrawText() and DT_* + * NOGDI - All GDI defines and routines + * NOKERNEL - All KERNEL defines and routines + * NOUSER - All USER defines and routines + * NONLS - All NLS defines and routines + * NOMB - MB_* and MessageBox() + * NOMEMMGR - GMEM_*, LMEM_*, GHND, LHND, associated routines + * NOMETAFILE - typedef METAFILEPICT + * NOMINMAX - Macros min(a,b) and max(a,b) + * NOMSG - typedef MSG and associated routines + * NOOPENFILE - OpenFile(), OemToAnsi, AnsiToOem, and OF_* + * NOSCROLL - SB_* and scrolling routines + * NOSERVICE - All Service Controller routines, SERVICE_ equates, etc. + * NOSOUND - Sound driver routines + * NOTEXTMETRIC - typedef TEXTMETRIC and associated routines + * NOWH - SetWindowsHook and WH_* + * NOWINOFFSETS - GWL_*, GCL_*, associated routines + * NOCOMM - COMM driver routines + * NOKANJI - Kanji support stuff. + * NOHELP - Help engine interface. + * NOPROFILER - Profiler interface. + * NODEFERWINDOWPOS - DeferWindowPos routines + * NOMCX - Modem Configuration Extensions + */ + +#endif /* defined(_WIN64) || defined(_WIN32) */ + +#endif /* _C4_WINDOWS_PUSH_HPP_ */ + + +// (end src/c4/windows_push.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/windows.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_WINDOWS_HPP_ +#define _C4_WINDOWS_HPP_ + +#if defined(_WIN64) || defined(_WIN32) +// amalgamate: removed include of +// c4/windows_push.hpp +//#include "c4/windows_push.hpp" +#if !defined(C4_WINDOWS_PUSH_HPP_) && !defined(_C4_WINDOWS_PUSH_HPP_) +#error "amalgamate: file c4/windows_push.hpp must have been included at this point" +#endif /* C4_WINDOWS_PUSH_HPP_ */ + +#include +// amalgamate: removed include of +// c4/windows_pop.hpp +//#include "c4/windows_pop.hpp" +#if !defined(C4_WINDOWS_POP_HPP_) && !defined(_C4_WINDOWS_POP_HPP_) +#error "amalgamate: file c4/windows_pop.hpp must have been included at this point" +#endif /* C4_WINDOWS_POP_HPP_ */ + +#endif + +#endif /* _C4_WINDOWS_HPP_ */ + + +// (end src/c4/windows.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/windows_pop.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_WINDOWS_POP_HPP_ +#define _C4_WINDOWS_POP_HPP_ + +#if defined(_WIN64) || defined(_WIN32) + +#ifdef _c4_AMD64_ +# undef _c4_AMD64_ +# undef _AMD64_ +#endif +#ifdef _c4_X86_ +# undef _c4_X86_ +# undef _X86_ +#endif +#ifdef _c4_ARM_ +# undef _c4_ARM_ +# undef _ARM_ +#endif + +#ifdef _c4_NOMINMAX +# undef _c4_NOMINMAX +# undef NOMINMAX +#endif + +#ifdef NOGDI +# undef _c4_NOGDI +# undef NOGDI +#endif + +#ifdef VC_EXTRALEAN +# undef _c4_VC_EXTRALEAN +# undef VC_EXTRALEAN +#endif + +#ifdef WIN32_LEAN_AND_MEAN +# undef _c4_WIN32_LEAN_AND_MEAN +# undef WIN32_LEAN_AND_MEAN +#endif + +#endif /* defined(_WIN64) || defined(_WIN32) */ + +#endif /* _C4_WINDOWS_POP_HPP_ */ + + +// (end src/c4/windows_pop.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/error.cpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifdef C4CORE_SINGLE_HDR_DEFINE_NOW +// amalgamate: removed include of +// c4/error.hpp +//#include "c4/error.hpp" +#if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) +#error "amalgamate: file c4/error.hpp must have been included at this point" +#endif /* C4_ERROR_HPP_ */ + +// amalgamate: removed include of +// c4/language.hpp +//#include "c4/language.hpp" +#if !defined(C4_LANGUAGE_HPP_) && !defined(_C4_LANGUAGE_HPP_) +#error "amalgamate: file c4/language.hpp must have been included at this point" +#endif /* C4_LANGUAGE_HPP_ */ + + +//included above: +//#include +//included above: +//#include +//included above: +//#include + +#define C4_LOGF_ERR(...) (void)fprintf(stderr, __VA_ARGS__); (void)fflush(stderr) +#define C4_LOGF_WARN(...) (void)fprintf(stderr, __VA_ARGS__); (void)fflush(stderr) +#define C4_LOGP(msg, ...) (void)printf(msg) + +#if defined(C4_XBOX) || (defined(C4_WIN) && defined(C4_MSVC)) +// amalgamate: removed include of +// c4/windows.hpp +//# include "c4/windows.hpp" +#if !defined(C4_WINDOWS_HPP_) && !defined(_C4_WINDOWS_HPP_) +#error "amalgamate: file c4/windows.hpp must have been included at this point" +#endif /* C4_WINDOWS_HPP_ */ + +#elif defined(C4_PS4) +# include +#elif defined(C4_UNIX) || defined(C4_LINUX) +# include +//included above: +//# include +# include +#elif defined(C4_MACOS) || defined(C4_IOS) +//included above: +//# include +# include +# include +# include +#endif +// the amalgamation tool is dumb and was omitting this include under MACOS. +// So do it only once: +#if defined(C4_UNIX) || defined(C4_LINUX) || defined(C4_MACOS) || defined(C4_IOS) +# include +#endif + +#if defined(C4_EXCEPTIONS_ENABLED) && defined(C4_ERROR_THROWS_EXCEPTION) +# include +#endif + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wformat-nonliteral" +# pragma clang diagnostic ignored "-Wold-style-cast" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wformat-nonliteral" +# pragma GCC diagnostic ignored "-Wold-style-cast" +#endif +// NOLINTBEGIN(*use-anonymous-namespace*,cert-dcl50-cpp) + + +//----------------------------------------------------------------------------- +namespace c4 { + +static error_flags s_error_flags = ON_ERROR_DEFAULTS; +static error_callback_type s_error_callback = nullptr; + + +//----------------------------------------------------------------------------- + +error_flags get_error_flags() +{ + return s_error_flags; +} +void set_error_flags(error_flags flags) +{ + s_error_flags = flags; +} + +error_callback_type get_error_callback() +{ + return s_error_callback; +} +/** Set the function which is called when an error occurs. */ +void set_error_callback(error_callback_type cb) +{ + s_error_callback = cb; +} + + +//----------------------------------------------------------------------------- + +void handle_error(srcloc where, const char *fmt, ...) // NOLINT +{ + char buf[1024]; + size_t msglen = 0; + if(s_error_flags & (ON_ERROR_LOG|ON_ERROR_CALLBACK)) + { + va_list args; + va_start(args, fmt); + int ilen = vsnprintf(buf, sizeof(buf), fmt, args); // NOLINT(clang-analyzer-valist.Uninitialized) + va_end(args); + msglen = ilen >= 0 && ilen < (int)sizeof(buf) ? static_cast(ilen) : sizeof(buf)-1; + } + + if(s_error_flags & ON_ERROR_LOG) + { + C4_LOGF_ERR("\n"); +#if defined(C4_ERROR_SHOWS_FILELINE) && defined(C4_ERROR_SHOWS_FUNC) + C4_LOGF_ERR("%s:%d: ERROR: %s\n", where.file, where.line, buf); + C4_LOGF_ERR("%s:%d: ERROR here: %s\n", where.file, where.line, where.func); +#elif defined(C4_ERROR_SHOWS_FILELINE) + C4_LOGF_ERR("%s:%d: ERROR: %s\n", where.file, where.line, buf); +#elif ! defined(C4_ERROR_SHOWS_FUNC) + (void)where; + C4_LOGF_ERR("ERROR: %s\n", buf); +#endif + } + + if(s_error_flags & ON_ERROR_CALLBACK) + { + if(s_error_callback) + { + s_error_callback(buf, msglen); + } + } + + if(s_error_flags & ON_ERROR_THROW) + { +#if defined(C4_EXCEPTIONS_ENABLED) && defined(C4_ERROR_THROWS_EXCEPTION) + throw std::runtime_error(buf); +#endif + } + + if(s_error_flags & ON_ERROR_ABORT) + { + abort(); + } + + abort(); // abort anyway, in case nothing was set + C4_UNREACHABLE_AFTER_ERR(); +} + +//----------------------------------------------------------------------------- + +void handle_warning(srcloc where, const char *fmt, ...) // NOLINT +{ + va_list args; + char buf[1024]; + va_start(args, fmt); + int ret = vsnprintf(buf, sizeof(buf), fmt, args); // NOLINT(clang-analyzer-valist.Uninitialized) + if(ret+1 > (int)sizeof(buf)) + buf[sizeof(buf) - 1] = '\0'; // truncate + else if(ret < 0) + buf[0] = '\0'; // output/format error + va_end(args); + C4_LOGF_WARN("\n"); +#if defined(C4_ERROR_SHOWS_FILELINE) && defined(C4_ERROR_SHOWS_FUNC) + C4_LOGF_WARN("%s:%d: WARNING: %s\n", where.file, where.line, buf); + C4_LOGF_WARN("%s:%d: WARNING: here: %s\n", where.file, where.line, where.func); +#elif defined(C4_ERROR_SHOWS_FILELINE) + C4_LOGF_WARN("%s:%d: WARNING: %s\n", where.file, where.line, buf); +#elif ! defined(C4_ERROR_SHOWS_FUNC) + (void)where; + C4_LOGF_WARN("WARNING: %s\n", buf); +#endif +} + +//----------------------------------------------------------------------------- +bool is_debugger_attached() +{ +#if defined(C4_UNIX) || defined(C4_LINUX) + static bool first_call = true; + static bool first_call_result = false; + if(first_call) + { + first_call = false; + C4_SUPPRESS_WARNING_GCC_PUSH + #if defined(__GNUC__) && __GNUC__ > 9 + C4_SUPPRESS_WARNING_GCC("-Wanalyzer-fd-leak") + #endif + //! @see http://stackoverflow.com/questions/3596781/how-to-detect-if-the-current-process-is-being-run-by-gdb + //! (this answer: http://stackoverflow.com/a/24969863/3968589 ) + char buf[1024] = ""; + int status_fd = open("/proc/self/status", O_RDONLY); // NOLINT + if (status_fd == -1) + return false; + ssize_t num_read = ::read(status_fd, buf, sizeof(buf)); + if (num_read > 0) + { + static const char TracerPid[] = "TracerPid:"; + char *tracer_pid; + if(num_read < 1024) + buf[num_read] = 0; + tracer_pid = strstr(buf, TracerPid); + if(tracer_pid) + first_call_result = !!::atoi(tracer_pid + sizeof(TracerPid) - 1); // NOLINT + } + close(status_fd); + C4_SUPPRESS_WARNING_GCC_POP + } + return first_call_result; +#elif defined(C4_PS4) + return (sceDbgIsDebuggerAttached() != 0); +#elif defined(C4_XBOX) || (defined(C4_WIN) && defined(C4_MSVC)) + return IsDebuggerPresent() != 0; +#elif defined(C4_MACOS) || defined(C4_IOS) + // https://stackoverflow.com/questions/2200277/detecting-debugger-on-mac-os-x + // Returns true if the current process is being debugged (either + // running under the debugger or has a debugger attached post facto). + int junk; + int mib[4]; + struct kinfo_proc info; + size_t size; + + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + + info.kp_proc.p_flag = 0; + + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + + // Call sysctl. + + size = sizeof(info); + junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0); + assert(junk == 0); + (void)junk; + + // We're being debugged if the P_TRACED flag is set. + return ((info.kp_proc.p_flag & P_TRACED) != 0); +#else + return false; +#endif +} // is_debugger_attached() + +} // namespace c4 + +// NOLINTEND(*use-anonymous-namespace*,cert-dcl50-cpp) + +#ifdef __clang__ +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#endif + +#endif /* C4CORE_SINGLE_HDR_DEFINE_NOW */ + + +// (end src/c4/error.cpp) + +#endif /* _C4CORE_SINGLE_HEADER_AMALGAMATED_HPP_ */ + + + +// (end src/c4/c4core_all.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/export.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef C4_YML_EXPORT_HPP_ +#define C4_YML_EXPORT_HPP_ + +#ifdef _WIN32 + #ifdef RYML_SHARED + #ifdef RYML_EXPORTS + #define RYML_EXPORT __declspec(dllexport) + #define RYML_EXPORT_EXTERN + #else + #define RYML_EXPORT __declspec(dllimport) + #define RYML_EXPORT_EXTERN extern + #endif + #else + #define RYML_EXPORT + #define RYML_EXPORT_EXTERN + #endif +#else + #define RYML_EXPORT + #define RYML_EXPORT_EXTERN +#endif + +#endif /* C4_YML_EXPORT_HPP_ */ + + +// (end src/c4/yml/export.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/fwd.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_YML_FWD_HPP_ +#define _C4_YML_FWD_HPP_ + +/** @file fwd.hpp forward declarations */ + +namespace c4 { +namespace yml { + +struct NodeScalar; +struct NodeInit; +struct NodeData; +struct NodeType; +class NodeRef; +class ConstNodeRef; +class Tree; +struct ReferenceResolver; +template class ParseEngine; +struct EventHandlerTree; +using Parser = ParseEngine; + +} // namespace c4 +} // namespace yml + +#endif /* _C4_YML_FWD_HPP_ */ + + +// (end src/c4/yml/fwd.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/version.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_YML_VERSION_HPP_ +#define _C4_YML_VERSION_HPP_ + +/** @file version.hpp */ + +#define RYML_VERSION "0.15.2" +#define RYML_VERSION_MAJOR 0 +#define RYML_VERSION_MINOR 15 +#define RYML_VERSION_PATCH 2 + +// amalgamate: removed include of +// c4/substr.hpp +//#include +#if !defined(C4_SUBSTR_HPP_) && !defined(_C4_SUBSTR_HPP_) +#error "amalgamate: file c4/substr.hpp must have been included at this point" +#endif /* C4_SUBSTR_HPP_ */ + +// amalgamate: removed include of +// c4/yml/export.hpp +//#include +#if !defined(C4_YML_EXPORT_HPP_) && !defined(_C4_YML_EXPORT_HPP_) +#error "amalgamate: file c4/yml/export.hpp must have been included at this point" +#endif /* C4_YML_EXPORT_HPP_ */ + + +namespace c4 { +namespace yml { + +RYML_EXPORT csubstr version(); +RYML_EXPORT int version_major(); +RYML_EXPORT int version_minor(); +RYML_EXPORT int version_patch(); + +} // namespace yml +} // namespace c4 + +#endif /* _C4_YML_VERSION_HPP_ */ + + +// (end src/c4/yml/version.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/common.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_YML_COMMON_HPP_ +#define _C4_YML_COMMON_HPP_ + +/** @file common.hpp Common utilities and infrastructure used by ryml. */ + +//included above: +//#include + +#ifndef _C4_SUBSTR_HPP_ +// amalgamate: removed include of +// c4/substr.hpp +//#include +#if !defined(C4_SUBSTR_HPP_) && !defined(_C4_SUBSTR_HPP_) +#error "amalgamate: file c4/substr.hpp must have been included at this point" +#endif /* C4_SUBSTR_HPP_ */ + +#endif +#ifndef _C4_CHARCONV_HPP_ +// amalgamate: removed include of +// c4/charconv.hpp +//#include +#if !defined(C4_CHARCONV_HPP_) && !defined(_C4_CHARCONV_HPP_) +#error "amalgamate: file c4/charconv.hpp must have been included at this point" +#endif /* C4_CHARCONV_HPP_ */ + +#endif +#ifndef _C4_YML_EXPORT_HPP_ +// amalgamate: removed include of +// c4/yml/export.hpp +//#include +#if !defined(C4_YML_EXPORT_HPP_) && !defined(_C4_YML_EXPORT_HPP_) +#error "amalgamate: file c4/yml/export.hpp must have been included at this point" +#endif /* C4_YML_EXPORT_HPP_ */ + +#endif + + +//----------------------------------------------------------------------------- + +#ifndef RYML_DEFAULT_TREE_CAPACITY +/// default capacity for the tree when not set explicitly +#define RYML_DEFAULT_TREE_CAPACITY (16) +#endif + +#ifndef RYML_DEFAULT_TREE_ARENA_CAPACITY +/// default capacity for the tree's arena when not set explicitly +#define RYML_DEFAULT_TREE_ARENA_CAPACITY (0) +#endif + + +#ifndef RYML_LOCATIONS_SMALL_THRESHOLD +/// threshold at which a location search will revert from linear to +/// binary search. +#define RYML_LOCATIONS_SMALL_THRESHOLD (30) +#endif + + +#ifndef RYML_ERRMSG_SIZE +/// size for the error message buffer +#define RYML_ERRMSG_SIZE (1024) +#endif + + +#ifndef RYML_LOGBUF_SIZE +/// size for the buffer used to format individual values to string +/// while preparing an error message. This is only used for formatting +/// individual values in the message; final messages will be larger +/// than this value (see @ref RYML_ERRMSG_SIZE). This is also used for +/// the detailed debug log messages when RYML_DBG is defined. +#define RYML_LOGBUF_SIZE (256) +#endif + + +#ifndef RYML_LOGBUF_SIZE +/// size for the buffer used to format individual values to string +/// while preparing an error message. This is only used for formatting +/// individual values in the message; final messages will be larger +/// than this value (see @ref RYML_ERRMSG_SIZE). This size is also +/// used for the detailed debug log messages when RYML_DBG is defined. +#define RYML_LOGBUF_SIZE (256) +#endif +static_assert(RYML_LOGBUF_SIZE < RYML_ERRMSG_SIZE, "invalid size"); + + +#ifndef RYML_LOGBUF_SIZE_MAX +/// size for the fallback larger log buffer. When @ref +/// RYML_LOGBUF_SIZE is not large enough to convert a value to string, +/// then temporary stack memory is allocated up to +/// RYML_LOGBUF_SIZE_MAX. This limit is in place to prevent a stack +/// overflow. If the printed value requires more than +/// RYML_LOGBUF_SIZE_MAX, the value is silently skipped. +#define RYML_LOGBUF_SIZE_MAX (1024) +#endif + + +//----------------------------------------------------------------------------- +// Specify groups to have a predefined topic order in doxygen: + +/** @defgroup doc_quickstart Quickstart + * + * Example code for every feature. + */ + +/** @defgroup doc_parse Parse utilities + * @see sample::sample_parse_in_place + * @see sample::sample_parse_in_arena + * @see sample::sample_parse_file + * @see sample::sample_parse_reuse_tree + * @see sample::sample_parse_reuse_parser + * @see sample::sample_parse_reuse_tree_and_parser + * @see sample::sample_location_tracking + */ + +/** @defgroup doc_emit Emit utilities + * + * Utilities to emit YAML and JSON, either to a memory buffer or to a + * file or ostream-like class. + * + * @see sample::sample_emit_to_container + * @see sample::sample_emit_to_stream + * @see sample::sample_emit_to_file + * @see sample::sample_emit_nested_node + * @see sample::sample_emit_style + */ + +/** @defgroup doc_node_type Node types + */ + +/** @defgroup doc_tree Tree utilities + * @see sample::sample_quick_overview + * @see sample::sample_iterate_trees + * @see sample::sample_create_trees + * @see sample::sample_tree_arena + * + * @see sample::sample_static_trees + * @see sample::sample_location_tracking + * + * @see sample::sample_docs + * @see sample::sample_anchors_and_aliases + * @see sample::sample_tags + */ + +/** @defgroup doc_node_classes Node classes + * + * High-level node classes. + * + * @see sample::sample_quick_overview + * @see sample::sample_iterate_trees + * @see sample::sample_create_trees + * @see sample::sample_tree_arena + */ + +/** @defgroup doc_error_handling Error handling + * + * Utilities to report handle errors, and to build and report error + * messages. + * + * @see sample::sample_error_handler + */ + +/** @defgroup doc_callbacks Callbacks for errors and allocation + * + * Functions called by ryml to allocate/free memory and to report + * errors. + * + * @see sample::sample_error_handler + * @see sample::sample_global_allocator + * @see sample::sample_per_tree_allocator + */ + +/** @defgroup doc_serialization Serialization/deserialization + * + * Contains information on how to serialize and deserialize + * fundamental types, user scalar types, user container types and + * interop with std scalar/container types. + * + */ + +/** @defgroup doc_ref_utils Anchor/Reference utilities + * + * @see sample::sample_anchors_and_aliases + * */ + +/** @defgroup doc_tag_utils Tag utilities + * @see sample::sample_tags + */ + +/** @defgroup doc_preprocessors Preprocessors + * + * Functions for preprocessing YAML prior to parsing. + */ + +/** @defgroup doc_file_utils File utils + * + * Functions for loading/saving a file from/to disk. + */ + + +//----------------------------------------------------------------------------- + +// document macros for doxygen +#ifdef __DOXYGEN__ // defined in Doxyfile::PREDEFINED + +/** define this macro with a boolean value to enable/disable + * assertions to check preconditions and assumptions throughout the + * codebase; this causes a slowdown of the code, and larger code + * size. By default, this macro is defined unless NDEBUG is defined + * (see C4_USE_ASSERT); as a result, by default this macro is truthy + * only in debug builds. */ +# define RYML_USE_ASSERT + +/** (Undefined by default) Define this macro to disable ryml's default + * implementation of the callback functions. See @ref doc_callbacks. */ +# define RYML_NO_DEFAULT_CALLBACKS + +/** (Undefined by default) When this macro is defined (and + * @ref RYML_NO_DEFAULT_CALLBACKS is not defined), the default error + * handler will throw exceptions. See @ref doc_error_handling. */ +# define RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS + +/** Conditionally expands to `noexcept` when @ref RYML_USE_ASSERT is 0 and + * is empty otherwise. The user is unable to override this macro. */ +# define RYML_NOEXCEPT + +/** (Undefined by default) Use shorter error message from + * checks/asserts: do not show the check condition in the error + * message. */ +# defined RYML_SHORT_CHECK_MSG + +#endif + + +//----------------------------------------------------------------------------- + +/** @cond dev */ + +#ifndef RYML_USE_ASSERT +# define RYML_USE_ASSERT C4_USE_ASSERT +#endif + +#if RYML_USE_ASSERT +# define RYML_NOEXCEPT +#else +# define RYML_NOEXCEPT noexcept +#endif + +#define RYML_DEPRECATED(msg) C4_DEPRECATED(msg) + +/** @endcond */ + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +namespace c4 { +namespace yml { + +C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wold-style-cast") + +class Tree; + + +#ifndef RYML_ID_TYPE +/** The type of a node id in the YAML tree. In the future, the default + * will likely change to int32_t, which was observed to be faster. + * @see id_type */ +#define RYML_ID_TYPE size_t +#endif + + +/** The type of a node id in the YAML tree; to override the default + * type, define the macro @ref RYML_ID_TYPE to a suitable integer + * type. */ +using id_type = RYML_ID_TYPE; +static_assert(std::is_integral::value, "id_type must be an integer type"); + + +C4_SUPPRESS_WARNING_GCC_WITH_PUSH("-Wuseless-cast") +enum : id_type { // NOLINT + /** an index to none */ + NONE = id_type(-1), // NOLINT +}; +C4_SUPPRESS_WARNING_GCC_CLANG_POP + + +enum : size_t { // NOLINT + /** a null string position */ + npos = size_t(-1) // NOLINT +}; + + +typedef enum Encoding_ { // NOLINT + NOBOM, //!< No Byte Order Mark was found + UTF8, //!< UTF8 + UTF16LE, //!< UTF16, Little-Endian + UTF16BE, //!< UTF16, Big-Endian + UTF32LE, //!< UTF32, Little-Endian + UTF32BE, //!< UTF32, Big-Endian +} Encoding_e; + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +C4_SUPPRESS_WARNING_MSVC_WITH_PUSH(4251) // csubstr needs to have dll-interface to be used by clients of Location + +/** holds a source or yaml file position, for example when an error is + * detected; See also @ref location_format() and @ref + * location_format_with_context(). + * + * @ingroup doc_error_handling */ +struct RYML_EXPORT Location +{ + size_t offset; ///< number of bytes from the beginning of the source buffer + size_t line; ///< line + size_t col; ///< column + csubstr name; ///< name of the file + + operator bool () const noexcept { return !name.empty() || line != npos || offset != npos || col != npos; } + + C4_NO_INLINE Location() noexcept : offset(npos), line(npos), col(npos), name() {} + C4_NO_INLINE Location( size_t l ) noexcept : offset(npos), line(l), col(npos), name() {} + C4_NO_INLINE Location( size_t l, size_t c) noexcept : offset(npos), line(l), col(c ), name() {} + C4_NO_INLINE Location( size_t b, size_t l, size_t c) noexcept : offset(b ), line(l), col(c ), name() {} + C4_NO_INLINE Location( csubstr n, size_t l ) noexcept : offset(npos), line(l), col(npos), name(n) {} + C4_NO_INLINE Location( csubstr n, size_t l, size_t c) noexcept : offset(npos), line(l), col(c ), name(n) {} + C4_NO_INLINE Location( csubstr n, size_t b, size_t l, size_t c) noexcept : offset(b ), line(l), col(c ), name(n) {} + C4_NO_INLINE Location(const char *n, size_t l ) noexcept : offset(npos), line(l), col(npos), name(to_csubstr(n)) {} + C4_NO_INLINE Location(const char *n, size_t l, size_t c) noexcept : offset(npos), line(l), col(c ), name(to_csubstr(n)) {} + C4_NO_INLINE Location(const char *n, size_t b, size_t l, size_t c) noexcept : offset(b ), line(l), col(c ), name(to_csubstr(n)) {} +}; +static_assert(std::is_standard_layout::value, "Location not trivial"); + +C4_SUPPRESS_WARNING_MSVC_POP + +/// @cond dev +#define RYML_LOC_HERE() (::c4::yml::Location(__FILE__, static_cast(__LINE__))) +/// @endcond + + +/** Data for a basic error. + * @ingroup doc_error_handling */ +struct RYML_EXPORT ErrorDataBasic +{ + Location location; ///< location where the error was detected (may be from YAML or C++ source code) + ErrorDataBasic() noexcept = default; + ErrorDataBasic(Location const& cpploc_) noexcept : location(cpploc_) {} +}; + +/** Data for a parse error. + * @ingroup doc_error_handling */ +struct RYML_EXPORT ErrorDataParse +{ + Location cpploc; ///< location in the C++ source file where the error was detected. + Location ymlloc; ///< location in the YAML source buffer where the error was detected. + ErrorDataParse() noexcept = default; + ErrorDataParse(Location const& cpploc_, Location const& ymlloc_) noexcept : cpploc(cpploc_), ymlloc(ymlloc_) {} +}; + +/** Data for a visit error. + * @ingroup doc_error_handling */ +struct RYML_EXPORT ErrorDataVisit +{ + Location cpploc; ///< location in the C++ source file where the error was detected. + Tree const* tree; ///< tree where the error was detected + id_type node; ///< node where the error was detected + ErrorDataVisit() noexcept = default; + ErrorDataVisit(Location const& cpploc_, Tree const *tree_ , id_type node_) noexcept : cpploc(cpploc_), tree(tree_), node(node_) {} +}; + + +//----------------------------------------------------------------------------- + +/** @addtogroup doc_callbacks + * + * @{ */ + +struct Callbacks; + + +/** set the global callbacks for the library; after a call to this + * function, these callbacks will be used by newly created objects + * (unless they are copying older objects with different + * callbacks). If @ref RYML_NO_DEFAULT_CALLBACKS is defined, it is + * mandatory to call this function prior to using any other library + * facility. + * + * @warning This function is NOT thread-safe, because it sets global static data + * */ +RYML_EXPORT void set_callbacks(Callbacks const& c); + +/** get the global callbacks + * + * @warning This function is NOT thread-safe, because it reads global static data + * */ +RYML_EXPORT Callbacks const& get_callbacks(); + +/** set the global callbacks back to their defaults. + * + * @warning This function is NOT thread-safe, because it sets global static data + * */ +RYML_EXPORT void reset_callbacks(); + + +/** the type of the function used to allocate memory; ryml will only + * allocate memory through this callback. */ +using pfn_allocate = void* (*)(size_t len, void* hint, void *user_data); + + +/** the type of the function used to free memory; ryml will only free + * memory through this callback. */ +using pfn_free = void (*)(void* mem, size_t size, void *user_data); + + +/** the type of the function used to report basic errors. + * + * @warning Must not return. When implemented by the user, this + * function MUST interrupt execution (and ideally be marked with + * `[[noreturn]]`). If the function returned, the caller could enter + * into an infinite loop, or the program may crash. It is up to the + * user to choose the interruption mechanism; typically by either + * throwing an exception, or using `std::longjmp()` ([see + * documentation](https://en.cppreference.com/w/cpp/utility/program/setjmp)) + * or ultimately by calling `std::abort()`. */ +using pfn_error_basic = void (*) (csubstr msg, ErrorDataBasic const& errdata, void *user_data); +/** the type of the function used to report parse errors. + * + * @warning Must not return. When implemented by the user, this + * function MUST interrupt execution (and ideally be marked with + * `[[noreturn]]`). If the function returned, the caller could enter + * into an infinite loop, or the program may crash. It is up to the + * user to choose the interruption mechanism; typically by either + * throwing an exception, or using `std::longjmp()` ([see + * documentation](https://en.cppreference.com/w/cpp/utility/program/setjmp)) + * or ultimately by calling `std::abort()`. */ +using pfn_error_parse = void (*) (csubstr msg, ErrorDataParse const& errdata, void *user_data); +/** the type of the function used to report visit errors. + * + * @warning Must not return. When implemented by the user, this + * function MUST interrupt execution (and ideally be marked with + * `[[noreturn]]`). If the function returned, the caller could enter + * into an infinite loop, or the program may crash. It is up to the + * user to choose the interruption mechanism; typically by either + * throwing an exception, or using `std::longjmp()` ([see + * documentation](https://en.cppreference.com/w/cpp/utility/program/setjmp)) + * or ultimately by calling `std::abort()`. */ +using pfn_error_visit = void (*) (csubstr msg, ErrorDataVisit const& errdata, void *user_data); + +/// @cond dev +using pfn_error RYML_DEPRECATED("use a more specific error type: `basic`, `parse` or `visit`") = void (*) (const char* msg, size_t msg_len, Location const& cpploc, void *user_data); +/// @endcond + + +/** A c-style callbacks class to customize behavior on errors or + * allocation. Can be used globally by the library and/or locally by + * @ref Tree and @ref Parser objects. */ +struct RYML_EXPORT Callbacks +{ + void * m_user_data; ///< data to be forwarded in every call to a callback + pfn_allocate m_allocate; ///< a pointer to an allocate handler function + pfn_free m_free; ///< a pointer to a free handler function + pfn_error_basic m_error_basic; ///< a pointer to a basic error handler function + pfn_error_parse m_error_parse; ///< a pointer to a parse error handler function + pfn_error_visit m_error_visit; ///< a pointer to a visit error handler function + +public: + + /** Construct an object with the default callbacks. If + * @ref RYML_NO_DEFAULT_CALLBACKS is defined, the object will be set with null + * members.*/ + Callbacks() noexcept; + + RYML_DEPRECATED("use the default constructor, followed by the appropriate setters") + Callbacks(void *user_data, pfn_allocate alloc, pfn_free free, pfn_error_basic error_basic); + +public: + + /** Set the user data. */ + Callbacks& set_user_data(void* user_data); + + /** Set or reset the allocate callback. When the parameter is + * null, m_allocate will fall back to ryml's default allocate + * implementation, unless @ref RYML_NO_DEFAULT_CALLBACKS is + * defined. */ + Callbacks& set_allocate(pfn_allocate allocate=nullptr); + + /** Set or reset the free callback. When the parameter is null, + * m_free will fall back to ryml's default free implementation, + * unless @ref RYML_NO_DEFAULT_CALLBACKS is defined. */ + Callbacks& set_free(pfn_free free=nullptr); + + /** Set or reset the error_basic callback. When the parameter is null, + * m_error_basic will fall back to ryml's default error_basic implementation, + * unless @ref RYML_NO_DEFAULT_CALLBACKS is defined. */ + Callbacks& set_error_basic(pfn_error_basic error_basic=nullptr); + + /** Set or reset the error_parse callback. When the parameter is null, + * m_error_parse will fall back to ryml's default error_parse implementation, + * unless @ref RYML_NO_DEFAULT_CALLBACKS is defined. */ + Callbacks& set_error_parse(pfn_error_parse error_parse=nullptr); + + /** Set or reset the error_visit callback. When the parameter is null, + * m_error_visit will fall back to ryml's default error_visit implementation, + * unless @ref RYML_NO_DEFAULT_CALLBACKS is defined. */ + Callbacks& set_error_visit(pfn_error_visit error_visit=nullptr); + +public: + + bool operator!= (Callbacks const& that) const { return !operator==(that); } + bool operator== (Callbacks const& that) const + { + return (m_user_data == that.m_user_data && + m_allocate == that.m_allocate && + m_free == that.m_free && + m_error_basic == that.m_error_basic && + m_error_parse == that.m_error_parse && + m_error_visit == that.m_error_visit); + } +}; + + +/** @} */ + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/// @cond dev + +#define _RYML_CB_ALLOC_HINT(cb, T, num, hint) (T*) (cb).m_allocate((num) * sizeof(T), (hint), (cb).m_user_data) +#define _RYML_CB_ALLOC(cb, T, num) _RYML_CB_ALLOC_HINT((cb), T, (num), nullptr) +#define _RYML_CB_FREE(cb, buf, T, num) \ + do { \ + (cb).m_free((buf), (num) * sizeof(T), (cb).m_user_data); \ + (buf) = nullptr; \ + } while(false) + +namespace detail { +template +struct _charconstant_t // is there a better way to do this? + : public std::conditional::value, + std::integral_constant(unsignedval)>, + std::integral_constant>::type +{}; +#define _RYML_CHCONST(signedval, unsignedval) ::c4::yml::detail::_charconstant_t::value +} // namespace detail + +inline csubstr _c4prc(const char &C4_RESTRICT c) // pass by reference! +{ + switch(c) + { + case '\n': return csubstr("\\n"); + case '\t': return csubstr("\\t"); + case '\0': return csubstr("\\0"); + case '\r': return csubstr("\\r"); + case '\f': return csubstr("\\f"); + case '\b': return csubstr("\\b"); + case '\v': return csubstr("\\v"); + case '\a': return csubstr("\\a"); + default: return csubstr(&c, 1); + } +} + +/// @endcond + +C4_SUPPRESS_WARNING_GCC_POP + +} // namespace yml +} // namespace c4 + +#endif /* _C4_YML_COMMON_HPP_ */ + + +// (end src/c4/yml/common.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/error.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_YML_ERROR_HPP_ +#define _C4_YML_ERROR_HPP_ + +/** @file error.hpp Error utilities used by ryml. */ + +#ifndef _C4_YML_COMMON_HPP_ +// amalgamate: removed include of +// c4/yml/common.hpp +//#include +#if !defined(C4_YML_COMMON_HPP_) && !defined(_C4_YML_COMMON_HPP_) +#error "amalgamate: file c4/yml/common.hpp must have been included at this point" +#endif /* C4_YML_COMMON_HPP_ */ + +#endif + +//included above: +//#include + +/// @cond dev +#if (defined(C4_EXCEPTIONS) && (!defined(RYML_NO_DEFAULT_CALLBACKS) && defined(RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS))) || defined(__DOXYGEN__) +#define _RYML_WITH_EXCEPTIONS +#endif +#if defined(RYML_DBG) && !defined(NDEBUG) && !defined(C4_NO_DEBUG_BREAK) +# define RYML_DEBUG_BREAK() \ + do { \ + if(c4::get_error_flags() & c4::ON_ERROR_DEBUGBREAK) \ + { \ + C4_DEBUG_BREAK(); \ + } \ + } while(false) +#else +# define RYML_DEBUG_BREAK() +#endif +/// @endcond + +#ifdef _RYML_WITH_EXCEPTIONS +//included above: +//#include +#endif + + +namespace c4 { +namespace yml { + + +/// @cond dev + +namespace detail { +struct _SubstrWriter +{ + substr buf; + size_t pos; + _SubstrWriter(substr buf_, size_t pos_=0) : buf(buf_), pos(pos_) { C4_ASSERT(buf.str); } + void append(csubstr s) + { + C4_ASSERT(!s.overlaps(buf)); + C4_ASSERT(s.str || !s.len); + if(s.len && pos + s.len <= buf.len) + { + C4_ASSERT(s.str); + memcpy(buf.str + pos, s.str, s.len); + } + pos += s.len; + } + void append(char c) + { + C4_ASSERT(buf.str); + if(pos < buf.len) + buf.str[pos] = c; + ++pos; + } + void append_n(char c, size_t numtimes) + { + C4_ASSERT(buf.str); + if(numtimes && pos + numtimes < buf.len) + memset(buf.str + pos, c, numtimes); + pos += numtimes; + } + size_t slack() const { return pos <= buf.len ? buf.len - pos : 0; } + size_t excess() const { return pos > buf.len ? pos - buf.len : 0; } + //! get the part written so far + csubstr curr() const { return pos <= buf.len ? buf.first(pos) : buf; } + //! get the part that is still free to write to (the remainder) + substr rem() const { return pos < buf.len ? buf.sub(pos) : buf.last(0); } + + size_t advance(size_t more) { pos += more; return pos; } +}; + + +// truncate the result to the buffer, adding ellipsis if it +// doesn't fit +inline C4_NO_INLINE csubstr _maybe_add_ellipsis(substr buf, size_t len) +{ + if(C4_UNLIKELY(len > buf.len)) + { + const size_t numdots = (buf.len > 3) ? 3 : buf.len; + buf.last(numdots).fill('.'); + len = buf.len; + } + return buf.first(len); +} + + +// std::remove_cvref appeared in c++20 +template +struct _remove_cvref +{ + using type = typename std::remove_cv::type>::type; +}; +template +struct _dump_directly : public c4::is_string::type> +{ +}; + + +#if C4_CPP >= 17 +template using _remove_cvref_t = typename _remove_cvref::type; +template using _dump_directly_v = typename _dump_directly::value; +template +C4_NO_INLINE csubstr _to_chars_limited(substr buf, T &&var) +{ + if constexpr (_dump_directly::value) + { + (void)buf; + return to_csubstr(std::forward(var)); // no need to convert to buf + } + else + { + size_t len = to_chars(buf, std::forward(var)); + return _maybe_add_ellipsis(buf, len); + } +} +#else +template +C4_NO_INLINE auto _to_chars_limited(substr, T &&var) + -> typename std::enable_if<_dump_directly::value, csubstr>::type +{ + return to_csubstr(std::forward(var)); // no need to convert to buf +} +template +C4_NO_INLINE auto _to_chars_limited(substr buf, T &&var) + -> typename std::enable_if< ! _dump_directly::value, csubstr>::type +{ + size_t len = to_chars(buf, std::forward(var)); + return _maybe_add_ellipsis(buf, len); +} +#endif + + +// dumpfn is a function abstracting prints to terminal (or to string). +template +C4_NO_INLINE void _dump(DumpFn &&dumpfn, substr, csubstr fmt) +{ + std::forward(dumpfn)(fmt); +} +template +C4_NO_INLINE void _dump(DumpFn &&dumpfn, substr argbuf, csubstr fmt, Arg const& arg, Args const& ...more) +{ + size_t pos = fmt.find("{}"); + if(pos == csubstr::npos) + return std::forward(dumpfn)(fmt); // NOLINT // LCOV_EXCL_LINE + std::forward(dumpfn)(fmt.first(pos)); // NOLINT + std::forward(dumpfn)(_to_chars_limited(argbuf, arg)); // NOLINT + _dump(std::forward(dumpfn), argbuf, fmt.sub(pos + 2), more...); // NOLINT +} + + +template +C4_NO_INLINE csubstr _mk_err_msg(substr buf, csubstr fmt, Args const& ...args) +{ + detail::_SubstrWriter writer(buf); + auto dumpfn = [&writer](csubstr s){ writer.append(s); }; + char writebuf[RYML_LOGBUF_SIZE]; + _dump(dumpfn, writebuf, fmt, args...); + return _maybe_add_ellipsis(buf, writer.pos); +} + +RYML_EXPORT csubstr _get_text_region(csubstr text, size_t pos, size_t num_lines_before, size_t num_lines_after); + +} // namespace detail + +/// @endcond + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** @addtogroup doc_error_handling + * + * @{ */ + + +/** generic formatting of a location + * + * @param dumpfn function taking a csubstr and abstracting a string + * concatenation operation, such as appending to a std::string or + * printing to terminal. + * @param loc the location to be formatted + * + * For example: + * + * ```c++ + * /// to output to std::cerr: + * location_format([&s](csubstr s){ + * std::cerr.write(s.str, s.len); + * }, loc); + * + * /// to build a string: + * std::string msg; + * location_format([&s](csubstr s){ + * msg.append(s.str, s.len); + * }, loc); + * ``` + */ +template +C4_NO_INLINE size_t location_format(DumpFn &&dumpfn, Location const& loc); + + +/** Generic formatting of a location, printing the source code buffer + * region around the location. + * + * @param dumpfn function taking a csubstr and abstracting a string + * concatenation operation, such as appending to a std::string or + * printing to terminal. + * @param location the location + * @param source_buffer the source buffer + * @param call a string with a call of attention to print in the + * message (see examples below) + * @param num_lines_before how many source buffer lines to print + * before the location line + * @param num_lines_after how many source buffer lines to print + * after the location line + * @param first_col_highlight the first column to highlight + * around the location line + * @param last_col_highlight the last column to highlight + * around the location line + * @param maxlen the maximum number of columns to show in the error + * message; source buffer lines will have at most this number + * of columns shown; if the line is longer than this, the line + * will be trimmed as needed at the end and/or beginning, and + * only the relevant columns *around* the location are shown + * + * For example: + * + * ```c++ + * std::string out; + * auto dumpfn = [&out](csubstr s){ out.append(s.str, s.len); }; + * format_location_with_context(dumpfn, location, source, "error"); + * ``` + * + * will result in this string: + * + * ``` + * file.yaml:3: col=3 (11B): error: + * error: + * error: ccc + * error: | + * error: (here) + * error: + * error: see region: + * error: + * error: aaa + * error: bbb + * error: ccc + * error: | + * error: (here) + * ``` + * + * If an empty string is passed for the call of attention, + * + * ```c++ + * format_location_with_context(dumpfn, location, source); + * ``` + * + * the returned string becomes: + * + * ``` + * file.yaml:3: col=3 (11B): ccc + * | + * (here) + * file.yaml:3: col=3 (11B): see region: + * aaa + * bbb + * ccc + * | + * (here) + * ``` + */ +template +C4_NO_INLINE void location_format_with_context(DumpFn &&dumpfn, + Location const &location, + csubstr source_buffer, + csubstr call = "", + size_t num_lines_before = 3, + size_t num_lines_after = 0, + size_t first_col_highlight = 0, + size_t last_col_highlight = 0, + size_t maxlen = 80u); + + +//----------------------------------------------------------------------------- + +/** Given an error message and associated basic error data, format it fully as a basic error message. + * + * @param dumpfn function taking a csubstr and abstracting a string + * concatenation operation, such as appending to a std::string or + * printing to terminal. + * @param msg the error message + * @param errdata the error data + * + * For example: + * + * ```c++ + * /// to output to cerr: + * err_basic_format([](csubstr s){ + * std::cerr.write(s.str, s.len); + * }, errmsg, errdata); + * + * /// to build a string: + * std::string msg; + * error_basic_format([&msg](csubstr s){ + * msg.append(s.str, s.len); + * }, errmsg, errdata); + * ``` + */ +template +C4_NO_INLINE void err_basic_format(DumpFn &&dumpfn, csubstr msg, ErrorDataBasic const& errdata); + +/** trigger a basic error to its respective handler, with a non-formatted error message. */ +C4_NORETURN RYML_EXPORT C4_NO_INLINE void err_basic(Callbacks const& callbacks, ErrorDataBasic const& errdata, const char* msg_); +/** trigger a basic error to its respective handler, with a non-formatted error message. Like (1), but use the current global callbacks. */ +C4_NORETURN RYML_EXPORT C4_NO_INLINE void err_basic(ErrorDataBasic const& errdata, const char* msg); +/** trigger a basic error to its respective handler, with a formatted error message. */ +template +C4_NORETURN C4_NO_INLINE void err_basic(Callbacks const& callbacks, ErrorDataBasic const& errdata, const char *fmt, Args const& ...args) +{ + char errbuf[RYML_ERRMSG_SIZE]; + csubstr msg = detail::_mk_err_msg(errbuf, to_csubstr(fmt), args...); + callbacks.m_error_basic(msg, errdata, callbacks.m_user_data); + std::abort(); // the call above should not return, but force it here in case it does // LCOV_EXCL_LINE + C4_UNREACHABLE_AFTER_ERR(); +} +/** trigger a basic error to its respective handler, with a formatted error message. Like (1), but use the current global callbacks. */ +template +C4_NORETURN C4_NO_INLINE void err_basic(ErrorDataBasic const& errdata, const char *fmt, Args const& ...args) +{ + err_basic(get_callbacks(), errdata, fmt, args...); + C4_UNREACHABLE_AFTER_ERR(); +} + + +/** Given an error message and associated parse error data, format it fully as a parse error message. + * + * @param dumpfn function taking a csubstr and abstracting a string + * concatenation operation, such as appending to a std::string or + * printing to terminal. + * @param msg the error message + * @param errdata the error data + * + * For example: + * + * ```c++ + * /// to output to cerr: + * /// this is what err_parse_print() does + * err_parse_format([](csubstr s){ + * std::cerr.write(s.str, s.len); + * }, errmsg, errdata); + * + * /// to build a string: + * std::string msg; + * err_parse_format([](csubstr s){ + * s.append(s.str, s.len); + * }, errmsg, errdata); + * ``` + * + * @note if the (preferably original) source buffer is kept, @ref + * location_format_with_context() can be used to also an additional + * rich error message showing the YAML source buffer region around + * that location. + */ +template +C4_NO_INLINE void err_parse_format(DumpFn &&dumpfn, csubstr msg, ErrorDataParse const& errdata); + +/** trigger a parse error to its respective handler, with a non-formatted error message */ +C4_NORETURN RYML_EXPORT C4_NO_INLINE void err_parse(Callbacks const& callbacks, ErrorDataParse const& errdata, const char *msg); +/** trigger a parse error to its respective handler, with a non-formatted error message. Like (1), but use the current global callbacks. */ +C4_NORETURN RYML_EXPORT C4_NO_INLINE void err_parse(ErrorDataParse const& errdata, const char *msg); +/** trigger a parse error to its respective handler, with a formatted error message */ +template +C4_NORETURN C4_NO_INLINE void err_parse(Callbacks const& callbacks, ErrorDataParse const& errdata, const char *fmt, Args const& ...args) +{ + char errbuf[RYML_ERRMSG_SIZE]; + csubstr msg = detail::_mk_err_msg(errbuf, to_csubstr(fmt), args...); + if(callbacks.m_error_parse) + callbacks.m_error_parse(msg, errdata, callbacks.m_user_data); + // fall to basic error if there is no parse handler set, but use errdata.ymlloc instead of errdata.cpploc + else if(callbacks.m_error_basic) + callbacks.m_error_basic(msg, errdata.ymlloc, callbacks.m_user_data); + std::abort(); // the call above should not return, so force it here in case it does // LCOV_EXCL_LINE + C4_UNREACHABLE_AFTER_ERR(); +} +/** trigger a parse error to its respective handler, with a formatted error message. Like (1), but use the current global callbacks. */ +template +C4_NORETURN C4_NO_INLINE void err_parse(ErrorDataParse const& errdata, const char *fmt, Args const& ...args) +{ + err_parse(get_callbacks(), errdata, fmt, args...); + C4_UNREACHABLE_AFTER_ERR(); +} + + +/** Given an error message and associated visit error data, format it + * fully as a visit error message. + * + * @param dumpfn function taking a csubstr and abstracting a string + * concatenation operation, such as appending to a std::string or + * printing to terminal. + * @param msg the error message + * @param errdata the error data + * + * For example: + * + * ```c++ + * /// to output to cerr: + * err_visit_format([](csubstr s){ + * std::cerr.write(s.str, s.len); + * }, errmsg, errdata); + * + * /// to build a string: + * std::string msg; + * err_visit_format([&msg](csubstr s){ + * msg.append(s.str, s.len); + * }, errmsg, errdata); + * + * @note under certain conditions, it is possible to obtain an + * associated location, and subsequently use @ref + * location_format_with_context() to also create a rich error message + * showing the YAML source buffer region around that location. This is + * possible if the (preferably original) source buffer is kept, and + * the node location can be retrieved from the parser. + * ``` + */ +template +C4_NO_INLINE void err_visit_format(DumpFn &&dumpfn, csubstr msg, ErrorDataVisit const& errdata); + + +/** trigger a visit error to its respective handler, with a non-formatted error message */ +C4_NORETURN RYML_EXPORT C4_NO_INLINE void err_visit(Callbacks const& callbacks, ErrorDataVisit const& errdata, const char *msg); +/** trigger a visit error to its respective handler, with a non-formatted error message. Like (1), but uses the current global callbacks. */ +C4_NORETURN RYML_EXPORT C4_NO_INLINE void err_visit(ErrorDataVisit const& errdata, const char *msg); +/** trigger a visit error to its respective handler, with a formatted error message */ +template +C4_NORETURN C4_NO_INLINE void err_visit(Callbacks const& callbacks, ErrorDataVisit const& errdata, const char *fmt, Args const& ...args) +{ + char errbuf[RYML_ERRMSG_SIZE]; + csubstr msg = detail::_mk_err_msg(errbuf, to_csubstr(fmt), args...); + if(callbacks.m_error_visit) + callbacks.m_error_visit(msg, errdata, callbacks.m_user_data); + // fall to basic error if there is no visit handler set + else if(callbacks.m_error_basic) + callbacks.m_error_basic(msg, errdata.cpploc, callbacks.m_user_data); + std::abort(); // the call above should not return, so force it here in case it does // LCOV_EXCL_LINE + C4_UNREACHABLE_AFTER_ERR(); +} +/** trigger a visit error to its respective handler, with a formatted error message. Like (1), but use the current global callbacks. */ +template +C4_NORETURN C4_NO_INLINE void err_visit(ErrorDataVisit const& errdata, const char *fmt, Args const& ...args) +{ + err_visit(get_callbacks(), errdata, fmt, args...); + C4_UNREACHABLE_AFTER_ERR(); +} + + +//----------------------------------------------------------------------------- + +#if defined(_RYML_WITH_EXCEPTIONS) || defined(__DOXYGEN__) + +/** Exception thrown by the default basic error implementation. To + * obtain the full error message, use @ref err_basic_format(), or the + * helper @ref format_exc(). + * + * @note Available only if @ref + * RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS is defined, and @ref + * RYML_NO_DEFAULT_CALLBACKS is NOT defined. */ +struct RYML_EXPORT ExceptionBasic : public std::exception +{ + ExceptionBasic(csubstr msg, ErrorDataBasic const& errdata_) noexcept; + const char* what() const noexcept override { return msg; } + ErrorDataBasic errdata_basic; ///< error data + char msg[RYML_ERRMSG_SIZE]; ///< the reported error message, without location indication. +}; + + +/** Exception thrown by the default parse error implementation. To + * obtain the full error message containing context, use @ref + * err_parse_format(), or the helper @ref format_exc(). + * + * @note This exception derives from @ref ExceptionBasic and can be + * catched using either type. + * + * @note Available only if @ref + * RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS is defined, and @ref + * RYML_NO_DEFAULT_CALLBACKS is NOT defined. */ +struct RYML_EXPORT ExceptionParse : public ExceptionBasic +{ + ExceptionParse(csubstr msg, ErrorDataParse const& errdata_) noexcept; + ErrorDataParse errdata_parse; +}; + + +/** Exception thrown by the default visit error implementation. To + * obtain the full error message containing context, use @ref + * err_visit_format(), or the helper @ref format_exc(). + * + * @note This exception derives from @ref ExceptionBasic and can be + * catched using either type. + * + * @note Available only if @ref + * RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS is defined, and @ref + * RYML_NO_DEFAULT_CALLBACKS is NOT defined. */ +struct RYML_EXPORT ExceptionVisit : public ExceptionBasic +{ + ExceptionVisit(csubstr msg, ErrorDataVisit const& errdata_) noexcept; + ErrorDataVisit errdata_visit; +}; + + +/** Format a basic exception to an existing char container + * + * @note Available only if @ref + * RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS is defined, and @ref + * RYML_NO_DEFAULT_CALLBACKS is NOT defined. */ +template +void format_exc(CharContainer *out, ExceptionBasic const& exc) +{ + out->clear(); + err_basic_format([out](csubstr s){ + out->append(s.str, s.len); + }, csubstr{exc.msg, strlen(exc.msg)}, exc.errdata_basic); +} +/** Format a parse exception to an existing char container + * + * @note Available only if @ref + * RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS is defined, and @ref + * RYML_NO_DEFAULT_CALLBACKS is NOT defined. */ +template +void format_exc(CharContainer *out, ExceptionParse const& exc) +{ + out->clear(); + err_parse_format([out](csubstr s){ + out->append(s.str, s.len); + }, csubstr{exc.msg, strlen(exc.msg)}, exc.errdata_parse); +} +/** Format a visit exception to an existing char container + * + * @note Available only if @ref + * RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS is defined, and @ref + * RYML_NO_DEFAULT_CALLBACKS is NOT defined. */ +template +void format_exc(CharContainer *out, ExceptionVisit const& exc) +{ + out->clear(); + err_visit_format([out](csubstr s){ + out->append(s.str, s.len); + }, csubstr{exc.msg, strlen(exc.msg)}, exc.errdata_visit); +} +/** Format a parse exception, and return a newly-created char + * container + * + * @note Available only if @ref + * RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS is defined, and @ref + * RYML_NO_DEFAULT_CALLBACKS is NOT defined. */ +template +CharContainer format_exc(ExceptionT const& exc) +{ + CharContainer str; + format_exc(&str, exc); + return str; +} + +#endif // _RYML_WITH_EXCEPTIONS + +/** @} */ + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/// @cond dev + + +#if RYML_USE_ASSERT +# define _RYML_ASSERT_BASIC(cond) _RYML_CHECK_BASIC(cond) +# define _RYML_ASSERT_BASIC_(cb, cond) _RYML_CHECK_BASIC_((cb), cond) +# define _RYML_ASSERT_BASIC_MSG(cond, ...) _RYML_CHECK_BASIC_MSG(cond, __VA_ARGS__) +# define _RYML_ASSERT_BASIC_MSG_(cb, cond, ...) _RYML_CHECK_BASIC_MSG_((cb), cond, __VA_ARGS__) +# define _RYML_ASSERT_PARSE(cond, ymlloc) _RYML_CHECK_PARSE(cond, (ymlloc)) +# define _RYML_ASSERT_PARSE_(cb, cond, ymlloc) _RYML_CHECK_PARSE_((cb), cond, (ymlloc)) +# define _RYML_ASSERT_PARSE_MSG(cond, ymlloc, ...) _RYML_CHECK_PARSE_MSG(cond, (ymlloc), __VA_ARGS__) +# define _RYML_ASSERT_PARSE_MSG_(cb, cond, ymlloc, ...) _RYML_CHECK_PARSE_MSG_((cb), cond, (ymlloc), __VA_ARGS__) +# define _RYML_ASSERT_VISIT(cond, tree, node) _RYML_CHECK_VISIT(cond, (tree), (node)) +# define _RYML_ASSERT_VISIT_(cb, cond, tree, node) _RYML_CHECK_VISIT_((cb), cond, (tree), (node)) +# define _RYML_ASSERT_VISIT_MSG(cond, tree, node, ...) _RYML_CHECK_VISIT_MSG(cond, (tree), (node), __VA_ARGS__) +# define _RYML_ASSERT_VISIT_MSG_(cb, cond, tree, node, ...) _RYML_CHECK_VISIT_MSG_((cb), cond, (tree), (node), __VA_ARGS__) +#else +# define _RYML_ASSERT_BASIC(cond) +# define _RYML_ASSERT_BASIC_(cb, cond) +# define _RYML_ASSERT_BASIC_MSG(cond, ...) +# define _RYML_ASSERT_BASIC_MSG_(cb, cond, ...) +# define _RYML_ASSERT_PARSE(cond, ymlloc) +# define _RYML_ASSERT_PARSE_(cb, cond, ymlloc) +# define _RYML_ASSERT_PARSE_MSG(cond, ymlloc, ...) +# define _RYML_ASSERT_PARSE_MSG_(cb, cond, ymlloc, ...) +# define _RYML_ASSERT_VISIT(cont, tree, node) +# define _RYML_ASSERT_VISIT_(cb, cont, tree, node) +# define _RYML_ASSERT_VISIT_MSG(cont, tree, node, ...) +# define _RYML_ASSERT_VISIT_MSG_(cb, cont, tree, node, ...) +#endif + +#define _RYML_ERR_BASIC(...) \ + do \ + { \ + RYML_DEBUG_BREAK(); \ + ::c4::yml::err_basic((::c4::yml::ErrorDataBasic{RYML_LOC_HERE()}), __VA_ARGS__); \ + C4_UNREACHABLE_AFTER_ERR(); \ + } while(false) +#define _RYML_ERR_PARSE(ymlloc, ...) \ + do \ + { \ + RYML_DEBUG_BREAK(); \ + ::c4::yml::err_parse((::c4::yml::ErrorDataParse{RYML_LOC_HERE(), ymlloc}), __VA_ARGS__); \ + C4_UNREACHABLE_AFTER_ERR(); \ + } while(false) +#define _RYML_ERR_VISIT(tree, node, ...) \ + do \ + { \ + RYML_DEBUG_BREAK(); \ + ::c4::yml::err_visit((::c4::yml::ErrorDataVisit{RYML_LOC_HERE(), tree, node}), __VA_ARGS__); \ + C4_UNREACHABLE_AFTER_ERR(); \ + } while(false) + + +#define _RYML_ERR_BASIC_(cb, ...) \ + do \ + { \ + RYML_DEBUG_BREAK(); \ + ::c4::yml::err_basic((cb), (::c4::yml::ErrorDataBasic{RYML_LOC_HERE()}), __VA_ARGS__); \ + C4_UNREACHABLE_AFTER_ERR(); \ + } while(false) +#define _RYML_ERR_PARSE_(cb, ymlloc, ...) \ + do \ + { \ + RYML_DEBUG_BREAK(); \ + ::c4::yml::err_parse((cb), (::c4::yml::ErrorDataParse{RYML_LOC_HERE(), ymlloc}), __VA_ARGS__); \ + C4_UNREACHABLE_AFTER_ERR(); \ + } while(false) +#define _RYML_ERR_VISIT_(cb, tree, node, ...) \ + do \ + { \ + RYML_DEBUG_BREAK(); \ + ::c4::yml::err_visit((cb), (::c4::yml::ErrorDataVisit{RYML_LOC_HERE(), tree, node}), __VA_ARGS__); \ + C4_UNREACHABLE_AFTER_ERR(); \ + } while(false) + + +#ifndef RYML_SHORT_CHECK_MSG +#define _RYML_MAYBE_MSG(cond) ": " #cond +#define _RYML_MAYBE_MSG_(cond) ": " #cond ": " +#else +#define _RYML_MAYBE_MSG(cond) +#define _RYML_MAYBE_MSG_(cond) ": " +#endif + +#define _RYML_CHECK_BASIC(cond) \ + do { \ + if(C4_UNLIKELY(!(cond))) \ + { \ + RYML_DEBUG_BREAK(); \ + ::c4::yml::err_basic((::c4::yml::ErrorDataBasic{RYML_LOC_HERE()}), "check failed" _RYML_MAYBE_MSG(cond)); \ + C4_UNREACHABLE_AFTER_ERR(); \ + } \ + } while(false) +#define _RYML_CHECK_PARSE(cond, ymlloc) \ + do { \ + if(C4_UNLIKELY(!(cond))) \ + { \ + RYML_DEBUG_BREAK(); \ + ::c4::yml::err_parse((::c4::yml::ErrorDataParse{RYML_LOC_HERE(), ymlloc}), "check failed" _RYML_MAYBE_MSG(cond)); \ + C4_UNREACHABLE_AFTER_ERR(); \ + } \ + } while(false) +#define _RYML_CHECK_VISIT(cond, tree, node) \ + do { \ + if(C4_UNLIKELY(!(cond))) \ + { \ + RYML_DEBUG_BREAK(); \ + ::c4::yml::err_visit((::c4::yml::ErrorDataVisit{RYML_LOC_HERE(), tree, node}), "check failed" _RYML_MAYBE_MSG(cond)); \ + C4_UNREACHABLE_AFTER_ERR(); \ + } \ + } while(false) + + +#define _RYML_CHECK_BASIC_(cb, cond) \ + do { \ + if(C4_UNLIKELY(!(cond))) \ + { \ + RYML_DEBUG_BREAK(); \ + ::c4::yml::err_basic((cb), (::c4::yml::ErrorDataBasic{RYML_LOC_HERE()}), "check failed" _RYML_MAYBE_MSG(cond)); \ + C4_UNREACHABLE_AFTER_ERR(); \ + } \ + } while(false) +#define _RYML_CHECK_PARSE_(cb, cond, ymlloc) \ + do { \ + if(C4_UNLIKELY(!(cond))) \ + { \ + RYML_DEBUG_BREAK(); \ + ::c4::yml::err_parse((cb), (::c4::yml::ErrorDataParse{RYML_LOC_HERE(), ymlloc}), "check failed" _RYML_MAYBE_MSG(cond)); \ + C4_UNREACHABLE_AFTER_ERR(); \ + } \ + } while(false) +#define _RYML_CHECK_VISIT_(cb, cond, tree, node) \ + do { \ + if(C4_UNLIKELY(!(cond))) \ + { \ + RYML_DEBUG_BREAK(); \ + ::c4::yml::err_visit((cb), (::c4::yml::ErrorDataVisit{RYML_LOC_HERE(), tree, node}), "check failed" _RYML_MAYBE_MSG(cond)); \ + C4_UNREACHABLE_AFTER_ERR(); \ + } \ + } while(false) + + +#define _RYML_CHECK_BASIC_MSG(cond, ...) \ + do { \ + if(C4_UNLIKELY(!(cond))) \ + { \ + RYML_DEBUG_BREAK(); \ + ::c4::yml::err_basic((::c4::yml::ErrorDataBasic{RYML_LOC_HERE()}), "check failed" _RYML_MAYBE_MSG_(cond) __VA_ARGS__); \ + C4_UNREACHABLE_AFTER_ERR(); \ + } \ + } while(false) +#define _RYML_CHECK_PARSE_MSG(cond, ymlloc, ...) \ + do { \ + if(C4_UNLIKELY(!(cond))) \ + { \ + RYML_DEBUG_BREAK(); \ + ::c4::yml::err_parse((::c4::yml::ErrorDataParse{RYML_LOC_HERE(), ymlloc}), "check failed" _RYML_MAYBE_MSG_(cond) __VA_ARGS__); \ + C4_UNREACHABLE_AFTER_ERR(); \ + } \ + } while(false) +#define _RYML_CHECK_VISIT_MSG(cond, tree, node, ...) \ + do { \ + if(C4_UNLIKELY(!(cond))) \ + { \ + RYML_DEBUG_BREAK(); \ + ::c4::yml::err_visit((::c4::yml::ErrorDataVisit{RYML_LOC_HERE(), tree, node}), "check failed" _RYML_MAYBE_MSG_(cond) __VA_ARGS__); \ + C4_UNREACHABLE_AFTER_ERR(); \ + } \ + } while(false) + + +#define _RYML_CHECK_BASIC_MSG_(cb, cond, ...) \ + do { \ + if(C4_UNLIKELY(!(cond))) \ + { \ + RYML_DEBUG_BREAK(); \ + ::c4::yml::err_basic((cb), (::c4::yml::ErrorDataBasic{RYML_LOC_HERE()}), "check failed" _RYML_MAYBE_MSG_(cond) __VA_ARGS__); \ + C4_UNREACHABLE_AFTER_ERR(); \ + } \ + } while(false) +#define _RYML_CHECK_PARSE_MSG_(cb, cond, ymlloc, ...) \ + do { \ + if(C4_UNLIKELY(!(cond))) \ + { \ + RYML_DEBUG_BREAK(); \ + ::c4::yml::err_parse((cb), (::c4::yml::ErrorDataParse{RYML_LOC_HERE(), ymlloc}), "check failed" _RYML_MAYBE_MSG_(cond) __VA_ARGS__); \ + C4_UNREACHABLE_AFTER_ERR(); \ + } \ + } while(false) +#define _RYML_CHECK_VISIT_MSG_(cb, cond, tree, node, ...) \ + do { \ + if(C4_UNLIKELY(!(cond))) \ + { \ + RYML_DEBUG_BREAK(); \ + ::c4::yml::err_visit((cb), (::c4::yml::ErrorDataVisit{RYML_LOC_HERE(), tree, node}), "check failed" _RYML_MAYBE_MSG_(cond) __VA_ARGS__); \ + C4_UNREACHABLE_AFTER_ERR(); \ + } \ + } while(false) + +/// @endcond + +} // namespace yml +} // namespace c4 + +#endif /* _C4_YML_ERROR_HPP_ */ + + +// (end src/c4/yml/error.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/error.def.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_YML_ERROR_DEF_HPP_ +#define _C4_YML_ERROR_DEF_HPP_ + +/** @file error.def.hpp Definitions of error utilities used by ryml. */ + +#ifndef _C4_YML_ERROR_HPP_ +// amalgamate: removed include of +// c4/yml/error.hpp +//#include +#if !defined(C4_YML_ERROR_HPP_) && !defined(_C4_YML_ERROR_HPP_) +#error "amalgamate: file c4/yml/error.hpp must have been included at this point" +#endif /* C4_YML_ERROR_HPP_ */ + +#endif + +//included above: +//#include + +// NOLINTBEGIN(bugprone-use-after-move,hicpp-invalid-access-moved) + +namespace c4 { +namespace yml { + +template +C4_NO_INLINE size_t location_format(DumpFn &&dumpfn, Location const& loc) +{ + if(!loc) + return 0; + char buf_[32]; + substr buf(buf_); + size_t count = 0; + if(!loc.name.empty()) + { + std::forward(dumpfn)(loc.name); + std::forward(dumpfn)(":"); + count += loc.name.len + 1; + } + if(loc.line != npos) + { + csubstr val = detail::_to_chars_limited(buf, loc.line); + if(loc.name.empty()) + { + std::forward(dumpfn)("line="); + std::forward(dumpfn)(val); + if(loc.col == npos) + { + std::forward(dumpfn)(":"); + ++count; + } + count += val.len + 5; + } + else + { + std::forward(dumpfn)(val); + std::forward(dumpfn)(":"); + count += val.len + 1; + } + } + if(loc.col != npos) + { + csubstr val = detail::_to_chars_limited(buf, loc.col); + if(loc.line != npos || !loc.name.empty()) + { + std::forward(dumpfn)(" "); + ++count; + } + std::forward(dumpfn)("col="); + std::forward(dumpfn)(val); + count += val.len + 4; + if(loc.offset == npos) + { + std::forward(dumpfn)(":"); + ++count; + } + } + if(loc.offset != npos) + { + csubstr val = detail::_to_chars_limited(buf, loc.offset); + if(loc.line != npos || loc.col != npos || !loc.name.empty()) + { + std::forward(dumpfn)(" "); + ++count; + } + std::forward(dumpfn)("("); + std::forward(dumpfn)(val); + std::forward(dumpfn)("B):"); + count += val.len + 5; + } + return count; +} + +template +C4_NO_INLINE void location_format_with_context(DumpFn &&dumpfn, + Location const &location, + csubstr source_buffer, + csubstr call, + size_t num_lines_before, + size_t num_lines_after, + size_t first_col_highlight, + size_t last_col_highlight, + size_t maxlen) +{ + if(!location) + return; + char buf_[32]; + substr buf(buf_); + auto pr = [&](csubstr s){ std::forward(dumpfn)(s); }; + auto prn = [&](csubstr s, size_t num_times){ + for(size_t i = 0; i < num_times; ++i) + std::forward(dumpfn)(s); + }; + csubstr line = detail::_get_text_region(source_buffer, location.offset, 0, 0); + size_t target_col = location.col != npos ? location.col : (last_col_highlight > first_col_highlight ? first_col_highlight : npos); + size_t first_col_to_show = 0; + if(target_col != npos && target_col > maxlen) + first_col_to_show = target_col - maxlen + 1; + auto print_line_maybe_truncated = [&](csubstr contents){ + if(contents.len <= maxlen) + { + if(first_col_to_show == 0) + { + pr(contents); + } + else if(first_col_to_show < contents.len) + { + csubstr show = contents.sub(first_col_to_show); + pr("[...]"); + pr(show); + if(maxlen > show.len) + prn(" ", maxlen - show.len + 5); + pr(" (showing columns "); + pr(detail::_to_chars_limited(buf, first_col_to_show)); + pr("-"); + pr(detail::_to_chars_limited(buf, contents.len)); + pr("/"); + pr(detail::_to_chars_limited(buf, contents.len)); + pr(")"); + } + else + { + pr("[...]"); + prn(" ", maxlen + 5); + pr(" (not showing, columns="); + pr(detail::_to_chars_limited(buf, contents.len)); + pr(")"); + } + } + else + { + if(first_col_to_show == 0) + { + csubstr show = contents.first(maxlen); + pr(show); + pr("[...] (showing columns 0-"); + pr(detail::_to_chars_limited(buf, show.len)); + pr("/"); + pr(detail::_to_chars_limited(buf, contents.len)); + pr(")"); + } + else if(first_col_to_show < contents.len && first_col_to_show + maxlen <= contents.len) + { + csubstr show = contents.sub(first_col_to_show, maxlen); + pr("[...]"); + pr(show); + pr("[...] (showing columns "); + pr(detail::_to_chars_limited(buf, first_col_to_show)); + pr("-"); + pr(detail::_to_chars_limited(buf, first_col_to_show + maxlen)); + pr("/"); + pr(detail::_to_chars_limited(buf, contents.len)); + pr(")"); + } + else if(first_col_to_show < contents.len) + { + csubstr show = contents.sub(first_col_to_show); + pr("[...]"); + pr(show); + if(maxlen > show.len) + prn(" ", maxlen - show.len + 5); + pr(" (showing columns "); + pr(detail::_to_chars_limited(buf, first_col_to_show)); + pr("-"); + pr(detail::_to_chars_limited(buf, contents.len)); + pr("/"); + pr(detail::_to_chars_limited(buf, contents.len)); + pr(")"); + } + else + { + pr("[...]"); + prn(" ", maxlen + 5); + pr(" (not showing, columns="); + pr(detail::_to_chars_limited(buf, contents.len)); + pr(")"); + } + } + }; + // print the location, and compute how many cols it took + size_t locsize = location_format(pr, location); + // print line + if(locsize) + { + pr(" "); + //++locsize; + } + auto print_call = [&](csubstr after){ + pr(call); + pr(":"); + if(after.len) + pr(after); + }; + size_t jump; + if(call.empty()) + { + print_line_maybe_truncated(line); + pr("\n"); + jump = locsize + location.col - first_col_to_show; + } + else + { + print_call("\n"); + print_call("\n"); + print_call(" "); + pr(" "); + print_line_maybe_truncated(line); + pr("\n"); + jump = call.len + 2; + } + // when skipping to the first col, add 5 to adjust for the [...] + // leading the line as shown + const size_t first_col_jump = first_col_to_show == 0 ? 0 : 5; + // print a cursor pointing at the column on the previous printed line + auto print_cursor = [&](size_t nocall_jump){ + if(location.offset == npos) + return; + if(call.empty()) + { + if(nocall_jump != npos) + { + prn(" ", nocall_jump + first_col_jump); + pr("|\n"); + prn(" ", nocall_jump + first_col_jump); + pr("(here)\n"); + } + } + else if(location.col != npos) + { + print_call(" "); + pr(" "); + prn(" ", location.col - first_col_to_show + first_col_jump); + pr("|\n"); + print_call(" "); + pr(" "); + prn(" ", location.col - first_col_to_show + first_col_jump); + pr("(here)\n"); + } + }; + // maybe highlighted zone + size_t firstcol = first_col_highlight < line.len ? first_col_highlight : line.len; + size_t lastcol = last_col_highlight < line.len ? last_col_highlight : line.len; + firstcol = firstcol < maxlen ? firstcol : maxlen; + lastcol = lastcol < maxlen ? lastcol : maxlen; + if(firstcol < lastcol) + { + if(!call.empty()) + { + print_call(" "); + pr(" "); + } + else + { + for(size_t i = 0; i < locsize + firstcol; ++i) + pr(" "); + } + for(size_t i = locsize + firstcol; i < locsize + lastcol; ++i) + pr("~"); + pr(" (cols "); + pr(detail::_to_chars_limited(buf, firstcol)); + pr("-"); + pr(detail::_to_chars_limited(buf, lastcol)); + pr("/"); + pr(detail::_to_chars_limited(buf, line.len)); + pr(")\n"); + } + if(location.col != npos) + { + print_cursor(jump); + } + // maybe print the region + if(num_lines_before || num_lines_after) + { + if(!call.empty()) + { + print_call("\n"); + print_call(" "); + pr("see region:\n"); + print_call("\n"); + } + else + { + if(location) + { + location_format(pr, location); + pr(" "); + } + pr("see region:\n"); + } + csubstr region = detail::_get_text_region(source_buffer, location.offset, num_lines_before, num_lines_after); + for(csubstr contents : region.split('\n')) + { + if(!call.empty()) + { + print_call(" "); + } + print_line_maybe_truncated(contents); + pr("\n"); + } + assert(location.col == npos || location.col >= first_col_to_show); + print_cursor(location.col - first_col_to_show); + } +} + + +template +C4_NO_INLINE void err_basic_format(DumpFn &&dumpfn, csubstr msg, ErrorDataBasic const& errdata) +{ + if(errdata.location) + { + location_format(dumpfn, errdata.location); + std::forward(dumpfn)(" "); + } + std::forward(dumpfn)("ERROR: [basic] "); + std::forward(dumpfn)(msg); +} + + +template +C4_NO_INLINE void err_parse_format(DumpFn &&dumpfn, csubstr msg, ErrorDataParse const& errdata) +{ + if(errdata.ymlloc) + { + location_format(std::forward(dumpfn), errdata.ymlloc); + std::forward(dumpfn)(" "); + } + std::forward(dumpfn)("ERROR: [parse] "); + std::forward(dumpfn)(msg); + if(errdata.cpploc) + { + std::forward(dumpfn)("\n"); + location_format(std::forward(dumpfn), errdata.cpploc); + std::forward(dumpfn)(" (detected here)"); + } +} + + +template +C4_NO_INLINE void err_visit_format(DumpFn &&dumpfn, csubstr msg, ErrorDataVisit const& errdata) +{ + char buf_[32]; + substr buf(buf_); + if(errdata.cpploc) + { + location_format(dumpfn, errdata.cpploc); + std::forward(dumpfn)(" "); + } + std::forward(dumpfn)("ERROR: [visit] "); + std::forward(dumpfn)(msg); + if(errdata.node != NONE && errdata.tree != nullptr) + { + if(errdata.cpploc) + { + std::forward(dumpfn)("\n"); + location_format(dumpfn, errdata.cpploc); + std::forward(dumpfn)(" "); + } + std::forward(dumpfn)("ERROR: ("); + if(errdata.node != NONE) + { + std::forward(dumpfn)("node="); + std::forward(dumpfn)(detail::_to_chars_limited(buf, errdata.node)); + if(errdata.tree != nullptr) + std::forward(dumpfn)(" "); + } + if(errdata.tree != nullptr) + { + std::forward(dumpfn)("tree="); + std::forward(dumpfn)(detail::_to_chars_limited(buf, static_cast(errdata.tree))); + } + std::forward(dumpfn)(")"); + } +} + +} // namespace yml +} // namespace c4 + +// NOLINTEND(bugprone-use-after-move,hicpp-invalid-access-moved) + +#endif /* _C4_YML_ERROR_HPP_ */ + + +// (end src/c4/yml/error.def.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/escape_scalar.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_YML_ESCAPE_SCALAR_HPP_ +#define _C4_YML_ESCAPE_SCALAR_HPP_ + +#ifndef _C4_YML_COMMON_HPP_ +// amalgamate: removed include of +// c4/yml/common.hpp +//#include "c4/yml/common.hpp" +#if !defined(C4_YML_COMMON_HPP_) && !defined(_C4_YML_COMMON_HPP_) +#error "amalgamate: file c4/yml/common.hpp must have been included at this point" +#endif /* C4_YML_COMMON_HPP_ */ + +#endif + +namespace c4 { +namespace yml { + + +/** Iterate through a scalar and escape special characters in it. This + * function takes a callback (which accepts a single parameter of + * csubstr type) and, while processing, calls this callback as + * appropriate, passing ranges of the scalar and/or escaped + * characters. + * + * @param fn a sink function receiving a csubstr + * @param scalar the scalar to be escaped + * @param keep_newlines when true, `\n` will be escaped as `\\n\n` instead of just `\\n` + * + * Example usage: + * + * ```c++ + * // escape to stdout + * void escape_scalar(FILE *file, csubstr scalar) + * { + * auto print_ = [](csubstr repl){ + * fwrite(repl.len, 1, repl.str, file); + * }; + * escape_scalar_fn(std::ref(print_), scalar); + * } + * + * // escape to a different buffer and return the required buffer size + * size_t escape_scalar(substr buffer, csubstr scalar) + * { + * C4_ASSERT(!buffer.overlaps(scalar)); + * size_t pos = 0; + * auto _append = [&](csubstr repl){ + * if(repl.len && (pos + repl.len <= buffer.len)) + * memcpy(buffer.str + pos, repl.str, repl.len); + * pos += repl.len; + * }; + * escape_scalar_fn(std::ref(_append), scalar); + * return pos; + * } + * ``` + */ +template +C4_NO_INLINE void escape_scalar_fn(Fn &&fn, csubstr scalar, bool keep_newlines=false) +{ + size_t prev = 0; // the last position that was flushed + size_t skip = 0; // how much to add to prev + csubstr repl; // replacement string + bool newl = false; // to add a newline + // cast to u8 to avoid having to deal with negative + // signed chars (which are present in some platforms) + uint8_t const* C4_RESTRICT s = reinterpret_cast(scalar.str); // NOLINT(*-reinterpret-cast) + // NOLINTBEGIN(*-goto,bugprone-use-after-move,hicpp-invalid-access-moved) + for(size_t i = 0; i < scalar.len; ++i) + { + switch(s[i]) + { + case UINT8_C(0x0a): // \n + repl = "\\n"; + skip = 1; + if(keep_newlines) + newl = true; + goto flush_now; + case UINT8_C(0x5c): // '\\' + repl = "\\\\"; + skip = 1; + goto flush_now; + case UINT8_C(0x09): // \t + repl = "\\t"; + skip = 1; + goto flush_now; + case UINT8_C(0x0d): // \r + repl = "\\r"; + skip = 1; + goto flush_now; + case UINT8_C(0x00): // \0 + repl = "\\0"; + skip = 1; + goto flush_now; + case UINT8_C(0x0c): // \f (form feed) + repl = "\\f"; + skip = 1; + goto flush_now; + case UINT8_C(0x08): // \b (backspace) + repl = "\\b"; + skip = 1; + goto flush_now; + case UINT8_C(0x07): // \a (bell) + repl = "\\a"; + skip = 1; + goto flush_now; + case UINT8_C(0x0b): // \v (vertical tab) + repl = "\\v"; + skip = 1; + goto flush_now; + case UINT8_C(0x1b): // \e (escape) + repl = "\\e"; + skip = 1; + goto flush_now; + case UINT8_C(0xc2): // AKA -0x3e + if(i+1 < scalar.len) + { + if(s[i+1] == UINT8_C(0xa0)) // AKA -0x60 + { + repl = "\\_"; + skip = 2; + goto flush_now; + } + else if(s[i+1] == UINT8_C(0x85)) // AKA -0x7b + { + repl = "\\N"; + skip = 2; + goto flush_now; + } + } + continue; + case UINT8_C(0xe2): // AKA -0x1e + if(i+2 < scalar.len) + { + if(s[i+1] == UINT8_C(0x80)) // AKA -0x80 + { + if(s[i+2] == UINT8_C(0xa8)) // AKA -0x58 + { + repl = "\\L"; + skip = 3; + goto flush_now; + } + else if(s[i+2] == UINT8_C(0xa9)) // AKA -0x57 + { + repl = "\\P"; + skip = 3; + goto flush_now; + } + } + } + continue; + default: + continue; + } + flush_now: + std::forward(fn)(scalar.range(prev, i)); + std::forward(fn)(repl); + if(newl) + { + std::forward(fn)("\n"); + newl = false; + } + prev = i + skip; + } + // flush the rest + if(scalar.len > prev) + std::forward(fn)(scalar.sub(prev)); + // NOLINTEND(*-goto,bugprone-use-after-move,hicpp-invalid-access-moved) +} + + +C4_SUPPRESS_WARNING_GCC_WITH_PUSH("-Wattributes") + +/** Adjust a position in a scalar, increasing it to account for any + * escaped characters. + * + * @note This is a utility/debugging function, so it is provided in + * this optional header. For this reason, we inline it to obey to the + * One Definition Rule. But then we set the noinline attribute to + * ensure they are not inlined in calling code. */ +inline C4_NO_INLINE size_t adjust_pos_with_escapes(csubstr scalar, size_t pos, bool keep_newlines=false) +{ + // cast to u8 to avoid having to deal with negative + // signed chars (which are present in some platforms) + uint8_t const* C4_RESTRICT s = reinterpret_cast(scalar.str); // NOLINT(*-reinterpret-cast) + const size_t newbump = keep_newlines ? 2 : 1; + size_t ret = 0; + size_t excess = pos > scalar.len ? pos - scalar.len : 0; + pos = pos < scalar.len ? pos : scalar.len; + for(size_t i = 0; i < pos; ++i) + { + ++ret; + switch(s[i]) + { + case UINT8_C(0x5c): // '\\' + case UINT8_C(0x09): // \t + case UINT8_C(0x0d): // \r + case UINT8_C(0x00): // \0 + case UINT8_C(0x0c): // \f (form feed) + case UINT8_C(0x08): // \b (backspace) + case UINT8_C(0x07): // \a (bell) + case UINT8_C(0x0b): // \v (vertical tab) + case UINT8_C(0x1b): // \e (escape) + ++ret; // add the backslash + break; + case UINT8_C(0x0a): // \n + ret += newbump; + break; + case UINT8_C(0xc2): // AKA -0x3e + if(i+1 < scalar.len) + { + if(s[i+1] == UINT8_C(0xa0) // AKA -0x60 -> \_ + || + s[i+1] == UINT8_C(0x85)) // AKA -0x7b -> \N + { + ++ret; + ++i; // skip the next entry + } + } + break; + case UINT8_C(0xe2): // AKA -0x1e + if(i+2 < scalar.len) + { + if(s[i+1] == UINT8_C(0x80)) // AKA -0x80 + { + if(s[i+2] == UINT8_C(0xa8) // AKA -0x58 -> \L + || + s[i+2] == UINT8_C(0xa9)) // AKA -0x57 -> \P + { + ++ret; + i += 2; // skip the next two entries + } + } + } + break; + default: + break; + } + } + return ret + excess; +} + + +/** Escape a scalar to an existing buffer, using @ref escape_scalar_fn + * + * @note This is a utility/debugging function, so it is provided in + * this optional header. For this reason, we inline it to obey to the + * One Definition Rule. But then we set the noinline attribute to + * ensure they are not inlined in calling code. */ +inline C4_NO_INLINE size_t escape_scalar(substr buffer, csubstr scalar, bool keep_newlines=false) +{ + size_t pos = 0; + C4_ASSERT(!buffer.overlaps(scalar)); + auto append_ = [&pos, &buffer](csubstr repl){ + if(repl.len && (pos + repl.len <= buffer.len)) + memcpy(buffer.str + pos, repl.str, repl.len); + pos += repl.len; + }; + escape_scalar_fn(append_, scalar, keep_newlines); + return pos; +} + + +/** formatting helper to escape a scalar with @ref escape_scalar_fn() */ +struct escaped_scalar +{ + escaped_scalar(csubstr s, bool keep_newl=false) : scalar(s), keep_newlines(keep_newl) {} + csubstr scalar; + bool keep_newlines; +}; + +/** formatting implementation to escape a scalar with @ref escape_scalar() */ +inline C4_NO_INLINE size_t to_chars(substr buf, escaped_scalar e) +{ + return escape_scalar(buf, e.scalar, e.keep_newlines); +} +/** dumping implementation to escape a scalar with @ref escape_scalar_fn() */ +template +C4_NO_INLINE size_t dump(SinkPfn &&sinkfn, substr buf, escaped_scalar const& e) +{ + (void)buf; + C4_ASSERT(!buf.overlaps(e.scalar)); + escape_scalar_fn(std::forward(sinkfn), e.scalar, e.keep_newlines); + return 0; +} + +C4_SUPPRESS_WARNING_GCC_POP + +} // namespace yml +} // namespace c4 + +#endif /* _C4_YML_ESCAPE_SCALAR_HPP_ */ + + +// (end src/c4/yml/escape_scalar.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/node_type.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_YML_NODE_TYPE_HPP_ +#define _C4_YML_NODE_TYPE_HPP_ + +#ifndef _C4_YML_COMMON_HPP_ +// amalgamate: removed include of +// c4/yml/common.hpp +//#include "c4/yml/common.hpp" +#if !defined(C4_YML_COMMON_HPP_) && !defined(_C4_YML_COMMON_HPP_) +#error "amalgamate: file c4/yml/common.hpp must have been included at this point" +#endif /* C4_YML_COMMON_HPP_ */ + +#endif + +C4_SUPPRESS_WARNING_MSVC_PUSH +C4_SUPPRESS_WARNING_GCC_CLANG_PUSH +C4_SUPPRESS_WARNING_GCC_CLANG("-Wold-style-cast") +#if defined(__GNUC__) && __GNUC__ >= 6 +C4_SUPPRESS_WARNING_GCC("-Wnull-dereference") +#endif +// NOLINTBEGIN(modernize-avoid-c-style-cast) + +namespace c4 { +namespace yml { + +/** @addtogroup doc_node_type + * + * @{ + */ + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + + +/** the integral type necessary to cover all the bits for NodeType_e */ +using type_bits = uint32_t; + + +/** a bit mask for marking node types and styles */ +typedef enum : type_bits { // NOLINT + #define __(v) (type_bits(1) << v) // a convenience define, undefined below // NOLINT + NOTYPE = 0, ///< no node type or style is set + KEY = __(0), ///< the scalar to the left of `:` in a map's member + VAL = __(1), ///< a scalar: has a scalar (ie string) value, possibly empty. must be a leaf node, and cannot be MAP or SEQ + MAP = __(2), ///< a map: a parent of KEYVAL/KEYSEQ/KEYMAP nodes + SEQ = __(3), ///< a seq: a parent of VAL/SEQ/MAP nodes + DOC = __(4), ///< a document + STREAM = __(5)|SEQ, ///< a stream: a seq of docs + KEYREF = __(6), ///< a *reference: the key references an &anchor + VALREF = __(7), ///< a *reference: the val references an &anchor + KEYANCH = __(8), ///< the key has an &anchor + VALANCH = __(9), ///< the val has an &anchor + KEYTAG = __(10), ///< the key has a tag + VALTAG = __(11), ///< the val has a tag + KEYNIL = __(12), ///< the key is null (eg `{ : b}` results in a null key) + VALNIL = __(13), ///< the val is null (eg `{a : }` results in a null val) + _TYMASK = __(14)-1, ///< all the bits up to here + // + // unfiltered flags: + // + KEY_UNFILT = __(14), ///< the key scalar was left unfiltered; the parser was set not to filter. @see @ref ParserOptions::scalar_filtering() + VAL_UNFILT = __(15), ///< the val scalar was left unfiltered; the parser was set not to filter. @see @ref ParserOptions::scalar_filtering() + // + // style flags: + // + FLOW_SL = __(16), ///< mark container with single-line flow style + ///< - seqs as + ///< @code{yaml} + ///< [val1,val2] + ///< @endcode + ///< when @ref FLOW_SPC is not set, or + ///< @code{yaml} + ///< [val1, val2] + ///< @endcode + ///< when @ref FLOW_SPC is set (or @ref EmitOptions::force_flow_spc() is set) + ///< - maps as + ///< @code{yaml} + ///< {key1: val1,key2: val2} + ///< @endcode + ///< when @ref FLOW_SPC is not set, or + ///< @code{yaml} + ///< {key1: val1, key2: val2} + ///< @endcode + ///< when @ref FLOW_SPC is set (or @ref EmitOptions::force_flow_spc() is set) + FLOW_ML1 = __(17), ///< mark container with multi-line flow style, 1 element per line + ///< - seqs as + ///< @code{yaml} + ///< [ + ///< val1, + ///< val2 + ///< ] + ///< @endcode + ///< - maps as + ///< @code{yaml} + ///< { + ///< key: val, + ///< key2: val2 + ///< } + ///< @endcode + FLOW_MLN = __(18), ///< mark container with multi-line flow style, n elements per line, + ///< wrapped (as set by @ref EmitOptions::max_cols()): + ///< - seqs as + ///< @code{yaml} + ///< [ + ///< val,val,... + ///< val,val,... + ///< val,val,... + ///< ... + ///< val + ///< ] + ///< @endcode + ///< when @ref FLOW_SPC is not set, or + ///< @code{yaml} + ///< [ + ///< val, val,... + ///< val, val,... + ///< val, val,... + ///< ... + ///< val + ///< ] + ///< @endcode + ///< when @ref FLOW_SPC is set (or @ref EmitOptions::force_flow_spc() is set) + ///< - maps as + ///< @code{yaml} + ///< { + ///< key: val,key: val,... + ///< key: val,key: val,... + ///< ... + ///< key: val + ///< } + ///< @endcode + ///< when @ref FLOW_SPC is not set, or + ///< @code{yaml} + ///< { + ///< key: val, key: val,... + ///< key: val, key: val,... + ///< ... + ///< key: val + ///< } + ///< @endcode + ///< when @ref FLOW_SPC is set (or @ref EmitOptions::force_flow_spc() is set) + FLOW_SPC = __(19), ///< mark container with spaces after comma when in flow mode. + ///< Applies to both @ref FLOW_SL and @ref FLOW_MLN (but not + ///< to @ref FLOW_ML1), and can be overriden globally by + ///< @ref EmitOptions::force_flow_spc(). + BLOCK = __(20), ///< mark container with block style + ///< - seqs as + ///< @code{yaml} + ///< - val1 + ///< - val2 + ///< @endcode + ///< - maps as + ///< @code{yaml} + ///< key1: val1 + ///< key2: val2 + ///< @endcode + KEY_LITERAL = __(21), ///< mark key scalar as multiline, block literal | + VAL_LITERAL = __(22), ///< mark val scalar as multiline, block literal | + KEY_FOLDED = __(23), ///< mark key scalar as multiline, block folded > + VAL_FOLDED = __(24), ///< mark val scalar as multiline, block folded > + KEY_SQUO = __(25), ///< mark key scalar as single quoted ' + VAL_SQUO = __(26), ///< mark val scalar as single quoted ' + KEY_DQUO = __(27), ///< mark key scalar as double quoted " + VAL_DQUO = __(28), ///< mark val scalar as double quoted " + KEY_PLAIN = __(29), ///< mark key scalar as plain scalar (unquoted, even when multiline) + VAL_PLAIN = __(30), ///< mark val scalar as plain scalar (unquoted, even when multiline) + // + // type combination masks: + // + KEYVAL = KEY|VAL, ///< mask of @ref KEY|@ref VAL + KEYSEQ = KEY|SEQ, ///< mask of @ref KEY|@ref SEQ + KEYMAP = KEY|MAP, ///< mask of @ref KEY|@ref MAP + DOCMAP = DOC|MAP, ///< mask of @ref DOC|@ref MAP + DOCSEQ = DOC|SEQ, ///< mask of @ref DOC|@ref SEQ + DOCVAL = DOC|VAL, ///< mask of @ref DOC|@ref VAL + // + // style combination masks: + // + SCALAR_LITERAL = KEY_LITERAL|VAL_LITERAL, ///< mask of @ref KEY_LITERAL|@ref VAL_LITERAL, + SCALAR_FOLDED = KEY_FOLDED|VAL_FOLDED, ///< mask of @ref KEY_FOLDED|@ref VAL_FOLDED, + SCALAR_SQUO = KEY_SQUO|VAL_SQUO, ///< mask of @ref KEY_SQUO|@ref VAL_SQUO, + SCALAR_DQUO = KEY_DQUO|VAL_DQUO, ///< mask of @ref KEY_DQUO|@ref VAL_DQUO, + SCALAR_PLAIN = KEY_PLAIN|VAL_PLAIN, ///< mask of @ref KEY_PLAIN|@ref VAL_PLAIN, + KEYQUO = KEY_SQUO|KEY_DQUO|KEY_FOLDED|KEY_LITERAL, ///< key style is one of `'">|`. mask of @ref KEY_SQUO|@ref KEY_DQUO|@ref KEY_FOLDED|@ref KEY_LITERAL + VALQUO = VAL_SQUO|VAL_DQUO|VAL_FOLDED|VAL_LITERAL, ///< val style is one of `'">|`. mask of @ref VAL_SQUO|@ref VAL_DQUO|@ref VAL_FOLDED|@ref VAL_LITERAL + KEY_STYLE = KEYQUO|KEY_PLAIN, ///< mask of @ref KEYQUO|@ref KEY_PLAIN : all the key scalar styles for key (not container styles!) + VAL_STYLE = VALQUO|VAL_PLAIN, ///< mask of @ref VALQUO|@ref VAL_PLAIN : all the val scalar styles for val (not container styles!) + SCALAR_STYLE = KEY_STYLE|VAL_STYLE, ///< mask of @ref KEY_STYLE|@ref VAL_STYLE : all the key+val scalar styles + FLOW_MLX = FLOW_ML1|FLOW_MLN, ///< mask of @ref FLOW_ML1|@ref FLOW_MLN : all the flow multiline styles + CONTAINER_STYLE_FLOW = FLOW_SL|FLOW_MLX|FLOW_SPC, ///< mask of @ref FLOW_SL|@ref FLOW_MLX|@ref FLOW_SPC : all flow flags + CONTAINER_STYLE_BLOCK = BLOCK, ///< alias to @ref BLOCK + CONTAINER_STYLE = CONTAINER_STYLE_FLOW|CONTAINER_STYLE_BLOCK, ///< mask of @ref CONTAINER_STYLE_FLOW|@ref CONTAINER_STYLE_BLOCK : all container style flags + STYLE = SCALAR_STYLE | CONTAINER_STYLE, ///< mask of @ref SCALAR_STYLE | @ref CONTAINER_STYLE : all style flags + /** @cond dev */ + // + // mixed masks + _KEYMASK = KEY | KEYQUO | KEYANCH | KEYREF | KEYTAG, + _VALMASK = VAL | VALQUO | VALANCH | VALREF | VALTAG, + #undef __ + #if C4_CPP >= 17 \ + || (defined(__GNUC__) && __GNUC__ >= 6) \ + || (defined(_MSC_VER) && !defined(__clang__)) + #define RYML_HAS_DEPRECATED_ENUMS__ + FLOW_ML RYML_DEPRECATED("use one of FLOW_ML{1,N,X}") = FLOW_ML1, + #endif + /** @endcond */ +} NodeType_e; +/** @cond dev */ +#if !defined(RYML_HAS_DEPRECATED_ENUMS__) +// defined here because the current c++ standard / compiler cannot +// handle deprecated enums +RYML_DEPRECATED("use one of FLOW_ML{1,N,X}") +constexpr const NodeType_e FLOW_ML = FLOW_ML1; +#endif +/** @endcond */ + +constexpr C4_ALWAYS_INLINE C4_CONST NodeType_e operator| (NodeType_e lhs, NodeType_e rhs) noexcept { return (NodeType_e)(((type_bits)lhs) | ((type_bits)rhs)); } +constexpr C4_ALWAYS_INLINE C4_CONST NodeType_e operator& (NodeType_e lhs, NodeType_e rhs) noexcept { return (NodeType_e)(((type_bits)lhs) & ((type_bits)rhs)); } +constexpr C4_ALWAYS_INLINE C4_CONST NodeType_e operator>> (NodeType_e bits, uint32_t n) noexcept { return (NodeType_e)(((type_bits)bits) >> n); } +constexpr C4_ALWAYS_INLINE C4_CONST NodeType_e operator<< (NodeType_e bits, uint32_t n) noexcept { return (NodeType_e)(((type_bits)bits) << n); } +constexpr C4_ALWAYS_INLINE C4_CONST NodeType_e operator~ (NodeType_e bits) noexcept { return (NodeType_e)(~(type_bits)bits); } +C4_ALWAYS_INLINE NodeType_e& operator&= (NodeType_e &subject, NodeType_e bits) noexcept { subject = (NodeType_e)((type_bits)subject & (type_bits)bits); return subject; } +C4_ALWAYS_INLINE NodeType_e& operator|= (NodeType_e &subject, NodeType_e bits) noexcept { subject = (NodeType_e)((type_bits)subject | (type_bits)bits); return subject; } + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** wraps a NodeType_e element with some syntactic sugar and predicates */ +struct RYML_EXPORT NodeType +{ +public: + + NodeType_e type; + +public: + + C4_ALWAYS_INLINE NodeType() noexcept : type(NOTYPE) {} + C4_ALWAYS_INLINE NodeType(NodeType_e t) noexcept : type(t) {} + C4_ALWAYS_INLINE NodeType(type_bits t) noexcept : type((NodeType_e)t) {} + + C4_ALWAYS_INLINE bool has_any(NodeType_e t) const noexcept { return (type & t) != 0u; } + C4_ALWAYS_INLINE bool has_all(NodeType_e t) const noexcept { return (type & t) == t; } + C4_ALWAYS_INLINE bool has_none(NodeType_e t) const noexcept { return (type & t) == 0; } + + C4_ALWAYS_INLINE void set(NodeType_e t) noexcept { type = t; } + C4_ALWAYS_INLINE void add(NodeType_e t) noexcept { type = (type|t); } + C4_ALWAYS_INLINE void rem(NodeType_e t) noexcept { type = (type & ~t); } + C4_ALWAYS_INLINE void addrem(NodeType_e bits_to_add, NodeType_e bits_to_remove) noexcept { type |= bits_to_add; type &= ~bits_to_remove; } + + C4_ALWAYS_INLINE void clear() noexcept { type = NOTYPE; } + +public: + + C4_ALWAYS_INLINE operator NodeType_e & C4_RESTRICT () noexcept { return type; } + C4_ALWAYS_INLINE operator NodeType_e const& C4_RESTRICT () const noexcept { return type; } + +public: + + /** @name node type queries + * @{ */ + + /** return a preset string based on the node type */ + C4_ALWAYS_INLINE const char *type_str() const noexcept { return type_str(type); } + /** return a preset string based on the node type */ + static const char* type_str(NodeType_e t) noexcept; + + /** fill a string with the node type flags. */ + C4_ALWAYS_INLINE size_t type_str(substr buf) const noexcept { return type_str(buf, type); } + /** fill a string with the node type flags. */ + static size_t type_str(substr buf, NodeType_e t) noexcept; + + /** fill a string with the node type flags. If the string is small, returns {null, len} */ + C4_ALWAYS_INLINE csubstr type_str_sub(substr buf) const noexcept { return type_str_sub(buf, type); } + /** fill a string with the node type flags. If the string is small, returns {null, len} */ + static csubstr type_str_sub(substr buf, NodeType_e t) noexcept + { + csubstr ret; + ret.len = type_str(buf, t); + ret.str = ret.len < buf.len ? buf.str : nullptr; + return ret; + } + +public: + + /** @name node type queries + * @{ */ + + C4_ALWAYS_INLINE bool is_notype() const noexcept { return type == NOTYPE; } + C4_ALWAYS_INLINE bool is_stream() const noexcept { return ((type & STREAM) == STREAM) != 0; } + C4_ALWAYS_INLINE bool is_doc() const noexcept { return (type & DOC) != 0; } + C4_ALWAYS_INLINE bool is_container() const noexcept { return (type & (MAP|SEQ|STREAM)) != 0; } + C4_ALWAYS_INLINE bool is_map() const noexcept { return (type & MAP) != 0; } + C4_ALWAYS_INLINE bool is_seq() const noexcept { return (type & SEQ) != 0; } + C4_ALWAYS_INLINE bool has_key() const noexcept { return (type & KEY) != 0; } + C4_ALWAYS_INLINE bool has_val() const noexcept { return (type & VAL) != 0; } + C4_ALWAYS_INLINE bool is_val() const noexcept { return (type & KEYVAL) == VAL; } + C4_ALWAYS_INLINE bool is_keyval() const noexcept { return (type & KEYVAL) == KEYVAL; } + C4_ALWAYS_INLINE bool key_is_null() const noexcept { return (type & KEYNIL) != 0; } + C4_ALWAYS_INLINE bool val_is_null() const noexcept { return (type & VALNIL) != 0; } + C4_ALWAYS_INLINE bool has_key_tag() const noexcept { return (type & KEYTAG) != 0; } + C4_ALWAYS_INLINE bool has_val_tag() const noexcept { return (type & VALTAG) != 0; } + C4_ALWAYS_INLINE bool has_key_anchor() const noexcept { return (type & KEYANCH) != 0; } + C4_ALWAYS_INLINE bool has_val_anchor() const noexcept { return (type & VALANCH) != 0; } + C4_ALWAYS_INLINE bool has_anchor() const noexcept { return (type & (KEYANCH|VALANCH)) != 0; } + C4_ALWAYS_INLINE bool is_key_ref() const noexcept { return (type & KEYREF) != 0; } + C4_ALWAYS_INLINE bool is_val_ref() const noexcept { return (type & VALREF) != 0; } + C4_ALWAYS_INLINE bool is_ref() const noexcept { return (type & (KEYREF|VALREF)) != 0; } + + C4_ALWAYS_INLINE bool is_key_unfiltered() const noexcept { return (type & (KEY_UNFILT)) != 0; } + C4_ALWAYS_INLINE bool is_val_unfiltered() const noexcept { return (type & (VAL_UNFILT)) != 0; } + + /** @} */ + +public: + + /** @name style functions + * @{ */ + + C4_ALWAYS_INLINE bool is_container_styled() const noexcept { return (type & (CONTAINER_STYLE)) != 0; } + C4_ALWAYS_INLINE bool is_block() const noexcept { return (type & (BLOCK)) != 0; } + C4_ALWAYS_INLINE bool is_flow_sl() const noexcept { return (type & (FLOW_SL)) != 0; } + C4_ALWAYS_INLINE bool is_flow_ml1() const noexcept { return (type & (FLOW_ML1)) != 0; } + C4_ALWAYS_INLINE bool is_flow_mln() const noexcept { return (type & (FLOW_MLN)) != 0; } + C4_ALWAYS_INLINE bool is_flow_mlx() const noexcept { return (type & (FLOW_ML1|FLOW_MLN)) != 0; } + C4_ALWAYS_INLINE bool is_flow() const noexcept { return (type & (FLOW_ML1|FLOW_MLN|FLOW_SL)) != 0; } + C4_ALWAYS_INLINE bool has_flow_space() const noexcept { return (type & (FLOW_SPC)) != 0; } + + C4_ALWAYS_INLINE bool is_key_styled() const noexcept { return (type & (KEY_STYLE)) != 0; } + C4_ALWAYS_INLINE bool is_val_styled() const noexcept { return (type & (VAL_STYLE)) != 0; } + C4_ALWAYS_INLINE bool is_key_literal() const noexcept { return (type & (KEY_LITERAL)) != 0; } + C4_ALWAYS_INLINE bool is_val_literal() const noexcept { return (type & (VAL_LITERAL)) != 0; } + C4_ALWAYS_INLINE bool is_key_folded() const noexcept { return (type & (KEY_FOLDED)) != 0; } + C4_ALWAYS_INLINE bool is_val_folded() const noexcept { return (type & (VAL_FOLDED)) != 0; } + C4_ALWAYS_INLINE bool is_key_squo() const noexcept { return (type & (KEY_SQUO)) != 0; } + C4_ALWAYS_INLINE bool is_val_squo() const noexcept { return (type & (VAL_SQUO)) != 0; } + C4_ALWAYS_INLINE bool is_key_dquo() const noexcept { return (type & (KEY_DQUO)) != 0; } + C4_ALWAYS_INLINE bool is_val_dquo() const noexcept { return (type & (VAL_DQUO)) != 0; } + C4_ALWAYS_INLINE bool is_key_plain() const noexcept { return (type & (KEY_PLAIN)) != 0; } + C4_ALWAYS_INLINE bool is_val_plain() const noexcept { return (type & (VAL_PLAIN)) != 0; } + C4_ALWAYS_INLINE bool is_key_quoted() const noexcept { return (type & KEYQUO) != 0; } + C4_ALWAYS_INLINE bool is_val_quoted() const noexcept { return (type & VALQUO) != 0; } + C4_ALWAYS_INLINE bool is_quoted() const noexcept { return (type & (KEYQUO|VALQUO)) != 0; } + + C4_ALWAYS_INLINE NodeType key_style() const noexcept { return (type & (KEY_STYLE)); } + C4_ALWAYS_INLINE NodeType val_style() const noexcept { return (type & (VAL_STYLE)); } + + C4_ALWAYS_INLINE void set_container_style(NodeType_e style) noexcept { type = ((style & CONTAINER_STYLE) | (type & ~CONTAINER_STYLE)); } + C4_ALWAYS_INLINE void set_key_style(NodeType_e style) noexcept { type = ((style & KEY_STYLE) | (type & ~KEY_STYLE)); } + C4_ALWAYS_INLINE void set_val_style(NodeType_e style) noexcept { type = ((style & VAL_STYLE) | (type & ~VAL_STYLE)); } + C4_ALWAYS_INLINE void clear_style() noexcept { type &= ~STYLE; } + + /** @} */ + +public: // deprecated methods + + /** @cond dev */ // LCOV_EXCL_START + RYML_DEPRECATED("use has_key_anchor()") bool is_key_anchor() const noexcept { return has_key_anchor(); } + RYML_DEPRECATED("use has_val_anchor()") bool is_val_anchor() const noexcept { return has_val_anchor(); } + RYML_DEPRECATED("use has_anchor()") bool is_anchor() const noexcept { return has_anchor(); } + RYML_DEPRECATED("use has_anchor() || is_ref()") bool is_anchor_or_ref() const noexcept { return has_anchor() || is_ref(); } + + RYML_DEPRECATED("use one of .is_flow_ml{1,n,x}()") + bool is_flow_ml() const noexcept { return (type & (FLOW_ML1)) != 0; } + /** @endcond */ // LCOV_EXCL_STOP +}; + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** @name scalar style helpers + * @{ */ + +/** choose a YAML scalar style based on the scalar's contents, while + * in flow mode. Plain scalars [have more constraints in flow mode + * than in block + * mode](https://www.yaml.info/learn/quote.html#noplain). @ref + * scalar_style_choose_block() is the block mode analogous + * function. */ +RYML_EXPORT NodeType_e scalar_style_choose_flow(csubstr scalar) noexcept; +/** choose a YAML scalar style based on the scalar's contents, while + * in block mode. Plain scalars [have more constraints in flow mode + * than in block + * mode](https://www.yaml.info/learn/quote.html#noplain). @ref + * scalar_style_choose_block() is the flow mode analogous function. */ +RYML_EXPORT NodeType_e scalar_style_choose_block(csubstr scalar) noexcept; +/** choose a YAML emitting style based on the scalar's + * contents. Legacy compatibility function: assumes flow mode which is + * more constraining, and delegates to either @ref + * scalar_style_choose_flow() or @ref scalar_style_choose_block(). */ +inline NodeType_e scalar_style_choose(csubstr s, bool flow=true) noexcept +{ + return flow ? scalar_style_choose_flow(s) : scalar_style_choose_block(s); +} + + +/** choose a json scalar style based on the scalar's contents */ +RYML_EXPORT NodeType_e scalar_style_choose_json(csubstr scalar) noexcept; +/** @cond dev */ // LCOV_EXCL_START +RYML_DEPRECATED("use scalar_style_choose_json()") +inline NodeType_e scalar_style_json_choose(csubstr scalar) noexcept +{ + return scalar_style_choose_json(scalar); +} +/** @endcond */ // LCOV_EXCL_STOP + + +/** query whether a scalar can be encoded using single quotes. + * It may not be possible, notably when there is leading + * whitespace after a newline. */ +RYML_EXPORT bool scalar_style_query_squo(csubstr s) noexcept; + +/** query whether a scalar can be encoded using plain style while in + * flow mode. Plain scalars [have more constraints in flow mode than + * in block + * mode](https://www.yaml.info/learn/quote.html#noplain). @ref + * scalar_style_query_plain_block() is the block mode analogous function.*/ +RYML_EXPORT bool scalar_style_query_plain_flow(csubstr s) noexcept; +/** query whether a scalar can be encoded using plain style while in + * block mode. Plain scalars [have more constraints in flow mode than + * in block + * mode](https://www.yaml.info/learn/quote.html#noplain). @ref + * scalar_style_query_plain_flow() is the flow mode analogous function.*/ +RYML_EXPORT bool scalar_style_query_plain_block(csubstr s) noexcept; +/** query whether a scalar can be encoded using plain style. Legacy + * compatibility function: assumes flow mode which is more + * constraining, and delegates to either @ref + * scalar_style_query_plain_flow() or @ref + * scalar_style_query_plain_block(). */ +inline bool scalar_style_query_plain(csubstr s, bool flow=true) noexcept +{ + return flow ? scalar_style_query_plain_flow(s) : scalar_style_query_plain_block(s); +} + +/** YAML-sense query of nullity. returns true if the scalar points + * to `nullptr` or is otherwise equal to one of the strings + * `"~"`,`"null"`,`"Null"`,`"NULL"` */ +RYML_EXPORT bool scalar_is_null(csubstr s) noexcept; + +/** @} */ + +/** @} */ + +} // namespace yml +} // namespace c4 + +// NOLINTEND(modernize-avoid-c-style-cast) +C4_SUPPRESS_WARNING_MSVC_POP +C4_SUPPRESS_WARNING_GCC_CLANG_POP + +#endif /* _C4_YML_NODE_TYPE_HPP_ */ + + +// (end src/c4/yml/node_type.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/parse_options.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_YML_PARSE_OPTIONS_HPP_ +#define _C4_YML_PARSE_OPTIONS_HPP_ + +/** @file parse_options.hpp */ + +#ifndef _C4_YML_COMMON_HPP_ +// amalgamate: removed include of +// c4/yml/common.hpp +//#include +#if !defined(C4_YML_COMMON_HPP_) && !defined(_C4_YML_COMMON_HPP_) +#error "amalgamate: file c4/yml/common.hpp must have been included at this point" +#endif /* C4_YML_COMMON_HPP_ */ + +#endif +#ifndef _C4_YML_NODE_TYPE_HPP_ +// amalgamate: removed include of +// c4/yml/node_type.hpp +//#include +#if !defined(C4_YML_NODE_TYPE_HPP_) && !defined(_C4_YML_NODE_TYPE_HPP_) +#error "amalgamate: file c4/yml/node_type.hpp must have been included at this point" +#endif /* C4_YML_NODE_TYPE_HPP_ */ + +#endif +#ifndef _C4_YML_ERROR_HPP_ +// amalgamate: removed include of +// c4/yml/error.hpp +//#include +#if !defined(C4_YML_ERROR_HPP_) && !defined(_C4_YML_ERROR_HPP_) +#error "amalgamate: file c4/yml/error.hpp must have been included at this point" +#endif /* C4_YML_ERROR_HPP_ */ + +#endif + + +namespace c4 { +namespace yml { + +/** Options to give to the parser to control its behavior. */ +struct RYML_EXPORT ParserOptions +{ +private: + + typedef enum : uint32_t { // NOLINT + DETECT_FLOW_ML = (1u << 0u), + RESOLVE_TAGS = (1u << 1u), + RESOLVE_TAGS_ALL = (1u << 2u), + SCALAR_FILTERING = (1u << 3u), + LOCATIONS = (1u << 4u), + DEFAULTS = SCALAR_FILTERING|DETECT_FLOW_ML, + } Flags_e; + + uint32_t m_flags = DEFAULTS; + NodeType_e m_flow_ml_style = FLOW_ML1; + + ParserOptions& set_flags_(bool enabled, Flags_e f) + { + if(enabled) + m_flags |= f; + else + m_flags &= ~f; + return *this; + } + +public: + + ParserOptions() noexcept = default; + +public: + + /** @name detection of @ref FLOW_ML container style + * @{ */ + + /** enable/disable detection of flow multiline container + * style. When enabled, the parser will set either @ref FLOW_ML1 + * or @ref FLOW_MLN flow multiline style as the style of flow + * containers which have the terminating bracket on a line + * different from that of the opening bracket. Default is to + * detect flow multiline. Use @ref ParserOptions::flow_ml_style() + * to choose between the @ref FLOW_ML1 or @ref FLOW_MLN styles. */ + ParserOptions& detect_flow_ml(bool enabled) noexcept + { + return set_flags_(enabled, DETECT_FLOW_ML); + } + /** query status of detection of flow multiline container style. */ + C4_ALWAYS_INLINE bool detect_flow_ml() const noexcept { return (m_flags & DETECT_FLOW_ML); } + + /** choose the default style of multiline flow containers, when a + * container is detected as flow multiline. Input should be @ref + * FLOW_ML1 or @ref FLOW_MLN . Default is @ref FLOW_ML1 (the old + * behavior). */ + ParserOptions& flow_ml_style(NodeType style) noexcept + { + _RYML_ASSERT_BASIC(style & (FLOW_ML1|FLOW_MLN)); + m_flow_ml_style = style & (FLOW_ML1|FLOW_MLN); + return *this; + } + C4_ALWAYS_INLINE NodeType flow_ml_style() const noexcept { return m_flow_ml_style; } + + /** @} */ + +public: + + /** @name resolution of tags */ + /** @{ */ + + /** enable/disable resolution of YAML tags during parsing. When + * enabled, tags are resolved according to existing tag + * directives. Disabled by default. See also @ref + * ParserOptions::resolve_tags_all(). */ + ParserOptions& resolve_tags(bool enabled) noexcept + { + return set_flags_(enabled, RESOLVE_TAGS); + } + /** query status of tag resolution setting. */ + C4_ALWAYS_INLINE bool resolve_tags() const noexcept { return (m_flags & RESOLVE_TAGS); } + + /** When resolve_tags() is enabled, resolve not just prefixed tags + * of the form
!handle!tag
, but also non-prefixed tags + * (
!!tag
and
!tag!
). Disabled by default. */ + ParserOptions& resolve_tags_all(bool enabled) noexcept + { + return set_flags_(enabled, RESOLVE_TAGS_ALL); + } + /** query status of non-prefixed tag resolution setting. */ + C4_ALWAYS_INLINE bool resolve_tags_all() const noexcept { return (m_flags & RESOLVE_TAGS_ALL); } + + /** @} */ + +public: + + /** @name source location tracking */ + /** @{ */ + + /** enable/disable source location tracking. Disabled by default. */ + ParserOptions& locations(bool enabled) noexcept + { + return set_flags_(enabled, LOCATIONS); + } + /** query source location tracking status */ + C4_ALWAYS_INLINE bool locations() const noexcept { return (m_flags & LOCATIONS); } + + /** @} */ + +public: + + /** @name scalar filtering status (experimental; disable at your discretion) */ + /** @{ */ + + /** enable/disable scalar filtering while parsing. Enabled by default. */ + ParserOptions& scalar_filtering(bool enabled) noexcept + { + return set_flags_(enabled, SCALAR_FILTERING); + } + /** query scalar filtering status */ + C4_ALWAYS_INLINE bool scalar_filtering() const noexcept { return (m_flags & SCALAR_FILTERING); } + + /** @} */ +}; + +} // namespace yml +} // namespace c4 + +#endif /* _C4_YML_PARSE_OPTIONS_HPP_ */ + + +// (end src/c4/yml/parse_options.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/detail/stack.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_YML_DETAIL_STACK_HPP_ +#define _C4_YML_DETAIL_STACK_HPP_ + +#ifndef _C4_YML_COMMON_HPP_ +#include "../common.hpp" +#endif +#ifndef _C4_YML_ERROR_HPP_ +#include "../error.hpp" +#endif + +#ifdef RYML_DBG +//included above: +//# include +#endif + +//included above: +//#include + +namespace c4 { +namespace yml { + +C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wold-style-cast") +// NOLINTBEGIN(modernize-avoid-c-style-cast) + +namespace detail { + +/** A lightweight contiguous stack with Small Storage + * Optimization. This is required because std::vector can throw + * exceptions, and we don't want to enforce any particular error + * mechanism. */ +template +class stack +{ + static_assert(std::is_trivially_copyable::value, "T must be trivially copyable"); + static_assert(std::is_trivially_destructible::value, "T must be trivially destructible"); + +public: + + enum : id_type { sso_size = N }; // NOLINT + +public: + + T m_buf[size_t(N)]; + T *C4_RESTRICT m_stack; + id_type m_size; + id_type m_capacity; + Callbacks m_callbacks; + +public: + + constexpr static bool is_contiguous() noexcept { return true; } + + stack(Callbacks const& cb) + : m_buf() + , m_stack(m_buf) + , m_size(0) + , m_capacity(N) + , m_callbacks(cb) {} + stack() : stack(get_callbacks()) {} + ~stack() + { + _free(); + } + + stack(stack const& that) RYML_NOEXCEPT : stack(that.m_callbacks) + { + resize(that.m_size); + _cp(&that); + } + + stack(stack &&that) noexcept : stack(that.m_callbacks) + { + _mv(&that); + } + + stack& operator= (stack const& that) RYML_NOEXCEPT + { + if(&that != this) + { + _cb(that.m_callbacks); + resize(that.m_size); + _cp(&that); + } + return *this; + } + + stack& operator= (stack &&that) noexcept + { + _cb(that.m_callbacks); + _mv(&that); + return *this; + } + +public: + + id_type size() const { return m_size; } + id_type empty() const { return m_size == 0; } + id_type capacity() const { return m_capacity; } + + void clear() + { + _free(); + } + + void resize(id_type sz) + { + reserve(sz); + m_size = sz; + } + + void reserve(id_type sz); + + void push(T const& C4_RESTRICT n) + { + _RYML_ASSERT_BASIC_(m_callbacks, !csubstr((const char*)&n, sizeof(T)).overlaps(csubstr((const char*)m_stack, m_capacity * sizeof(T)))); + if(m_size == m_capacity) + reserve(m_capacity + 1); + m_stack[m_size] = n; + ++m_size; + } + + void push_top() + { + _RYML_ASSERT_BASIC_(m_callbacks, m_size > 0); + if(m_size == m_capacity) + reserve(m_capacity + 1); + m_stack[m_size] = m_stack[m_size - 1]; + ++m_size; + } + + T const& C4_RESTRICT pop() + { + _RYML_ASSERT_BASIC_(m_callbacks, m_size > 0); + --m_size; + return m_stack[m_size]; + } + + C4_ALWAYS_INLINE T const& C4_RESTRICT top() const { _RYML_ASSERT_BASIC_(m_callbacks, m_size > 0); return m_stack[m_size - 1]; } + C4_ALWAYS_INLINE T & C4_RESTRICT top() { _RYML_ASSERT_BASIC_(m_callbacks, m_size > 0); return m_stack[m_size - 1]; } + + C4_ALWAYS_INLINE T const& C4_RESTRICT bottom() const { _RYML_ASSERT_BASIC_(m_callbacks, m_size > 0); return m_stack[0]; } + C4_ALWAYS_INLINE T & C4_RESTRICT bottom() { _RYML_ASSERT_BASIC_(m_callbacks, m_size > 0); return m_stack[0]; } + + C4_ALWAYS_INLINE T const& C4_RESTRICT top(id_type i) const { _RYML_ASSERT_BASIC_(m_callbacks, i < m_size); return m_stack[m_size - 1 - i]; } + C4_ALWAYS_INLINE T & C4_RESTRICT top(id_type i) { _RYML_ASSERT_BASIC_(m_callbacks, i < m_size); return m_stack[m_size - 1 - i]; } + + C4_ALWAYS_INLINE T const& C4_RESTRICT bottom(id_type i) const { _RYML_ASSERT_BASIC_(m_callbacks, i < m_size); return m_stack[i]; } + C4_ALWAYS_INLINE T & C4_RESTRICT bottom(id_type i) { _RYML_ASSERT_BASIC_(m_callbacks, i < m_size); return m_stack[i]; } + + C4_ALWAYS_INLINE T const& C4_RESTRICT operator[](id_type i) const { _RYML_ASSERT_BASIC_(m_callbacks, i < m_size); return m_stack[i]; } + C4_ALWAYS_INLINE T & C4_RESTRICT operator[](id_type i) { _RYML_ASSERT_BASIC_(m_callbacks, i < m_size); return m_stack[i]; } + +public: + + using iterator = T *; + using const_iterator = T const *; + + iterator begin() noexcept { return m_stack; } + iterator end () noexcept { return m_stack + m_size; } + + const_iterator begin() const noexcept { return (const_iterator)m_stack; } + const_iterator end () const noexcept { return (const_iterator)m_stack + m_size; } + +public: + + void _free(); + void _cp(stack const* C4_RESTRICT that); + void _mv(stack * that); + void _cb(Callbacks const& cb); + +}; + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +template +void stack::reserve(id_type cap) +{ + _RYML_ASSERT_BASIC_(m_callbacks, m_size <= m_capacity); + _RYML_ASSERT_BASIC_(m_callbacks, m_capacity); + if(cap <= m_capacity || (cap <= N && m_stack == m_buf)) + return; + id_type next = 2 * m_capacity; + cap = cap > next ? cap : next; + T *ptr = (T*) m_callbacks.m_allocate((size_t)cap * sizeof(T), m_stack, m_callbacks.m_user_data); + _RYML_ASSERT_BASIC_(m_callbacks, ((uintptr_t)ptr % alignof(T)) == 0u); + if(m_size) + memcpy(ptr, m_stack, (size_t)m_size * sizeof(T)); + if(m_stack != m_buf) + m_callbacks.m_free(m_stack, (size_t)m_capacity * sizeof(T), m_callbacks.m_user_data); + m_stack = ptr; + m_capacity = cap; +} + + +//----------------------------------------------------------------------------- + +template +void stack::_free() +{ + _RYML_ASSERT_BASIC_(m_callbacks, m_stack != nullptr); // this structure cannot be memset() to zero + if(m_stack != m_buf) + { + m_callbacks.m_free(m_stack, (size_t)m_capacity * sizeof(T), m_callbacks.m_user_data); + m_stack = m_buf; + m_capacity = N; + } + else + { + _RYML_ASSERT_BASIC_(m_callbacks, m_capacity == N); + } + m_size = 0; +} + + +//----------------------------------------------------------------------------- + +template +void stack::_cp(stack const* C4_RESTRICT that) +{ + if(that->m_stack != that->m_buf) + { + _RYML_ASSERT_BASIC_(m_callbacks, that->m_capacity > N); + _RYML_ASSERT_BASIC_(m_callbacks, that->m_size <= that->m_capacity); + } + else + { + _RYML_ASSERT_BASIC_(m_callbacks, that->m_capacity <= N); + _RYML_ASSERT_BASIC_(m_callbacks, that->m_size <= that->m_capacity); + } + memcpy(m_stack, that->m_stack, that->m_size * sizeof(T)); + m_size = that->m_size; + m_capacity = that->m_size < N ? N : that->m_size; + m_callbacks = that->m_callbacks; +} + + +//----------------------------------------------------------------------------- + +template +void stack::_mv(stack * that) +{ + if(that->m_stack != that->m_buf) + { + _RYML_ASSERT_BASIC_(m_callbacks, that->m_capacity > N); + _RYML_ASSERT_BASIC_(m_callbacks, that->m_size <= that->m_capacity); + m_stack = that->m_stack; + } + else + { + _RYML_ASSERT_BASIC_(m_callbacks, that->m_capacity <= N); + _RYML_ASSERT_BASIC_(m_callbacks, that->m_size <= that->m_capacity); + memcpy(m_buf, that->m_buf, that->m_size * sizeof(T)); + m_stack = m_buf; + } + m_size = that->m_size; + m_capacity = that->m_capacity; + m_callbacks = that->m_callbacks; + // make sure no deallocation happens on destruction + _RYML_ASSERT_BASIC_(m_callbacks, that->m_stack != m_buf); + that->m_stack = that->m_buf; + that->m_capacity = N; + that->m_size = 0; +} + + +//----------------------------------------------------------------------------- + +template +void stack::_cb(Callbacks const& cb) +{ + if(cb != m_callbacks) + { + _free(); + m_callbacks = cb; + } +} + +} // namespace detail + +// NOLINTEND(modernize-avoid-c-style-cast) +C4_SUPPRESS_WARNING_GCC_CLANG_POP + +} // namespace yml +} // namespace c4 + +#endif /* _C4_YML_DETAIL_STACK_HPP_ */ + + +// (end src/c4/yml/detail/stack.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/file.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_YML_FILE_HPP_ +#define _C4_YML_FILE_HPP_ + +/** @name file.hpp Helpers to read/write files from disk */ + + +#ifndef _C4_YML_ERROR_HPP_ +// amalgamate: removed include of +// c4/yml/error.hpp +//#include +#if !defined(C4_YML_ERROR_HPP_) && !defined(_C4_YML_ERROR_HPP_) +#error "amalgamate: file c4/yml/error.hpp must have been included at this point" +#endif /* C4_YML_ERROR_HPP_ */ + +#endif +//included above: +//#include +//included above: +//#include + + +namespace c4 { +namespace yml { + +C4_SUPPRESS_WARNING_MSVC_WITH_PUSH(4996) // fopen: this function may be unsafe +C4_SUPPRESS_WARNING_CLANG_WITH_PUSH("-Wdeprecated-declarations") // fopen is deprecated + +/** @cond dev */ +namespace detail { +struct ScopedFILE +{ + FILE *file; + inline ScopedFILE(const char *filename, const char *access) // NOLINT + { + file = std::fopen(filename, access); // NOLINT + if(file == nullptr) + _RYML_ERR_BASIC("{}: could not open file", filename); + } + inline ~ScopedFILE() noexcept // NOLINT + { + std::fclose(file); // NOLINT + } + ScopedFILE(const ScopedFILE&) = delete; + ScopedFILE( ScopedFILE&&) = delete; + ScopedFILE& operator=(const ScopedFILE&) = delete; + ScopedFILE& operator=( ScopedFILE&&) = delete; +}; +} // detail +/** @endcond */ + + +//----------------------------------------------------------------------------- + +/** @defgroup doc_file_put_contents file_put_contents() + * + * Save a buffer to disk + * + * @ingroup doc_file_utils + * + * @addtogroup doc_file_put_contents + * @{ + */ + +/** save a contiguous buffer into a file */ +inline void file_put_contents(void const* buf, size_t sz, const char *filename, const char* access="wb") +{ + detail::ScopedFILE f(filename, access); + size_t written = std::fwrite(buf, 1, sz, f.file); // NOLINT + if(C4_UNLIKELY(written != sz)) + _RYML_ERR_BASIC("{}: failed file write: expected={}B actual={}B", filename, sz, written); // LCOV_EXCL_LINE +} + +/** save a contiguous buffer into a file */ +template +void file_put_contents(ContiguousContainer const& v, const char *filename, const char* access="wb") +{ + size_t vsz = static_cast(v.size()) * sizeof(typename ContiguousContainer::value_type); + void const* vbuf = v.empty() ? nullptr : &v[0]; + file_put_contents(vbuf, vsz, filename, access); +} + +/** @} */ + + +//----------------------------------------------------------------------------- + +/** @defgroup doc_file_get_contents file_get_contents() + * + * Load a file from disk into a buffer. + * + * @ingroup doc_file_utils + * + * @addtogroup doc_file_get_contents + * @{ + */ + +/** load a file of specified size from disk into an existing contiguous buffer. + */ +inline void file_get_contents(const char *filename, FILE *fp, size_t filesz, void *buf, size_t bufsz) +{ + _RYML_ASSERT_BASIC(filesz <= bufsz);(void)bufsz; + size_t read = std::fread(buf, 1, filesz, fp); + if(C4_UNLIKELY(read != filesz)) + _RYML_ERR_BASIC("{}: failed file read: expected={}B actual={}B", filename, filesz, read); // LCOV_EXCL_LINE +} + + +/** load a file from disk into an existing contiguous buffer. + * + * @return true if the file was successfully read and the buffer was + * large enough to fit the file size */ +C4_NODISCARD inline size_t file_get_contents(const char *filename, FILE *fp, void *buf, size_t bufsz) +{ + std::fseek(fp, 0, SEEK_END); // NOLINT + size_t filesz = static_cast(std::ftell(fp)); // NOLINT + std::rewind(fp); // NOLINT + if(filesz <= bufsz) + file_get_contents(filename, fp, filesz, buf, bufsz); + return filesz; +} + + +/** load a file from disk into an existing contiguous buffer. + * + * @return the size required for the buffer. It is up to the caller to + * check that the returned size is smaller than the buffer's size. + */ +C4_NODISCARD inline size_t file_get_contents(const char *filename, void *buf, size_t bufsz, const char *access="rb") +{ + detail::ScopedFILE f(filename, access); + return file_get_contents(filename, f.file, buf, bufsz); +} + + +/** load a file from disk into an existing ContiguousContainer, + * resizing it to fit the file's contents */ +template +void file_get_contents(ContiguousContainer *v, const char *filename, const char *access="rb") +{ + using value_type = typename ContiguousContainer::value_type; + using size_type = typename ContiguousContainer::size_type; + detail::ScopedFILE f(filename, access); + void * dat = !v->empty() ? &(*v)[0] : nullptr; + size_t vsz = static_cast(v->size()); + size_t fsz = file_get_contents(filename, f.file, dat, vsz); + size_t num_elms = fsz / sizeof(value_type); + if(C4_UNLIKELY(fsz != num_elms * sizeof(value_type))) + _RYML_ERR_BASIC("{}: file size ({}B) not a multiple of element size ({}B)", filename, fsz, sizeof(value_type)); + v->resize(static_cast(num_elms)); + if(fsz > vsz * sizeof(value_type)) + file_get_contents(filename, f.file, fsz, &(*v)[0], fsz); +} + + +/** load a file from disk and return a newly created + * ContiguousContainer with the file contents */ +template +ContiguousContainer file_get_contents(const char *filename, const char *access="rb") +{ + ContiguousContainer cc; + file_get_contents(&cc, filename, access); + return cc; +} + + +/** @} */ + + + +//----------------------------------------------------------------------------- + +/** @defgroup doc_stdin_get_contents stdin_get_contents() + * + * Load a file from stdin (or similar stream-like file) into a buffer. + * + * @ingroup doc_file_utils + * + * @addtogroup doc_stdin_get_contents + * @{ + */ + +/** load a file from stdin (or similar stream-like file) and + * return a newly created ContiguousContainer with the file + * contents */ +template +void stdin_get_contents(ContiguousContainer *cont, FILE *f=stdin) +{ + using I = typename ContiguousContainer::size_type; + using T = typename ContiguousContainer::value_type; + int c; + I pos = 0; + I num_bytes = 128; + I elmsz = static_cast(sizeof(T)); + I sz = (num_bytes + elmsz - 1) / elmsz; // round up to next multiple of elmsz + num_bytes = sz * elmsz; + cont->resize(sz); + unsigned char *buf = reinterpret_cast(&(*cont)[0]); // NOLINT + while((c = fgetc(f)) != EOF) + { + if(pos == num_bytes) + { + num_bytes = 2 * num_bytes; + if(num_bytes % sizeof(T)) goto errsize; // NOLINT // LCOV_EXCL_LINE + cont->resize(num_bytes / sizeof(T)); + buf = reinterpret_cast(&(*cont)[0]); // NOLINT + } + buf[pos++] = static_cast(c); + } + if(pos % sizeof(T)) + goto errsize; // NOLINT + cont->resize(pos / sizeof(T)); + return; +errsize: + _RYML_ERR_BASIC("file size is not multiple of element size"); +} + +/** load a file from stdin and return a newly created + * ContiguousContainer with the file contents */ +template +ContiguousContainer stdin_get_contents(FILE *f=stdin) +{ + ContiguousContainer cc; + stdin_get_contents(&cc, f); + return cc; +} + +/** @} */ + +C4_SUPPRESS_WARNING_CLANG_POP +C4_SUPPRESS_WARNING_MSVC_POP + + +} // namespace yml +} // namespace c4 + +#endif /* _C4_YML_FILE_HPP_ */ + + +// (end src/c4/yml/file.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/tag.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_YML_TAG_HPP_ +#define _C4_YML_TAG_HPP_ + +#ifndef _C4_YML_COMMON_HPP_ +// amalgamate: removed include of +// c4/yml/common.hpp +//#include +#if !defined(C4_YML_COMMON_HPP_) && !defined(_C4_YML_COMMON_HPP_) +#error "amalgamate: file c4/yml/common.hpp must have been included at this point" +#endif /* C4_YML_COMMON_HPP_ */ + +#endif +#ifndef _C4_YML_DETAIL_STACK_HPP_ +// amalgamate: removed include of +// c4/yml/detail/stack.hpp +//#include +#if !defined(C4_YML_DETAIL_STACK_HPP_) && !defined(_C4_YML_DETAIL_STACK_HPP_) +#error "amalgamate: file c4/yml/detail/stack.hpp must have been included at this point" +#endif /* C4_YML_DETAIL_STACK_HPP_ */ + +#endif + +namespace c4 { +namespace yml { + +C4_SUPPRESS_WARNING_MSVC_WITH_PUSH(4251) // csubstr needs to have dll-interface to be used by clients of LookupResult + +class Tree; + +/** @addtogroup doc_tag_utils + * + * @{ + */ + + +#ifndef RYML_MAX_TAG_DIRECTIVES +/** the maximum number of tag directives in a Tree */ +#define RYML_MAX_TAG_DIRECTIVES 4 +#endif + +/** the integral type necessary to cover all the bits marking node tags */ +using tag_bits = uint16_t; + +/** a bit mask for marking tags for types */ +typedef enum : tag_bits { // NOLINT + TAG_NONE = 0, + // container types + TAG_MAP = 1, /**< !!map Unordered set of key: value pairs without duplicates. @see https://yaml.org/type/map.html */ + TAG_OMAP = 2, /**< !!omap Ordered sequence of key: value pairs without duplicates. @see https://yaml.org/type/omap.html */ + TAG_PAIRS = 3, /**< !!pairs Ordered sequence of key: value pairs allowing duplicates. @see https://yaml.org/type/pairs.html */ + TAG_SET = 4, /**< !!set Unordered set of non-equal values. @see https://yaml.org/type/set.html */ + TAG_SEQ = 5, /**< !!seq Sequence of arbitrary values. @see https://yaml.org/type/seq.html */ + // scalar types + TAG_BINARY = 6, /**< !!binary A sequence of zero or more octets (8 bit values). @see https://yaml.org/type/binary.html */ + TAG_BOOL = 7, /**< !!bool Mathematical Booleans. @see https://yaml.org/type/bool.html */ + TAG_FLOAT = 8, /**< !!float Floating-point approximation to real numbers. https://yaml.org/type/float.html */ + TAG_INT = 9, /**< !!float Mathematical integers. https://yaml.org/type/int.html */ + TAG_MERGE = 10, /**< !!merge Specify one or more mapping to be merged with the current one. https://yaml.org/type/merge.html */ + TAG_NULL = 11, /**< !!null Devoid of value. https://yaml.org/type/null.html */ + TAG_STR = 12, /**< !!str A sequence of zero or more Unicode characters. https://yaml.org/type/str.html */ + TAG_TIMESTAMP = 13, /**< !!timestamp A point in time https://yaml.org/type/timestamp.html */ + TAG_VALUE = 14, /**< !!value Specify the default value of a mapping https://yaml.org/type/value.html */ + TAG_YAML = 15, /**< !!yaml Specify the default value of a mapping https://yaml.org/type/yaml.html */ +} YamlTag_e; + +RYML_EXPORT YamlTag_e to_tag(csubstr tag); +RYML_EXPORT csubstr from_tag(YamlTag_e tag); +RYML_EXPORT csubstr from_tag_long(YamlTag_e tag); +RYML_EXPORT csubstr normalize_tag(csubstr tag); +RYML_EXPORT csubstr normalize_tag_long(csubstr tag); +RYML_EXPORT csubstr normalize_tag_long(csubstr tag, substr output); + +/** is a tag of the form `!handle!tag`? */ +RYML_EXPORT bool is_custom_tag(csubstr tag); +RYML_EXPORT bool is_valid_tag_handle(csubstr handle); + + +//----------------------------------------------------------------------------- + +/** Accelerator structure to reduce memory requirements by enabling + * reuse of resolved tags. */ +struct TagCache +{ + struct Entry + { + csubstr tag; + csubstr resolved; + id_type doc_id; + }; + using Entries = detail::stack; + using const_iterator = id_type; + struct RYML_EXPORT LookupResult + { + csubstr resolved; + const_iterator pos; + operator bool() const noexcept { return resolved.len > 0; } + }; + +public: + + TagCache() noexcept : m_entries() {} + RYML_EXPORT LookupResult find(csubstr tag, id_type doc_id, id_type linear_threshold=Entries::sso_size) const noexcept; + RYML_EXPORT void add(csubstr tag, csubstr resolved, id_type doc_id, const_iterator pos) RYML_NOEXCEPT; + + void clear() noexcept { m_entries.clear(); } + +public: + + /** @cond dev */ + Entries m_entries; + /** @endcond */ + +}; + + +//----------------------------------------------------------------------------- + +struct RYML_EXPORT TagDirective +{ + /** Eg
!e!
in
%TAG !e! tag:example.com,2000:app/
*/ + csubstr handle; + /** Eg
tag:example.com,2000:app/
in
%TAG !e! tag:example.com,2000:app/
*/ + csubstr prefix; + /** ID of the target document */ + id_type doc_id; +}; + +struct RYML_EXPORT TagDirectiveRange +{ + TagDirective const* C4_RESTRICT b; + TagDirective const* C4_RESTRICT e; + C4_ALWAYS_INLINE TagDirective const* begin() const noexcept { return b; } + C4_ALWAYS_INLINE TagDirective const* end() const noexcept { return e; } + id_type size() const noexcept { return static_cast(e - b); } +}; + +struct RYML_EXPORT TagDirectives +{ + TagDirective m_directives[RYML_MAX_TAG_DIRECTIVES]; + bool redefines_qmrk() const noexcept; + TagDirective const* add(csubstr handle, csubstr prefix, id_type doc_id) noexcept; + void clear() noexcept; + id_type size() const noexcept; + TagDirective const* lookup(csubstr tag, id_type id) const noexcept; + TagDirective * begin() noexcept { return m_directives; } + TagDirective * end() noexcept { return m_directives + size(); } + TagDirective const* begin() const noexcept { return m_directives; } + TagDirective const* end() const noexcept { return m_directives + size(); } + TagDirectiveRange directives() const noexcept { return TagDirectiveRange{m_directives, m_directives + size()}; } + TagDirectiveRange lookup_range(id_type doc_id) const noexcept; + /** @note the str member of the return value may be null, meaning + * that the buffer was not enough to fit the transformed tag. + * + * @note the return value may actually be not a substring of the + * input buffer. */ + csubstr resolve(substr buf, size_t *bufsz, csubstr tag, id_type doc_id, Location const& ymlloc, Callbacks const& callbacks, bool with_brackets=true) const; +}; + +/** returns the length of the transformed tag, or 0 to signal that the + * tag is local and cannot be resolved */ +RYML_EXPORT size_t transform_tag(substr output, csubstr handle, csubstr prefix, csubstr tag, + Callbacks const& callbacks, Location const& ymlloc={}, + bool with_brackets=true); + +/** @} */ + +C4_SUPPRESS_WARNING_MSVC_POP + +} // namespace yml +} // namespace c4 + +#endif /* _C4_YML_TAG_HPP_ */ + + +// (end src/c4/yml/tag.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/tree.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_YML_TREE_HPP_ +#define _C4_YML_TREE_HPP_ + +/** @file tree.hpp */ + +#ifndef _C4_ERROR_HPP_ +// amalgamate: removed include of +// c4/error.hpp +//#include "c4/error.hpp" +#if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) +#error "amalgamate: file c4/error.hpp must have been included at this point" +#endif /* C4_ERROR_HPP_ */ + +#endif +#ifndef _C4_TYPES_HPP_ +// amalgamate: removed include of +// c4/types.hpp +//#include "c4/types.hpp" +#if !defined(C4_TYPES_HPP_) && !defined(_C4_TYPES_HPP_) +#error "amalgamate: file c4/types.hpp must have been included at this point" +#endif /* C4_TYPES_HPP_ */ + +#endif +#ifndef _C4_YML_FWD_HPP_ +// amalgamate: removed include of +// c4/yml/fwd.hpp +//#include "c4/yml/fwd.hpp" +#if !defined(C4_YML_FWD_HPP_) && !defined(_C4_YML_FWD_HPP_) +#error "amalgamate: file c4/yml/fwd.hpp must have been included at this point" +#endif /* C4_YML_FWD_HPP_ */ + +#endif +#ifndef _C4_YML_COMMON_HPP_ +// amalgamate: removed include of +// c4/yml/common.hpp +//#include "c4/yml/common.hpp" +#if !defined(C4_YML_COMMON_HPP_) && !defined(_C4_YML_COMMON_HPP_) +#error "amalgamate: file c4/yml/common.hpp must have been included at this point" +#endif /* C4_YML_COMMON_HPP_ */ + +#endif +#ifndef _C4_YML_NODE_TYPE_HPP_ +// amalgamate: removed include of +// c4/yml/node_type.hpp +//#include "c4/yml/node_type.hpp" +#if !defined(C4_YML_NODE_TYPE_HPP_) && !defined(_C4_YML_NODE_TYPE_HPP_) +#error "amalgamate: file c4/yml/node_type.hpp must have been included at this point" +#endif /* C4_YML_NODE_TYPE_HPP_ */ + +#endif +#ifndef _C4_YML_TAG_HPP_ +// amalgamate: removed include of +// c4/yml/tag.hpp +//#include "c4/yml/tag.hpp" +#if !defined(C4_YML_TAG_HPP_) && !defined(_C4_YML_TAG_HPP_) +#error "amalgamate: file c4/yml/tag.hpp must have been included at this point" +#endif /* C4_YML_TAG_HPP_ */ + +#endif +#ifndef _C4_YML_ERROR_HPP_ +// amalgamate: removed include of +// c4/yml/error.hpp +//#include "c4/yml/error.hpp" +#if !defined(C4_YML_ERROR_HPP_) && !defined(_C4_YML_ERROR_HPP_) +#error "amalgamate: file c4/yml/error.hpp must have been included at this point" +#endif /* C4_YML_ERROR_HPP_ */ + +#endif +#ifndef _C4_CHARCONV_HPP_ +// amalgamate: removed include of +// c4/charconv.hpp +//#include +#if !defined(C4_CHARCONV_HPP_) && !defined(_C4_CHARCONV_HPP_) +#error "amalgamate: file c4/charconv.hpp must have been included at this point" +#endif /* C4_CHARCONV_HPP_ */ + +#endif + +//included above: +//#include +//included above: +//#include + + +C4_SUPPRESS_WARNING_MSVC_PUSH +C4_SUPPRESS_WARNING_MSVC(4251) // needs to have dll-interface to be used by clients of struct +C4_SUPPRESS_WARNING_MSVC(4296) // expression is always 'boolean_value' +C4_SUPPRESS_WARNING_GCC_CLANG_PUSH +C4_SUPPRESS_WARNING_GCC_CLANG("-Wold-style-cast") +C4_SUPPRESS_WARNING_GCC("-Wuseless-cast") +C4_SUPPRESS_WARNING_GCC("-Wtype-limits") + +// NOLINTBEGIN(modernize-avoid-c-style-cast) + +namespace c4 { +namespace yml { + +/** @cond dev */ +template inline auto read(Tree const* C4_RESTRICT tree, id_type id, T *v) -> typename std::enable_if::value, bool>::type; +template inline auto read(Tree const* C4_RESTRICT tree, id_type id, T *v) -> typename std::enable_if::value && !std::is_floating_point::value, bool>::type; +template inline auto read(Tree const* C4_RESTRICT tree, id_type id, T *v) -> typename std::enable_if::value, bool>::type; +template bool read(Tree const* C4_RESTRICT tree, id_type id, T const& wrapper); + +template inline auto readkey(Tree const* C4_RESTRICT tree, id_type id, T *v) -> typename std::enable_if::value, bool>::type; +template inline auto readkey(Tree const* C4_RESTRICT tree, id_type id, T *v) -> typename std::enable_if::value && !std::is_floating_point::value, bool>::type; +template inline auto readkey(Tree const* C4_RESTRICT tree, id_type id, T *v) -> typename std::enable_if::value, bool>::type; +template bool readkey(Tree const* C4_RESTRICT tree, id_type id, T const& wrapper); +/** @endcond */ + + +template size_t to_chars_float(substr buf, T val); +template bool from_chars_float(csubstr buf, T *C4_RESTRICT val); + + +template +C4_ALWAYS_INLINE auto serialize_scalar(substr buf, T const& C4_RESTRICT a) + -> typename std::enable_if::value, size_t>::type +{ + return to_chars_float(buf, a); +} +template +C4_ALWAYS_INLINE auto serialize_scalar(substr buf, T const& C4_RESTRICT a) + -> typename std::enable_if< ! std::is_floating_point::value, size_t>::type +{ + return to_chars(buf, a); +} + + +template +csubstr serialize_to_arena(Tree * C4_RESTRICT tree, T const& C4_RESTRICT a); + +RYML_EXPORT csubstr serialize_to_arena(Tree * C4_RESTRICT tree, csubstr a); + +// these overloads are needed to ensure that these types are not +// dispatched to the general template overload +C4_ALWAYS_INLINE csubstr serialize_to_arena(Tree * C4_RESTRICT tree, substr a) +{ + return serialize_to_arena(tree, csubstr(a)); +} +C4_ALWAYS_INLINE csubstr serialize_to_arena(Tree * C4_RESTRICT tree, const char *a) +{ + return serialize_to_arena(tree, to_csubstr(a)); +} +C4_ALWAYS_INLINE csubstr serialize_to_arena(Tree * C4_RESTRICT, std::nullptr_t) +{ + return csubstr{}; +} + + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + + +/** @addtogroup doc_tree + * + * @{ + */ + +/** a node scalar is a csubstr, which may be tagged and anchored. */ +struct NodeScalar +{ + csubstr tag; + csubstr scalar; + csubstr anchor; + +public: + + /// initialize as an empty scalar + NodeScalar() noexcept : tag(), scalar(), anchor() {} // NOLINT + + /// initialize as an untagged scalar + template + NodeScalar(const char (&s)[N]) noexcept : tag(), scalar(s), anchor() {} + NodeScalar(csubstr s ) noexcept : tag(), scalar(s), anchor() {} + + /// initialize as a tagged scalar + template + NodeScalar(const char (&t)[N], const char (&s)[N]) noexcept : tag(t), scalar(s), anchor() {} + NodeScalar(csubstr t , csubstr s ) noexcept : tag(t), scalar(s), anchor() {} + +public: + + ~NodeScalar() noexcept = default; + NodeScalar(NodeScalar &&) noexcept = default; + NodeScalar(NodeScalar const&) noexcept = default; + NodeScalar& operator= (NodeScalar &&) noexcept = default; + NodeScalar& operator= (NodeScalar const&) noexcept = default; + +public: + + bool empty() const noexcept { return tag.empty() && scalar.empty() && anchor.empty(); } + + void clear() noexcept { tag.clear(); scalar.clear(); anchor.clear(); } + + void set_ref_maybe_replacing_scalar(csubstr ref, bool has_scalar) RYML_NOEXCEPT + { + csubstr trimmed = ref.begins_with('*') ? ref.sub(1) : ref; + anchor = trimmed; + if((!has_scalar) || !scalar.ends_with(trimmed)) + scalar = ref; + } +}; +C4_MUST_BE_TRIVIAL_COPY(NodeScalar); + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** convenience class to initialize nodes */ +struct NodeInit +{ + + NodeType type; + NodeScalar key; + NodeScalar val; + +public: + + /// initialize as an empty node + NodeInit() : type(NOTYPE), key(), val() {} + /// initialize as a typed node + NodeInit(NodeType_e t) : type(t), key(), val() {} + /// initialize as a sequence member + NodeInit(NodeScalar const& v) : type(VAL), key(), val(v) { _add_flags(); } + /// initialize as a sequence member with explicit type + NodeInit(NodeScalar const& v, NodeType_e t) : type(t|VAL), key(), val(v) { _add_flags(); } + /// initialize as a mapping member + NodeInit( NodeScalar const& k, NodeScalar const& v) : type(KEYVAL), key(k), val(v) { _add_flags(); } + /// initialize as a mapping member with explicit type + NodeInit(NodeType_e t, NodeScalar const& k, NodeScalar const& v) : type(t), key(k), val(v) { _add_flags(); } + /// initialize as a mapping member with explicit type (eg for SEQ or MAP) + NodeInit(NodeType_e t, NodeScalar const& k ) : type(t), key(k), val( ) { _add_flags(KEY); } + +public: + + void clear() + { + type.clear(); + key.clear(); + val.clear(); + } + + void _add_flags(type_bits more_flags=0) + { + type = (type|more_flags); + if( ! key.tag.empty()) + type = (type|KEYTAG); + if( ! val.tag.empty()) + type = (type|VALTAG); + if( ! key.anchor.empty()) + type = (type|KEYANCH); + if( ! val.anchor.empty()) + type = (type|VALANCH); + } + + bool _check() const + { + // key cannot be empty + _RYML_ASSERT_BASIC(key.scalar.empty() == ((type & KEY) == 0)); + // key tag cannot be empty + _RYML_ASSERT_BASIC(key.tag.empty() == ((type & KEYTAG) == 0)); + // val may be empty even though VAL is set. But when VAL is not set, val must be empty + _RYML_ASSERT_BASIC(((type & VAL) != 0) || val.scalar.empty()); + // val tag cannot be empty + _RYML_ASSERT_BASIC(val.tag.empty() == ((type & VALTAG) == 0)); + return true; + } +}; + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** contains the data for each YAML node. */ +struct NodeData +{ + NodeType m_type; + + NodeScalar m_key; + NodeScalar m_val; + + id_type m_parent; + id_type m_first_child; + id_type m_last_child; + id_type m_next_sibling; + id_type m_prev_sibling; +}; +C4_MUST_BE_TRIVIAL_COPY(NodeData); + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +class RYML_EXPORT Tree +{ +public: + + /** @name construction and assignment */ + /** @{ */ + + Tree() : Tree(get_callbacks()) {} + Tree(Callbacks const& cb); + Tree(id_type node_capacity, size_t arena_capacity=RYML_DEFAULT_TREE_ARENA_CAPACITY) : Tree(node_capacity, arena_capacity, get_callbacks()) {} + Tree(id_type node_capacity, size_t arena_capacity, Callbacks const& cb); + + ~Tree(); + + Tree(Tree const& that); + Tree(Tree && that) noexcept; + + Tree& operator= (Tree const& that); + Tree& operator= (Tree && that) noexcept; + + /** @} */ + +public: + + /** @name memory and sizing */ + /** @{ */ + + void reserve(id_type node_capacity=RYML_DEFAULT_TREE_CAPACITY); + + /** clear the tree and zero every node + * @note does NOT clear the arena + * @see clear_arena() */ + void clear(); + void clear_arena() { m_arena_pos = 0; } + + bool empty() const { return m_size == 0; } + + id_type size() const { return m_size; } + id_type capacity() const { return m_cap; } + id_type slack() const { _RYML_ASSERT_BASIC(m_cap >= m_size); return m_cap - m_size; } + + Callbacks const& callbacks() const { return m_callbacks; } + void callbacks(Callbacks const& cb) { m_callbacks = cb; } + + /** @} */ + +public: + + /** @name node getters */ + /** @{ */ + + //! get the index of a node belonging to this tree. + //! @p n can be nullptr, in which case NONE is returned + id_type id(NodeData const* n) const + { + if( ! n) + return NONE; + _RYML_ASSERT_VISIT_(m_callbacks, n >= m_buf && n < m_buf + m_cap, this, NONE); + return static_cast(n - m_buf); + } + + //! get a pointer to a node's NodeData. + //! i can be NONE, in which case a nullptr is returned + NodeData *get(id_type node) // NOLINT(readability-make-member-function-const) + { + if(node == NONE) + return nullptr; + _RYML_ASSERT_VISIT_(m_callbacks, node >= 0 && node < m_cap, this, node); + return m_buf + node; + } + //! get a pointer to a node's NodeData. + //! i can be NONE, in which case a nullptr is returned. + NodeData const *get(id_type node) const + { + if(node == NONE) + return nullptr; + _RYML_ASSERT_VISIT_(m_callbacks, node >= 0 && node < m_cap, this, node); + return m_buf + node; + } + + //! An if-less form of get() that demands a valid node index. + //! This function is implementation only; use at your own risk. + NodeData * _p(id_type node) { _RYML_ASSERT_VISIT_(m_callbacks, node != NONE && node >= 0 && node < m_cap, this, node); return m_buf + node; } // NOLINT(readability-make-member-function-const) + //! An if-less form of get() that demands a valid node index. + //! This function is implementation only; use at your own risk. + NodeData const * _p(id_type node) const { _RYML_ASSERT_VISIT_(m_callbacks, node != NONE && node >= 0 && node < m_cap, this, node); return m_buf + node; } + + //! Get the id of the root node. The tree must not be empty. + id_type root_id() const { _RYML_ASSERT_VISIT_(m_callbacks, m_cap > 0 && m_size > 0, this, id_type(0)); return 0; } + + //! Get a NodeRef of a node by id + NodeRef ref(id_type node); + //! Get a NodeRef of a node by id + ConstNodeRef ref(id_type node) const; + //! Get a NodeRef of a node by id + ConstNodeRef cref(id_type node) const; + + //! Get the root as a NodeRef + NodeRef rootref(); + //! Get the root as a ConstNodeRef + ConstNodeRef rootref() const; + //! Get the root as a ConstNodeRef + ConstNodeRef crootref() const; + + //! get the i-th document of the stream + //! @note @p i is NOT the node id, but the doc position within the stream + NodeRef docref(id_type i); + //! get the i-th document of the stream + //! @note @p i is NOT the node id, but the doc position within the stream + ConstNodeRef docref(id_type i) const; + //! get the i-th document of the stream + //! @note @p i is NOT the node id, but the doc position within the stream + ConstNodeRef cdocref(id_type i) const; + + //! find a root child (ie child of root) by name, return it as a NodeRef + //! @note requires the root to be a map. + NodeRef operator[] (csubstr key); + //! find a root child (ie child of root) by name, return it as a NodeRef + //! @note requires the root to be a map. + ConstNodeRef operator[] (csubstr key) const; + + //! find a root child (ie child of root) by index: return the root node's @p i-th child as a NodeRef + //! @note @p i is NOT the node id, but the child's position + NodeRef operator[] (id_type i); + //! find a root child (ie child of root) by index: return the root node's @p i-th child as a NodeRef + //! @note @p i is NOT the node id, but the child's position + ConstNodeRef operator[] (id_type i) const; + + /** @} */ + +public: + + /** @name node property getters */ + /** @{ */ + + NodeType type(id_type node) const { return _p(node)->m_type; } + const char* type_str(id_type node) const { return NodeType::type_str(_p(node)->m_type); } + + csubstr const& key (id_type node) const { _RYML_ASSERT_VISIT_(m_callbacks, has_key(node), this, node); return _p(node)->m_key.scalar; } + csubstr const& key_tag (id_type node) const { _RYML_ASSERT_VISIT_(m_callbacks, has_key_tag(node), this, node); return _p(node)->m_key.tag; } + csubstr const& key_ref (id_type node) const { _RYML_ASSERT_VISIT_(m_callbacks, is_key_ref(node), this, node); return _p(node)->m_key.anchor; } + csubstr const& key_anchor(id_type node) const { _RYML_ASSERT_VISIT_(m_callbacks, has_key_anchor(node), this, node); return _p(node)->m_key.anchor; } + NodeScalar const& keysc (id_type node) const { _RYML_ASSERT_VISIT_(m_callbacks, has_key(node), this, node); return _p(node)->m_key; } + + csubstr const& val (id_type node) const { _RYML_ASSERT_VISIT_(m_callbacks, has_val(node), this, node); return _p(node)->m_val.scalar; } + csubstr const& val_tag (id_type node) const { _RYML_ASSERT_VISIT_(m_callbacks, has_val_tag(node), this, node); return _p(node)->m_val.tag; } + csubstr const& val_ref (id_type node) const { _RYML_ASSERT_VISIT_(m_callbacks, is_val_ref(node), this, node); return _p(node)->m_val.anchor; } + csubstr const& val_anchor(id_type node) const { _RYML_ASSERT_VISIT_(m_callbacks, has_val_anchor(node), this, node); return _p(node)->m_val.anchor; } + NodeScalar const& valsc (id_type node) const { _RYML_ASSERT_VISIT_(m_callbacks, has_val(node), this, node); return _p(node)->m_val; } + + /** @} */ + +public: + + /** @name node type predicates */ + /** @{ */ + + C4_ALWAYS_INLINE bool type_has_any(id_type node, NodeType_e bits) const { return _p(node)->m_type.has_any(bits); } + C4_ALWAYS_INLINE bool type_has_all(id_type node, NodeType_e bits) const { return _p(node)->m_type.has_all(bits); } + C4_ALWAYS_INLINE bool type_has_none(id_type node, NodeType_e bits) const { return _p(node)->m_type.has_none(bits); } + + C4_ALWAYS_INLINE bool is_stream(id_type node) const { return _p(node)->m_type.is_stream(); } + C4_ALWAYS_INLINE bool is_doc(id_type node) const { return _p(node)->m_type.is_doc(); } + C4_ALWAYS_INLINE bool is_container(id_type node) const { return _p(node)->m_type.is_container(); } + C4_ALWAYS_INLINE bool is_map(id_type node) const { return _p(node)->m_type.is_map(); } + C4_ALWAYS_INLINE bool is_seq(id_type node) const { return _p(node)->m_type.is_seq(); } + C4_ALWAYS_INLINE bool has_key(id_type node) const { return _p(node)->m_type.has_key(); } + C4_ALWAYS_INLINE bool has_val(id_type node) const { return _p(node)->m_type.has_val(); } + C4_ALWAYS_INLINE bool is_val(id_type node) const { return _p(node)->m_type.is_val(); } + C4_ALWAYS_INLINE bool is_keyval(id_type node) const { return _p(node)->m_type.is_keyval(); } + C4_ALWAYS_INLINE bool has_key_tag(id_type node) const { return _p(node)->m_type.has_key_tag(); } + C4_ALWAYS_INLINE bool has_val_tag(id_type node) const { return _p(node)->m_type.has_val_tag(); } + C4_ALWAYS_INLINE bool has_key_anchor(id_type node) const { return _p(node)->m_type.has_key_anchor(); } + C4_ALWAYS_INLINE bool has_val_anchor(id_type node) const { return _p(node)->m_type.has_val_anchor(); } + C4_ALWAYS_INLINE bool has_anchor(id_type node) const { return _p(node)->m_type.has_anchor(); } + C4_ALWAYS_INLINE bool is_key_ref(id_type node) const { return _p(node)->m_type.is_key_ref(); } + C4_ALWAYS_INLINE bool is_val_ref(id_type node) const { return _p(node)->m_type.is_val_ref(); } + C4_ALWAYS_INLINE bool is_ref(id_type node) const { return _p(node)->m_type.is_ref(); } + + C4_ALWAYS_INLINE bool parent_is_seq(id_type node) const { _RYML_ASSERT_VISIT_(m_callbacks, has_parent(node), this, node); return is_seq(_p(node)->m_parent); } + C4_ALWAYS_INLINE bool parent_is_map(id_type node) const { _RYML_ASSERT_VISIT_(m_callbacks, has_parent(node), this, node); return is_map(_p(node)->m_parent); } + + /** true when the node has an anchor named a */ + C4_ALWAYS_INLINE bool has_anchor(id_type node, csubstr a) const { return _p(node)->m_key.anchor == a || _p(node)->m_val.anchor == a; } + + /** true if the node key is empty, or its scalar verifies @ref scalar_is_null(). + * @warning the node must verify @ref Tree::has_key() (asserted) (ie must be a member of a map) + * @see https://github.com/biojppm/rapidyaml/issues/413 */ + C4_ALWAYS_INLINE bool key_is_null(id_type node) const { _RYML_ASSERT_VISIT_(m_callbacks, has_key(node), this, node); NodeData const* C4_RESTRICT n = _p(node); return !n->m_type.is_key_quoted() && (n->m_type.key_is_null() || scalar_is_null(n->m_key.scalar)); } + /** true if the node val is empty, or its scalar verifies @ref scalar_is_null(). + * @warning the node must verify @ref Tree::has_val() (asserted) (ie must be a scalar / must not be a container) + * @see https://github.com/biojppm/rapidyaml/issues/413 */ + C4_ALWAYS_INLINE bool val_is_null(id_type node) const { _RYML_ASSERT_VISIT_(m_callbacks, has_val(node), this, node); NodeData const* C4_RESTRICT n = _p(node); return !n->m_type.is_val_quoted() && (n->m_type.val_is_null() || scalar_is_null(n->m_val.scalar)); } + + /// true if the key was a scalar requiring filtering and was left + /// unfiltered during the parsing (see ParserOptions) + C4_ALWAYS_INLINE bool is_key_unfiltered(id_type node) const { return _p(node)->m_type.is_key_unfiltered(); } + /// true if the val was a scalar requiring filtering and was left + /// unfiltered during the parsing (see ParserOptions) + C4_ALWAYS_INLINE bool is_val_unfiltered(id_type node) const { return _p(node)->m_type.is_val_unfiltered(); } + + RYML_DEPRECATED("use has_key_anchor()") bool is_key_anchor(id_type node) const { return _p(node)->m_type.has_key_anchor(); } + RYML_DEPRECATED("use has_val_anchor()") bool is_val_anchor(id_type node) const { return _p(node)->m_type.has_val_anchor(); } + RYML_DEPRECATED("use has_anchor()") bool is_anchor(id_type node) const { return _p(node)->m_type.has_anchor(); } + RYML_DEPRECATED("use has_anchor_or_ref()") bool is_anchor_or_ref(id_type node) const { return _p(node)->m_type.has_anchor() || _p(node)->m_type.is_ref(); } + + /** @} */ + +public: + + /** @name hierarchy predicates */ + /** @{ */ + + bool is_root(id_type node) const { _RYML_ASSERT_VISIT_(m_callbacks, _p(node)->m_parent != NONE || node == 0, this, node); return _p(node)->m_parent == NONE; } + + bool has_parent(id_type node) const { return _p(node)->m_parent != NONE; } + + /** true when ancestor is parent or parent of a parent of node */ + bool is_ancestor(id_type node, id_type ancestor) const; + + /** true when key and val are empty, and has no children */ + bool empty(id_type node) const { return ! has_children(node) && _p(node)->m_key.empty() && (( ! (_p(node)->m_type & VAL)) || _p(node)->m_val.empty()); } + + /** true if @p node has a child with id @p ch */ + bool has_child(id_type node, id_type ch) const { return _p(ch)->m_parent == node; } + /** true if @p node has a child with key @p key */ + bool has_child(id_type node, csubstr key) const { return find_child(node, key) != NONE; } + /** true if @p node has any children key */ + bool has_children(id_type node) const { return _p(node)->m_first_child != NONE; } + + /** true if @p node has a sibling with id @p sib */ + bool has_sibling(id_type node, id_type sib) const { return _p(node)->m_parent == _p(sib)->m_parent; } + /** true if one of the node's siblings has the given key */ + bool has_sibling(id_type node, csubstr key) const { return find_sibling(node, key) != NONE; } + /** true if node is not a single child */ + bool has_other_siblings(id_type node) const + { + NodeData const *n = _p(node); + if(C4_LIKELY(n->m_parent != NONE)) + { + n = _p(n->m_parent); + return n->m_first_child != n->m_last_child; + } + return false; + } + + RYML_DEPRECATED("use has_other_siblings()") static bool has_siblings(id_type /*node*/) { return true; } + + /** @} */ + +public: + + /** @name hierarchy getters */ + /** @{ */ + + id_type parent(id_type node) const { return _p(node)->m_parent; } + + id_type prev_sibling(id_type node) const { return _p(node)->m_prev_sibling; } + id_type next_sibling(id_type node) const { return _p(node)->m_next_sibling; } + + /** O(#num_children) */ + id_type num_children(id_type node) const; + id_type child_pos(id_type node, id_type ch) const; + id_type first_child(id_type node) const { return _p(node)->m_first_child; } + id_type last_child(id_type node) const { return _p(node)->m_last_child; } + id_type child(id_type node, id_type pos) const; + id_type find_child(id_type node, csubstr const& key) const; + + /** O(#num_siblings) */ + /** counts with this */ + id_type num_siblings(id_type node) const { return is_root(node) ? 1 : num_children(_p(node)->m_parent); } + /** does not count with this */ + id_type num_other_siblings(id_type node) const { id_type ns = num_siblings(node); _RYML_ASSERT_VISIT_(m_callbacks, ns > 0, this, node); return ns-1; } + id_type sibling_pos(id_type node, id_type sib) const { _RYML_ASSERT_VISIT_(m_callbacks, ! is_root(node) || node == root_id(), this, node); return child_pos(_p(node)->m_parent, sib); } + id_type first_sibling(id_type node) const { return is_root(node) ? node : _p(_p(node)->m_parent)->m_first_child; } + id_type last_sibling(id_type node) const { return is_root(node) ? node : _p(_p(node)->m_parent)->m_last_child; } + id_type sibling(id_type node, id_type pos) const { return child(_p(node)->m_parent, pos); } + id_type find_sibling(id_type node, csubstr const& key) const { return find_child(_p(node)->m_parent, key); } + + id_type depth_asc(id_type node) const; /**< O(log(num_tree_nodes)) get the ascending depth of the node: number of levels between root and node */ + id_type depth_desc(id_type node) const; /**< O(num_tree_nodes) get the descending depth of the node: number of levels between node and deepest child */ + + /** gets the @p i document node index. requires that the root node is a stream. */ + id_type doc(id_type i) const { id_type rid = root_id(); _RYML_ASSERT_VISIT_(m_callbacks, is_stream(rid), this, rid); return child(rid, i); } + + /** get the document which is a parent document of node i, or the root if the tree is not a stream */ + id_type ancestor_doc(id_type node) const + { + NodeData const *nd; + do + { + nd = _p(node); + if(nd->m_type.is_doc() || nd->m_parent == NONE) + break; + node = nd->m_parent; + } while(nd->m_parent != NONE); + return node; + } + + /** @} */ + +public: + + /** @name node style predicates and modifiers. see the corresponding predicate in NodeType */ + /** @{ */ + + C4_ALWAYS_INLINE bool is_container_styled(id_type node) const { return _p(node)->m_type.is_container_styled(); } + C4_ALWAYS_INLINE bool is_block(id_type node) const { return _p(node)->m_type.is_block(); } + C4_ALWAYS_INLINE bool is_flow_sl(id_type node) const { return _p(node)->m_type.is_flow_sl(); } + RYML_DEPRECATED("use one of .is_flow_ml{1,n,x}()") + C4_ALWAYS_INLINE bool is_flow_ml(id_type node) const { return _p(node)->m_type.is_flow_ml1(); } + C4_ALWAYS_INLINE bool is_flow_ml1(id_type node) const { return _p(node)->m_type.is_flow_ml1(); } + C4_ALWAYS_INLINE bool is_flow_mln(id_type node) const { return _p(node)->m_type.is_flow_mln(); } + C4_ALWAYS_INLINE bool is_flow_mlx(id_type node) const { return _p(node)->m_type.is_flow_mlx(); } + C4_ALWAYS_INLINE bool is_flow(id_type node) const { return _p(node)->m_type.is_flow(); } + C4_ALWAYS_INLINE bool has_flow_space(id_type node) const { return _p(node)->m_type.has_flow_space(); } + + C4_ALWAYS_INLINE bool is_key_styled(id_type node) const { return _p(node)->m_type.is_key_styled(); } + C4_ALWAYS_INLINE bool is_val_styled(id_type node) const { return _p(node)->m_type.is_val_styled(); } + C4_ALWAYS_INLINE bool is_key_literal(id_type node) const { return _p(node)->m_type.is_key_literal(); } + C4_ALWAYS_INLINE bool is_val_literal(id_type node) const { return _p(node)->m_type.is_val_literal(); } + C4_ALWAYS_INLINE bool is_key_folded(id_type node) const { return _p(node)->m_type.is_key_folded(); } + C4_ALWAYS_INLINE bool is_val_folded(id_type node) const { return _p(node)->m_type.is_val_folded(); } + C4_ALWAYS_INLINE bool is_key_squo(id_type node) const { return _p(node)->m_type.is_key_squo(); } + C4_ALWAYS_INLINE bool is_val_squo(id_type node) const { return _p(node)->m_type.is_val_squo(); } + C4_ALWAYS_INLINE bool is_key_dquo(id_type node) const { return _p(node)->m_type.is_key_dquo(); } + C4_ALWAYS_INLINE bool is_val_dquo(id_type node) const { return _p(node)->m_type.is_val_dquo(); } + C4_ALWAYS_INLINE bool is_key_plain(id_type node) const { return _p(node)->m_type.is_key_plain(); } + C4_ALWAYS_INLINE bool is_val_plain(id_type node) const { return _p(node)->m_type.is_val_plain(); } + C4_ALWAYS_INLINE bool is_key_quoted(id_type node) const { return _p(node)->m_type.is_key_quoted(); } + C4_ALWAYS_INLINE bool is_val_quoted(id_type node) const { return _p(node)->m_type.is_val_quoted(); } + C4_ALWAYS_INLINE bool is_quoted(id_type node) const { return _p(node)->m_type.is_quoted(); } + + C4_ALWAYS_INLINE NodeType key_style(id_type node) const { _RYML_ASSERT_VISIT_(m_callbacks, has_key(node), this, node); return _p(node)->m_type.key_style(); } + C4_ALWAYS_INLINE NodeType val_style(id_type node) const { _RYML_ASSERT_VISIT_(m_callbacks, has_val(node) || is_root(node), this, node); return _p(node)->m_type.val_style(); } + + C4_ALWAYS_INLINE void set_container_style(id_type node, NodeType_e style) { _RYML_ASSERT_VISIT_(m_callbacks, is_container(node), this, node); _p(node)->m_type.set_container_style(style); } + C4_ALWAYS_INLINE void set_key_style(id_type node, NodeType_e style) { _RYML_ASSERT_VISIT_(m_callbacks, has_key(node), this, node); _p(node)->m_type.set_key_style(style); } + C4_ALWAYS_INLINE void set_val_style(id_type node, NodeType_e style) { _RYML_ASSERT_VISIT_(m_callbacks, has_val(node), this, node); _p(node)->m_type.set_val_style(style); } + + void clear_style(id_type node, bool recurse=false); + void set_style_conditionally(id_type node, + NodeType type_mask, + NodeType rem_style_flags, + NodeType add_style_flags, + bool recurse=false); + /** @} */ + +public: + + /** @name node type modifiers */ + /** @{ */ + + void to_keyval(id_type node, csubstr key, csubstr val, type_bits more_flags=0); + void to_map(id_type node, csubstr key, type_bits more_flags=0); + void to_seq(id_type node, csubstr key, type_bits more_flags=0); + void to_val(id_type node, csubstr val, type_bits more_flags=0); + void to_map(id_type node, type_bits more_flags=0); + void to_seq(id_type node, type_bits more_flags=0); + void to_doc(id_type node, type_bits more_flags=0); + void to_stream(id_type node, type_bits more_flags=0); + + void set_key(id_type node, csubstr key) { _RYML_ASSERT_VISIT_(m_callbacks, has_key(node), this, node); _p(node)->m_key.scalar = key; } + void set_val(id_type node, csubstr val) { _RYML_ASSERT_VISIT_(m_callbacks, has_val(node), this, node); _p(node)->m_val.scalar = val; } + + void set_key_tag(id_type node, csubstr tag) { _RYML_ASSERT_VISIT_(m_callbacks, has_key(node), this, node); _p(node)->m_key.tag = tag; _add_flags(node, KEYTAG); } + void set_val_tag(id_type node, csubstr tag) { _RYML_ASSERT_VISIT_(m_callbacks, has_val(node) || is_container(node), this, node); _p(node)->m_val.tag = tag; _add_flags(node, VALTAG); } + + void set_key_anchor(id_type node, csubstr anchor) { _RYML_ASSERT_VISIT_(m_callbacks, ! is_key_ref(node), this, node); _p(node)->m_key.anchor = anchor.triml('&'); _add_flags(node, KEYANCH); } + void set_val_anchor(id_type node, csubstr anchor) { _RYML_ASSERT_VISIT_(m_callbacks, ! is_val_ref(node), this, node); _p(node)->m_val.anchor = anchor.triml('&'); _add_flags(node, VALANCH); } + void set_key_ref (id_type node, csubstr ref ) { _RYML_ASSERT_VISIT_(m_callbacks, ! has_key_anchor(node), this, node); NodeData* C4_RESTRICT n = _p(node); n->m_key.set_ref_maybe_replacing_scalar(ref, n->m_type.has_key()); _add_flags(node, KEY|KEYREF); } + void set_val_ref (id_type node, csubstr ref ) { _RYML_ASSERT_VISIT_(m_callbacks, ! has_val_anchor(node), this, node); NodeData* C4_RESTRICT n = _p(node); n->m_val.set_ref_maybe_replacing_scalar(ref, n->m_type.has_val()); _add_flags(node, VAL|VALREF); } + + void rem_key_anchor(id_type node) { _p(node)->m_key.anchor.clear(); _rem_flags(node, KEYANCH); } + void rem_val_anchor(id_type node) { _p(node)->m_val.anchor.clear(); _rem_flags(node, VALANCH); } + void rem_key_ref (id_type node) { _p(node)->m_key.anchor.clear(); _rem_flags(node, KEYREF); } + void rem_val_ref (id_type node) { _p(node)->m_val.anchor.clear(); _rem_flags(node, VALREF); } + void rem_anchor_ref(id_type node) { _p(node)->m_key.anchor.clear(); _p(node)->m_val.anchor.clear(); _rem_flags(node, KEYANCH|VALANCH|KEYREF|VALREF); } + + /** @} */ + +public: + + /** @name tree modifiers */ + /** @{ */ + + /** reorder the tree in memory so that all the nodes are stored + * in a linear sequence when visited in depth-first order. + * This will invalidate existing ids, since the node id is its + * position in the tree's node array. */ + void reorder(); + + /** @} */ + +public: + + /** @name anchors and references/aliases */ + /** @{ */ + + /** Resolve references (aliases <- anchors), by forwarding to @ref + * ReferenceResolver::resolve(); refer to @ref + * ReferenceResolver::resolve() for further details. */ + void resolve(ReferenceResolver *C4_RESTRICT rr, bool clear_anchors=true); + + /** Resolve references (aliases <- anchors), by forwarding to @ref + * ReferenceResolver::resolve(); refer to @ref + * ReferenceResolver::resolve() for further details. This overload + * uses a throwaway resolver object. */ + void resolve(bool clear_anchors=true); + + /** @} */ + +public: + + /** @name tags and tag directives */ + /** @{ */ + + /** Resolve tags in the tree such as `"!!str"` -> + * `""`, `"!foo"` -> + * `""` and custom tags as well, ie tags of the form + * `"!handle!tag"` for which there is a corresponding `"%%TAG"` + * directive + * + * @param cache an object of type @ref TagCache to minimize memory + * usage by avoiding repeated instantiation of the resolved + * tags in the tree's arena. + * + * @param all if true, resolve all tags; if false resolve only + * custom tags, ie those that have a prefix such as + * `"!m!tag"` with a matching `"%TAG"` directive */ + void resolve_tags(TagCache &cache, bool all=true); + void normalize_tags(); + void normalize_tags_long(); + + id_type num_tag_directives() const; + void add_tag_directive(csubstr handle, csubstr prefix, id_type id); + void clear_tag_directives(); + + /** resolve the given tag, appearing at node_id. Write the result into output. + * @return the number of characters required for the resolved tag */ + size_t resolve_tag(substr output, csubstr tag, id_type node_id) const; + /** Wrapper for @ref Tree::resolve_tag(), returning a substring */ + csubstr resolve_tag_sub(substr output, csubstr tag, id_type node_id) const + { + size_t needed = resolve_tag(output, tag, node_id); + return needed <= output.len ? output.first(needed) : output; + } + + c4::yml::TagDirectiveRange tag_directives() const { return m_tag_directives.directives(); } + + /** @cond dev */ + RYML_DEPRECATED("use c4::yml::tag_directive_const_iterator") typedef TagDirective const* tag_directive_const_iterator; + RYML_DEPRECATED("use c4::yml::TagDirectiveRange") typedef c4::yml::TagDirectiveRange TagDirectiveProxy; + /** @endcond */ + + /** @} */ + +public: + + /** @name modifying hierarchy */ + /** @{ */ + + /** create and insert a new child of @p parent. insert after the (to-be) + * sibling @p after, which must be a child of @p parent. To insert as the + * first child, set after to NONE */ + C4_ALWAYS_INLINE id_type insert_child(id_type parent, id_type after) + { + _RYML_ASSERT_VISIT_(m_callbacks, parent != NONE, this, parent); + _RYML_ASSERT_VISIT_(m_callbacks, is_container(parent) || is_root(parent), this, parent); + _RYML_ASSERT_VISIT_(m_callbacks, after == NONE || (_p(after)->m_parent == parent), this, parent); + id_type child = _claim(); + _set_hierarchy(child, parent, after); + return child; + } + /** create and insert a node as the first child of @p parent */ + C4_ALWAYS_INLINE id_type prepend_child(id_type parent) { return insert_child(parent, NONE); } + /** create and insert a node as the last child of @p parent */ + C4_ALWAYS_INLINE id_type append_child(id_type parent) { return insert_child(parent, _p(parent)->m_last_child); } + C4_ALWAYS_INLINE id_type _append_child__unprotected(id_type parent) + { + id_type child = _claim(); + _set_hierarchy(child, parent, _p(parent)->m_last_child); + return child; + } + +public: + + #if defined(__clang__) // NOLINT + # pragma clang diagnostic push + # pragma clang diagnostic ignored "-Wnull-dereference" + #elif defined(__GNUC__) + # pragma GCC diagnostic push + # if __GNUC__ >= 6 + # pragma GCC diagnostic ignored "-Wnull-dereference" + # endif + #endif + + //! create and insert a new sibling of n. insert after "after" + C4_ALWAYS_INLINE id_type insert_sibling(id_type node, id_type after) + { + return insert_child(_p(node)->m_parent, after); + } + /** create and insert a node as the first node of @p parent */ + C4_ALWAYS_INLINE id_type prepend_sibling(id_type node) { return prepend_child(_p(node)->m_parent); } + C4_ALWAYS_INLINE id_type append_sibling(id_type node) { return append_child(_p(node)->m_parent); } + +public: + + /** remove an entire branch at once: ie remove the children and the node itself */ + void remove(id_type node) + { + remove_children(node); + _release(node); + } + + /** remove all the node's children, but keep the node itself */ + void remove_children(id_type node); + + /** change the @p type of the node to one of MAP, SEQ or VAL. @p + * type must have one and only one of MAP,SEQ,VAL; @p type may + * possibly have KEY, but if it does, then the @p node must also + * have KEY. Changing to the same type is a no-op. Otherwise, + * changing to a different type will initialize the node with an + * empty value of the desired type: changing to VAL will + * initialize with a null scalar (~), changing to MAP will + * initialize with an empty map ({}), and changing to SEQ will + * initialize with an empty seq ([]). */ + bool change_type(id_type node, NodeType type); + + bool change_type(id_type node, type_bits type) + { + return change_type(node, (NodeType)type); + } + + #if defined(__clang__) + # pragma clang diagnostic pop + #elif defined(__GNUC__) + # pragma GCC diagnostic pop + #endif + +public: + + /** change the node's position in the parent */ + void move(id_type node, id_type after); + + /** change the node's parent and position */ + void move(id_type node, id_type new_parent, id_type after); + + /** change the node's parent and position to a different tree + * @return the index of the new node in the destination tree */ + id_type move(Tree * src, id_type node, id_type new_parent, id_type after); + + /** ensure the first node is a stream. Eg, change this tree + * + * DOCMAP + * MAP + * KEYVAL + * KEYVAL + * SEQ + * VAL + * + * to + * + * STREAM + * DOCMAP + * MAP + * KEYVAL + * KEYVAL + * SEQ + * VAL + * + * If the root is already a stream, this is a no-op. + */ + void set_root_as_stream(); + +public: + + /** recursively duplicate a node from this tree into a new parent, + * placing it after one of its children + * @return the index of the copy */ + id_type duplicate(id_type node, id_type new_parent, id_type after); + /** recursively duplicate a node from a different tree into a new parent, + * placing it after one of its children + * @return the index of the copy */ + id_type duplicate(Tree const* src, id_type node, id_type new_parent, id_type after); + + /** recursively duplicate the node's children (but not the node) + * @return the index of the last duplicated child */ + id_type duplicate_children(id_type node, id_type parent, id_type after); + /** recursively duplicate the node's children (but not the node), where + * the node is from a different tree + * @return the index of the last duplicated child */ + id_type duplicate_children(Tree const* src, id_type node, id_type parent, id_type after); + + /** duplicate the node's children (but not the node) in a new parent, but + * omit repetitions where a duplicated node has the same key (in maps) or + * value (in seqs). If one of the duplicated children has the same key + * (in maps) or value (in seqs) as one of the parent's children, the one + * that is placed closest to the end will prevail. */ + id_type duplicate_children_no_rep(id_type node, id_type parent, id_type after); + id_type duplicate_children_no_rep(Tree const* src, id_type node, id_type parent, id_type after); + + void duplicate_contents(id_type node, id_type where); + void duplicate_contents(Tree const* src, id_type node, id_type where); + +public: + + void merge_with(Tree const* src, id_type src_node=NONE, id_type dst_root=NONE); + + /** @} */ + +public: + + /** @name locations */ + /** @{ */ + + /** Get the location of a node from the parse used to parse this tree. */ + Location location(Parser const& p, id_type node) const; + +private: + + bool _location_from_node(Parser const& p, id_type node, Location *C4_RESTRICT loc, id_type level) const; + bool _location_from_cont(Parser const& p, id_type node, Location *C4_RESTRICT loc) const; + + /** @} */ + +public: + + /** @name internal string arena */ + /** @{ */ + + /** get the current size of the tree's internal arena */ + size_t arena_size() const { return m_arena_pos; } + /** get the current capacity of the tree's internal arena */ + size_t arena_capacity() const { return m_arena.len; } + /** get the current slack of the tree's internal arena */ + size_t arena_slack() const { _RYML_ASSERT_VISIT_(m_callbacks, m_arena.len >= m_arena_pos, this, NONE); return m_arena.len - m_arena_pos; } + RYML_DEPRECATED("use arena_size() instead") size_t arena_pos() const { return m_arena_pos; } + + /** get the current arena */ + csubstr arena() const { return m_arena.first(m_arena_pos); } + /** get the current arena */ + substr arena() { return m_arena.first(m_arena_pos); } // NOLINT(readability-make-member-function-const) + + /** return true if the given substring is part of the tree's string arena */ + C4_ALWAYS_INLINE bool in_arena(csubstr s) const + { + return m_arena.is_super(s); + } + + /** serialize the given variable to the tree's + * arena, growing it as needed to accomodate the serialization. + * + * @note To customize how the type gets serialized to a string, + * you can overload c4::to_chars(substr, T const&) + * + * @note To customize how the type gets serialized to the arena, + * you can overload @ref serialize_scalar() + * + * @note Growing the arena may cause relocation of the entire + * existing arena, and thus change the contents of individual + * nodes, and thus cost O(numnodes)+O(arenasize). To avoid this + * cost, ensure that the arena is reserved to an appropriate size + * using @ref Tree::reserve_arena(). + * + * @see alloc_arena() */ + template + C4_ALWAYS_INLINE csubstr to_arena(T const& C4_RESTRICT a) + { + return serialize_to_arena(this, a); + } + + /** copy the given string to the tree's arena, growing the arena + * by the required size. + * + * @note this method differs from @ref to_arena() in that it + * returns a mutable substr, and further it does not deal + * with some corner cases for null/empty strings + * + * @note Growing the arena may cause relocation of the entire + * existing arena, and thus change the contents of individual + * nodes, and thus cost O(numnodes)+O(arenasize). To avoid this + * cost, ensure that the arena is reserved to an appropriate size + * before using @ref Tree::reserve_arena() + * + * @see reserve_arena() + * @see alloc_arena() + */ + substr copy_to_arena(csubstr s) + { + _RYML_ASSERT_VISIT_(m_callbacks, !s.overlaps(m_arena), this, NONE); + substr cp = alloc_arena(s.len); + _RYML_ASSERT_VISIT_(m_callbacks, cp.len == s.len, this, NONE); + _RYML_ASSERT_VISIT_(m_callbacks, !s.overlaps(cp), this, NONE); + #if (!defined(__clang__)) && (defined(__GNUC__) && __GNUC__ >= 10) + C4_SUPPRESS_WARNING_GCC_PUSH + C4_SUPPRESS_WARNING_GCC("-Wstringop-overflow=") // no need for terminating \0 + C4_SUPPRESS_WARNING_GCC("-Wrestrict") // there's an assert to ensure no violation of restrict behavior + #endif + if(s.len) + memcpy(cp.str, s.str, s.len); + #if (!defined(__clang__)) && (defined(__GNUC__) && __GNUC__ >= 10) + C4_SUPPRESS_WARNING_GCC_POP + #endif + return cp; + } + + /** grow the tree's string arena by the given size and return a substr + * of the added portion + * + * @note Growing the arena may cause relocation of the entire + * existing arena, and thus change the contents of individual + * nodes, and thus cost O(numnodes)+O(arenasize). To avoid this + * cost, ensure that the arena is reserved to an appropriate size + * using .reserve_arena(). + * + * @see reserve_arena() */ + substr alloc_arena(size_t sz) + { + if(sz > arena_slack()) + _grow_arena(sz - arena_slack()); + substr s = _request_span(sz); + return s; + } + + /** ensure the tree's internal string arena is at least the given capacity + * @warning This operation may be expensive, with a potential complexity of O(numNodes)+O(arenasize). + * @warning Growing the arena may cause relocation of the entire + * existing arena, and thus change the contents of individual nodes. */ + void reserve_arena(size_t arena_cap=RYML_DEFAULT_TREE_ARENA_CAPACITY) + { + if(arena_cap > m_arena.len) + { + substr buf; + buf.str = _RYML_CB_ALLOC(m_callbacks, char, arena_cap); + buf.len = arena_cap; + if(m_arena.str) + { + _RYML_ASSERT_VISIT_(m_callbacks, m_arena.len >= 0, this, NONE); + _relocate(buf); // does a memcpy and changes nodes using the arena + _RYML_CB_FREE(m_callbacks, m_arena.str, char, m_arena.len); + } + m_arena = buf; + } + } + + /** @} */ + +public: + + /** @cond dev */ + + substr _grow_arena(size_t more) + { + size_t cap = m_arena.len + more; + cap = cap < 2 * m_arena.len ? 2 * m_arena.len : cap; + cap = cap < 64 ? 64 : cap; + reserve_arena(cap); + return m_arena.sub(m_arena_pos); + } + + substr _request_span(size_t sz) + { + _RYML_ASSERT_VISIT_(m_callbacks, m_arena_pos + sz <= m_arena.len, this, NONE); + substr s; + s = m_arena.sub(m_arena_pos, sz); + m_arena_pos += sz; + return s; + } + + substr _relocated(csubstr s, substr next_arena) const + { + _RYML_ASSERT_VISIT_(m_callbacks, m_arena.is_super(s) || s.len == 0, this, NONE); + _RYML_ASSERT_VISIT_(m_callbacks, m_arena.sub(0, m_arena_pos).is_super(s) || s.len == 0, this, NONE); + auto pos = (s.str - m_arena.str); // this is larger than 0 based on the assertions above + substr r(next_arena.str + pos, s.len); + _RYML_ASSERT_VISIT_(m_callbacks, r.str - next_arena.str == pos, this, NONE); + _RYML_ASSERT_VISIT_(m_callbacks, next_arena.sub(0, m_arena_pos).is_super(r) || r.len == 0, this, NONE); + return r; + } + + /** @endcond */ + +public: + + /** @name lookup */ + /** @{ */ + + struct RYML_EXPORT lookup_result + { + id_type target; + id_type closest; + size_t path_pos; + csubstr path; + + operator bool() const { return target != NONE; } + + lookup_result() : target(NONE), closest(NONE), path_pos(0), path() {} + lookup_result(csubstr path_, id_type start) : target(NONE), closest(start), path_pos(0), path(path_) {} + + /** get the part ot the input path that was resolved */ + csubstr resolved() const; + /** get the part ot the input path that was unresolved */ + csubstr unresolved() const; + }; + + /** for example foo.bar[0].baz */ + lookup_result lookup_path(csubstr path, id_type start=NONE) const; + + /** defaulted lookup: lookup @p path; if the lookup fails, recursively modify + * the tree so that the corresponding lookup_path() would return the + * default value. + * @see lookup_path() */ + id_type lookup_path_or_modify(csubstr default_value, csubstr path, id_type start=NONE); + + /** defaulted lookup: lookup @p path; if the lookup fails, recursively modify + * the tree so that the corresponding lookup_path() would return the + * branch @p src_node (from the tree @p src). + * @see lookup_path() */ + id_type lookup_path_or_modify(Tree const *src, id_type src_node, csubstr path, id_type start=NONE); + + /** @} */ + +private: + + struct _lookup_path_token + { + csubstr value; + NodeType type; + _lookup_path_token() : value(), type() {} + _lookup_path_token(csubstr v, NodeType t) : value(v), type(t) {} + operator bool() const { return type != NOTYPE; } + bool is_index() const { return value.begins_with('[') && value.ends_with(']'); } + }; + + id_type _lookup_path_or_create(csubstr path, id_type start); + + void _lookup_path (lookup_result *r) const; + void _lookup_path_modify(lookup_result *r); + + id_type _next_node (lookup_result *r, _lookup_path_token *parent) const; + id_type _next_node_modify(lookup_result *r, _lookup_path_token *parent); + + static void _advance(lookup_result *r, size_t more); + + _lookup_path_token _next_token(lookup_result *r, _lookup_path_token const& parent) const; + +private: + + void _clear(); + void _free(); + void _copy(Tree const& that); + void _move(Tree & that) noexcept; + + void _relocate(substr next_arena); + +public: + + /** @cond dev*/ + + #if ! RYML_USE_ASSERT + C4_ALWAYS_INLINE void _check_next_flags(id_type, type_bits) {} + #else + void _check_next_flags(id_type node, type_bits f) + { + NodeData *n = _p(node); + type_bits o = n->m_type; // old + C4_UNUSED(o); + if(f & MAP) + { + _RYML_ASSERT_VISIT_MSG_(m_callbacks, (f & SEQ) == 0, this, node, "cannot mark simultaneously as map and seq"); + _RYML_ASSERT_VISIT_MSG_(m_callbacks, (f & VAL) == 0, this, node, "cannot mark simultaneously as map and val"); + _RYML_ASSERT_VISIT_MSG_(m_callbacks, (o & SEQ) == 0, this, node, "cannot turn a seq into a map; clear first"); + _RYML_ASSERT_VISIT_MSG_(m_callbacks, (o & VAL) == 0, this, node, "cannot turn a val into a map; clear first"); + } + else if(f & SEQ) + { + _RYML_ASSERT_VISIT_MSG_(m_callbacks, (f & MAP) == 0, this, node, "cannot mark simultaneously as seq and map"); + _RYML_ASSERT_VISIT_MSG_(m_callbacks, (f & VAL) == 0, this, node, "cannot mark simultaneously as seq and val"); + _RYML_ASSERT_VISIT_MSG_(m_callbacks, (o & MAP) == 0, this, node, "cannot turn a map into a seq; clear first"); + _RYML_ASSERT_VISIT_MSG_(m_callbacks, (o & VAL) == 0, this, node, "cannot turn a val into a seq; clear first"); + } + if(f & KEY) + { + _RYML_ASSERT_VISIT_(m_callbacks, !is_root(node), this, node); + auto pid = parent(node); C4_UNUSED(pid); + _RYML_ASSERT_VISIT_(m_callbacks, is_map(pid), this, node); + } + if((f & VAL) && !is_root(node)) + { + auto pid = parent(node); C4_UNUSED(pid); + _RYML_ASSERT_VISIT_(m_callbacks, is_map(pid) || is_seq(pid), this, node); + } + } + #endif + + void _set_flags(id_type node, NodeType_e f) { _check_next_flags(node, f); _p(node)->m_type = f; } + void _set_flags(id_type node, type_bits f) { _check_next_flags(node, f); _p(node)->m_type = f; } + + void _add_flags(id_type node, NodeType_e f) { NodeData *d = _p(node); type_bits fb = f | d->m_type; _check_next_flags(node, fb); d->m_type = (NodeType_e) fb; } + void _add_flags(id_type node, type_bits f) { NodeData *d = _p(node); f |= d->m_type; _check_next_flags(node, f); d->m_type = f; } + + void _rem_flags(id_type node, NodeType_e f) { NodeData *d = _p(node); type_bits fb = d->m_type & ~f; _check_next_flags(node, fb); d->m_type = (NodeType_e) fb; } + void _rem_flags(id_type node, type_bits f) { NodeData *d = _p(node); f = d->m_type & ~f; _check_next_flags(node, f); d->m_type = f; } + + void _set_key(id_type node, csubstr key, type_bits more_flags=0) + { + _p(node)->m_key.scalar = key; + _add_flags(node, KEY|more_flags); + } + void _set_key(id_type node, NodeScalar const& key, type_bits more_flags=0) + { + _p(node)->m_key = key; + _add_flags(node, KEY|more_flags); + } + + void _set_val(id_type node, csubstr val, type_bits more_flags=0) + { + _RYML_ASSERT_VISIT_(m_callbacks, num_children(node) == 0, this, node); + _RYML_ASSERT_VISIT_(m_callbacks, !is_seq(node) && !is_map(node), this, node); + _p(node)->m_val.scalar = val; + _add_flags(node, VAL|more_flags); + } + void _set_val(id_type node, NodeScalar const& val, type_bits more_flags=0) + { + _RYML_ASSERT_VISIT_(m_callbacks, num_children(node) == 0, this, node); + _RYML_ASSERT_VISIT_(m_callbacks, ! is_container(node), this, node); + _p(node)->m_val = val; + _add_flags(node, VAL|more_flags); + } + + void _set(id_type node, NodeInit const& i) + { + _RYML_ASSERT_VISIT_(m_callbacks, i._check(), this, node); + NodeData *n = _p(node); + _RYML_ASSERT_VISIT_(m_callbacks, n->m_key.scalar.empty() || i.key.scalar.empty() || i.key.scalar == n->m_key.scalar, this, node); + _add_flags(node, i.type); + if(n->m_key.scalar.empty()) + { + if( ! i.key.scalar.empty()) + { + _set_key(node, i.key.scalar); + } + } + n->m_key.tag = i.key.tag; + n->m_val = i.val; + } + + void _set_parent_as_container_if_needed(id_type in) + { + NodeData const* n = _p(in); + id_type ip = parent(in); + if(ip != NONE) + { + if( ! (is_seq(ip) || is_map(ip))) + { + if((in == first_child(ip)) && (in == last_child(ip))) + { + if( ! n->m_key.empty() || has_key(in)) + { + _add_flags(ip, MAP); + } + else + { + _add_flags(ip, SEQ); + } + } + } + } + } + + void _seq2map(id_type node) + { + _RYML_ASSERT_VISIT_(m_callbacks, is_seq(node), this, node); + for(id_type i = first_child(node); i != NONE; i = next_sibling(i)) + { + NodeData *C4_RESTRICT ch = _p(i); + if(ch->m_type.is_keyval()) + continue; + ch->m_type.add(KEY); + ch->m_key = ch->m_val; + } + auto *C4_RESTRICT n = _p(node); + n->m_type.rem(SEQ); + n->m_type.add(MAP); + } + + id_type _do_reorder(id_type *node, id_type count); + + void _swap(id_type n_, id_type m_); + void _swap_props(id_type n_, id_type m_); + void _swap_hierarchy(id_type n_, id_type m_); + void _copy_hierarchy(id_type dst_, id_type src_); + + void _copy_props(id_type dst_, id_type src_) + { + _copy_props(dst_, this, src_); + } + + void _copy_props_wo_key(id_type dst_, id_type src_) + { + _copy_props_wo_key(dst_, this, src_); + } + + void _copy_props(id_type dst_, Tree const* that_tree, id_type src_) + { + NodeData & C4_RESTRICT dst = *_p(dst_); + NodeData const& C4_RESTRICT src = *that_tree->_p(src_); + dst.m_type = src.m_type; + dst.m_key = src.m_key; + dst.m_val = src.m_val; + } + + void _copy_props(id_type dst_, Tree const* that_tree, id_type src_, type_bits src_mask) + { + NodeData & C4_RESTRICT dst = *_p(dst_); + NodeData const& C4_RESTRICT src = *that_tree->_p(src_); + dst.m_type = (src.m_type & src_mask) | (dst.m_type & ~src_mask); + dst.m_key = src.m_key; + dst.m_val = src.m_val; + } + + void _copy_props_wo_key(id_type dst_, Tree const* that_tree, id_type src_) + { + auto & C4_RESTRICT dst = *_p(dst_); + auto const& C4_RESTRICT src = *that_tree->_p(src_); + dst.m_type = (src.m_type & ~_KEYMASK) | (dst.m_type & _KEYMASK); + dst.m_val = src.m_val; + } + + void _copy_props_wo_key(id_type dst_, Tree const* that_tree, id_type src_, type_bits src_mask) + { + auto & C4_RESTRICT dst = *_p(dst_); + auto const& C4_RESTRICT src = *that_tree->_p(src_); + dst.m_type = (src.m_type & ((~_KEYMASK)|src_mask)) | (dst.m_type & (_KEYMASK|~src_mask)); + dst.m_val = src.m_val; + } + + void _clear_type(id_type node) + { + _p(node)->m_type = NOTYPE; + } + + void _clear(id_type node) + { + auto *C4_RESTRICT n = _p(node); + n->m_type = NOTYPE; + n->m_key.clear(); + n->m_val.clear(); + n->m_parent = NONE; + n->m_first_child = NONE; + n->m_last_child = NONE; + } + + void _clear_key(id_type node) + { + _p(node)->m_key.clear(); + _rem_flags(node, KEY); + } + + void _clear_val(id_type node) + { + _p(node)->m_val.clear(); + _rem_flags(node, VAL); + } + + /** @endcond */ + +private: + + void _clear_range(id_type first, id_type num); + +public: + id_type _claim(); +private: + void _claim_root(); + void _release(id_type node); + void _free_list_add(id_type node); + void _free_list_rem(id_type node); + + void _set_hierarchy(id_type node, id_type parent, id_type after_sibling); + void _rem_hierarchy(id_type node); + +public: + + // members are exposed, but you should NOT access them directly + + NodeData *m_buf; + id_type m_cap; + id_type m_size; + + id_type m_free_head; + id_type m_free_tail; + + substr m_arena; + size_t m_arena_pos; + + Callbacks m_callbacks; + + TagDirectives m_tag_directives; + +public: + /** @cond dev */ + RYML_DEPRECATED("use Tree::resolve_tags(TagCache&)") void resolve_tags() { TagCache cache; resolve_tags(cache); } + /** @endcond */ +}; + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** @defgroup doc_serialization_helpers Serialization helpers + * + * @{ + */ + +template +bool read(Tree const* C4_RESTRICT tree, id_type id, T const& wrapper) +{ + return C4_LIKELY(!(tree->type(id) & VALNIL)) ? from_chars(tree->val(id), wrapper) : false; +} +template +bool readkey(Tree const* C4_RESTRICT tree, id_type id, T const& wrapper) +{ + return C4_LIKELY(!(tree->type(id) & KEYNIL)) ? from_chars(tree->key(id), wrapper) : false; +} + + + +// NON-ARITHMETIC ------------------------------------------------------------- + +/** convert the val of a scalar node to a particular non-arithmetic + * non-float type, by forwarding its val to @ref from_chars(). The + * full string is used. + * @return false if the conversion failed, or if the key was empty and unquoted */ +template +inline auto read(Tree const* C4_RESTRICT tree, id_type id, T *v) + -> typename std::enable_if::value, bool>::type +{ + return C4_LIKELY(!(tree->type(id) & VALNIL)) ? from_chars(tree->val(id), v) : false; +} + +/** convert the key of a node to a particular non-arithmetic + * non-float type, by forwarding its key to @ref from_chars(). The + * full string is used. + * @return false if the conversion failed, or if the key was empty and unquoted */ +template +inline auto readkey(Tree const* C4_RESTRICT tree, id_type id, T *v) + -> typename std::enable_if::value, bool>::type +{ + return C4_LIKELY(!(tree->type(id) & KEYNIL)) ? from_chars(tree->key(id), v) : false; +} + + +// INTEGRAL, NOT FLOATING ------------------------------------------------------------- + +/** convert the val of a scalar node to a particular arithmetic + * integral non-float type, by forwarding its val to @ref + * from_chars(). The full string is used. + * + * @return false if the conversion failed */ +template +inline auto read(Tree const* C4_RESTRICT tree, id_type id, T *v) + -> typename std::enable_if::value && !std::is_floating_point::value, bool>::type +{ + using U = typename std::remove_cv::type; + enum { ischar = std::is_same::value || std::is_same::value || std::is_same::value }; // NOLINT + csubstr val = tree->val(id); + NodeType ty = tree->type(id); + if(C4_UNLIKELY((ty & VALNIL) || val.empty())) + return false; + // quote integral numbers if they have a leading 0 + // https://github.com/biojppm/rapidyaml/issues/291 + char first = val.str[0]; + if(ty.is_val_quoted() && (first != '0' && !ischar)) + return false; + else if(first == '+') + val = val.sub(1); + return from_chars(val, v); +} + +/** convert the key of a node to a particular arithmetic + * integral non-float type, by forwarding its val to @ref + * from_chars(). The full string is used. + * + * @return false if the conversion failed */ +template +inline auto readkey(Tree const* C4_RESTRICT tree, id_type id, T *v) + -> typename std::enable_if::value && !std::is_floating_point::value, bool>::type +{ + using U = typename std::remove_cv::type; + enum { ischar = std::is_same::value || std::is_same::value || std::is_same::value }; // NOLINT + csubstr key = tree->key(id); + NodeType ty = tree->type(id); + if((ty & KEYNIL) || key.empty()) + return false; + // quote integral numbers if they have a leading 0 + // https://github.com/biojppm/rapidyaml/issues/291 + char first = key.str[0]; + if(ty.is_key_quoted() && (first != '0' && !ischar)) + return false; + else if(first == '+') + key = key.sub(1); + return from_chars(key, v); +} + + +// FLOATING ------------------------------------------------------------- + +/** encode a floating point value to a string. */ +template +size_t to_chars_float(substr buf, T val) +{ + static_assert(std::is_floating_point::value, "must be floating point"); + C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wfloat-equal") + if(C4_UNLIKELY(std::isnan(val))) + return to_chars(buf, csubstr(".nan")); + else if(C4_UNLIKELY(val == std::numeric_limits::infinity())) + return to_chars(buf, csubstr(".inf")); + else if(C4_UNLIKELY(val == -std::numeric_limits::infinity())) + return to_chars(buf, csubstr("-.inf")); + return to_chars(buf, val); + C4_SUPPRESS_WARNING_GCC_CLANG_POP +} + + +/** decode a floating point from string. Accepts special values: .nan, + * .inf, -.inf */ +template +bool from_chars_float(csubstr buf, T *C4_RESTRICT val) +{ + static_assert(std::is_floating_point::value, "must be floating point"); + if(buf.begins_with('+')) + { + buf = buf.sub(1); + } + if(C4_LIKELY(from_chars(buf, val))) + { + return true; + } + else if(C4_UNLIKELY(buf == ".nan" || buf == ".NaN" || buf == ".NAN")) + { + *val = std::numeric_limits::quiet_NaN(); + return true; + } + else if(C4_UNLIKELY(buf == ".inf" || buf == ".Inf" || buf == ".INF")) + { + *val = std::numeric_limits::infinity(); + return true; + } + else if(C4_UNLIKELY(buf == "-.inf" || buf == "-.Inf" || buf == "-.INF")) + { + *val = -std::numeric_limits::infinity(); + return true; + } + else + { + return false; + } +} + +/** convert the val of a scalar node to a floating point type, by + * forwarding its val to @ref from_chars_float(). + * + * @return false if the conversion failed + * + * @warning Unlike non-floating types, only the leading part of the + * string that may constitute a number is processed. This happens + * because the float parsing is delegated to fast_float, which is + * implemented that way. Consequently, for example, all of `"34"`, + * `"34 "` `"34hg"` `"34 gh"` will be read as 34. If you are not sure + * about the contents of the data, you can use + * csubstr::first_real_span() to check before calling `>>`, for + * example like this: + * + * ```cpp + * csubstr val = node.val(); + * if(val.first_real_span() == val) + * node >> v; + * else + * ERROR("not a real") + * ``` + */ +template +typename std::enable_if::value, bool>::type +inline read(Tree const* C4_RESTRICT tree, id_type id, T *v) +{ + csubstr val = tree->val(id); + return C4_LIKELY(!val.empty()) ? from_chars_float(val, v) : false; +} + +/** convert the key of a scalar node to a floating point type, by + * forwarding its key to @ref from_chars_float(). + * + * @return false if the conversion failed + * + * @warning Unlike non-floating types, only the leading part of the + * string that may constitute a number is processed. This happens + * because the float parsing is delegated to fast_float, which is + * implemented that way. Consequently, for example, all of `"34"`, + * `"34 "` `"34hg"` `"34 gh"` will be read as 34. If you are not sure + * about the contents of the data, you can use + * csubstr::first_real_span() to check before calling `>>`, for + * example like this: + * + * ```cpp + * csubstr key = node.key(); + * if(key.first_real_span() == key) + * node >> v; + * else + * ERROR("not a real") + * ``` + */ +template +typename std::enable_if::value, bool>::type +inline readkey(Tree const* C4_RESTRICT tree, id_type id, T *v) +{ + csubstr key = tree->key(id); + return C4_LIKELY(!key.empty()) ? from_chars_float(key, v) : false; +} + + +//----------------------------------------------------------------------------- + +template +csubstr serialize_to_arena(Tree * C4_RESTRICT tree, T const& C4_RESTRICT a) +{ + substr rem(tree->m_arena.sub(tree->m_arena_pos)); + size_t num = serialize_scalar(rem, a); + if(num > rem.len) + { + rem = tree->_grow_arena(num); + num = serialize_scalar(rem, a); + _RYML_ASSERT_VISIT_(tree->m_callbacks, num <= rem.len, tree, NONE); + } + rem = tree->_request_span(num); + return rem; +} + + + +/** @} */ + +/** @} */ + + +} // namespace yml +} // namespace c4 + +// NOLINTEND(modernize-avoid-c-style-cast) + +C4_SUPPRESS_WARNING_MSVC_POP +C4_SUPPRESS_WARNING_GCC_CLANG_POP + + +#endif /* _C4_YML_TREE_HPP_ */ + + +// (end src/c4/yml/tree.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/node.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_YML_NODE_HPP_ +#define _C4_YML_NODE_HPP_ + +/** @file node.hpp Node classes */ + +//included above: +//#include + +// amalgamate: removed include of +// c4/yml/tree.hpp +//#include "c4/yml/tree.hpp" +#if !defined(C4_YML_TREE_HPP_) && !defined(_C4_YML_TREE_HPP_) +#error "amalgamate: file c4/yml/tree.hpp must have been included at this point" +#endif /* C4_YML_TREE_HPP_ */ + + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wtype-limits" +# pragma clang diagnostic ignored "-Wold-style-cast" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wtype-limits" +# pragma GCC diagnostic ignored "-Wold-style-cast" +# pragma GCC diagnostic ignored "-Wuseless-cast" +#elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable: 4251/*needs to have dll-interface to be used by clients of struct*/) +# pragma warning(disable: 4296/*expression is always 'boolean_value'*/) +# pragma warning(disable: 4996/*deprecated*/) +#endif + +// NOLINTBEGIN(modernize-avoid-c-style-cast) + +namespace c4 { +namespace yml { + +/** @addtogroup doc_node_classes + * + * @{ + */ + + +/** @defgroup doc_serialization_helpers Serialization helpers + * + * @{ + */ +template struct Key { K && k; }; // NOLINT + +template C4_ALWAYS_INLINE Key key(K && k) { return Key{std::forward(k)}; } + + +template void write(NodeRef *n, T const& v); + +template inline bool read(ConstNodeRef const& C4_RESTRICT n, T *v); +template inline bool read(ConstNodeRef const& C4_RESTRICT n, T const& wrapper); +template inline bool read(NodeRef const& C4_RESTRICT n, T *v); +template inline bool read(NodeRef const& C4_RESTRICT n, T const& wrapper); +template inline bool readkey(ConstNodeRef const& C4_RESTRICT n, T *v); +template inline bool readkey(ConstNodeRef const& C4_RESTRICT n, T const& wrapper); +template inline bool readkey(NodeRef const& C4_RESTRICT n, T *v); +template inline bool readkey(NodeRef const& C4_RESTRICT n, T const& wrapper); + +/** @} */ + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +// forward decls +class NodeRef; +class ConstNodeRef; + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** @cond dev */ +namespace detail { + +template +struct child_iterator +{ + using value_type = NodeRefType; + using tree_type = typename NodeRefType::tree_type; + + tree_type * C4_RESTRICT m_tree; + id_type m_child_id; + + child_iterator(tree_type * t, id_type id) : m_tree(t), m_child_id(id) {} + + child_iterator& operator++ () { _RYML_ASSERT_VISIT_(m_tree->m_callbacks, m_child_id != NONE, m_tree, NONE); m_child_id = m_tree->next_sibling(m_child_id); return *this; } + child_iterator& operator-- () { _RYML_ASSERT_VISIT_(m_tree->m_callbacks, m_child_id != NONE, m_tree, NONE); m_child_id = m_tree->prev_sibling(m_child_id); return *this; } + + NodeRefType operator* () const { return NodeRefType(m_tree, m_child_id); } + NodeRefType operator-> () const { return NodeRefType(m_tree, m_child_id); } + + bool operator!= (child_iterator that) const { _RYML_ASSERT_VISIT(m_tree == that.m_tree, m_tree, NONE); return m_child_id != that.m_child_id; } + bool operator== (child_iterator that) const { _RYML_ASSERT_VISIT(m_tree == that.m_tree, m_tree, NONE); return m_child_id == that.m_child_id; } +}; + +template +struct children_view_ +{ + using n_iterator = child_iterator; + + n_iterator b, e; + + children_view_(n_iterator const& C4_RESTRICT b_, + n_iterator const& C4_RESTRICT e_) : b(b_), e(e_) {} + + n_iterator begin() const { return b; } + n_iterator end () const { return e; } +}; + +template +bool _visit(NodeRefType &node, Visitor fn, id_type indentation_level, bool skip_root=false) +{ + id_type increment = 0; + if( ! (node.is_root() && skip_root)) + { + if(fn(node, indentation_level)) + return true; + ++increment; + } + if(node.has_children()) + { + for(auto ch : node.children()) + { + if(_visit(ch, fn, indentation_level + increment, false)) // no need to forward skip_root as it won't be root + { + return true; + } + } + } + return false; +} + +template +bool _visit_stacked(NodeRefType &node, Visitor fn, id_type indentation_level, bool skip_root=false) +{ + id_type increment = 0; + if( ! (node.is_root() && skip_root)) + { + if(fn(node, indentation_level)) + { + return true; + } + ++increment; + } + if(node.has_children()) + { + fn.push(node, indentation_level); + for(auto ch : node.children()) + { + if(_visit_stacked(ch, fn, indentation_level + increment, false)) // no need to forward skip_root as it won't be root + { + fn.pop(node, indentation_level); + return true; + } + } + fn.pop(node, indentation_level); + } + return false; +} + +template +struct RoNodeMethods; +} // detail +/** @endcond */ + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + + +/** a CRTP base providing read-only methods for @ref ConstNodeRef and @ref NodeRef */ +namespace detail { +template +struct RoNodeMethods // NOLINT +{ + C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wcast-align") + /** @cond dev */ + // helper CRTP macros, undefined at the end + #define tree_ ((ConstImpl const* C4_RESTRICT)this)->m_tree + #define id_ ((ConstImpl const* C4_RESTRICT)this)->m_id + #define tree__ ((Impl const* C4_RESTRICT)this)->m_tree + #define id__ ((Impl const* C4_RESTRICT)this)->m_id + // require readable: this is a precondition for reading from the + // tree using this object. + #define _C4RR() \ + _RYML_ASSERT_BASIC(tree_ != nullptr); \ + _RYML_ASSERT_VISIT_(tree_->m_callbacks, id_ != NONE, tree_, id_); \ + _RYML_ASSERT_VISIT_(tree_->m_callbacks, (((Impl const* C4_RESTRICT)this)->readable()), tree_, id_) + // a SFINAE beautifier to enable a function only if the + // implementation is mutable + #define _C4_IF_MUTABLE(ty) typename std::enable_if::value, ty>::type + /** @endcond */ + +public: + + /** @name node property getters */ + /** @{ */ + + /** returns the data or null when the id is NONE */ + C4_ALWAYS_INLINE NodeData const* get() const RYML_NOEXCEPT { return ((Impl const*)this)->readable() ? tree_->get(id_) : nullptr; } + + /** returns the data or null when the id is NONE */ + template + C4_ALWAYS_INLINE auto get() RYML_NOEXCEPT -> _C4_IF_MUTABLE(NodeData*) { return ((Impl const*)this)->readable() ? tree__->get(id__) : nullptr; } + + C4_ALWAYS_INLINE NodeType type() const RYML_NOEXCEPT { _C4RR(); return tree_->type(id_); } /**< Forward to @ref Tree::type(). Node must be readable. */ + C4_ALWAYS_INLINE const char* type_str() const RYML_NOEXCEPT { _C4RR(); return tree_->type_str(id_); } /**< Forward to @ref Tree::type_str(). Node must be readable. */ + + C4_ALWAYS_INLINE csubstr key() const RYML_NOEXCEPT { _C4RR(); return tree_->key(id_); } /**< Forward to @ref Tree::key(). Node must be readable. */ + C4_ALWAYS_INLINE csubstr key_tag() const RYML_NOEXCEPT { _C4RR(); return tree_->key_tag(id_); } /**< Forward to @ref Tree::key_tag(). Node must be readable. */ + C4_ALWAYS_INLINE csubstr key_ref() const RYML_NOEXCEPT { _C4RR(); return tree_->key_ref(id_); } /**< Forward to @ref Tree::key_ref(). Node must be readable. */ + C4_ALWAYS_INLINE csubstr key_anchor() const RYML_NOEXCEPT { _C4RR(); return tree_->key_anchor(id_); } /**< Forward to @ref Tree::key_anchor(). Node must be readable. */ + + C4_ALWAYS_INLINE csubstr val() const RYML_NOEXCEPT { _C4RR(); return tree_->val(id_); } /**< Forward to @ref Tree::val(). Node must be readable. */ + C4_ALWAYS_INLINE csubstr val_tag() const RYML_NOEXCEPT { _C4RR(); return tree_->val_tag(id_); } /**< Forward to @ref Tree::val_tag(). Node must be readable. */ + C4_ALWAYS_INLINE csubstr val_ref() const RYML_NOEXCEPT { _C4RR(); return tree_->val_ref(id_); } /**< Forward to @ref Tree::val_ref(). Node must be readable. */ + C4_ALWAYS_INLINE csubstr val_anchor() const RYML_NOEXCEPT { _C4RR(); return tree_->val_anchor(id_); } /**< Forward to @ref Tree::val_anchor(). Node must be readable. */ + + C4_ALWAYS_INLINE NodeScalar const& keysc() const RYML_NOEXCEPT { _C4RR(); return tree_->keysc(id_); } /**< Forward to @ref Tree::keysc(). Node must be readable. */ + C4_ALWAYS_INLINE NodeScalar const& valsc() const RYML_NOEXCEPT { _C4RR(); return tree_->valsc(id_); } /**< Forward to @ref Tree::valsc(). Node must be readable. */ + + C4_ALWAYS_INLINE bool key_is_null() const RYML_NOEXCEPT { _C4RR(); return tree_->key_is_null(id_); } /**< Forward to @ref Tree::key_is_null(). Node must be readable. */ + C4_ALWAYS_INLINE bool val_is_null() const RYML_NOEXCEPT { _C4RR(); return tree_->val_is_null(id_); } /**< Forward to @ref Tree::val_is_null(). Node must be readable. */ + + C4_ALWAYS_INLINE bool is_key_unfiltered() const noexcept { _C4RR(); return tree_->is_key_unfiltered(id_); } /**< Forward to @ref Tree::is_key_unfiltered(). Node must be readable. */ + C4_ALWAYS_INLINE bool is_val_unfiltered() const noexcept { _C4RR(); return tree_->is_val_unfiltered(id_); } /**< Forward to @ref Tree::is_val_unfiltered(). Node must be readable. */ + + /** @} */ + +public: + + /** @name node type predicates */ + /** @{ */ + + C4_ALWAYS_INLINE bool empty() const RYML_NOEXCEPT { _C4RR(); return tree_->empty(id_); } /**< Forward to @ref Tree::empty(). Node must be readable. */ + C4_ALWAYS_INLINE bool is_stream() const RYML_NOEXCEPT { _C4RR(); return tree_->is_stream(id_); } /**< Forward to @ref Tree::is_stream(). Node must be readable. */ + C4_ALWAYS_INLINE bool is_doc() const RYML_NOEXCEPT { _C4RR(); return tree_->is_doc(id_); } /**< Forward to @ref Tree::is_doc(). Node must be readable. */ + C4_ALWAYS_INLINE bool is_container() const RYML_NOEXCEPT { _C4RR(); return tree_->is_container(id_); } /**< Forward to @ref Tree::is_container(). Node must be readable. */ + C4_ALWAYS_INLINE bool is_map() const RYML_NOEXCEPT { _C4RR(); return tree_->is_map(id_); } /**< Forward to @ref Tree::is_map(). Node must be readable. */ + C4_ALWAYS_INLINE bool is_seq() const RYML_NOEXCEPT { _C4RR(); return tree_->is_seq(id_); } /**< Forward to @ref Tree::is_seq(). Node must be readable. */ + C4_ALWAYS_INLINE bool has_val() const RYML_NOEXCEPT { _C4RR(); return tree_->has_val(id_); } /**< Forward to @ref Tree::has_val(). Node must be readable. */ + C4_ALWAYS_INLINE bool has_key() const RYML_NOEXCEPT { _C4RR(); return tree_->has_key(id_); } /**< Forward to @ref Tree::has_key(). Node must be readable. */ + C4_ALWAYS_INLINE bool is_val() const RYML_NOEXCEPT { _C4RR(); return tree_->is_val(id_); } /**< Forward to @ref Tree::is_val(). Node must be readable. */ + C4_ALWAYS_INLINE bool is_keyval() const RYML_NOEXCEPT { _C4RR(); return tree_->is_keyval(id_); } /**< Forward to @ref Tree::is_keyval(). Node must be readable. */ + C4_ALWAYS_INLINE bool has_key_tag() const RYML_NOEXCEPT { _C4RR(); return tree_->has_key_tag(id_); } /**< Forward to @ref Tree::has_key_tag(). Node must be readable. */ + C4_ALWAYS_INLINE bool has_val_tag() const RYML_NOEXCEPT { _C4RR(); return tree_->has_val_tag(id_); } /**< Forward to @ref Tree::has_val_tag(). Node must be readable. */ + C4_ALWAYS_INLINE bool has_key_anchor() const RYML_NOEXCEPT { _C4RR(); return tree_->has_key_anchor(id_); } /**< Forward to @ref Tree::has_key_anchor(). Node must be readable. */ + C4_ALWAYS_INLINE bool has_val_anchor() const RYML_NOEXCEPT { _C4RR(); return tree_->has_val_anchor(id_); } /**< Forward to @ref Tree::has_val_anchor(). Node must be readable. */ + C4_ALWAYS_INLINE bool has_anchor() const RYML_NOEXCEPT { _C4RR(); return tree_->has_anchor(id_); } /**< Forward to @ref Tree::has_anchor(). Node must be readable. */ + C4_ALWAYS_INLINE bool is_key_ref() const RYML_NOEXCEPT { _C4RR(); return tree_->is_key_ref(id_); } /**< Forward to @ref Tree::is_key_ref(). Node must be readable. */ + C4_ALWAYS_INLINE bool is_val_ref() const RYML_NOEXCEPT { _C4RR(); return tree_->is_val_ref(id_); } /**< Forward to @ref Tree::is_val_ref(). Node must be readable. */ + C4_ALWAYS_INLINE bool is_ref() const RYML_NOEXCEPT { _C4RR(); return tree_->is_ref(id_); } /**< Forward to @ref Tree::is_ref(). Node must be readable. */ + C4_ALWAYS_INLINE bool parent_is_seq() const RYML_NOEXCEPT { _C4RR(); return tree_->parent_is_seq(id_); } /**< Forward to @ref Tree::parent_is_seq(). Node must be readable. */ + C4_ALWAYS_INLINE bool parent_is_map() const RYML_NOEXCEPT { _C4RR(); return tree_->parent_is_map(id_); } /**< Forward to @ref Tree::parent_is_map(). Node must be readable. */ + + RYML_DEPRECATED("use has_key_anchor()") bool is_key_anchor() const noexcept { _C4RR(); return tree_->has_key_anchor(id_); } + RYML_DEPRECATED("use has_val_anchor()") bool is_val_hanchor() const noexcept { _C4RR(); return tree_->has_val_anchor(id_); } + RYML_DEPRECATED("use has_anchor()") bool is_anchor() const noexcept { _C4RR(); return tree_->has_anchor(id_); } + RYML_DEPRECATED("use has_anchor() || is_ref()") bool is_anchor_or_ref() const noexcept { _C4RR(); return tree_->is_anchor_or_ref(id_); } + + /** @} */ + +public: + + /** @name style predicates */ + /** @{ */ + + // documentation to the right --> + + C4_ALWAYS_INLINE bool type_has_any(NodeType_e bits) const RYML_NOEXCEPT { _C4RR(); return tree_->type_has_any(id_, bits); } /**< Forward to @ref Tree::type_has_any(). Node must be readable. */ + C4_ALWAYS_INLINE bool type_has_all(NodeType_e bits) const RYML_NOEXCEPT { _C4RR(); return tree_->type_has_all(id_, bits); } /**< Forward to @ref Tree::type_has_all(). Node must be readable. */ + C4_ALWAYS_INLINE bool type_has_none(NodeType_e bits) const RYML_NOEXCEPT { _C4RR(); return tree_->type_has_none(id_, bits); } /**< Forward to @ref Tree::type_has_none(). Node must be readable. */ + + C4_ALWAYS_INLINE NodeType key_style() const RYML_NOEXCEPT { _C4RR(); return tree_->key_style(id_); } /**< Forward to @ref Tree::key_style(). Node must be readable. */ + C4_ALWAYS_INLINE NodeType val_style() const RYML_NOEXCEPT { _C4RR(); return tree_->val_style(id_); } /**< Forward to @ref Tree::val_style(). Node must be readable. */ + + C4_ALWAYS_INLINE bool is_container_styled() const RYML_NOEXCEPT { _C4RR(); return tree_->is_container_styled(id_); } /**< Forward to @ref Tree::is_container_styled(). Node must be readable. */ + C4_ALWAYS_INLINE bool is_block() const RYML_NOEXCEPT { _C4RR(); return tree_->is_block(id_); } /**< Forward to @ref Tree::is_block(). Node must be readable. */ + C4_ALWAYS_INLINE bool is_flow() const RYML_NOEXCEPT { _C4RR(); return tree_->is_flow(id_); } /**< Forward to @ref Tree::is_flow(). Node must be readable. */ + C4_ALWAYS_INLINE bool is_flow_sl() const RYML_NOEXCEPT { _C4RR(); return tree_->is_flow_sl(id_); } /**< Forward to @ref Tree::is_flow_sl(). Node must be readable. */ + RYML_DEPRECATED("use one of .is_flow_ml{1,x,n}()") + C4_ALWAYS_INLINE bool is_flow_ml() const RYML_NOEXCEPT { _C4RR(); return tree_->is_flow_ml1(id_); } /**< Forward to @ref Tree::is_flow_ml1(). Node must be readable. */ + C4_ALWAYS_INLINE bool is_flow_ml1() const RYML_NOEXCEPT { _C4RR(); return tree_->is_flow_ml1(id_); } /**< Forward to @ref Tree::is_flow_ml1(). Node must be readable. */ + C4_ALWAYS_INLINE bool is_flow_mln() const RYML_NOEXCEPT { _C4RR(); return tree_->is_flow_mln(id_); } /**< Forward to @ref Tree::is_flow_mln(). Node must be readable. */ + C4_ALWAYS_INLINE bool is_flow_mlx() const RYML_NOEXCEPT { _C4RR(); return tree_->is_flow_mlx(id_); } /**< Forward to @ref Tree::is_flow_mlx(). Node must be readable. */ + C4_ALWAYS_INLINE bool has_flow_space() const RYML_NOEXCEPT { _C4RR(); return tree_->has_flow_space(id_); } /**< Forward to @ref Tree::has_flow_space(). Node must be readable. */ + + C4_ALWAYS_INLINE bool is_key_styled() const RYML_NOEXCEPT { _C4RR(); return tree_->is_key_styled(id_); } /**< Forward to @ref Tree::is_key_styled(). Node must be readable. */ + C4_ALWAYS_INLINE bool is_val_styled() const RYML_NOEXCEPT { _C4RR(); return tree_->is_val_styled(id_); } /**< Forward to @ref Tree::is_val_styled(). Node must be readable. */ + C4_ALWAYS_INLINE bool is_key_literal() const RYML_NOEXCEPT { _C4RR(); return tree_->is_key_literal(id_); } /**< Forward to @ref Tree::is_key_literal(). Node must be readable. */ + C4_ALWAYS_INLINE bool is_val_literal() const RYML_NOEXCEPT { _C4RR(); return tree_->is_val_literal(id_); } /**< Forward to @ref Tree::is_val_literal(). Node must be readable. */ + C4_ALWAYS_INLINE bool is_key_folded() const RYML_NOEXCEPT { _C4RR(); return tree_->is_key_folded(id_); } /**< Forward to @ref Tree::is_key_folded(). Node must be readable. */ + C4_ALWAYS_INLINE bool is_val_folded() const RYML_NOEXCEPT { _C4RR(); return tree_->is_val_folded(id_); } /**< Forward to @ref Tree::is_val_folded(). Node must be readable. */ + C4_ALWAYS_INLINE bool is_key_squo() const RYML_NOEXCEPT { _C4RR(); return tree_->is_key_squo(id_); } /**< Forward to @ref Tree::is_key_squo(). Node must be readable. */ + C4_ALWAYS_INLINE bool is_val_squo() const RYML_NOEXCEPT { _C4RR(); return tree_->is_val_squo(id_); } /**< Forward to @ref Tree::is_val_squo(). Node must be readable. */ + C4_ALWAYS_INLINE bool is_key_dquo() const RYML_NOEXCEPT { _C4RR(); return tree_->is_key_dquo(id_); } /**< Forward to @ref Tree::is_key_dquo(). Node must be readable. */ + C4_ALWAYS_INLINE bool is_val_dquo() const RYML_NOEXCEPT { _C4RR(); return tree_->is_val_dquo(id_); } /**< Forward to @ref Tree::is_val_dquo(). Node must be readable. */ + C4_ALWAYS_INLINE bool is_key_plain() const RYML_NOEXCEPT { _C4RR(); return tree_->is_key_plain(id_); } /**< Forward to @ref Tree::is_key_plain(). Node must be readable. */ + C4_ALWAYS_INLINE bool is_val_plain() const RYML_NOEXCEPT { _C4RR(); return tree_->is_val_plain(id_); } /**< Forward to @ref Tree::is_val_plain(). Node must be readable. */ + C4_ALWAYS_INLINE bool is_key_quoted() const RYML_NOEXCEPT { _C4RR(); return tree_->is_key_quoted(id_); } /**< Forward to @ref Tree::is_key_quoted(). Node must be readable. */ + C4_ALWAYS_INLINE bool is_val_quoted() const RYML_NOEXCEPT { _C4RR(); return tree_->is_val_quoted(id_); } /**< Forward to @ref Tree::is_val_quoted(). Node must be readable. */ + C4_ALWAYS_INLINE bool is_quoted() const RYML_NOEXCEPT { _C4RR(); return tree_->is_quoted(id_); } /**< Forward to @ref Tree::is_quoted(). Node must be readable. */ + + /** @} */ + +public: + + /** @name hierarchy predicates */ + /** @{ */ + + // documentation to the right --> + + C4_ALWAYS_INLINE bool is_root() const RYML_NOEXCEPT { _C4RR(); return tree_->is_root(id_); } /**< Forward to @ref Tree::is_root(). Node must be readable. */ + C4_ALWAYS_INLINE bool has_parent() const RYML_NOEXCEPT { _C4RR(); return tree_->has_parent(id_); } /**< Forward to @ref Tree::has_parent() Node must be readable. */ + C4_ALWAYS_INLINE bool is_ancestor(ConstImpl const& ancestor) const RYML_NOEXCEPT { _C4RR(); return tree_->is_ancestor(id_, ancestor.m_id); } /**< Forward to @ref Tree::is_ancestor() Node must be readable. */ + + C4_ALWAYS_INLINE bool has_child(ConstImpl const& n) const RYML_NOEXCEPT { _C4RR(); return n.readable() ? tree_->has_child(id_, n.m_id) : false; } /**< Forward to @ref Tree::has_child(). Node must be readable. */ + C4_ALWAYS_INLINE bool has_child(id_type node) const RYML_NOEXCEPT { _C4RR(); return tree_->has_child(id_, node); } /**< Forward to @ref Tree::has_child(). Node must be readable. */ + C4_ALWAYS_INLINE bool has_child(csubstr name) const RYML_NOEXCEPT { _C4RR(); return tree_->has_child(id_, name); } /**< Forward to @ref Tree::has_child(). Node must be readable. */ + C4_ALWAYS_INLINE bool has_children() const RYML_NOEXCEPT { _C4RR(); return tree_->has_children(id_); } /**< Forward to @ref Tree::has_children(). Node must be readable. */ + + C4_ALWAYS_INLINE bool has_sibling(ConstImpl const& n) const RYML_NOEXCEPT { _C4RR(); return n.readable() ? tree_->has_sibling(id_, n.m_id) : false; } /**< Forward to @ref Tree::has_sibling(). Node must be readable. */ + C4_ALWAYS_INLINE bool has_sibling(id_type node) const RYML_NOEXCEPT { _C4RR(); return tree_->has_sibling(id_, node); } /**< Forward to @ref Tree::has_sibling(). Node must be readable. */ + C4_ALWAYS_INLINE bool has_sibling(csubstr name) const RYML_NOEXCEPT { _C4RR(); return tree_->has_sibling(id_, name); } /**< Forward to @ref Tree::has_sibling(). Node must be readable. */ + C4_ALWAYS_INLINE bool has_other_siblings() const RYML_NOEXCEPT { _C4RR(); return tree_->has_other_siblings(id_); } /**< Forward to @ref Tree::has_other_siblings(). Node must be readable. */ + + RYML_DEPRECATED("use has_other_siblings()") bool has_siblings() const RYML_NOEXCEPT { _C4RR(); return tree_->has_siblings(id_); } + + /** @} */ + +public: + + /** @name hierarchy getters */ + /** @{ */ + + // documentation to the right --> + + template + C4_ALWAYS_INLINE auto doc(id_type i) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _RYML_ASSERT_BASIC(tree_); return {tree__, tree__->doc(i)}; } /**< Forward to @ref Tree::doc(). Node must be readable. */ + C4_ALWAYS_INLINE ConstImpl doc(id_type i) const RYML_NOEXCEPT { _RYML_ASSERT_BASIC(tree_); return {tree_, tree_->doc(i)}; } /**< Forward to @ref Tree::doc(). Node must be readable. succeeds even when the node may have invalid or seed id */ + + template + C4_ALWAYS_INLINE auto parent() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); return {tree__, tree__->parent(id__)}; } /**< Forward to @ref Tree::parent(). Node must be readable. */ + C4_ALWAYS_INLINE ConstImpl parent() const RYML_NOEXCEPT { _C4RR(); return {tree_, tree_->parent(id_)}; } /**< Forward to @ref Tree::parent(). Node must be readable. */ + + template + C4_ALWAYS_INLINE auto first_child() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); return {tree__, tree__->first_child(id__)}; } /**< Forward to @ref Tree::first_child(). Node must be readable. */ + C4_ALWAYS_INLINE ConstImpl first_child() const RYML_NOEXCEPT { _C4RR(); return {tree_, tree_->first_child(id_)}; } /**< Forward to @ref Tree::first_child(). Node must be readable. */ + + template + C4_ALWAYS_INLINE auto last_child() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); return {tree__, tree__->last_child(id__)}; } /**< Forward to @ref Tree::last_child(). Node must be readable. */ + C4_ALWAYS_INLINE ConstImpl last_child () const RYML_NOEXCEPT { _C4RR(); return {tree_, tree_->last_child (id_)}; } /**< Forward to @ref Tree::last_child(). Node must be readable. */ + + template + C4_ALWAYS_INLINE auto child(id_type pos) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); return {tree__, tree__->child(id__, pos)}; } /**< Forward to @ref Tree::child(). Node must be readable. */ + C4_ALWAYS_INLINE ConstImpl child(id_type pos) const RYML_NOEXCEPT { _C4RR(); return {tree_, tree_->child(id_, pos)}; } /**< Forward to @ref Tree::child(). Node must be readable. */ + + template + C4_ALWAYS_INLINE auto find_child(csubstr name) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); return {tree__, tree__->find_child(id__, name)}; } /**< Forward to @ref Tree::find_child(). Node must be readable. */ + C4_ALWAYS_INLINE ConstImpl find_child(csubstr name) const RYML_NOEXCEPT { _C4RR(); return {tree_, tree_->find_child(id_, name)}; } /**< Forward to @ref Tree::find_child(). Node must be readable. */ + + template + C4_ALWAYS_INLINE auto prev_sibling() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); return {tree__, tree__->prev_sibling(id__)}; } /**< Forward to @ref Tree::prev_sibling(). Node must be readable. */ + C4_ALWAYS_INLINE ConstImpl prev_sibling() const RYML_NOEXCEPT { _C4RR(); return {tree_, tree_->prev_sibling(id_)}; } /**< Forward to @ref Tree::prev_sibling(). Node must be readable. */ + + template + C4_ALWAYS_INLINE auto next_sibling() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); return {tree__, tree__->next_sibling(id__)}; } /**< Forward to @ref Tree::next_sibling(). Node must be readable. */ + C4_ALWAYS_INLINE ConstImpl next_sibling() const RYML_NOEXCEPT { _C4RR(); return {tree_, tree_->next_sibling(id_)}; } /**< Forward to @ref Tree::next_sibling(). Node must be readable. */ + + template + C4_ALWAYS_INLINE auto first_sibling() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); return {tree__, tree__->first_sibling(id__)}; } /**< Forward to @ref Tree::first_sibling(). Node must be readable. */ + C4_ALWAYS_INLINE ConstImpl first_sibling() const RYML_NOEXCEPT { _C4RR(); return {tree_, tree_->first_sibling(id_)}; } /**< Forward to @ref Tree::first_sibling(). Node must be readable. */ + + template + C4_ALWAYS_INLINE auto last_sibling() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); return {tree__, tree__->last_sibling(id__)}; } /**< Forward to @ref Tree::last_sibling(). Node must be readable. */ + C4_ALWAYS_INLINE ConstImpl last_sibling () const RYML_NOEXCEPT { _C4RR(); return {tree_, tree_->last_sibling(id_)}; } /**< Forward to @ref Tree::last_sibling(). Node must be readable. */ + + template + C4_ALWAYS_INLINE auto sibling(id_type pos) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); return {tree__, tree__->sibling(id__, pos)}; } /**< Forward to @ref Tree::sibling(). Node must be readable. */ + C4_ALWAYS_INLINE ConstImpl sibling(id_type pos) const RYML_NOEXCEPT { _C4RR(); return {tree_, tree_->sibling(id_, pos)}; } /**< Forward to @ref Tree::sibling(). Node must be readable. */ + + template + C4_ALWAYS_INLINE auto find_sibling(csubstr name) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); return {tree__, tree__->find_sibling(id__, name)}; } /**< Forward to @ref Tree::find_sibling(). Node must be readable. */ + C4_ALWAYS_INLINE ConstImpl find_sibling(csubstr name) const RYML_NOEXCEPT { _C4RR(); return {tree_, tree_->find_sibling(id_, name)}; } /**< Forward to @ref Tree::find_sibling(). Node must be readable. */ + + template + C4_ALWAYS_INLINE auto ancestor_doc() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); return {tree__, tree__->ancestor_doc(id__)}; } /**< Forward to @ref Tree::ancestor_doc(). Node must be readable. */ + C4_ALWAYS_INLINE ConstImpl ancestor_doc() const RYML_NOEXCEPT { _C4RR(); return {tree_, tree_->ancestor_doc(id_)}; } /**< Forward to @ref Tree::ancestor_doc(). Node must be readable. */ + + C4_ALWAYS_INLINE id_type num_children() const RYML_NOEXCEPT { _C4RR(); return tree_->num_children(id_); } /**< O(num_children). Forward to @ref Tree::num_children(). */ + C4_ALWAYS_INLINE id_type num_siblings() const RYML_NOEXCEPT { _C4RR(); return tree_->num_siblings(id_); } /**< O(num_children). Forward to @ref Tree::num_siblings(). */ + C4_ALWAYS_INLINE id_type num_other_siblings() const RYML_NOEXCEPT { _C4RR(); return tree_->num_other_siblings(id_); } /**< O(num_siblings). Forward to @ref Tree::num_other_siblings(). */ + C4_ALWAYS_INLINE id_type child_pos(ConstImpl const& n) const RYML_NOEXCEPT { _C4RR(); _RYML_ASSERT_VISIT_(tree_->m_callbacks, n.readable(), n.tree(), n.id()); return tree_->child_pos(id_, n.m_id); } /**< O(num_children). Forward to @ref Tree::child_pos(). */ + C4_ALWAYS_INLINE id_type sibling_pos(ConstImpl const& n) const RYML_NOEXCEPT { _C4RR(); _RYML_ASSERT_VISIT_(tree_->callbacks(), n.readable(), n.tree(), n.id()); return tree_->child_pos(tree_->parent(id_), n.m_id); } /**< O(num_siblings). Forward to @ref Tree::sibling_pos(). */ + + C4_ALWAYS_INLINE id_type depth_asc() const RYML_NOEXCEPT { _C4RR(); return tree_->depth_asc(id_); } /** O(log(num_nodes)). Forward to Tree::depth_asc(). Node must be readable. */ + C4_ALWAYS_INLINE id_type depth_desc() const RYML_NOEXCEPT { _C4RR(); return tree_->depth_desc(id_); } /** O(num_nodes). Forward to Tree::depth_desc(). Node must be readable. */ + + /** @} */ + +public: + + /** @name square_brackets + * operator[] */ + /** @{ */ + + /** Find child by key; complexity is O(num_children). + * + * Returns the requested node, or an object in seed state if no + * such child is found (see @ref NodeRef for an explanation of + * what is seed state). When the object is in seed state, using it + * to read from the tree is UB. The seed node can be used to write + * to the tree provided that its create() method is called prior + * to writing, which happens in most modifying methods in + * NodeRef. It is the caller's responsibility to verify that the + * returned node is readable before subsequently using it to read + * from the tree. + * + * @warning the calling object must be readable. This precondition + * is asserted. The assertion is performed only if @ref + * RYML_USE_ASSERT is set to true. As with the non-const overload, + * it is UB to call this method if the node is not readable. + * + * @see https://github.com/biojppm/rapidyaml/issues/389 */ + template + C4_ALWAYS_INLINE auto operator[] (csubstr key) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) + { + _C4RR(); + id_type ch = tree__->find_child(id__, key); + return ch != NONE ? Impl(tree__, ch) : Impl(tree__, id__, key); + } + + /** Find child by position; complexity is O(pos). + * + * Returns the requested node, or an object in seed state if no + * such child is found (see @ref NodeRef for an explanation of + * what is seed state). When the object is in seed state, using it + * to read from the tree is UB. The seed node can be used to write + * to the tree provided that its create() method is called prior + * to writing, which happens in most modifying methods in + * NodeRef. It is the caller's responsibility to verify that the + * returned node is readable before subsequently using it to read + * from the tree. + * + * @warning the calling object must be readable. This precondition + * is asserted. The assertion is performed only if @ref + * RYML_USE_ASSERT is set to true. As with the non-const overload, + * it is UB to call this method if the node is not readable. + * + * @see https://github.com/biojppm/rapidyaml/issues/389 */ + template + C4_ALWAYS_INLINE auto operator[] (id_type pos) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) + { + _C4RR(); + id_type ch = tree__->child(id__, pos); + return ch != NONE ? Impl(tree__, ch) : Impl(tree__, id__, pos); + } + + /** Find a child by key; complexity is O(num_children). + * + * Behaves similar to the non-const overload, but further asserts + * that the returned node is readable (because it can never be in + * a seed state). The assertion is performed only if @ref + * RYML_USE_ASSERT is set to true. As with the non-const overload, + * it is UB to use the return value if it is not valid. + * + * @see https://github.com/biojppm/rapidyaml/issues/389 */ + C4_ALWAYS_INLINE ConstImpl operator[] (csubstr key) const RYML_NOEXCEPT + { + _C4RR(); + id_type ch = tree_->find_child(id_, key); + _RYML_ASSERT_VISIT_(tree_->m_callbacks, ch != NONE, tree_, id_); + return {tree_, ch}; + } + + /** Find a child by position; complexity is O(pos). + * + * Behaves similar to the non-const overload, but further asserts + * that the returned node is readable (because it can never be in + * a seed state). This assertion is performed only if @ref + * RYML_USE_ASSERT is set to true. As with the non-const overload, + * it is UB to use the return value if it is not valid. + * + * @see https://github.com/biojppm/rapidyaml/issues/389 */ + C4_ALWAYS_INLINE ConstImpl operator[] (id_type pos) const RYML_NOEXCEPT + { + _C4RR(); + id_type ch = tree_->child(id_, pos); + _RYML_ASSERT_VISIT_(tree_->m_callbacks, ch != NONE, tree_, id_); + return {tree_, ch}; + } + + /** @} */ + +public: + + /** @name at + * + * These functions are the analogue to operator[], with the + * difference that they emit an error instead of an + * assertion. That is, if any of the pre or post conditions is + * violated, an error is always emitted (resulting in a call to + * the error callback). + * + * @{ */ + + /** Find child by key; complexity is O(num_children). + * + * Returns the requested node, or an object in seed state if no + * such child is found (see @ref NodeRef for an explanation of + * what is seed state). When the object is in seed state, using it + * to read from the tree is UB. The seed node can be subsequently + * used to write to the tree provided that its create() method is + * called prior to writing, which happens inside most mutating + * methods in NodeRef. It is the caller's responsibility to verify + * that the returned node is readable before subsequently using it + * to read from the tree. + * + * @warning This method will call the error callback (regardless + * of build type or of the value of RYML_USE_ASSERT) whenever any + * of the following preconditions is violated: a) the object is + * valid (points at a tree and a node), b) the calling object must + * be readable (must not be in seed state), c) the calling object + * must be pointing at a MAP node. The preconditions are similar + * to the non-const operator[](csubstr), but instead of using + * assertions, this function directly checks those conditions and + * calls the error callback if any of the checks fail. + * + * @note since it is valid behavior for the returned node to be in + * seed state, the error callback is not invoked when this + * happens. */ + template + C4_ALWAYS_INLINE auto at(csubstr key) -> _C4_IF_MUTABLE(Impl) + { + _RYML_CHECK_BASIC(tree_ != nullptr); + _RYML_CHECK_VISIT_(tree_->m_callbacks, (id_ >= 0 && id_ < tree_->capacity()), tree_, id_); + _RYML_CHECK_VISIT_(tree_->m_callbacks, ((Impl const*)this)->readable(), tree_, id_); + _RYML_CHECK_VISIT_(tree_->m_callbacks, tree_->is_map(id_), tree_, id_); + id_type ch = tree__->find_child(id__, key); + return ch != NONE ? Impl(tree__, ch) : Impl(tree__, id__, key); + } + + /** Find child by position; complexity is O(pos). + * + * Returns the requested node, or an object in seed state if no + * such child is found (see @ref NodeRef for an explanation of + * what is seed state). When the object is in seed state, using it + * to read from the tree is UB. The seed node can be used to write + * to the tree provided that its create() method is called prior + * to writing, which happens in most modifying methods in + * NodeRef. It is the caller's responsibility to verify that the + * returned node is readable before subsequently using it to read + * from the tree. + * + * @warning This method will call the error callback (regardless + * of build type or of the value of RYML_USE_ASSERT) whenever any + * of the following preconditions is violated: a) the object is + * valid (points at a tree and a node), b) the calling object must + * be readable (must not be in seed state), c) the calling object + * must be pointing at a MAP node. The preconditions are similar + * to the non-const operator[](id_type), but instead of using + * assertions, this function directly checks those conditions and + * calls the error callback if any of the checks fail. + * + * @note since it is valid behavior for the returned node to be in + * seed state, the error callback is not invoked when this + * happens. */ + template + C4_ALWAYS_INLINE auto at(id_type pos) -> _C4_IF_MUTABLE(Impl) + { + _RYML_CHECK_BASIC(tree_ != nullptr); + const id_type cap = tree_->capacity(); + _RYML_CHECK_VISIT_(tree_->m_callbacks, (id_ >= 0 && id_ < cap), tree_, id_); + _RYML_CHECK_VISIT_(tree_->m_callbacks, (pos >= 0 && pos < cap), tree_, id_); + _RYML_CHECK_VISIT_(tree_->m_callbacks, ((Impl const*)this)->readable(), tree_, id_); + _RYML_CHECK_VISIT_(tree_->m_callbacks, tree_->is_container(id_), tree_, id_); + id_type ch = tree__->child(id__, pos); + return ch != NONE ? Impl(tree__, ch) : Impl(tree__, id__, pos); + } + + /** Get a child by name, with error checking; complexity is + * O(num_children). + * + * Behaves as operator[](csubstr) const, but always raises an + * error (even when RYML_USE_ASSERT is set to false) when the + * returned node does not exist, or when this node is not + * readable, or when it is not a map. This behaviour is similar to + * std::vector::at(), but the error consists in calling the error + * callback instead of directly raising an exception. */ + ConstImpl at(csubstr key) const + { + _RYML_CHECK_BASIC(tree_ != nullptr); + _RYML_CHECK_VISIT_(tree_->m_callbacks, (id_ >= 0 && id_ < tree_->capacity()), tree_, id_); + _RYML_CHECK_VISIT_(tree_->m_callbacks, ((Impl const*)this)->readable(), tree_, id_); + _RYML_CHECK_VISIT_(tree_->m_callbacks, tree_->is_map(id_), tree_, id_); + id_type ch = tree_->find_child(id_, key); + _RYML_CHECK_VISIT_(tree_->m_callbacks, ch != NONE, tree_, id_); + return {tree_, ch}; + } + + /** Get a child by position, with error checking; complexity is + * O(pos). + * + * Behaves as operator[](id_type) const, but always raises an error + * (even when RYML_USE_ASSERT is set to false) when the returned + * node does not exist, or when this node is not readable, or when + * it is not a container. This behaviour is similar to + * std::vector::at(), but the error consists in calling the error + * callback instead of directly raising an exception. */ + ConstImpl at(id_type pos) const + { + _RYML_CHECK_BASIC(tree_ != nullptr); + const id_type cap = tree_->capacity(); + _RYML_CHECK_VISIT_(tree_->m_callbacks, (id_ >= 0 && id_ < cap), tree_, id_); + _RYML_CHECK_VISIT_(tree_->m_callbacks, (pos >= 0 && pos < cap), tree_, id_); + _RYML_CHECK_VISIT_(tree_->m_callbacks, ((Impl const*)this)->readable(), tree_, id_); + _RYML_CHECK_VISIT_(tree_->m_callbacks, tree_->is_container(id_), tree_, id_); + const id_type ch = tree_->child(id_, pos); + _RYML_CHECK_VISIT_(tree_->m_callbacks, ch != NONE, tree_, id_); + return {tree_, ch}; + } + + /** @} */ + +public: + + /** @name locations */ + /** @{ */ + + Location location(Parser const& parser) const + { + _C4RR(); + return tree_->location(parser, id_); + } + + /** @} */ + +public: + + /** @name deserialization */ + /** @{ */ + + /** deserialize the node's val to the given variable, forwarding + * to the user-overrideable @ref read() function. */ + template + ConstImpl const& operator>> (T &v) const + { + _C4RR(); + if( ! read((ConstImpl const&)*this, &v)) + _RYML_ERR_VISIT_(tree_->m_callbacks, tree_, id_, "could not deserialize value"); + return *((ConstImpl const*)this); + } + template + ConstImpl const& operator>> (T const& wrapper) const + { + _C4RR(); + if( ! read((ConstImpl const&)*this, wrapper)) + _RYML_ERR_VISIT_(tree_->m_callbacks, tree_, id_, "could not deserialize value"); + return *((ConstImpl const*)this); + } + + /** deserialize the node's key to the given variable, forwarding + * to the user-overrideable @ref read() function; use @ref key() + * to disambiguate; for example: `node >> ryml::key(var)` */ + template + ConstImpl const& operator>> (Key v) const + { + _C4RR(); + if( ! readkey((ConstImpl const&)*this, &v.k)) + _RYML_ERR_VISIT_(tree_->m_callbacks, tree_, id_, "could not deserialize key"); + return *((ConstImpl const*)this); + } + + /** look for a child by name, if it exists assign to var. return + * true if the child existed. */ + template + bool get_if(csubstr name, T *var) const + { + _C4RR(); + ConstImpl ch = find_child(name); + if(!ch.readable()) + return false; + ch >> *var; + return true; + } + + /** look for a child by name, if it exists assign to var, + * otherwise default to fallback. return true if the child + * existed. */ + template + bool get_if(csubstr name, T *var, T const& fallback) const + { + _C4RR(); + ConstImpl ch = find_child(name); + if(ch.readable()) + { + ch >> *var; + return true; + } + else + { + *var = fallback; + return false; + } + } + + /** @} */ + +public: + + #if defined(__clang__) // NOLINT + # pragma clang diagnostic push + # pragma clang diagnostic ignored "-Wnull-dereference" + #elif defined(__GNUC__) + # pragma GCC diagnostic push + # if __GNUC__ >= 6 + # pragma GCC diagnostic ignored "-Wnull-dereference" + # endif + #endif + + /** @name iteration */ + /** @{ */ + + using iterator = detail::child_iterator; + using const_iterator = detail::child_iterator; + using children_view = detail::children_view_; + using const_children_view = detail::children_view_; + + template + C4_ALWAYS_INLINE auto begin() RYML_NOEXCEPT -> _C4_IF_MUTABLE(iterator) { _C4RR(); return iterator(tree__, tree__->first_child(id__)); } /**< get a mutable iterator to the first child. NOT AVAILABLE for ConstNodeRef. */ + C4_ALWAYS_INLINE const_iterator begin() const RYML_NOEXCEPT { _C4RR(); return const_iterator(tree_, tree_->first_child(id_)); } /**< get an iterator to the first child */ + C4_ALWAYS_INLINE const_iterator cbegin() const RYML_NOEXCEPT { _C4RR(); return const_iterator(tree_, tree_->first_child(id_)); } /**< get an iterator to the first child */ + + template + C4_ALWAYS_INLINE auto end() RYML_NOEXCEPT -> _C4_IF_MUTABLE(iterator) { _C4RR(); return iterator(tree__, NONE); } /**< get an iterator to after the last child. NOT AVAILABLE for ConstNodeRef. */ + /** get an iterator to after the last child */ + C4_ALWAYS_INLINE const_iterator end() const RYML_NOEXCEPT { _C4RR(); return const_iterator(tree_, NONE); } /**< get an iterator to after the last child */ + /** get an iterator to after the last child */ + C4_ALWAYS_INLINE const_iterator cend() const RYML_NOEXCEPT { _C4RR(); return const_iterator(tree_, NONE); } /**< get an iterator to after the last child */ + + template + C4_ALWAYS_INLINE auto children() RYML_NOEXCEPT -> _C4_IF_MUTABLE(children_view) { _C4RR(); return children_view(begin(), end()); } /**< get an iterable view over children. NOT AVAILABLE for ConstNodeRef. */ + C4_ALWAYS_INLINE const_children_view children() const RYML_NOEXCEPT { _C4RR(); return const_children_view(begin(), end()); } /**< get an iterable view over children */ + C4_ALWAYS_INLINE const_children_view cchildren() const RYML_NOEXCEPT { _C4RR(); return const_children_view(begin(), end()); } /**< get an iterable view over children */ + + /** get an iterable view over all siblings (including the calling node) */ + template + C4_ALWAYS_INLINE auto siblings() RYML_NOEXCEPT -> _C4_IF_MUTABLE(children_view) + { + _C4RR(); + NodeData const *nd = tree__->get(id__); + return (nd->m_parent != NONE) ? // does it have a parent? + children_view(iterator(tree__, tree_->get(nd->m_parent)->m_first_child), iterator(tree__, NONE)) + : + children_view(end(), end()); + } + /** get an iterable view over all siblings (including the calling node) */ + C4_ALWAYS_INLINE const_children_view siblings() const RYML_NOEXCEPT + { + _C4RR(); + NodeData const *nd = tree_->get(id_); + return (nd->m_parent != NONE) ? // does it have a parent? + const_children_view(const_iterator(tree_, tree_->get(nd->m_parent)->m_first_child), const_iterator(tree_, NONE)) + : + const_children_view(end(), end()); + } + /** get an iterable view over all siblings (including the calling node) */ + C4_ALWAYS_INLINE const_children_view csiblings() const RYML_NOEXCEPT { return siblings(); } + + /** visit every child node calling fn(node) */ + template + bool visit(Visitor fn, id_type indentation_level=0, bool skip_root=true) const RYML_NOEXCEPT + { + _C4RR(); + return detail::_visit(*(ConstImpl const*)this, fn, indentation_level, skip_root); + } + /** visit every child node calling fn(node) */ + template + auto visit(Visitor fn, id_type indentation_level=0, bool skip_root=true) RYML_NOEXCEPT + -> _C4_IF_MUTABLE(bool) + { + _C4RR(); + return detail::_visit(*(Impl*)this, fn, indentation_level, skip_root); + } + + /** visit every child node calling fn(node, level) */ + template + bool visit_stacked(Visitor fn, id_type indentation_level=0, bool skip_root=true) const RYML_NOEXCEPT + { + _C4RR(); + return detail::_visit_stacked(*(ConstImpl const*)this, fn, indentation_level, skip_root); + } + /** visit every child node calling fn(node, level) */ + template + auto visit_stacked(Visitor fn, id_type indentation_level=0, bool skip_root=true) RYML_NOEXCEPT + -> _C4_IF_MUTABLE(bool) + { + _C4RR(); + return detail::_visit_stacked(*(Impl*)this, fn, indentation_level, skip_root); + } + + /** @} */ + + #if defined(__clang__) + # pragma clang diagnostic pop + #elif defined(__GNUC__) + # pragma GCC diagnostic pop + #endif + + #undef _C4_IF_MUTABLE + #undef _C4RR + #undef tree_ + #undef tree__ + #undef id_ + #undef id__ + + C4_SUPPRESS_WARNING_GCC_CLANG_POP +}; +} // detail + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +/** Holds a pointer to an existing tree, and a node id. It can be used + * only to read from the tree. + * + * @warning The lifetime of the tree must be larger than that of this + * object. It is up to the user to ensure that this happens. */ +class RYML_EXPORT ConstNodeRef : public detail::RoNodeMethods // NOLINT +{ +public: + + using tree_type = Tree const; + +public: + + Tree const* C4_RESTRICT m_tree; + id_type m_id; + + friend NodeRef; + friend struct detail::RoNodeMethods; + +public: + + /** @name construction */ + /** @{ */ + + ConstNodeRef() noexcept : m_tree(nullptr), m_id(NONE) {} + ConstNodeRef(Tree const &t) noexcept : m_tree(&t), m_id(t .root_id()) {} + ConstNodeRef(Tree const *t) noexcept : m_tree(t ), m_id(t->root_id()) {} + ConstNodeRef(Tree const *t, id_type id) noexcept : m_tree(t), m_id(id) {} + ConstNodeRef(std::nullptr_t) noexcept : m_tree(nullptr), m_id(NONE) {} + + ConstNodeRef(ConstNodeRef const&) noexcept = default; + ConstNodeRef(ConstNodeRef &&) noexcept = default; + + inline ConstNodeRef(NodeRef const&) noexcept; + inline ConstNodeRef(NodeRef &&) noexcept; + + /** @} */ + +public: + + /** @name assignment */ + /** @{ */ + + ConstNodeRef& operator= (std::nullptr_t) noexcept { m_tree = nullptr; m_id = NONE; return *this; } + + ConstNodeRef& operator= (ConstNodeRef const&) noexcept = default; + ConstNodeRef& operator= (ConstNodeRef &&) noexcept = default; + + ConstNodeRef& operator= (NodeRef const&) noexcept; + ConstNodeRef& operator= (NodeRef &&) noexcept; + + /** @} */ + +public: + + /** @name state queries + * + * see @ref NodeRef for an explanation on what these states mean */ + /** @{ */ + + C4_ALWAYS_INLINE bool invalid() const noexcept { return (!m_tree) || (m_id == NONE); } + /** because a ConstNodeRef cannot be used to write to the tree, + * readable() has the same meaning as !invalid() */ + C4_ALWAYS_INLINE bool readable() const noexcept { return m_tree != nullptr && m_id != NONE; } + /** because a ConstNodeRef cannot be used to write to the tree, it can never be a seed. + * This method is provided for API equivalence between ConstNodeRef and NodeRef. */ + constexpr static C4_ALWAYS_INLINE bool is_seed() noexcept { return false; } + + RYML_DEPRECATED("use one of readable(), is_seed() or !invalid()") bool valid() const noexcept { return m_tree != nullptr && m_id != NONE; } + + /** @} */ + +public: + + /** @name member getters */ + /** @{ */ + + C4_ALWAYS_INLINE Tree const* tree() const noexcept { return m_tree; } + C4_ALWAYS_INLINE id_type id() const noexcept { return m_id; } + + /** @} */ + +public: + + /** @name comparisons */ + /** @{ */ + + C4_ALWAYS_INLINE bool operator== (ConstNodeRef const& that) const RYML_NOEXCEPT { return that.m_tree == m_tree && m_id == that.m_id; } + C4_ALWAYS_INLINE bool operator!= (ConstNodeRef const& that) const RYML_NOEXCEPT { return ! this->operator== (that); } + + /** @cond dev */ + RYML_DEPRECATED("use invalid()") bool operator== (std::nullptr_t) const noexcept { return m_tree == nullptr || m_id == NONE; } + RYML_DEPRECATED("use !invalid()") bool operator!= (std::nullptr_t) const noexcept { return !(m_tree == nullptr || m_id == NONE); } + + RYML_DEPRECATED("use (this->val() == s)") bool operator== (csubstr s) const RYML_NOEXCEPT { _RYML_ASSERT_BASIC(m_tree); _RYML_ASSERT_VISIT_(m_tree->m_callbacks, m_id != NONE, m_tree, NONE); return m_tree->val(m_id) == s; } + RYML_DEPRECATED("use (this->val() != s)") bool operator!= (csubstr s) const RYML_NOEXCEPT { _RYML_ASSERT_BASIC(m_tree); _RYML_ASSERT_VISIT_(m_tree->m_callbacks, m_id != NONE, m_tree, NONE); return m_tree->val(m_id) != s; } + /** @endcond */ + + /** @} */ + +}; + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +// NOLINTBEGIN(cppcoreguidelines-c-copy-assignment-signature,misc-unconventional-assign-operator) + +/** A reference to a node in an existing yaml tree, offering a more + * convenient API than the index-based API used in the tree. + * + * Unlike its imutable ConstNodeRef peer, a NodeRef can be used to + * mutate the tree, both by writing to existing nodes and by creating + * new nodes to subsequently write to. Semantically, a NodeRef + * object can be in one of three states: + * + * ```text + * invalid := not pointing at anything + * readable := points at an existing tree/node + * seed := points at an existing tree, and the node + * may come to exist, if we write to it. + * ``` + * + * So both `readable` and `seed` are states where the node is also `valid`. + * + * ```cpp + * Tree t = parse_in_arena("{a: b}"); + * NodeRef invalid; // not pointing at anything. + * NodeRef readable = t["a"]; // also valid, because "a" exists + * NodeRef seed = t["none"]; // also valid, but is seed because "none" is not in the map + * ``` + * + * When the object is in seed state, using it to read from the tree is + * UB. The seed node can be used to write to the tree, provided that + * its create() method is called prior to writing, which happens in + * most modifying methods in NodeRef. + * + * It is the owners's responsibility to verify that an existing + * node is readable before subsequently using it to read from the + * tree. + * + * @warning The lifetime of the tree must be larger than that of this + * object. It is up to the user to ensure that this happens. + */ +class RYML_EXPORT NodeRef : public detail::RoNodeMethods // NOLINT +{ +public: + + using tree_type = Tree; + using base_type = detail::RoNodeMethods; + +private: + + Tree *C4_RESTRICT m_tree; + id_type m_id; + + /** This member is used to enable lazy operator[] writing. When a child + * with a key or index is not found, m_id is set to the id of the parent + * and the asked-for key or index are stored in this member until a write + * does happen. Then it is given as key or index for creating the child. + * When a key is used, the csubstr stores it (so the csubstr's string is + * non-null and the csubstr's size is different from NONE). When an index is + * used instead, the csubstr's string is set to null, and only the csubstr's + * size is set to a value different from NONE. Otherwise, when operator[] + * does find the child then this member is empty: the string is null and + * the size is NONE. */ + csubstr m_seed; + + friend ConstNodeRef; + friend struct detail::RoNodeMethods; + + // require valid: a helper macro, undefined at the end + #define _C4RR() \ + _RYML_ASSERT_BASIC(m_tree != nullptr); \ + _RYML_ASSERT_VISIT_(m_tree->m_callbacks, m_id != NONE && !is_seed(), m_tree, m_id) + // require id: a helper macro, undefined at the end + #define _C4RID() \ + _RYML_ASSERT_BASIC(m_tree != nullptr); \ + _RYML_ASSERT_VISIT_(m_tree->m_callbacks, m_id != NONE, m_tree, m_id) + +public: + + /** @name construction */ + /** @{ */ + + NodeRef() noexcept : m_tree(nullptr), m_id(NONE), m_seed() { _clear_seed(); } + NodeRef(Tree &t) noexcept : m_tree(&t), m_id(t .root_id()), m_seed() { _clear_seed(); } + NodeRef(Tree *t) noexcept : m_tree(t ), m_id(t->root_id()), m_seed() { _clear_seed(); } + NodeRef(Tree *t, id_type id) noexcept : m_tree(t), m_id(id), m_seed() { _clear_seed(); } + NodeRef(Tree *t, id_type id, id_type seed_pos) noexcept : m_tree(t), m_id(id), m_seed() { m_seed.str = nullptr; m_seed.len = (size_t)seed_pos; } + NodeRef(Tree *t, id_type id, csubstr seed_key) noexcept : m_tree(t), m_id(id), m_seed(seed_key) {} + NodeRef(std::nullptr_t) noexcept : m_tree(nullptr), m_id(NONE), m_seed() {} + + void _clear_seed() noexcept { /*do the following manually or an assert is triggered: */ m_seed.str = nullptr; m_seed.len = npos; } + + /** @} */ + +public: + + /** @name assignment */ + /** @{ */ + + NodeRef(NodeRef const&) noexcept = default; + NodeRef(NodeRef &&) noexcept = default; + + NodeRef& operator= (NodeRef const&) noexcept = default; + NodeRef& operator= (NodeRef &&) noexcept = default; + + /** @} */ + +public: + + /** @name state_queries + * @{ */ + + /** true if the object is not referring to any existing or seed node. @see the doc for @ref NodeRef */ + bool invalid() const noexcept { return m_tree == nullptr || m_id == NONE; } + /** true if the object is not invalid and in seed state. @see the doc for @ref NodeRef */ + bool is_seed() const noexcept { return (m_tree != nullptr && m_id != NONE) && (m_seed.str != nullptr || m_seed.len != (size_t)NONE); } + /** true if the object is not invalid and not in seed state. @see the doc for @ref NodeRef */ + bool readable() const noexcept { return (m_tree != nullptr && m_id != NONE) && (m_seed.str == nullptr && m_seed.len == (size_t)NONE); } + + RYML_DEPRECATED("use one of readable(), is_seed() or !invalid()") inline bool valid() const { return m_tree != nullptr && m_id != NONE; } + + /** @} */ + +public: + + /** @name comparisons */ + /** @{ */ + + bool operator== (NodeRef const& that) const + { + if(m_tree == that.m_tree && m_id == that.m_id) + { + bool seed = is_seed(); + if(seed == that.is_seed()) + { + if(seed) + { + return (m_seed.len == that.m_seed.len) + && (m_seed.str == that.m_seed.str + || m_seed == that.m_seed); // do strcmp only in the last resort + } + return true; + } + } + return false; + } + bool operator!= (NodeRef const& that) const { return ! this->operator==(that); } + + bool operator== (ConstNodeRef const& that) const { return m_tree == that.m_tree && m_id == that.m_id && !is_seed(); } + bool operator!= (ConstNodeRef const& that) const { return ! this->operator==(that); } + + /** @cond dev */ + RYML_DEPRECATED("use !readable()") bool operator== (std::nullptr_t) const { return m_tree == nullptr || m_id == NONE || is_seed(); } + RYML_DEPRECATED("use readable()") bool operator!= (std::nullptr_t) const { return !(m_tree == nullptr || m_id == NONE || is_seed()); } + + RYML_DEPRECATED("use `this->val() == s`") bool operator== (csubstr s) const { _C4RR(); _RYML_ASSERT_VISIT_(m_tree->m_callbacks, has_val(), m_tree, m_id); return m_tree->val(m_id) == s; } + RYML_DEPRECATED("use `this->val() != s`") bool operator!= (csubstr s) const { _C4RR(); _RYML_ASSERT_VISIT_(m_tree->m_callbacks, has_val(), m_tree, m_id); return m_tree->val(m_id) != s; } + /** @endcond */ + +public: + + /** @name node_property_getters + * @{ */ + + C4_ALWAYS_INLINE Tree * tree() noexcept { return m_tree; } + C4_ALWAYS_INLINE Tree const* tree() const noexcept { return m_tree; } + + C4_ALWAYS_INLINE id_type id() const noexcept { return m_id; } + + /** @} */ + +public: + + /** @name node_modifiers */ + /** @{ */ + + void create() { _apply_seed(); } + + void change_type(NodeType t) { _C4RR(); m_tree->change_type(m_id, t); } + + void set_type(NodeType t) { _apply_seed(); m_tree->_set_flags(m_id, t); } + void set_key(csubstr key) { _apply_seed(); m_tree->_set_key(m_id, key); } + void set_val(csubstr val) { _apply_seed(); m_tree->_set_val(m_id, val); } + void set_key_tag(csubstr key_tag) { _apply_seed(); m_tree->set_key_tag(m_id, key_tag); } + void set_val_tag(csubstr val_tag) { _apply_seed(); m_tree->set_val_tag(m_id, val_tag); } + void set_key_anchor(csubstr key_anchor) { _apply_seed(); m_tree->set_key_anchor(m_id, key_anchor); } + void set_val_anchor(csubstr val_anchor) { _apply_seed(); m_tree->set_val_anchor(m_id, val_anchor); } + void set_key_ref(csubstr key_ref) { _apply_seed(); m_tree->set_key_ref(m_id, key_ref); } + void set_val_ref(csubstr val_ref) { _apply_seed(); m_tree->set_val_ref(m_id, val_ref); } + + void set_container_style(NodeType_e style) { _C4RR(); m_tree->set_container_style(m_id, style); } + void set_key_style(NodeType_e style) { _C4RR(); m_tree->set_key_style(m_id, style); } + void set_val_style(NodeType_e style) { _C4RR(); m_tree->set_val_style(m_id, style); } + void clear_style(bool recurse=false) { _C4RR(); m_tree->clear_style(m_id, recurse); } + void set_style_conditionally(NodeType type_mask, + NodeType rem_style_flags, + NodeType add_style_flags, + bool recurse=false) + { + _C4RR(); m_tree->set_style_conditionally(m_id, type_mask, rem_style_flags, add_style_flags, recurse); + } + +public: + + void clear() + { + if(is_seed()) + return; + m_tree->remove_children(m_id); + m_tree->_clear(m_id); + } + + void clear_key() + { + if(is_seed()) + return; + m_tree->_clear_key(m_id); + } + + void clear_val() + { + if(is_seed()) + return; + m_tree->_clear_val(m_id); + } + + void clear_children() + { + if(is_seed()) + return; + m_tree->remove_children(m_id); + } + + void operator= (NodeType_e t) + { + _apply_seed(); + m_tree->_add_flags(m_id, t); + } + + void operator|= (NodeType_e t) + { + _apply_seed(); + m_tree->_add_flags(m_id, t); + } + + void operator= (NodeInit const& v) + { + _apply_seed(); + _apply(v); + } + + void operator= (NodeScalar const& v) + { + _apply_seed(); + _apply(v); + } + + void operator= (std::nullptr_t) + { + _apply_seed(); + _apply(csubstr{}); + } + + void operator= (csubstr v) + { + _apply_seed(); + _apply(v); + } + + template + void operator= (const char (&v)[N]) + { + _apply_seed(); + csubstr sv; + sv.assign(v); + _apply(sv); + } + + /** @} */ + +public: + + /** @name serialization */ + /** @{ */ + + /** serialize a variable to the arena */ + template + csubstr to_arena(T const& C4_RESTRICT s) + { + _RYML_ASSERT_BASIC(m_tree); // no need for valid or readable + return m_tree->to_arena(s); + } + + template + size_t set_key_serialized(T const& C4_RESTRICT k) + { + _apply_seed(); + csubstr s = m_tree->to_arena(k); + m_tree->_set_key(m_id, s); + return s.len; + } + size_t set_key_serialized(std::nullptr_t) + { + _apply_seed(); + m_tree->_set_key(m_id, csubstr{}); + return 0; + } + + template + size_t set_val_serialized(T const& C4_RESTRICT v) + { + _apply_seed(); + csubstr s = m_tree->to_arena(v); + m_tree->_set_val(m_id, s); + return s.len; + } + size_t set_val_serialized(std::nullptr_t) + { + _apply_seed(); + m_tree->_set_val(m_id, csubstr{}); + return 0; + } + + /** serialize a variable, then assign the result to the node's val */ + NodeRef& operator<< (csubstr s) + { + // this overload is needed to prevent ambiguity (there's also + // operator<< for writing a substr to a stream) + _apply_seed(); + write(this, s); + _RYML_ASSERT_VISIT_(m_tree->m_callbacks, val() == s, m_tree, m_id); + return *this; + } + + template + NodeRef& operator<< (T const& C4_RESTRICT v) + { + _apply_seed(); + write(this, v); + return *this; + } + + /** serialize a variable, then assign the result to the node's key */ + template + NodeRef& operator<< (Key const& C4_RESTRICT v) + { + _apply_seed(); + set_key_serialized(v.k); + return *this; + } + + /** serialize a variable, then assign the result to the node's key */ + template + NodeRef& operator<< (Key const& C4_RESTRICT v) + { + _apply_seed(); + set_key_serialized(v.k); + return *this; + } + + /** @} */ + +private: + + void _apply_seed() + { + _C4RID(); + if(m_seed.str) // we have a seed key: use it to create the new child + { + m_id = m_tree->append_child(m_id); + m_tree->_set_key(m_id, m_seed); + m_seed.str = nullptr; + m_seed.len = (size_t)NONE; + } + else if(m_seed.len != (size_t)NONE) // we have a seed index: create a child at that position + { + _RYML_ASSERT_VISIT_(m_tree->m_callbacks, (size_t)m_tree->num_children(m_id) == m_seed.len, m_tree, m_id); + m_id = m_tree->append_child(m_id); + m_seed.str = nullptr; + m_seed.len = (size_t)NONE; + } + else + { + _RYML_ASSERT_VISIT_(m_tree->m_callbacks, readable(), m_tree, m_id); + } + } + + void _apply(csubstr v) + { + m_tree->_set_val(m_id, v); + } + + void _apply(NodeScalar const& v) + { + m_tree->_set_val(m_id, v); + } + + void _apply(NodeInit const& i) + { + m_tree->_set(m_id, i); + } + +public: + + /** @name modification of hierarchy */ + /** @{ */ + + NodeRef insert_child(NodeRef after) + { + _C4RR(); + _RYML_ASSERT_VISIT_(m_tree->m_callbacks, after.m_tree == m_tree, m_tree, m_id); + NodeRef r(m_tree, m_tree->insert_child(m_id, after.m_id)); + return r; + } + + NodeRef insert_child(NodeInit const& i, NodeRef after) + { + _C4RR(); + _RYML_ASSERT_VISIT_(m_tree->m_callbacks, after.m_tree == m_tree, m_tree, m_id); + NodeRef r(m_tree, m_tree->insert_child(m_id, after.m_id)); + r._apply(i); + return r; + } + + NodeRef prepend_child() + { + _C4RR(); + NodeRef r(m_tree, m_tree->insert_child(m_id, NONE)); + return r; + } + + NodeRef prepend_child(NodeInit const& i) + { + _C4RR(); + NodeRef r(m_tree, m_tree->insert_child(m_id, NONE)); + r._apply(i); + return r; + } + + NodeRef append_child() + { + _C4RR(); + NodeRef r(m_tree, m_tree->append_child(m_id)); + return r; + } + + NodeRef append_child(NodeInit const& i) + { + _C4RR(); + NodeRef r(m_tree, m_tree->append_child(m_id)); + r._apply(i); + return r; + } + + NodeRef insert_sibling(ConstNodeRef const& after) + { + _C4RR(); + _RYML_ASSERT_VISIT_(m_tree->m_callbacks, after.m_tree == m_tree, m_tree, m_id); + NodeRef r(m_tree, m_tree->insert_sibling(m_id, after.m_id)); + return r; + } + + NodeRef insert_sibling(NodeInit const& i, ConstNodeRef const& after) + { + _C4RR(); + _RYML_ASSERT_VISIT_(m_tree->m_callbacks, after.m_tree == m_tree, m_tree, m_id); + NodeRef r(m_tree, m_tree->insert_sibling(m_id, after.m_id)); + r._apply(i); + return r; + } + + NodeRef prepend_sibling() + { + _C4RR(); + NodeRef r(m_tree, m_tree->prepend_sibling(m_id)); + return r; + } + + NodeRef prepend_sibling(NodeInit const& i) + { + _C4RR(); + NodeRef r(m_tree, m_tree->prepend_sibling(m_id)); + r._apply(i); + return r; + } + + NodeRef append_sibling() + { + _C4RR(); + NodeRef r(m_tree, m_tree->append_sibling(m_id)); + return r; + } + + NodeRef append_sibling(NodeInit const& i) + { + _C4RR(); + NodeRef r(m_tree, m_tree->append_sibling(m_id)); + r._apply(i); + return r; + } + +public: + + void remove_child(NodeRef & child) + { + _C4RR(); + _RYML_ASSERT_VISIT_(m_tree->m_callbacks, has_child(child), m_tree, m_id); + _RYML_ASSERT_VISIT_(m_tree->m_callbacks, child.parent().id() == id(), m_tree, m_id); + m_tree->remove(child.id()); + child.clear(); + } + + //! remove the nth child of this node + void remove_child(id_type pos) + { + _C4RR(); + _RYML_ASSERT_VISIT_(m_tree->m_callbacks, pos >= 0 && pos < num_children(), m_tree, m_id); + id_type child = m_tree->child(m_id, pos); + _RYML_ASSERT_VISIT_(m_tree->m_callbacks, child != NONE, m_tree, m_id); + m_tree->remove(child); + } + + //! remove a child by name + void remove_child(csubstr key) + { + _C4RR(); + id_type child = m_tree->find_child(m_id, key); + _RYML_ASSERT_VISIT_(m_tree->m_callbacks, child != NONE, m_tree, m_id); + m_tree->remove(child); + } + +public: + + /** change the node's position within its parent, placing it after + * @p after. To move to the first position in the parent, simply + * pass an empty or default-constructed reference like this: + * `n.move({})`. */ + void move(ConstNodeRef const& after) + { + _C4RR(); + m_tree->move(m_id, after.m_id); + } + + /** move the node to a different @p parent (which may belong to a + * different tree), placing it after @p after. When the + * destination parent is in a new tree, then this node's tree + * pointer is reset to the tree of the parent node. */ + void move(NodeRef const& parent, ConstNodeRef const& after) + { + _C4RR(); + if(parent.m_tree == m_tree) + { + m_tree->move(m_id, parent.m_id, after.m_id); + } + else + { + parent.m_tree->move(m_tree, m_id, parent.m_id, after.m_id); + m_tree = parent.m_tree; + } + } + + /** duplicate the current node somewhere within its parent, and + * place it after the node @p after. To place into the first + * position of the parent, simply pass an empty or + * default-constructed reference like this: `n.move({})`. */ + NodeRef duplicate(ConstNodeRef const& after) const + { + _C4RR(); + _RYML_ASSERT_VISIT_(m_tree->m_callbacks, m_tree == after.m_tree || after.m_id == NONE, m_tree, m_id); + id_type dup = m_tree->duplicate(m_id, m_tree->parent(m_id), after.m_id); + NodeRef r(m_tree, dup); + return r; + } + + /** duplicate the current node somewhere into a different @p parent + * (possibly from a different tree), and place it after the node + * @p after. To place into the first position of the parent, + * simply pass an empty or default-constructed reference like + * this: `n.move({})`. */ + NodeRef duplicate(NodeRef const& parent, ConstNodeRef const& after) const + { + _C4RR(); + _RYML_ASSERT_VISIT_(m_tree->m_callbacks, parent.m_tree == after.m_tree || after.m_id == NONE, m_tree, m_id); + if(parent.m_tree == m_tree) + { + id_type dup = m_tree->duplicate(m_id, parent.m_id, after.m_id); + NodeRef r(m_tree, dup); + return r; + } + else + { + id_type dup = parent.m_tree->duplicate(m_tree, m_id, parent.m_id, after.m_id); + NodeRef r(parent.m_tree, dup); + return r; + } + } + + void duplicate_children(NodeRef const& parent, ConstNodeRef const& after) const + { + _C4RR(); + _RYML_ASSERT_VISIT_(m_tree->m_callbacks, parent.m_tree == after.m_tree, m_tree, m_id); + if(parent.m_tree == m_tree) + { + m_tree->duplicate_children(m_id, parent.m_id, after.m_id); + } + else + { + parent.m_tree->duplicate_children(m_tree, m_id, parent.m_id, after.m_id); + } + } + + /** @} */ + +#undef _C4RR +#undef _C4RID +}; + +// NOLINTEND(cppcoreguidelines-c-copy-assignment-signature,misc-unconventional-assign-operator) + + +//----------------------------------------------------------------------------- + +inline ConstNodeRef::ConstNodeRef(NodeRef const& that) noexcept + : m_tree(that.m_tree) + , m_id(!that.is_seed() ? that.id() : (id_type)NONE) +{ +} + +inline ConstNodeRef::ConstNodeRef(NodeRef && that) noexcept // NOLINT + : m_tree(that.m_tree) + , m_id(!that.is_seed() ? that.id() : (id_type)NONE) +{ +} + + +inline ConstNodeRef& ConstNodeRef::operator= (NodeRef const& that) noexcept +{ + m_tree = (that.m_tree); + m_id = (!that.is_seed() ? that.id() : (id_type)NONE); + return *this; +} + +inline ConstNodeRef& ConstNodeRef::operator= (NodeRef && that) noexcept // NOLINT +{ + m_tree = (that.m_tree); + m_id = (!that.is_seed() ? that.id() : (id_type)NONE); + return *this; +} + + +//----------------------------------------------------------------------------- + +/** @addtogroup doc_serialization_helpers + * + * @{ + */ + +template +C4_ALWAYS_INLINE void write(NodeRef *n, T const& v) +{ + n->set_val_serialized(v); +} + +template +C4_ALWAYS_INLINE bool read(ConstNodeRef const& C4_RESTRICT n, T *v) +{ + return read(n.m_tree, n.m_id, v); +} +template +C4_ALWAYS_INLINE bool read(ConstNodeRef const& C4_RESTRICT n, T const &wrapper) +{ + return read(n.m_tree, n.m_id, wrapper); +} + +template +C4_ALWAYS_INLINE bool read(NodeRef const& C4_RESTRICT n, T *v) +{ + return read(n.tree(), n.id(), v); +} +template +C4_ALWAYS_INLINE bool read(NodeRef const& C4_RESTRICT n, T const& wrapper) +{ + return read(n.tree(), n.id(), wrapper); +} + +template +C4_ALWAYS_INLINE bool readkey(ConstNodeRef const& C4_RESTRICT n, T *v) +{ + return readkey(n.m_tree, n.m_id, v); +} +template +C4_ALWAYS_INLINE bool readkey(ConstNodeRef const& C4_RESTRICT n, T const& wrapper) +{ + return readkey(n.m_tree, n.m_id, wrapper); +} + +template +C4_ALWAYS_INLINE bool readkey(NodeRef const& C4_RESTRICT n, T *v) +{ + return readkey(n.tree(), n.id(), v); +} +template +C4_ALWAYS_INLINE bool readkey(NodeRef const& C4_RESTRICT n, T const& wrapper) +{ + return readkey(n.tree(), n.id(), wrapper); +} + +/** @} */ + +/** @} */ + + +} // namespace yml +} // namespace c4 + +// NOLINTEND(modernize-avoid-c-style-cast) + +#ifdef __clang__ +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#elif defined(_MSC_VER) +# pragma warning(pop) +#endif + +#endif /* _C4_YML_NODE_HPP_ */ + + +// (end src/c4/yml/node.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/writer.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_YML_WRITER_HPP_ +#define _C4_YML_WRITER_HPP_ + +#ifndef _C4_YML_ERROR_HPP_ +// amalgamate: removed include of +// c4/yml/error.hpp +//#include "c4/yml/error.hpp" +#if !defined(C4_YML_ERROR_HPP_) && !defined(_C4_YML_ERROR_HPP_) +#error "amalgamate: file c4/yml/error.hpp must have been included at this point" +#endif /* C4_YML_ERROR_HPP_ */ + +#endif + +//included above: +//#include // fwrite(), fputc() +//included above: +//#include // memcpy() + + +namespace c4 { +namespace yml { + +/** @addtogroup doc_emit + * @{ + */ + +/** @defgroup doc_writers Writer objects to use with an Emitter + * @see Emitter + * @{ + */ + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** A writer that outputs to a C file handle, defaulting to + * stdout. This writer is *much* faster than @ref WriterOStream and + * should be preferred to it. */ +struct WriterFile +{ + FILE * m_file; + size_t m_pos; + + WriterFile(FILE *f = nullptr) : m_file(f ? f : stdout), m_pos(0) {} + + substr _get(bool /*error_on_excess*/) const + { + substr sp; + sp.str = nullptr; + sp.len = m_pos; + return sp; + } + + template + void _do_write(const char (&a)[N]) noexcept + { + static_assert(N > 1, "empty string"); + (void)fwrite(a, sizeof(char), N - 1, m_file); + m_pos += N - 1; + } + + void _do_write(csubstr s) noexcept + { + if(s.len) + { + C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wsign-conversion") + (void)fwrite(s.str, sizeof(csubstr::char_type), s.len, m_file); + m_pos += s.len; + C4_SUPPRESS_WARNING_GCC_CLANG_POP + } + } + + void _do_write(const char c) noexcept + { + (void)fputc(c, m_file); + ++m_pos; + } + + void _do_write(const char c, size_t num_times) noexcept + { + for(size_t i = 0; i < num_times; ++i) + (void)fputc(c, m_file); + m_pos += num_times; + } +}; + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** A writer that outputs to an STL-like ostream. + * + * @warning This writer is *much* slower than @ref WriterFile, and + * @ref WriterFile should be preferred to this. The slowness is due to + * how std::ostream works, and not because of anything in the code of + * this class. */ +template +struct WriterOStream +{ + OStream* m_stream; + size_t m_pos; + + WriterOStream(OStream &s) : m_stream(&s), m_pos(0) {} + + substr _get(bool /*error_on_excess*/) const noexcept + { + substr sp; + sp.str = nullptr; + sp.len = m_pos; + return sp; + } + + template + void _do_write(const char (&a)[N]) noexcept + { + static_assert(N > 1, "empty string"); + m_stream->write(a, N - 1); + m_pos += N - 1; + } + + void _do_write(csubstr s) noexcept + { + if(s.len) + { + C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wsign-conversion") + m_stream->write(s.str, s.len); + m_pos += s.len; + C4_SUPPRESS_WARNING_GCC_CLANG_POP + } + } + + void _do_write(const char c) noexcept + { + m_stream->put(c); + ++m_pos; + } + + void _do_write(const char c, size_t num_times) noexcept + { + for(size_t i = 0; i < num_times; ++i) + m_stream->put(c); + m_pos += num_times; + } +}; + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** A writer to a memory buffer, in the form of a @ref substr */ +struct WriterBuf +{ + substr m_buf; + size_t m_pos; + + WriterBuf(substr sp) noexcept : m_buf(sp), m_pos(0) {} + + substr _get(bool error_on_excess) const + { + if(m_pos <= m_buf.len) + return m_buf.first(m_pos); + else if(error_on_excess) + _RYML_ERR_BASIC("not enough space in the given buffer"); + substr sp; + sp.str = nullptr; + sp.len = m_pos; + return sp; + } + + template + void _do_write(const char (&a)[N]) noexcept + { + static_assert(N > 1, "empty string"); + _RYML_ASSERT_BASIC( ! m_buf.overlaps(a)); + if(m_pos + N-1 <= m_buf.len) + memcpy(m_buf.str + m_pos, a, N-1); + m_pos += N-1; + } + + void _do_write(csubstr s) noexcept + { + _RYML_ASSERT_BASIC( ! s.overlaps(m_buf)); + if(s.len && m_pos + s.len <= m_buf.len) + memcpy(m_buf.str + m_pos, s.str, s.len); + m_pos += s.len; + } + + void _do_write(const char c) noexcept + { + if(m_pos + 1 <= m_buf.len) + m_buf.str[m_pos] = c; + ++m_pos; + } + + void _do_write(const char c, size_t num_times) noexcept + { + if(m_pos + num_times <= m_buf.len) + memset(m_buf.str + m_pos, c, num_times); + m_pos += num_times; + } +}; + +/** @ } */ + +/** @ } */ + + +} // namespace yml +} // namespace c4 + +#endif /* _C4_YML_WRITER_HPP_ */ + + +// (end src/c4/yml/writer.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/detail/dbgprint.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_YML_DETAIL_DBGPRINT_HPP_ +#define _C4_YML_DETAIL_DBGPRINT_HPP_ + + +//----------------------------------------------------------------------------- +// debug prints + +#ifndef RYML_DBG +# define _c4dbgt(fmt, ...) +# define _c4dbgpf(fmt, ...) +# define _c4dbgpf_(fmt, ...) +# define _c4dbgp(msg) +# define _c4dbgp_(msg) +# define _c4dbgq(msg) +# define _c4presc(...) +# define _c4prscalar(msg, scalar, keep_newlines) +#else +# define _c4dbgt(fmt, ...) do { \ + if(_dbg_enabled()) { \ + this->_dbg("{}:{}: " fmt , __FILE__, __LINE__, __VA_ARGS__); \ + } \ + } while(0) +# define _c4dbgpf(fmt, ...) _dbg_printf("{}:{}: " fmt "\n", __FILE__, __LINE__, __VA_ARGS__) +# define _c4dbgpf_(fmt, ...) _dbg_printf("{}:{}: " fmt , __FILE__, __LINE__, __VA_ARGS__) +# define _c4dbgp(msg) _dbg_printf("{}:{}: " msg "\n", __FILE__, __LINE__ ) +# define _c4dbgp_(msg) _dbg_printf("{}:{}: " msg , __FILE__, __LINE__ ) +# define _c4dbgq(msg) _dbg_printf(msg "\n") +# define _c4presc(...) __c4presc(__VA_ARGS__) +# define _c4prscalar(msg, scalar, keep_newlines) \ + do { \ + if(_dbg_enabled()) { \ + _c4dbgpf_("{}: [{}]~~~", msg, scalar.len); \ + __c4presc((scalar), (keep_newlines)); \ + _c4dbgq("~~~"); \ + } \ + } while(0) + + +//----------------------------------------------------------------------------- +// implementation (only with RYML_DBG) + +//included above: +//#include + +#if defined(C4_MSVC) || defined(C4_MINGW) +//included above: +//#include +#elif (defined(__clang__) && defined(_MSC_VER)) || \ + defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) +//included above: +//#include +#else +#include +#endif + +#ifndef _C4_YML_ESCAPE_SCALAR_HPP_ +// amalgamate: removed include of +// c4/yml/escape_scalar.hpp +//#include "c4/yml/escape_scalar.hpp" +#if !defined(C4_YML_ESCAPE_SCALAR_HPP_) && !defined(_C4_YML_ESCAPE_SCALAR_HPP_) +#error "amalgamate: file c4/yml/escape_scalar.hpp must have been included at this point" +#endif /* C4_YML_ESCAPE_SCALAR_HPP_ */ + +#endif + +#ifndef _C4_DUMP_HPP_ +// amalgamate: removed include of +// c4/dump.hpp +//#include "c4/dump.hpp" +#if !defined(C4_DUMP_HPP_) && !defined(_C4_DUMP_HPP_) +#error "amalgamate: file c4/dump.hpp must have been included at this point" +#endif /* C4_DUMP_HPP_ */ + +#endif +#ifndef _C4_FORMAT_HPP_ +// amalgamate: removed include of +// c4/format.hpp +//#include "c4/format.hpp" +#if !defined(C4_FORMAT_HPP_) && !defined(_C4_FORMAT_HPP_) +#error "amalgamate: file c4/format.hpp must have been included at this point" +#endif /* C4_FORMAT_HPP_ */ + +#endif + + +C4_SUPPRESS_WARNING_GCC_WITH_PUSH("-Wattributes") + +namespace c4 { +namespace yml { +inline bool& _dbg_enabled() { static bool enabled = true; return enabled; } +inline C4_NO_INLINE void _dbg_set_enabled(bool yes) { _dbg_enabled() = yes; } +inline C4_NO_INLINE void _dbg_dumper(csubstr s) +{ + _RYML_ASSERT_BASIC(s.str || !s.len); + if(s.len) + fwrite(s.str, 1, s.len, stdout); +} +template +C4_NO_INLINE void _dbg_dump(DumpFn &&dumpfn, csubstr fmt, Args&& ...args) +{ + DumpResults results; + // try writing everything: + { + // buffer for converting individual arguments. it is defined + // in a child scope to free it in case the buffer is too small + // for any of the arguments. + char writebuf[RYML_LOGBUF_SIZE]; + results = format_dump_resume(std::forward(dumpfn), writebuf, fmt, std::forward(args)...); + } + // if any of the arguments failed to fit the buffer, allocate a + // larger buffer (with alloca(), up to a limit) and resume writing. + // + // results.bufsize is set to the size of the largest element + // serialized. Eg int(1) will require 1 byte. + if(C4_UNLIKELY(results.bufsize > RYML_LOGBUF_SIZE)) + { + const size_t bufsize = results.bufsize <= RYML_LOGBUF_SIZE_MAX ? results.bufsize : RYML_LOGBUF_SIZE_MAX; + #ifdef C4_MSVC + substr largerbuf = {static_cast(_alloca(bufsize)), bufsize}; + #else + substr largerbuf = {static_cast(alloca(bufsize)), bufsize}; + #endif + results = format_dump_resume(std::forward(dumpfn), results, largerbuf, fmt, std::forward(args)...); + } +} +template +C4_NO_INLINE void _dbg_printf(csubstr fmt, Args const& ...args) +{ + if(_dbg_enabled()) + _dbg_dump(&_dbg_dumper, fmt, args...); +} +inline C4_NO_INLINE void __c4presc(csubstr s, bool keep_newlines=false) +{ + if(_dbg_enabled()) + escape_scalar_fn(_dbg_dumper, s, keep_newlines); +} +inline C4_NO_INLINE void __c4presc(const char *s, size_t len, bool keep_newlines=false) +{ + __c4presc(csubstr(s, len), keep_newlines); +} + +struct _maybe_null_str +{ + csubstr s; + _maybe_null_str(csubstr s_) noexcept : s(s_) {} +}; +// LCOV_EXCL_START +inline C4_NO_INLINE size_t to_chars(substr buf, _maybe_null_str const& v) +{ + return c4::format(buf, v.s.str ? v.s : csubstr("(out of size)")); +} +// LCOV_EXCL_STOP +template +C4_NO_INLINE size_t dump(SinkPfn &&sinkfn, substr /*buf*/, _maybe_null_str const& v) +{ + std::forward(sinkfn)(v.s.str ? v.s : csubstr("(out of size)")); + return 0; // no space needed in the buffer +} + +/** print string as [{s.len}]~~~{s}~~~ */ +struct _prs +{ + csubstr subject; + size_t maxsize; + bool escape; + bool keep_newlines; ///< keep newlines when escaping + _prs(csubstr s) noexcept + : subject(s) + , maxsize(s.len) + , escape(false) + , keep_newlines(true) + { + } + explicit _prs(csubstr s, size_t maxsz, bool esc=false, bool newl=false) noexcept + : subject(s) + , maxsize(maxsz == npos ? s.len : maxsz) + , escape(esc) + , keep_newlines(newl) + { + } + explicit _prs(csubstr s, bool esc, bool newl=true) noexcept + : subject(s) + , maxsize(s.len) + , escape(esc) + , keep_newlines(newl) + { + } +}; +// LCOV_EXCL_START +inline C4_NO_INLINE size_t to_chars(substr buf, _prs const& v) +{ + csubstr s = v.subject; + if(C4_LIKELY(s.str != nullptr)) + { + csubstr ellipsis = ""; + if(v.maxsize < s.len) + { + s = s.first(v.maxsize); + ellipsis = "..."; + } + return !v.escape ? + c4::format(buf, "[{}]~~~{}{}~~~", v.subject.len, s, ellipsis) + : + c4::format(buf, "[{}]~~~{}{}~~~", v.subject.len, escaped_scalar(s, v.keep_newlines), ellipsis); + } + return c4::format(buf, "[{}](out of size)", v.subject.len); +} +// LCOV_EXCL_STOP +template +C4_NO_INLINE size_t dump(SinkPfn &&sinkfn, substr buf, _prs const& v) +{ + csubstr s = v.subject; + size_t sz = to_chars(buf, s.len); + if(sz <= buf.len) + { + if(C4_LIKELY(s.str != nullptr)) + { + csubstr ellipsis = ""; + if(v.maxsize < s.len) + { + s = s.first(v.maxsize); + ellipsis = "..."; + } + std::forward(sinkfn)("["); + std::forward(sinkfn)(buf.first(sz)); + std::forward(sinkfn)("]~~~"); + if(!v.escape) + { + std::forward(sinkfn)(s); + } + else + { + dump(std::forward(sinkfn), buf, escaped_scalar(s, v.keep_newlines)); + } + std::forward(sinkfn)(ellipsis); + std::forward(sinkfn)("~~~"); + } + else + { + std::forward(sinkfn)("["); + std::forward(sinkfn)(buf.first(sz)); + std::forward(sinkfn)("](out of size)"); + } + } + return sz; // we require this space in the buffer +} + +} // namespace yml +} // namespace c4 + +C4_SUPPRESS_WARNING_GCC_POP + +#endif // RYML_DBG + +#endif /* _C4_YML_DETAIL_DBGPRINT_HPP_ */ + + +// (end src/c4/yml/detail/dbgprint.hpp) + +#define C4_YML_EMIT_DEF_HPP_ + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/emit.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_YML_EMIT_HPP_ +#define _C4_YML_EMIT_HPP_ + +/** @file emit.hpp Utilities to emit YAML and JSON. */ + +#ifndef _C4_YML_WRITER_HPP_ +#include "./writer.hpp" +#endif + +#ifndef _C4_YML_TREE_HPP_ +#include "./tree.hpp" +#endif + +#ifndef _C4_YML_NODE_HPP_ +#include "./node.hpp" +#endif + + +C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wold-style-cast") +// NOLINTBEGIN(modernize-avoid-c-style-cast) + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +namespace c4 { +namespace yml { + +/** @addtogroup doc_emit + * + * @{ + */ + +// fwd declarations +template class Emitter; +template +using EmitterOStream = Emitter>; +using EmitterFile = Emitter; +using EmitterBuf = Emitter; + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** Specifies the type of content to emit */ +typedef enum { // NOLINT + EMIT_YAML = 0, ///< emit YAML + EMIT_JSON = 1, ///< emit JSON +} EmitType_e; + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** A lightweight object containing options to be used when emitting. */ +struct EmitOptions +{ +public: + + /** @cond dev */ + typedef enum : uint32_t { // NOLINT + INDENT_FLOW_ML = 1u << 0u, + FORCE_FLOW_SPC = 1u << 1u, + EMIT_NONROOT_KEY = 1u << 2u, + EMIT_NONROOT_DASH = 1u << 3u, + EMIT_NONROOT_MARKUP = EMIT_NONROOT_KEY|EMIT_NONROOT_DASH, + JSON_ERR_ON_STREAM = 1u << 4u, + JSON_ERR_ON_TAG = 1u << 5u, + JSON_ERR_ON_ANCHOR = 1u << 6u, + _JSON_ERR_MASK = JSON_ERR_ON_STREAM|JSON_ERR_ON_TAG|JSON_ERR_ON_ANCHOR, + DEFAULT_FLAGS = EMIT_NONROOT_KEY|INDENT_FLOW_ML, + } Flags_e; + /** @endcond */ + +private: + + EmitOptions& set_flags_(bool enabled, Flags_e f) + { + if(enabled) + m_flags |= f; + else + m_flags &= ~f; + return *this; + } + +public: + + /** @name flow customization + * + * @{ */ + + /** Indent the contents of @ref FLOW_ML1 and @ref FLOW_MLN + * containers. Enabled by default. */ + C4_ALWAYS_INLINE bool indent_flow_ml() const noexcept { return (m_flags & INDENT_FLOW_ML) != 0; } + EmitOptions& indent_flow_ml(bool enabled) noexcept { return set_flags_(enabled, INDENT_FLOW_ML); } + + /** Force everywhere a space after comma in flow mode, overriding + * the @ref FLOW_SPC status of individual containers. This only + * applies to @ref FLOW_ML1 or @ref FLOW_MLN containers. Disabled + * by default. */ + EmitOptions& force_flow_spc(bool enabled) noexcept { return set_flags_(enabled, FORCE_FLOW_SPC); } + C4_ALWAYS_INLINE bool force_flow_spc() const noexcept { return (m_flags & FORCE_FLOW_SPC) != 0; } + + /** Set max columns for the emitted YAML in @ref FLOW_MLN + * mode. This will make the emitted YAML wrap around when the line + * reaches max cols, but only in containers with @ref FLOW_MLN + * mode. Defaults to 80 columns. */ + EmitOptions& max_cols(id_type cols) noexcept { m_max_cols = cols; return *this; } + C4_ALWAYS_INLINE id_type max_cols() const noexcept { return m_max_cols; } + static constexpr const id_type max_cols_default = 80; + + /** @} */ + +public: + + /** @name option flags - control emission of non-root (nested) nodes + * + * @{ */ + + /** When emit starts on a node which is not the root node, emit + * the node key as well. This will make the resuling YAML a map + * with the node as its single element. Enabled by default. */ + EmitOptions& emit_nonroot_key(bool enabled) noexcept { return set_flags_(enabled, EMIT_NONROOT_KEY); } + C4_ALWAYS_INLINE bool emit_nonroot_key() const noexcept { return (m_flags & EMIT_NONROOT_KEY) != 0; } + + /** When emit starts on a node which is not the root node, emit a + * leading dash. This will make the resulting YAML a seq with the + * node as its single element. Disabled by default. */ + EmitOptions& emit_nonroot_dash(bool enabled) noexcept { return set_flags_(enabled, EMIT_NONROOT_DASH); } + C4_ALWAYS_INLINE bool emit_nonroot_dash() const noexcept { return (m_flags & EMIT_NONROOT_DASH) != 0; } + + /** @} */ + +public: + + /** @name option flags - json behavior + * + * @{ */ + + /** Whether to trigger an error when findind a stream + * in json mode. Disabled by default. */ + EmitOptions& json_err_on_stream(bool enabled) noexcept { return set_flags_(enabled, JSON_ERR_ON_STREAM); } + C4_ALWAYS_INLINE bool json_err_on_stream() const noexcept { return (m_flags & JSON_ERR_ON_STREAM) != 0; } + + /** Whether to trigger an error (or ignore the tag) when finding a tag + * in json mode. Disabled by default. */ + EmitOptions& json_err_on_tag(bool enabled) noexcept { return set_flags_(enabled, JSON_ERR_ON_TAG); } + C4_ALWAYS_INLINE bool json_err_on_tag() const noexcept { return (m_flags & JSON_ERR_ON_TAG) != 0; } + + /** Whether to trigger an error (or ignore the anchor) when finding an + * anchor in json mode. Disabled by default. */ + EmitOptions& json_err_on_anchor(bool enabled) noexcept { return set_flags_(enabled, JSON_ERR_ON_ANCHOR); } + C4_ALWAYS_INLINE bool json_err_on_anchor() const noexcept { return (m_flags & JSON_ERR_ON_ANCHOR) != 0; } + + /** @cond dev */ + RYML_DEPRECATED("use .json_err_on_{tag,anchor}()") C4_ALWAYS_INLINE Flags_e json_error_flags() const noexcept { return (Flags_e)(m_flags & _JSON_ERR_MASK); } + RYML_DEPRECATED("use .json_err_on_{tag,anchor}()") EmitOptions& json_error_flags(Flags_e d) noexcept { m_flags = (d & _JSON_ERR_MASK); return *this; } + /** @endcond */ + + /** @} */ + +public: + + /** @name maximum depth for the emitted tree + * + * This prevents stack overflows by making the emitter fail when + * the tree exceeds the maximum depth. + * + * @{ */ + C4_ALWAYS_INLINE id_type max_depth() const noexcept { return m_max_depth; } + EmitOptions& max_depth(id_type d) noexcept { m_max_depth = d; return *this; } + static constexpr const id_type max_depth_default = 64; + /** @} */ + +public: + + bool operator== (const EmitOptions& that) const noexcept + { + return m_max_depth == that.m_max_depth && + m_flags == that.m_flags; + } + +private: + + /** @cond dev */ + id_type m_max_depth{max_depth_default}; + id_type m_max_cols{max_cols_default}; + uint32_t m_flags{DEFAULT_FLAGS}; + /** @endcond */ +}; + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** A stateful emitter, for use with a writer such as @ref WriterBuf, + * @ref WriterFile, or @ref WriterOStream */ +template +class Emitter : public Writer +{ +public: + + /** Construct the emitter and its internal Writer state. + * + * @param opts @ref EmitOptions + * @param args arguments to be forwarded to the constructor of the writer. + */ + template + Emitter(EmitOptions const& opts, WriterArgs &&...args) + : Writer(std::forward(args)...) + , m_tree() + , m_opts(opts) + , m_col() + , m_depth() + , m_ilevel() + , m_pws() + , m_flow_pws() + {} + + /** Construct the emitter and its internal Writer state, using default emit options. + * @param args arguments to be forwarded to the constructor of the writer. + */ + template + Emitter(WriterArgs &&...args) + : Writer(std::forward(args)...) + , m_tree() + , m_opts() + , m_col() + , m_depth() + , m_ilevel() + , m_pws() + , m_flow_pws() + {} + +public: + + /** emit! + * + * When writing to a buffer, returns a substr of the emitted YAML. + * If the given buffer has insufficient space, the returned substr + * will be null and its size will be the needed space. Whatever + * the size of the buffer, it is guaranteed that no writes are + * done past its end. + * + * When writing to a file, the returned substr will be null, but its + * length will be set to the number of bytes written. + * + * @param type specify what to emit (YAML or JSON) + * @param t the tree to emit + * @param id the id of the node to emit + * @param error_on_excess when true, an error is raised when the + * output buffer is too small for the emitted YAML/JSON + * */ + substr emit_as(EmitType_e type, Tree const& t, id_type id, bool error_on_excess); + /** emit starting at the root node */ + substr emit_as(EmitType_e type, Tree const& t, bool error_on_excess=true) + { + if(t.empty()) + return {}; + return this->emit_as(type, t, t.root_id(), error_on_excess); + } + /** emit starting at the given node */ + substr emit_as(EmitType_e type, ConstNodeRef const& n, bool error_on_excess=true) + { + if(!n.readable()) + return {}; + return this->emit_as(type, *n.tree(), n.id(), error_on_excess); + } + +public: + + /** get the emit options for this object */ + EmitOptions const& options() const noexcept { return m_opts; } + /** set the emit options for this object */ + void options(EmitOptions opts) noexcept { m_opts = opts; } + + /** set the max depth for emitted trees (to prevent a stack overflow) */ + void max_depth(id_type max_depth) noexcept { m_opts.max_depth(max_depth); } + /** get the max depth for emitted trees (to prevent a stack overflow) */ + id_type max_depth() const noexcept { return m_opts.max_depth(); } + +private: // pending whitespace + + /// pending whitespace + typedef enum : uint32_t { _PWS_NONE = 0u, _PWS_SPACE = 1u, _PWS_NEWL = 2u } Pws_e; // NOLINT + + /// set pending whitespace, ignoring pending + C4_ALWAYS_INLINE void _pend_none() noexcept + { + m_pws = _PWS_NONE; + } + /// set pending whitespace, ignoring pending + C4_ALWAYS_INLINE void _pend_newl() noexcept + { + m_pws = _PWS_NEWL; + } + /// set pending whitespace, ignoring pending + C4_ALWAYS_INLINE void _pend_space() noexcept + { + m_pws = _PWS_SPACE; + } + /// write pending whitespace, and then set the next pending whitespace + C4_ALWAYS_INLINE void _write_pws_and_pend(Pws_e next=_PWS_NONE) noexcept + { + if(m_pws == _PWS_SPACE) + { + _write(' '); + } + else if(m_pws == _PWS_NEWL) + { + _newl(); + _indent(m_ilevel); + } + m_pws = next; + } + + /// specs for obtaining pending whitespace in flow mode + struct flow_pws + { + size_t max_cols = 0; // leave this member first to avoid padding + Pws_e pend_after_comma = _PWS_NONE; + bool active = false; + C4_ALWAYS_INLINE Pws_e next_pws(size_t col) const noexcept + { + return (active && col >= max_cols) ? _PWS_NEWL : pend_after_comma; + } + void start(NodeType ty, size_t max_cols_) noexcept; + void stop() noexcept { active = false; } + }; + + C4_NODISCARD bool _maybe_start_flow_pws_ml(id_type node) noexcept + { + _RYML_ASSERT_VISIT_(m_tree->callbacks(), m_tree->type(node) & (FLOW_ML1|FLOW_MLN), m_tree, node); + if(m_flow_pws.active) + return false; + NodeType ty = m_tree->type(node); + if(m_opts.force_flow_spc()) + ty |= FLOW_SPC; + m_flow_pws.start(ty, m_opts.max_cols()); + return true; + } + C4_NODISCARD flow_pws _setup_flow_pws_sl(id_type node) noexcept + { + flow_pws ret = {}; + if(m_flow_pws.active) + { + ret = m_flow_pws; + } + else + { + NodeType ty = m_tree->type(node); + if(m_opts.force_flow_spc()) + ty |= FLOW_SPC; + ret.start(ty, 0); + } + return ret; + } + +private: + + /** @cond dev */ + + void _emit_yaml(id_type id); + + void _visit_stream(id_type id); + void _visit_doc(id_type id); + void _visit_doc_val(id_type id); + void _visit_blck_container(id_type id); + void _visit_flow_container(id_type id); + + void _visit_flow_sl(id_type id); + void _visit_flow_sl_seq(id_type id); + void _visit_flow_sl_map(id_type id); + + void _visit_flow_ml(id_type id); + void _visit_flow_ml_seq(id_type id); + void _visit_flow_ml_map(id_type id); + + void _visit_blck(id_type id); + void _visit_blck_seq(id_type id); + void _visit_blck_map(id_type id); + + void _top_open_entry(id_type id); + void _top_close_entry(id_type id); + void _blck_seq_open_entry(id_type id); + void _blck_map_open_entry(id_type id); + void _blck_close_entry(id_type id); + void _blck_write_scalar(csubstr str, type_bits type); + + void _flow_seq_open_entry(id_type id); + void _flow_map_open_entry(id_type id); + void _flow_close_entry_sl(id_type id, id_type last_sibling, Pws_e pend_after); + void _flow_close_entry_ml(id_type id, id_type last_sibling, Pws_e pend_after); + void _flow_write_scalar(csubstr str, type_bits type); + +private: + + void _json_emit(id_type id); + void _write_scalar_literal(csubstr s, id_type level); + void _write_scalar_folded(csubstr s, id_type level); + void _write_scalar_squo(csubstr s, id_type level); + void _write_scalar_dquo(csubstr s, id_type level); + void _write_scalar_plain(csubstr s, id_type level); + + size_t _write_escaped_newlines(csubstr s, size_t i); + size_t _write_indented_block(csubstr s, size_t i, id_type level); + +private: + + void _json_visit_ml(id_type id, NodeType ty, id_type depth); + void _json_visit_sl(id_type id, NodeType ty, id_type depth); + bool _json_maybe_write_naninf(csubstr s); + void _json_writek(id_type id, NodeType ty); + void _json_writev(id_type id, NodeType ty); + void _json_write_scalar_dquo(csubstr s); + void _json_write_number(csubstr s); + +private: + + void _write_tag(csubstr tag) + { + if(!tag.begins_with('!')) + _write('!'); + _write(tag); + } + void _write_ref(csubstr ref) + { + if(ref != "<<") + { + if(!ref.begins_with('*')) + _write('*'); + _write(ref); + } + } + +private: + + C4_ALWAYS_INLINE void _indent(id_type level) + { + size_t num = (size_t)(2u * level); + this->Writer::_do_write(' ', num); + m_col += num; + } + + template + C4_ALWAYS_INLINE void _write(const char (&a)[N]) + { + this->Writer::_do_write(std::forward(a)); + m_col += N-1; + } + C4_ALWAYS_INLINE void _write(csubstr s) + { + this->Writer::_do_write(s); + m_col += s.len; + } + C4_ALWAYS_INLINE void _write(char c) + { + this->Writer::_do_write(c); + ++m_col; + } + C4_ALWAYS_INLINE void _write(char c, size_t num) + { + this->Writer::_do_write(c, num); + m_col += num; + } + + /// write a newline and reset the column + C4_ALWAYS_INLINE void _newl() + { + this->Writer::_do_write('\n'); + m_col = 0; + } + +private: + + Tree const* C4_RESTRICT m_tree; + EmitOptions m_opts; + size_t m_col; + id_type m_depth; + id_type m_ilevel; + Pws_e m_pws; + flow_pws m_flow_pws; + +private: + + C4_SUPPRESS_WARNING_GCC_PUSH + #if defined(__GNUC__) && (__GNUC__ < 5) && (!defined(__clang__)) + // g++-4.x has problems with the operand types here... + C4_SUPPRESS_WARNING_GCC_WITH_PUSH("-Wparentheses") + #endif + enum : type_bits { // NOLINT + _styles_block_key = KEY_LITERAL|KEY_FOLDED, + _styles_block_val = VAL_LITERAL|VAL_FOLDED, + _styles_block = ((type_bits)_styles_block_key) | ((type_bits)_styles_block_val), + _styles_flow_key = KEY_STYLE & (~((type_bits)_styles_block_key)), + _styles_flow_val = VAL_STYLE & (~((type_bits)_styles_block_val)), + _styles_flow = ((type_bits)_styles_flow_key) | ((type_bits)_styles_flow_val), + _styles_squo = KEY_SQUO|VAL_SQUO, + _styles_dquo = KEY_DQUO|VAL_DQUO, + _styles_plain = KEY_PLAIN|VAL_PLAIN, + _styles_literal = KEY_LITERAL|VAL_LITERAL, + _styles_folded = KEY_FOLDED|VAL_FOLDED, + }; + C4_SUPPRESS_WARNING_GCC_POP + + /** @endcond */ +}; + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** @defgroup doc_emit_to_file Emit to file + * + * @{ + */ + + +// emit from tree and node id ----------------------- + +/** (1) emit YAML to the given file, starting at the given node. A null + * file defaults to stdout. Return the number of bytes written. */ +inline size_t emit_yaml(Tree const& t, id_type id, EmitOptions const& opts, FILE *f) +{ + EmitterFile em(opts, f); + return em.emit_as(EMIT_YAML, t, id, /*error_on_excess*/true).len; +} +/** (2) like (1), but use default emit options */ +inline size_t emit_yaml(Tree const& t, id_type id, FILE *f) +{ + EmitterFile em(f); + return em.emit_as(EMIT_YAML, t, id, /*error_on_excess*/true).len; +} +/** (1) emit JSON to the given file, starting at the given node. A null + * file defaults to stdout. Return the number of bytes written. */ +inline size_t emit_json(Tree const& t, id_type id, EmitOptions const& opts, FILE *f) +{ + EmitterFile em(opts, f); + return em.emit_as(EMIT_JSON, t, id, /*error_on_excess*/true).len; +} +/** (2) like (1), but use default emit options */ +inline size_t emit_json(Tree const& t, id_type id, FILE *f) +{ + EmitterFile em(f); + return em.emit_as(EMIT_JSON, t, id, /*error_on_excess*/true).len; +} + + +// emit from root ------------------------- + +/** (1) emit YAML to the given file, starting at the root node. A null file defaults to stdout. + * Return the number of bytes written. */ +inline size_t emit_yaml(Tree const& t, EmitOptions const& opts, FILE *f=nullptr) +{ + EmitterFile em(opts, f); + return em.emit_as(EMIT_YAML, t, /*error_on_excess*/true).len; +} +/** (2) like (1), but use default emit options */ +inline size_t emit_yaml(Tree const& t, FILE *f=nullptr) +{ + EmitterFile em(f); + return em.emit_as(EMIT_YAML, t, /*error_on_excess*/true).len; +} +/** (1) emit JSON to the given file. A null file defaults to stdout. + * Return the number of bytes written. */ +inline size_t emit_json(Tree const& t, EmitOptions const& opts, FILE *f=nullptr) +{ + EmitterFile em(opts, f); + return em.emit_as(EMIT_JSON, t, /*error_on_excess*/true).len; +} +/** (2) like (1), but use default emit options */ +inline size_t emit_json(Tree const& t, FILE *f=nullptr) +{ + EmitterFile em(f); + return em.emit_as(EMIT_JSON, t, /*error_on_excess*/true).len; +} + + +// emit from ConstNodeRef ------------------------ + +/** (1) emit YAML to the given file. A null file defaults to stdout. + * Return the number of bytes written. */ +inline size_t emit_yaml(ConstNodeRef const& r, EmitOptions const& opts, FILE *f=nullptr) +{ + if(!r.readable()) + return {}; + EmitterFile em(opts, f); + return em.emit_as(EMIT_YAML, r, /*error_on_excess*/true).len; +} +/** (2) like (1), but use default emit options */ +inline size_t emit_yaml(ConstNodeRef const& r, FILE *f=nullptr) +{ + if(!r.readable()) + return {}; + EmitterFile em(f); + return em.emit_as(EMIT_YAML, r, /*error_on_excess*/true).len; +} +/** (1) emit JSON to the given file. A null file defaults to stdout. + * Return the number of bytes written. */ +inline size_t emit_json(ConstNodeRef const& r, EmitOptions const& opts, FILE *f=nullptr) +{ + if(!r.readable()) + return {}; + EmitterFile em(opts, f); + return em.emit_as(EMIT_JSON, r, /*error_on_excess*/true).len; +} +/** (2) like (1), but use default emit options */ +inline size_t emit_json(ConstNodeRef const& r, FILE *f=nullptr) +{ + if(!r.readable()) + return {}; + EmitterFile em(f); + return em.emit_as(EMIT_JSON, r, /*error_on_excess*/true).len; +} + +/** @} */ + + +//----------------------------------------------------------------------------- + +/** @defgroup doc_emit_to_ostream Emit to an STL-like ostream + * + * @{ + */ + +/** emit YAML to an STL-like ostream */ +template +inline OStream& operator<< (OStream& s, Tree const& t) +{ + EmitterOStream em(s); + em.emit_as(EMIT_YAML, t); + return s; +} + +/** emit YAML to an STL-like ostream + * @overload */ +template +inline OStream& operator<< (OStream& s, ConstNodeRef const& n) +{ + if(!n.readable()) + return s; + EmitterOStream em(s); + em.emit_as(EMIT_YAML, n); + return s; +} + +/** mark a tree or node to be emitted as yaml when using @ref + * operator<<, with options. For example: + * + * ```cpp + * Tree t = parse_in_arena("{foo: bar}"); + * std::cout << t; // emits YAML + * std::cout << as_yaml(t); // emits YAML, same as above + * std::cout << as_yaml(t, EmitOptions().max_depth(10)); // emits JSON with a max tree depth + * ``` + * + * @see @ref operator<< */ +struct as_json +{ + Tree const* tree; + id_type node; + EmitOptions options; + as_json(Tree const& t, EmitOptions const& opts={}) : tree(&t), node(t.empty() ? NONE : t.root_id()), options(opts) {} + as_json(Tree const& t, id_type id, EmitOptions const& opts={}) : tree(&t), node(id), options(opts) {} + as_json(ConstNodeRef const& n, EmitOptions const& opts={}) : tree(n.tree()), node(n.id()), options(opts) {} +}; + +/** mark a tree or node to be emitted as yaml when using @ref + * operator<< . For example: + * + * ```cpp + * Tree t = parse_in_arena("{foo: bar}"); + * std::cout << t; // emits YAML + * std::cout << as_json(t); // emits JSON + * std::cout << as_json(t, EmitOptions().max_depth(10)); // emits JSON with a max tree depth + * ``` + * + * @see @ref operator<< */ +struct as_yaml +{ + Tree const* tree; + id_type node; + EmitOptions options; + as_yaml(Tree const& t, EmitOptions const& opts={}) : tree(&t), node(t.empty() ? NONE : t.root_id()), options(opts) {} + as_yaml(Tree const& t, id_type id, EmitOptions const& opts={}) : tree(&t), node(id), options(opts) {} + as_yaml(ConstNodeRef const& n, EmitOptions const& opts={}) : tree(n.tree()), node(n.id()), options(opts) {} +}; + +/** emit json to an STL-like stream */ +template +inline OStream& operator<< (OStream& s, as_json const& j) +{ + if(!j.tree || j.tree->empty()) + return s; + EmitterOStream em(j.options, s); + em.emit_as(EMIT_JSON, *j.tree, j.node != NONE ? j.node : j.tree->root_id(), true); + return s; +} + +/** emit yaml to an STL-like stream */ +template +inline OStream& operator<< (OStream& s, as_yaml const& y) +{ + if(!y.tree || y.tree->empty()) + return s; + EmitterOStream em(y.options, s); + em.emit_as(EMIT_YAML, *y.tree, y.node != NONE ? y.node : y.tree->root_id(), true); + return s; +} + +/** @} */ + + +//----------------------------------------------------------------------------- + +/** @defgroup doc_emit_to_buffer Emit to memory buffer + * + * @{ + */ + +// emit from tree and node id ----------------------- + +/** (1) emit YAML to the given buffer. Return a substr trimmed to the emitted YAML. + * @param t the tree to emit. + * @param id the node where to start emitting. + * @param opts emit options. + * @param buf the output buffer. + * @param error_on_excess Raise an error if the space in the buffer is insufficient. + * @return a substr trimmed to the result in the output buffer. If the buffer is + * insufficient (when error_on_excess is false), the string pointer of the + * result will be set to null, and the length will report the required buffer size. */ +inline substr emit_yaml(Tree const& t, id_type id, EmitOptions const& opts, substr buf, bool error_on_excess=true) +{ + EmitterBuf em(opts, buf); + return em.emit_as(EMIT_YAML, t, id, error_on_excess); +} +/** (2) like (1), but use default emit options */ +inline substr emit_yaml(Tree const& t, id_type id, substr buf, bool error_on_excess=true) +{ + EmitterBuf em(buf); + return em.emit_as(EMIT_YAML, t, id, error_on_excess); +} +/** (1) emit JSON to the given buffer. Return a substr trimmed to the emitted JSON. + * @param t the tree to emit. + * @param id the node where to start emitting. + * @param opts emit options. + * @param buf the output buffer. + * @param error_on_excess Raise an error if the space in the buffer is insufficient. + * @return a substr trimmed to the result in the output buffer. If the buffer is + * insufficient (when error_on_excess is false), the string pointer of the + * result will be set to null, and the length will report the required buffer size. */ +inline substr emit_json(Tree const& t, id_type id, EmitOptions const& opts, substr buf, bool error_on_excess=true) +{ + EmitterBuf em(opts, buf); + return em.emit_as(EMIT_JSON, t, id, error_on_excess); +} +/** (2) like (1), but use default emit options */ +inline substr emit_json(Tree const& t, id_type id, substr buf, bool error_on_excess=true) +{ + EmitterBuf em(buf); + return em.emit_as(EMIT_JSON, t, id, error_on_excess); +} + + +// emit from root ------------------------- + +/** (1) emit YAML to the given buffer. Return a substr trimmed to the emitted YAML. + * @param t the tree; will be emitted from the root node. + * @param opts emit options. + * @param buf the output buffer. + * @param error_on_excess Raise an error if the space in the buffer is insufficient. + * @return a substr trimmed to the result in the output buffer. If the buffer is + * insufficient (when error_on_excess is false), the string pointer of the + * result will be set to null, and the length will report the required buffer size. */ +inline substr emit_yaml(Tree const& t, EmitOptions const& opts, substr buf, bool error_on_excess=true) +{ + EmitterBuf em(opts, buf); + return em.emit_as(EMIT_YAML, t, error_on_excess); +} +/** (2) like (1), but use default emit options */ +inline substr emit_yaml(Tree const& t, substr buf, bool error_on_excess=true) +{ + EmitterBuf em(buf); + return em.emit_as(EMIT_YAML, t, error_on_excess); +} +/** (1) emit JSON to the given buffer. Return a substr trimmed to the emitted JSON. + * @param t the tree; will be emitted from the root node. + * @param opts emit options. + * @param buf the output buffer. + * @param error_on_excess Raise an error if the space in the buffer is insufficient. + * @return a substr trimmed to the result in the output buffer. If the buffer is + * insufficient (when error_on_excess is false), the string pointer of the + * result will be set to null, and the length will report the required buffer size. */ +inline substr emit_json(Tree const& t, EmitOptions const& opts, substr buf, bool error_on_excess=true) +{ + EmitterBuf em(opts, buf); + return em.emit_as(EMIT_JSON, t, error_on_excess); +} +/** (2) like (1), but use default emit options */ +inline substr emit_json(Tree const& t, substr buf, bool error_on_excess=true) +{ + EmitterBuf em(buf); + return em.emit_as(EMIT_JSON, t, error_on_excess); +} + + +// emit from ConstNodeRef ------------------------ + +/** (1) emit YAML to the given buffer. Return a substr trimmed to the emitted YAML. + * @param r the starting node. + * @param buf the output buffer. + * @param opts emit options. + * @param error_on_excess Raise an error if the space in the buffer is insufficient. + * @return a substr trimmed to the result in the output buffer. If the buffer is + * insufficient (when error_on_excess is false), the string pointer of the + * result will be set to null, and the length will report the required buffer size. */ +inline substr emit_yaml(ConstNodeRef const& r, EmitOptions const& opts, substr buf, bool error_on_excess=true) +{ + if(!r.readable()) + return {}; + EmitterBuf em(opts, buf); + return em.emit_as(EMIT_YAML, r, error_on_excess); +} +/** (2) like (1), but use default emit options */ +inline substr emit_yaml(ConstNodeRef const& r, substr buf, bool error_on_excess=true) +{ + if(!r.readable()) + return {}; + EmitterBuf em(buf); + return em.emit_as(EMIT_YAML, r, error_on_excess); +} +/** (1) emit JSON to the given buffer. Return a substr trimmed to the emitted JSON. + * @param r the starting node. + * @param buf the output buffer. + * @param opts emit options. + * @param error_on_excess Raise an error if the space in the buffer is insufficient. + * @return a substr trimmed to the result in the output buffer. If the buffer is + * insufficient (when error_on_excess is false), the string pointer of the + * result will be set to null, and the length will report the required buffer size. */ +inline substr emit_json(ConstNodeRef const& r, EmitOptions const& opts, substr buf, bool error_on_excess=true) +{ + if(!r.readable()) + return {}; + EmitterBuf em(opts, buf); + return em.emit_as(EMIT_JSON, r, error_on_excess); +} +/** (2) like (1), but use default emit options */ +inline substr emit_json(ConstNodeRef const& r, substr buf, bool error_on_excess=true) +{ + if(!r.readable()) + return {}; + EmitterBuf em(buf); + return em.emit_as(EMIT_JSON, r, error_on_excess); +} + + +//----------------------------------------------------------------------------- + +/** @defgroup doc_emit_to_container Emit to resizeable container + * + * @{ + */ + +// emit from tree and node id --------------------------- + +/** (1) emit+resize: emit YAML to the given `std::string`/`std::vector`-like + * container, resizing it as needed to fit the emitted YAML. If @p append is + * set to true, the emitted YAML is appended at the end of the container. + * + * @return a substr trimmed to the emitted YAML (excluding the initial contents, when appending) */ +template +substr emitrs_yaml(Tree const& t, id_type id, EmitOptions const& opts, CharOwningContainer * cont, bool append=false) +{ + size_t startpos = append ? cont->size() : 0u; + cont->resize(cont->capacity()); // otherwise the first emit would be certain to fail + substr buf = to_substr(*cont).sub(startpos); + substr ret = emit_yaml(t, id, opts, buf, /*error_on_excess*/false); + if(ret.str == nullptr && ret.len > 0) + { + cont->resize(startpos + ret.len); + buf = to_substr(*cont).sub(startpos); + ret = emit_yaml(t, id, opts, buf, /*error_on_excess*/true); + } + else + { + cont->resize(startpos + ret.len); + } + return ret; +} +/** (2) like (1), but use default emit options */ +template +substr emitrs_yaml(Tree const& t, id_type id, CharOwningContainer * cont, bool append=false) +{ + return emitrs_yaml(t, id, EmitOptions{}, cont, append); +} +/** (1) emit+resize: emit JSON to the given `std::string`/`std::vector`-like + * container, resizing it as needed to fit the emitted JSON. If @p append is + * set to true, the emitted YAML is appended at the end of the container. + * + * @return a substr trimmed to the emitted JSON (excluding the initial contents, when appending) */ +template +substr emitrs_json(Tree const& t, id_type id, EmitOptions const& opts, CharOwningContainer * cont, bool append=false) +{ + const size_t startpos = append ? cont->size() : 0u; + cont->resize(cont->capacity()); // otherwise the first emit would be certain to fail + substr buf = to_substr(*cont).sub(startpos); + EmitterBuf em(opts, buf); + substr ret = emit_json(t, id, opts, buf, /*error_on_excess*/false); + if(ret.str == nullptr && ret.len > 0) + { + cont->resize(startpos + ret.len); + buf = to_substr(*cont).sub(startpos); + ret = emit_json(t, id, opts, buf, /*error_on_excess*/true); + } + else + { + cont->resize(startpos + ret.len); + } + return ret; +} +/** (2) like (1), but use default emit options */ +template +substr emitrs_json(Tree const& t, id_type id, CharOwningContainer * cont, bool append=false) +{ + return emitrs_json(t, id, EmitOptions{}, cont, append); +} + + +/** (3) emit+resize: YAML to a newly-created `std::string`/`std::vector`-like container. */ +template +CharOwningContainer emitrs_yaml(Tree const& t, id_type id, EmitOptions const& opts={}) +{ + CharOwningContainer c; + emitrs_yaml(t, id, opts, &c); + return c; +} +/** (3) emit+resize: JSON to a newly-created `std::string`/`std::vector`-like container. */ +template +CharOwningContainer emitrs_json(Tree const& t, id_type id, EmitOptions const& opts={}) +{ + CharOwningContainer c; + emitrs_json(t, id, opts, &c); + return c; +} + + +// emit from root ------------------------- + +/** (1) emit+resize: YAML to the given `std::string`/`std::vector`-like + * container, resizing it as needed to fit the emitted YAML. + * @return a substr trimmed to the new emitted contents. */ +template +substr emitrs_yaml(Tree const& t, EmitOptions const& opts, CharOwningContainer * cont, bool append=false) +{ + if(t.empty()) + return {}; + return emitrs_yaml(t, t.root_id(), opts, cont, append); +} +/** (2) like (1), but use default emit options */ +template +substr emitrs_yaml(Tree const& t, CharOwningContainer * cont, bool append=false) +{ + if(t.empty()) + return {}; + return emitrs_yaml(t, t.root_id(), EmitOptions{}, cont, append); +} +/** (1) emit+resize: JSON to the given `std::string`/`std::vector`-like + * container, resizing it as needed to fit the emitted JSON. + * @return a substr trimmed to the new emitted contents. */ +template +substr emitrs_json(Tree const& t, EmitOptions const& opts, CharOwningContainer * cont, bool append=false) +{ + if(t.empty()) + return {}; + return emitrs_json(t, t.root_id(), opts, cont, append); +} +/** (2) like (1), but use default emit options */ +template +substr emitrs_json(Tree const& t, CharOwningContainer * cont, bool append=false) +{ + if(t.empty()) + return {}; + return emitrs_json(t, t.root_id(), EmitOptions{}, cont, append); +} + + +/** (3) emit+resize: YAML to a newly-created `std::string`/`std::vector`-like container. */ +template +CharOwningContainer emitrs_yaml(Tree const& t, EmitOptions const& opts={}) +{ + CharOwningContainer c; + if(t.empty()) + return c; + emitrs_yaml(t, t.root_id(), opts, &c); + return c; +} +/** (3) emit+resize: JSON to a newly-created `std::string`/`std::vector`-like container. */ +template +CharOwningContainer emitrs_json(Tree const& t, EmitOptions const& opts={}) +{ + CharOwningContainer c; + if(t.empty()) + return c; + emitrs_json(t, t.root_id(), opts, &c); + return c; +} + + +// emit from ConstNodeRef ------------------------ + + +/** (1) emit+resize: YAML to the given `std::string`/`std::vector`-like container, + * resizing it as needed to fit the emitted YAML. + * @return a substr trimmed to the new emitted contents */ +template +substr emitrs_yaml(ConstNodeRef const& n, EmitOptions const& opts, CharOwningContainer * cont, bool append=false) +{ + if(!n.readable()) + return {}; + return emitrs_yaml(*n.tree(), n.id(), opts, cont, append); +} +/** (2) like (1), but use default emit options */ +template +substr emitrs_yaml(ConstNodeRef const& n, CharOwningContainer * cont, bool append=false) +{ + if(!n.readable()) + return {}; + return emitrs_yaml(*n.tree(), n.id(), EmitOptions{}, cont, append); +} +/** (1) emit+resize: JSON to the given `std::string`/`std::vector`-like container, + * resizing it as needed to fit the emitted JSON. + * @return a substr trimmed to the new emitted contents */ +template +substr emitrs_json(ConstNodeRef const& n, EmitOptions const& opts, CharOwningContainer * cont, bool append=false) +{ + if(!n.readable()) + return {}; + return emitrs_json(*n.tree(), n.id(), opts, cont, append); +} +/** (2) like (1), but use default emit options */ +template +substr emitrs_json(ConstNodeRef const& n, CharOwningContainer * cont, bool append=false) +{ + if(!n.readable()) + return {}; + return emitrs_json(*n.tree(), n.id(), EmitOptions{}, cont, append); +} + + +/** (3) emit+resize: YAML to a newly-created `std::string`/`std::vector`-like container. */ +template +CharOwningContainer emitrs_yaml(ConstNodeRef const& n, EmitOptions const& opts={}) +{ + if(!n.readable()) + return {}; + CharOwningContainer c; + emitrs_yaml(*n.tree(), n.id(), opts, &c); + return c; +} +/** (3) emit+resize: JSON to a newly-created `std::string`/`std::vector`-like container. */ +template +CharOwningContainer emitrs_json(ConstNodeRef const& n, EmitOptions const& opts={}) +{ + if(!n.readable()) + return {}; + CharOwningContainer c; + emitrs_json(*n.tree(), n.id(), opts, &c); + return c; +} + + +/** @} */ + + +//----------------------------------------------------------------------------- + +/** @cond dev */ + +#define RYML_DEPRECATE_EMIT \ + RYML_DEPRECATED("use emit_yaml() instead. " \ + "See https://github.com/biojppm/rapidyaml/issues/120") +#define RYML_DEPRECATE_EMITRS \ + RYML_DEPRECATED("use emitrs_yaml() instead. " \ + "See https://github.com/biojppm/rapidyaml/issues/120") + +// workaround for Qt emit which is a macro; +// see https://github.com/biojppm/rapidyaml/issues/120. +// emit is defined in qobjectdefs.h (as an empty define). +#ifdef emit +#define RYML_TMP_EMIT_ +#undef emit +#endif + +RYML_DEPRECATE_EMIT inline size_t emit(Tree const& t, id_type id, FILE *f) +{ + return emit_yaml(t, id, f); +} +RYML_DEPRECATE_EMIT inline size_t emit(Tree const& t, FILE *f=nullptr) +{ + return emit_yaml(t, f); +} +RYML_DEPRECATE_EMIT inline size_t emit(ConstNodeRef const& r, FILE *f=nullptr) +{ + return emit_yaml(r, f); +} + +RYML_DEPRECATE_EMIT inline substr emit(Tree const& t, id_type id, substr buf, bool error_on_excess=true) +{ + return emit_yaml(t, id, buf, error_on_excess); +} +RYML_DEPRECATE_EMIT inline substr emit(Tree const& t, substr buf, bool error_on_excess=true) +{ + return emit_yaml(t, buf, error_on_excess); +} +RYML_DEPRECATE_EMIT inline substr emit(ConstNodeRef const& r, substr buf, bool error_on_excess=true) +{ + return emit_yaml(r, buf, error_on_excess); +} + +#ifdef RYML_TMP_EMIT_ +#define emit +#undef RYML_TMP_EMIT_ +#endif + +template +RYML_DEPRECATE_EMITRS substr emitrs(Tree const& t, id_type id, CharOwningContainer * cont) +{ + return emitrs_yaml(t, id, cont); +} +template +RYML_DEPRECATE_EMITRS CharOwningContainer emitrs(Tree const& t, id_type id) +{ + return emitrs_yaml(t, id); +} +template +RYML_DEPRECATE_EMITRS substr emitrs(Tree const& t, CharOwningContainer * cont) +{ + return emitrs_yaml(t, cont); +} +template +RYML_DEPRECATE_EMITRS CharOwningContainer emitrs(Tree const& t) +{ + return emitrs_yaml(t); +} +template +RYML_DEPRECATE_EMITRS substr emitrs(ConstNodeRef const& n, CharOwningContainer * cont) +{ + return emitrs_yaml(n, cont); +} +template +RYML_DEPRECATE_EMITRS CharOwningContainer emitrs(ConstNodeRef const& n) +{ + return emitrs_yaml(n); +} + +/** @endcond */ + + +} // namespace yml +} // namespace c4 + +// NOLINTEND(modernize-avoid-c-style-cast) +C4_SUPPRESS_WARNING_GCC_CLANG_POP + +#undef RYML_DEPRECATE_EMIT +#undef RYML_DEPRECATE_EMITRS + +// amalgamate: removed include of +// c4/yml/emit.def.hpp +//#include "c4/yml/emit.def.hpp" // NOLINT +#if !defined(C4_YML_EMIT_DEF_HPP_) && !defined(_C4_YML_EMIT_DEF_HPP_) +#error "amalgamate: file c4/yml/emit.def.hpp must have been included at this point" +#endif /* C4_YML_EMIT_DEF_HPP_ */ + + +#endif /* _C4_YML_EMIT_HPP_ */ + + +// (end src/c4/yml/emit.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/emit.def.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_YML_EMIT_DEF_HPP_ +#define _C4_YML_EMIT_DEF_HPP_ + +#ifndef _C4_YML_EMIT_HPP_ +// amalgamate: removed include of +// c4/yml/emit.hpp +//#include "c4/yml/emit.hpp" +#if !defined(C4_YML_EMIT_HPP_) && !defined(_C4_YML_EMIT_HPP_) +#error "amalgamate: file c4/yml/emit.hpp must have been included at this point" +#endif /* C4_YML_EMIT_HPP_ */ + +#endif + +/** @file emit.def.hpp Definitions for emit functions. */ +#ifndef _C4_YML_DETAIL_DBGPRINT_HPP_ +// amalgamate: removed include of +// c4/yml/detail/dbgprint.hpp +//#include "c4/yml/detail/dbgprint.hpp" +#if !defined(C4_YML_DETAIL_DBGPRINT_HPP_) && !defined(_C4_YML_DETAIL_DBGPRINT_HPP_) +#error "amalgamate: file c4/yml/detail/dbgprint.hpp must have been included at this point" +#endif /* C4_YML_DETAIL_DBGPRINT_HPP_ */ + +#endif + +C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wold-style-cast") +// NOLINTBEGIN(modernize-avoid-c-style-cast) + +namespace c4 { +namespace yml { + +template +substr Emitter::emit_as(EmitType_e type, Tree const& tree, id_type id, bool error_on_excess) +{ + if(tree.empty()) + { + _RYML_ASSERT_BASIC_(tree.callbacks(), id == NONE); + return {}; + } + if(id == NONE) + id = tree.root_id(); + _RYML_CHECK_VISIT_(tree.callbacks(), id < tree.capacity(), &tree, id); + m_tree = &tree; + m_col = 0; + m_depth = 0; + m_ilevel = 0; + m_pws = _PWS_NONE; + m_flow_pws = {}; + if(type == EMIT_YAML) + _emit_yaml(id); + else if(type == EMIT_JSON) + _json_emit(id); + else + _RYML_ERR_BASIC_(m_tree->callbacks(), "unknown emit type"); // LCOV_EXCL_LINE + m_tree = nullptr; + return this->Writer::_get(error_on_excess); +} + +/** @cond dev */ + +//----------------------------------------------------------------------------- + + +// The startup logic is made complicated from it having to accept +// initial non-root nodes, and having to deal with tricky tokens like +// doc separators, anchors, tags, optional keys or dashes, and +// comments. +// +// This function kickstarts the tree descent by handling all the +// initial and final logic at the top-level scope, thus avoiding +// top-level kickstart branches in the recursive descending code +// (which should be oblivious of such logic). This makes the recursive +// descending code a lot simpler. +template +void Emitter::_emit_yaml(id_type id) +{ + const NodeType ty = m_tree->type(id); + + // emit leading tokens, such as keys or comments + const bool has_parent = !m_tree->is_root(id); + const bool emit_key = has_parent && ty.has_key() && m_opts.emit_nonroot_key(); + const bool emit_dash = has_parent && !ty.has_key() && !ty.is_doc() && m_opts.emit_nonroot_dash(); + _RYML_ASSERT_VISIT_(m_tree->m_callbacks, !(emit_key && emit_dash), m_tree, id); + + // emit opening tokens (such as tags, anchors or comments) + if(emit_key) + { + _blck_map_open_entry(id); + ++m_ilevel; + } + else if(emit_dash) + { + _blck_seq_open_entry(id); + ++m_ilevel; + } + else + { + _top_open_entry(id); + } + + // emit the payload + if(ty.is_stream()) + { + _RYML_ASSERT_VISIT_(m_tree->m_callbacks, m_ilevel == 0, m_tree, id); + _visit_stream(id); + } + else if(ty.is_doc()) + { + _RYML_ASSERT_VISIT_(m_tree->m_callbacks, m_ilevel == 0, m_tree, id); + _visit_doc(id); + } + else if(ty.is_container()) + { + _visit_blck_container(id); + } + else if(ty.has_val()) + { + _visit_doc_val(id); + } + + // emit closing tokens (such as comments) + if(emit_key) + { + --m_ilevel; + _blck_close_entry(id); + } + else if(emit_dash) + { + --m_ilevel; + _blck_close_entry(id); + } + else + { + _top_close_entry(id); + } + + if(ty.is_flow_mlx()) + { + _newl(); + } + else if(m_tree->is_root(id) + || emit_dash || emit_key + || !ty.is_val() + || !ty.is_val_plain()) + { + _write_pws_and_pend(_PWS_NONE); + } +} + + +//----------------------------------------------------------------------------- + +template +void Emitter::_visit_stream(id_type id) +{ + auto write_tag_directives = [this](const id_type next_node){ + const id_type doc = m_tree->ancestor_doc(next_node); + const TagDirectiveRange tagds = m_tree->m_tag_directives.lookup_range(doc); + if(tagds.e != tagds.b) + { + const id_type parent = m_tree->parent(next_node); + if(next_node != m_tree->first_child(parent)) + { + _write_pws_and_pend(_PWS_NEWL); + _write("..."); + } + } + for(TagDirective const& td : tagds) + { + _write_pws_and_pend(_PWS_NONE); + _write("%TAG "); + _write(td.handle); + _write(' '); + _write(td.prefix); + _pend_newl(); + } + }; + const id_type first_child = m_tree->first_child(id); + if(first_child != NONE) + write_tag_directives(first_child); + ++m_depth; + for(id_type child = first_child; child != NONE; child = m_tree->next_sibling(child)) + { + m_ilevel = 0; + _write_pws_and_pend(_PWS_NONE); + _top_open_entry(child); + _visit_doc(child); + _top_close_entry(child); + if(m_tree->is_val(child)) + { + if(m_tree->type(child) & VALNIL) + _pend_newl(); + } + else if(m_tree->is_container(child)) + { + if(m_tree->is_flow(child)) + _pend_newl(); + } + if(m_tree->next_sibling(child) != NONE) + { + write_tag_directives(m_tree->next_sibling(child)); + } + } + --m_depth; +} + + +//----------------------------------------------------------------------------- + +template +void Emitter::_visit_blck_container(id_type id) +{ + NodeType ty = m_tree->type(id); + if(!(ty & CONTAINER_STYLE)) + ty |= (m_tree->empty(id) ? FLOW_SL : BLOCK); + _write_pws_and_pend(_PWS_NONE); + if(ty.is_flow_sl()) + _visit_flow_sl(id); + else if(ty.is_flow_mlx()) + _visit_flow_ml(id); + else + _visit_blck(id); +} + +template +void Emitter::_visit_flow_container(id_type id) +{ + NodeType ty = m_tree->type(id); + if(!(ty & CONTAINER_STYLE)) + ty |= FLOW_SL; + _write_pws_and_pend(_PWS_NONE); + if(ty.is_flow_mlx()) + _visit_flow_ml(id); + else // if(ty.is_flow_sl()) + _visit_flow_sl(id); +} + + +//----------------------------------------------------------------------------- + +template +void Emitter::_visit_doc_val(id_type id) +{ + // some plain scalars such as '...' and '---' must not + // appear at 0-indentation + NodeType ty = m_tree->type(id); + const csubstr val = m_tree->val(id); + type_bits val_style = ty & VAL_STYLE; + const bool is_ambiguous = ((ty & VAL_PLAIN) || !val_style) + && (val.begins_with("...") || val.begins_with("---")); + if(is_ambiguous) + { + ++m_ilevel; + if(m_pws != _PWS_NONE) + _pend_newl(); + else + _indent(m_ilevel); + } + _write_pws_and_pend(_PWS_NONE); + if(m_tree->is_val_ref(id)) + { + _write_ref(val); + } + else + { + if(!val_style) + val_style = scalar_style_choose_block(val); + _blck_write_scalar(val, val_style); + } + if(is_ambiguous) + { + --m_ilevel; + } +} + + +//----------------------------------------------------------------------------- + +template +void Emitter::_visit_doc(id_type id) +{ + const NodeType ty = m_tree->type(id); + _RYML_ASSERT_VISIT_(m_tree->m_callbacks, ty.is_doc(), m_tree, id); + _RYML_ASSERT_VISIT_(m_tree->m_callbacks, !ty.has_key(), m_tree, id); + if(ty.is_container()) // this is more frequent + { + _visit_blck_container(id); + } + else if(ty.is_val()) + { + _visit_doc_val(id); + } +} + + +//----------------------------------------------------------------------------- + +// to be called only at top level +template +void Emitter::_top_open_entry(id_type node) +{ + NodeType ty = m_tree->type(node); + if(ty.is_doc() && !m_tree->is_root(node)) + { + _write("---"); + _pend_space(); + } + if(ty.has_val_anchor()) + { + _write_pws_and_pend(_PWS_SPACE); + _write('&'); + _write(m_tree->val_anchor(node)); + } + if(ty.has_val_tag()) + { + _write_pws_and_pend(_PWS_SPACE); + _write_tag(m_tree->val_tag(node)); + } + if(m_pws == _PWS_SPACE) + { + if(ty.has_val()) + { + if(ty.is_val_plain() && !m_tree->val(node).len) + _pend_none(); + } + else + { + _RYML_ASSERT_VISIT_(m_tree->callbacks(), ty.is_container(), m_tree, node); + if(!ty.is_flow()) + _pend_newl(); + } + } +} + + +//----------------------------------------------------------------------------- + +template +void Emitter::_top_close_entry(id_type node) +{ + if(m_tree->is_val(node) && !(m_tree->type(node) & VALNIL)) + { + _pend_newl(); + } +} + + +//----------------------------------------------------------------------------- + +template +void Emitter::_flow_seq_open_entry(id_type node) +{ + NodeType ty = m_tree->type(node); + _write_pws_and_pend(_PWS_NONE); + if(ty & VALANCH) + { + _write_pws_and_pend(_PWS_SPACE); + _write('&'); + _write(m_tree->val_anchor(node)); + } + if(ty & VALTAG) + { + _write_pws_and_pend(_PWS_SPACE); + _write_tag(m_tree->val_tag(node)); + } +} + + +//----------------------------------------------------------------------------- + +template +void Emitter::_flow_map_open_entry(id_type node) +{ + NodeType ty = m_tree->type(node); + _write_pws_and_pend(_PWS_NONE); + _RYML_ASSERT_VISIT_(m_tree->callbacks(), ty.has_key(), m_tree, node); + if(ty & KEYANCH) + { + _write_pws_and_pend(_PWS_SPACE); + _write("&"); + _write(m_tree->key_anchor(node)); + } + if(ty & KEYTAG) + { + _write_pws_and_pend(_PWS_SPACE); + _write_tag(m_tree->key_tag(node)); + } + if(ty & KEYREF) + { + _write_pws_and_pend(_PWS_SPACE); + _write_ref(m_tree->key(node)); + } + else + { + _write_pws_and_pend(_PWS_NONE); + csubstr key = m_tree->key(node); + if(!(ty & (NodeType_e)_styles_flow_key)) + ty |= scalar_style_choose_flow(key) & (NodeType_e)_styles_flow_key; + _flow_write_scalar(key, ty & (NodeType_e)_styles_flow_key); + } + _write_pws_and_pend(_PWS_SPACE); + _write(':'); + if(ty & VALANCH) + { + _write_pws_and_pend(_PWS_SPACE); + _write('&'); + _write(m_tree->val_anchor(node)); + } + if(ty & VALTAG) + { + _write_pws_and_pend(_PWS_SPACE); + _write_tag(m_tree->val_tag(node)); + } +} + + +//----------------------------------------------------------------------------- + +template +void Emitter::flow_pws::start(NodeType ty, size_t max_cols_) noexcept +{ + max_cols = 0; + pend_after_comma = ty & FLOW_SPC ? _PWS_SPACE : _PWS_NONE; + if(ty & FLOW_MLN) + { + max_cols_ = max_cols_ >= 2 ? max_cols_ : 2; + // subtract 1 for the comma, and maybe the space from pend_after_comma + max_cols = max_cols_ - 1 - pend_after_comma; + // line above only works if: + static_assert((size_t)_PWS_NONE == 0 && (size_t)_PWS_SPACE == 1, "invalid assumptions"); + active = true; + } + else if(ty & FLOW_ML1) + { + pend_after_comma = _PWS_NEWL; + } +} + +template +void Emitter::_flow_close_entry_sl(id_type node, id_type last_sibling, Pws_e pend_after) +{ + if(node != last_sibling) + { + _write_pws_and_pend(pend_after); + _write(','); + } +} + +template +void Emitter::_flow_close_entry_ml(id_type node, id_type last_sibling, Pws_e pend_after) +{ + if(node != last_sibling) + { + _write_pws_and_pend(pend_after); + _write(','); + } + else + { + _pend_newl(); + } +} + + +//----------------------------------------------------------------------------- + +template +void Emitter::_blck_seq_open_entry(id_type node) +{ + NodeType ty = m_tree->type(node); + _write_pws_and_pend(_PWS_NONE); + _write_pws_and_pend(_PWS_SPACE); // pend the space after the following dash + _write('-'); + bool has_tag_or_anchor = false; + if(ty & VALANCH) + { + has_tag_or_anchor = true; + _write_pws_and_pend(_PWS_SPACE); + _write('&'); + _write(m_tree->val_anchor(node)); + } + if(ty & VALTAG) + { + has_tag_or_anchor = true; + _write_pws_and_pend(_PWS_SPACE); + _write_tag(m_tree->val_tag(node)); + } + if(has_tag_or_anchor && ty.is_container()) + { + if(!(ty & CONTAINER_STYLE)) + ty |= BLOCK; + if((ty & BLOCK) && m_tree->has_children(node)) + _pend_newl(); + } +} + + +//----------------------------------------------------------------------------- + +template +void Emitter::_blck_map_open_entry(id_type node) +{ + _RYML_ASSERT_VISIT_(m_tree->callbacks(), m_tree->has_key(node), m_tree, node); + NodeType ty = m_tree->type(node); + csubstr key = m_tree->key(node); + if(!(ty & (KEY_STYLE|KEYREF))) + ty |= (scalar_style_choose_block(key) & KEY_STYLE); + _write_pws_and_pend(_PWS_NONE); + if(ty & KEYANCH) + { + _write_pws_and_pend(_PWS_SPACE); + _write('&'); + _write(m_tree->key_anchor(node)); + } + if(ty & KEYTAG) + { + _write_pws_and_pend(_PWS_SPACE); + _write_tag(m_tree->key_tag(node)); + } + if(ty & KEYREF) + { + _write_pws_and_pend(_PWS_SPACE); + _write_ref(key); + } + else + { + _write_pws_and_pend(_PWS_NONE); + type_bits use_qmrk = ty & (NodeType_e)_styles_block_key; + if(!use_qmrk) + { + _blck_write_scalar(key, ty & KEY_STYLE); + } + else + { + _write("? "); + _blck_write_scalar(key, ty & KEY_STYLE); + _pend_newl(); + } + } + _write_pws_and_pend(_PWS_SPACE); // pend the space after the colon + _write(':'); + if(ty & VALANCH) + { + _write_pws_and_pend(_PWS_SPACE); + _write('&'); + _write(m_tree->val_anchor(node)); + } + if(ty & VALTAG) + { + _write_pws_and_pend(_PWS_SPACE); + _write_tag(m_tree->val_tag(node)); + } + if(ty.is_container() && m_tree->has_children(node)) + { + if(!(ty & CONTAINER_STYLE)) + ty |= BLOCK; + if(ty.is_block()) + _pend_newl(); + } +} + + +//----------------------------------------------------------------------------- + +template +void Emitter::_blck_close_entry(id_type node) +{ + (void)node; + _pend_newl(); +} + + +//----------------------------------------------------------------------------- + +template +void Emitter::_visit_blck_seq(id_type node) +{ + _RYML_ASSERT_VISIT_(m_tree->callbacks(), m_tree->is_seq(node), m_tree, node); + bool empty = true; + for(id_type child = m_tree->first_child(node); child != NONE; child = m_tree->next_sibling(child)) + { + empty = false; + NodeType ty = m_tree->type(child); + _RYML_ASSERT_VISIT_(m_tree->callbacks(), ty.is_val() || ty.is_container() || ty == NOTYPE, m_tree, node); + _blck_seq_open_entry(child); + if(ty.is_val()) + { + _write_pws_and_pend(_PWS_NONE); + csubstr val = m_tree->val(child); + if(!ty.is_val_ref()) + { + if(!(ty & VAL_STYLE)) + ty |= (scalar_style_choose_block(val) & VAL_STYLE); + _blck_write_scalar(val, ty & VAL_STYLE); + } + else + { + _write_ref(val); + } + } + else if(ty.is_container()) + { + ++m_depth; + ++m_ilevel; + _visit_blck_container(child); + --m_depth; + --m_ilevel; + } + _blck_close_entry(child); + } + if(empty) + { + _write_pws_and_pend(_PWS_NONE); + _write("[]"); + } +} + + +//----------------------------------------------------------------------------- + +template +void Emitter::_visit_blck_map(id_type node) +{ + _RYML_ASSERT_VISIT_(m_tree->callbacks(), m_tree->is_map(node), m_tree, node); + bool empty = true; + for(id_type child = m_tree->first_child(node); child != NONE; child = m_tree->next_sibling(child)) + { + empty = false; + NodeType ty = m_tree->type(child); + _RYML_ASSERT_VISIT_(m_tree->callbacks(), ty.is_keyval() || ty.is_container() || ty == NOTYPE, m_tree, node); + _blck_map_open_entry(child); // also writes the key + if(ty.is_keyval()) + { + _write_pws_and_pend(_PWS_NONE); + csubstr val = m_tree->val(child); + if(!ty.is_val_ref()) + { + if(!(ty & VAL_STYLE)) + ty |= (scalar_style_choose_block(val) & VAL_STYLE); + _blck_write_scalar(val, ty & VAL_STYLE); + } + else + { + _write_ref(val); + } + } + else if(ty.is_container()) + { + ++m_depth; + ++m_ilevel; + _visit_blck_container(child); + --m_depth; + --m_ilevel; + } + _blck_close_entry(child); + } + if(empty) + { + _write_pws_and_pend(_PWS_NONE); + _write("{}"); + } +} + + +//----------------------------------------------------------------------------- + +template +void Emitter::_visit_flow_sl_seq(id_type node) +{ + _RYML_ASSERT_VISIT_(m_tree->callbacks(), m_tree->is_seq(node), m_tree, node); + const flow_pws pws = _setup_flow_pws_sl(node); + _write('['); + for(id_type child = m_tree->first_child(node), last = m_tree->last_child(node); child != NONE; child = m_tree->next_sibling(child)) + { + NodeType ty = m_tree->type(child); + _RYML_ASSERT_VISIT_(m_tree->callbacks(), (ty & (VAL|SEQ|MAP)) || ty == NOTYPE, m_tree, node); + _flow_seq_open_entry(child); + if(ty & VAL) + { + _write_pws_and_pend(_PWS_NONE); + csubstr val = m_tree->val(child); + if(!ty.is_val_ref()) + { + if(!(ty & (NodeType_e)_styles_flow_val)) + ty |= (scalar_style_choose_flow(val) & (NodeType_e)_styles_flow_val); + _flow_write_scalar(val, ty & (NodeType_e)_styles_flow_val); + } + else + { + _write_ref(val); + } + } + else if(ty & (SEQ|MAP)) + { + ++m_depth; + _visit_flow_container(child); + --m_depth; + } + _flow_close_entry_sl(child, last, pws.next_pws(m_col)); + } + _write(']'); +} + + +//----------------------------------------------------------------------------- + +template +void Emitter::_visit_flow_ml_seq(id_type node) +{ + _RYML_ASSERT_VISIT_(m_tree->callbacks(), m_tree->is_seq(node), m_tree, node); + _write('['); + _pend_newl(); + if(m_opts.indent_flow_ml()) ++m_ilevel; + const bool stop_at_end = _maybe_start_flow_pws_ml(node); + for(id_type child = m_tree->first_child(node), last = m_tree->last_child(node); child != NONE; child = m_tree->next_sibling(child)) + { + NodeType ty = m_tree->type(child); + _RYML_ASSERT_VISIT_(m_tree->callbacks(), ty.is_val() || ty.is_container() || ty == NOTYPE, m_tree, node); + _flow_seq_open_entry(child); + if(ty.is_val()) + { + _write_pws_and_pend(_PWS_NONE); + csubstr val = m_tree->val(child); + if(!ty.is_val_ref()) + { + if(!(ty & (NodeType_e)_styles_flow_val)) + ty |= (scalar_style_choose_flow(val) & (NodeType_e)_styles_flow_val); + _flow_write_scalar(val, ty & (NodeType_e)_styles_flow_val); + } + else + { + _write_ref(val); + } + } + else if(ty.is_container()) + { + ++m_depth; + _visit_flow_container(child); + --m_depth; + } + _flow_close_entry_ml(child, last, m_flow_pws.next_pws(m_col)); + } + if(stop_at_end) + m_flow_pws.stop(); + if(m_opts.indent_flow_ml()) --m_ilevel; + _write_pws_and_pend(_PWS_NONE); + _write(']'); +} + + +//----------------------------------------------------------------------------- + +template +void Emitter::_visit_flow_sl_map(id_type node) +{ + _RYML_ASSERT_VISIT_(m_tree->callbacks(), m_tree->is_map(node), m_tree, node); + flow_pws pws = _setup_flow_pws_sl(node); + _write('{'); + for(id_type child = m_tree->first_child(node), last = m_tree->last_child(node); child != NONE; child = m_tree->next_sibling(child)) + { + NodeType ty = m_tree->type(child); + _RYML_ASSERT_VISIT_(m_tree->callbacks(), ty.has_key() && (ty.has_val() || ty.is_container() || ty == NOTYPE), m_tree, node); + _flow_map_open_entry(child); + if(ty.has_val()) + { + _write_pws_and_pend(_PWS_NONE); + csubstr val = m_tree->val(child); + if(!ty.is_val_ref()) + { + if(!(ty & (NodeType_e)_styles_flow_val)) + ty |= (scalar_style_choose_flow(val) & (NodeType_e)_styles_flow_val); + _flow_write_scalar(val, ty & (NodeType_e)_styles_flow_val); + } + else + { + _write_ref(val); + } + } + else if(ty.is_container()) + { + ++m_depth; + _visit_flow_container(child); + --m_depth; + } + _flow_close_entry_sl(child, last, pws.next_pws(m_col)); + } + _write_pws_and_pend(_PWS_NONE); + _write('}'); +} + + +//----------------------------------------------------------------------------- + +template +void Emitter::_visit_flow_ml_map(id_type node) +{ + _RYML_ASSERT_VISIT_(m_tree->callbacks(), m_tree->is_map(node), m_tree, node); + _write('{'); + _pend_newl(); + if(m_opts.indent_flow_ml()) ++m_ilevel; + const bool stop_at_end = _maybe_start_flow_pws_ml(node); + for(id_type child = m_tree->first_child(node), last = m_tree->last_child(node); child != NONE; child = m_tree->next_sibling(child)) + { + NodeType ty = m_tree->type(child); + _RYML_ASSERT_VISIT_(m_tree->callbacks(), ty.has_key() && (ty.has_val() || ty.is_container() || ty == NOTYPE), m_tree, node); + _flow_map_open_entry(child); + if(ty.has_val()) + { + _write_pws_and_pend(_PWS_NONE); + csubstr val = m_tree->val(child); + if(!ty.is_val_ref()) + { + if(!(ty & (NodeType_e)_styles_flow_val)) + ty |= (scalar_style_choose_flow(val) & (NodeType_e)_styles_flow_val); + _flow_write_scalar(val, ty & (NodeType_e)_styles_flow_val); + } + else + { + _write_ref(val); + } + } + else if(ty.is_container()) + { + ++m_depth; + _visit_flow_container(child); + --m_depth; + } + _flow_close_entry_ml(child, last, m_flow_pws.next_pws(m_col)); + } + if(stop_at_end) + m_flow_pws.stop(); + if(m_opts.indent_flow_ml()) --m_ilevel; + _write_pws_and_pend(_PWS_NONE); + _write('}'); +} + + +//----------------------------------------------------------------------------- + +template +void Emitter::_visit_blck(id_type node) +{ + _RYML_ASSERT_VISIT_(m_tree->callbacks(), !m_tree->is_stream(node), m_tree, node); + _RYML_ASSERT_VISIT_(m_tree->callbacks(), m_tree->is_container(node) || m_tree->is_doc(node), m_tree, node); + _RYML_ASSERT_VISIT_(m_tree->callbacks(), m_tree->is_root(node) || (m_tree->parent_is_map(node) || m_tree->parent_is_seq(node)), m_tree, node); + if(C4_UNLIKELY(m_depth > m_opts.max_depth())) + _RYML_ERR_VISIT_(m_tree->callbacks(), m_tree, node, "max depth exceeded"); + const NodeType ty = m_tree->type(node); + if(ty.is_seq()) + { + _visit_blck_seq(node); + } + else + { + _RYML_ASSERT_VISIT_(m_tree->callbacks(), ty.is_map(), m_tree, node); + _visit_blck_map(node); + } +} + + +//----------------------------------------------------------------------------- + +template +void Emitter::_visit_flow_sl(id_type node) +{ + _RYML_ASSERT_VISIT_(m_tree->callbacks(), !m_tree->is_stream(node), m_tree, node); + _RYML_ASSERT_VISIT_(m_tree->callbacks(), m_tree->is_container(node) || m_tree->is_doc(node), m_tree, node); + _RYML_ASSERT_VISIT_(m_tree->callbacks(), m_tree->is_root(node) || (m_tree->parent_is_map(node) || m_tree->parent_is_seq(node)), m_tree, node); + if(C4_UNLIKELY(m_depth > m_opts.max_depth())) + _RYML_ERR_VISIT_(m_tree->callbacks(), m_tree, node, "max depth exceeded"); + const NodeType ty = m_tree->type(node); + if(ty & SEQ) + { + _visit_flow_sl_seq(node); + } + else + { + _RYML_ASSERT_VISIT_(m_tree->callbacks(), ty.is_map(), m_tree, node); + _visit_flow_sl_map(node); + } +} + + +//----------------------------------------------------------------------------- + +template +void Emitter::_visit_flow_ml(id_type node) +{ + _RYML_ASSERT_VISIT_(m_tree->callbacks(), !m_tree->is_stream(node), m_tree, node); + _RYML_ASSERT_VISIT_(m_tree->callbacks(), m_tree->is_container(node) || m_tree->is_doc(node), m_tree, node); + _RYML_ASSERT_VISIT_(m_tree->callbacks(), m_tree->is_root(node) || (m_tree->parent_is_map(node) || m_tree->parent_is_seq(node)), m_tree, node); + if(C4_UNLIKELY(m_depth > m_opts.max_depth())) + _RYML_ERR_VISIT_(m_tree->callbacks(), m_tree, node, "max depth exceeded"); + const NodeType ty = m_tree->type(node); + if(ty & SEQ) + { + _visit_flow_ml_seq(node); + } + else + { + _RYML_ASSERT_VISIT_(m_tree->callbacks(), ty.is_map(), m_tree, node); + _visit_flow_ml_map(node); + } +} + + +//----------------------------------------------------------------------------- + +template +void Emitter::_flow_write_scalar(csubstr str, type_bits ty) +{ + _RYML_ASSERT_BASIC_(m_tree->callbacks(), !(ty & _styles_block)); + if((ty & _styles_plain) || !(ty & SCALAR_STYLE)) + { + _write_scalar_plain(str, m_ilevel); + } + else if(ty & _styles_squo) + { + _write_scalar_squo(str, m_ilevel); + } + else // if(ty & _styles_dquo) + { + _write_scalar_dquo(str, m_ilevel); + } +} + +template +void Emitter::_blck_write_scalar(csubstr str, type_bits ty) +{ + if((ty & _styles_plain) || !(ty & SCALAR_STYLE)) + { + _write_scalar_plain(str, m_ilevel); + } + else if(ty & _styles_squo) + { + _write_scalar_squo(str, m_ilevel); + } + else if(ty & _styles_dquo) + { + _write_scalar_dquo(str, m_ilevel); + } + else if(ty & _styles_literal) + { + _write_scalar_literal(str, m_ilevel); + } + else // if(ty & _styles_folded) + { + _write_scalar_folded(str, m_ilevel); + } +} + +template +size_t Emitter::_write_escaped_newlines(csubstr s, size_t i) +{ + _RYML_ASSERT_BASIC_(m_tree->callbacks(), s.len > i); + _RYML_ASSERT_BASIC_(m_tree->callbacks(), s.str[i] == '\n'); + //_c4dbgpf("nl@i={} rem=[{}]~~~{}~~~", i, s.sub(i).len, s.sub(i)); + // add an extra newline for each sequence of consecutive + // newline/whitespace + _newl(); + do + { + _newl(); // write the newline again + ++i; // increase the outer loop counter! + } while(i < s.len && s.str[i] == '\n'); + _RYML_ASSERT_BASIC_(m_tree->callbacks(), i > 0); + --i; + _RYML_ASSERT_BASIC_(m_tree->callbacks(), s.str[i] == '\n'); + return i; +} + + +inline bool _is_indented_block(csubstr s, size_t prev, size_t i) noexcept +{ + if(prev == 0 && s.begins_with_any(" \t")) + return true; + const size_t pos = s.first_not_of('\n', i); + return (pos != npos) && (s.str[pos] == ' ' || s.str[pos] == '\t'); +} + + +template +size_t Emitter::_write_indented_block(csubstr s, size_t i, id_type ilevel) +{ + //_c4dbgpf("indblock@i={} rem=[{}]~~~\n{}~~~", i, s.sub(i).len, s.sub(i)); + _RYML_ASSERT_BASIC_(m_tree->callbacks(), i > 0); + _RYML_ASSERT_BASIC_(m_tree->callbacks(), s.str[i-1] == '\n'); + _RYML_ASSERT_BASIC_(m_tree->callbacks(), i < s.len); + _RYML_ASSERT_BASIC_(m_tree->callbacks(), s.str[i] == ' ' || s.str[i] == '\t' || s.str[i] == '\n'); +again: + size_t pos = s.find("\n ", i); + if(pos == npos) + pos = s.find("\n\t", i); + if(pos != npos) + { + ++pos; + //_c4dbgpf("indblock line@i={} rem=[{}]~~~\n{}~~~", i, s.range(i, pos).len, s.range(i, pos)); + _indent(ilevel + 1); + _write(s.range(i, pos)); + i = pos; + goto again; // NOLINT + } + // consume the newlines after the indented block + // to prevent them from being escaped + pos = s.find('\n', i); + if(pos != npos) + { + const size_t pos2 = s.first_not_of('\n', pos); + pos = (pos2 != npos) ? pos2 : pos; + //_c4dbgpf("indblock line@i={} rem=[{}]~~~\n{}~~~", i, s.range(i, pos).len, s.range(i, pos)); + _indent(ilevel + 1); + _write(s.range(i, pos)); + i = pos; + } + return i; +} + +template +void Emitter::_write_scalar_literal(csubstr s, id_type ilevel) +{ + _RYML_ASSERT_BASIC_(m_tree->callbacks(), s.find("\r") == csubstr::npos); + csubstr trimmed = s.trimr('\n'); + const size_t numnewlines_at_end = s.len - trimmed.len; + const bool is_newline_only = (trimmed.len == 0 && (s.len > 0)); + const bool explicit_indentation = s.triml("\n\r").begins_with_any(" \t"); + // + _write('|'); + if(explicit_indentation) + _write('2'); + // + if(numnewlines_at_end > 1 || is_newline_only) + _write('+'); + else if(numnewlines_at_end == 0) + _write('-'); + // + if(trimmed.len) + { + _newl(); + size_t pos = 0; // tracks the last character that was already written + for(size_t i = 0; i < trimmed.len; ++i) + { + if(trimmed[i] != '\n') + continue; + // write everything up to this point + csubstr since_pos = trimmed.range(pos, i+1); // include the newline + _indent(ilevel + 1); + _write(since_pos); + pos = i+1; // already written + } + if(pos < trimmed.len) + { + _indent(ilevel + 1); + _write(trimmed.sub(pos)); + } + } + for(size_t i = !is_newline_only; i < numnewlines_at_end; ++i) + _newl(); +} + +template +void Emitter::_write_scalar_folded(csubstr s, id_type ilevel) +{ + _RYML_ASSERT_BASIC_(m_tree->callbacks(), s.find("\r") == csubstr::npos); + csubstr trimmed = s.trimr('\n'); + const size_t numnewlines_at_end = s.len - trimmed.len; + const bool is_newline_only = (trimmed.len == 0 && (s.len > 0)); + const bool explicit_indentation = s.triml("\n\r").begins_with_any(" \t"); + // + _write('>'); + if(explicit_indentation) + _write('2'); + // + if(numnewlines_at_end == 0) + _write('-'); + else if(numnewlines_at_end > 1 || is_newline_only) + _write('+'); + // + if(trimmed.len) + { + _newl(); + size_t pos = 0; // tracks the last character that was already written + for(size_t i = 0; i < trimmed.len; ++i) + { + if(trimmed[i] != '\n') + continue; + // escape newline sequences + if( ! _is_indented_block(s, pos, i)) + { + if(pos < i) + { + _indent(ilevel + 1); + _write(s.range(pos, i)); + i = _write_escaped_newlines(s, i); + pos = i + 1; + } + else + { + if(i+1 < s.len) + { + if(s.str[i+1] == '\n') + { + ++i; + i = _write_escaped_newlines(s, i); + pos = i+1; + } + else + { + _newl(); + pos = i+1; + } + } + } + } + else // do not escape newlines in indented blocks + { + ++i; + _indent(ilevel + 1); + _write(s.range(pos, i)); + if(pos > 0 || !s.begins_with_any(" \t")) + i = _write_indented_block(s, i, ilevel); + pos = i; + } + } + if(pos < trimmed.len) + { + _indent(ilevel + 1); + _write(trimmed.sub(pos)); + } + } + for(size_t i = !is_newline_only; i < numnewlines_at_end; ++i) + _newl(); +} + +template +void Emitter::_write_scalar_squo(csubstr s, id_type ilevel) +{ + size_t pos = 0; // tracks the last character that was already written + _write('\''); + for(size_t i = 0; i < s.len; ++i) + { + if(s[i] == '\n') + { + _write(s.range(pos, i)); // write everything up to (excluding) this char + //_c4dbgpf("newline at {}. writing ~~~{}~~~", i, s.range(pos, i)); + i = _write_escaped_newlines(s, i); + //_c4dbgpf("newline --> {}", i); + if(i < s.len) + _indent(ilevel + 1); + pos = i+1; + } + else if(s[i] == '\'') + { + csubstr sub = s.range(pos, i+1); + //_c4dbgpf("squote at {}. writing ~~~{}~~~", i, sub); + _write(sub); // write everything up to (including) this squote + _write('\''); // write the squote again + pos = i+1; + } + } + // write remaining characters at the end of the string + if(pos < s.len) + _write(s.sub(pos)); + _write('\''); +} + +template +void Emitter::_write_scalar_dquo(csubstr s, id_type ilevel) +{ + size_t pos = 0; // tracks the last character that was already written + _write('"'); + for(size_t i = 0; i < s.len; ++i) + { + const char curr = s.str[i]; + switch(curr) // NOLINT + { + case '"': + case '\\': + { + csubstr sub = s.range(pos, i); + _write(sub); // write everything up to (excluding) this char + _write('\\'); // write the escape + _write(curr); // write the char + pos = i+1; + break; + } +#ifndef prefer_writing_newlines_as_double_newlines + case '\n': + { + csubstr sub = s.range(pos, i); + _write(sub); // write everything up to (excluding) this char + _write("\\n"); // write the escape + pos = i+1; + (void)ilevel; + break; + } +#else + case '\n': + { + // write everything up to (excluding) this newline + //_c4dbgpf("nl@i={} rem=[{}]~~~{}~~~", i, s.sub(i).len, s.sub(i)); + _write(s.range(pos, i)); + i = _write_escaped_newlines(s, i); + ++i; + pos = i; + // as for the next line... + if(i < s.len) + { + _indent(ilevel + 1); // indent the next line + // escape leading whitespace, and flush it + size_t first = s.first_not_of(" \t", i); + //_c4dbgpf("@i={} first={} rem=[{}]~~~{}~~~", i, first, s.sub(i).len, s.sub(i)); + if(first > i) + { + if(first == npos) + first = s.len; + _write('\\'); + _write(s.range(i, first)); + _write('\\'); + i = first-1; + pos = first; + } + } + break; + } + // escape trailing whitespace before a newline + case ' ': + case '\t': + { + const size_t next = s.first_not_of(" \t\r", i); + if(next != npos && s.str[next] == '\n') + { + csubstr sub = s.range(pos, i); + _write(sub); // write everything up to (excluding) this char + _write('\\'); // escape the whitespace + pos = i; + } + break; + } +#endif + case '\r': + { + csubstr sub = s.range(pos, i); + _write(sub); // write everything up to (excluding) this char + _write("\\r"); // write the escaped char + pos = i+1; + break; + } + case '\b': + { + csubstr sub = s.range(pos, i); + _write(sub); // write everything up to (excluding) this char + _write("\\b"); // write the escaped char + pos = i+1; + break; + } + } + } + // write remaining characters at the end of the string + if(pos < s.len) + _write(s.sub(pos)); + _write('"'); +} + +template +void Emitter::_write_scalar_plain(csubstr s, id_type ilevel) +{ + if(C4_UNLIKELY(ilevel == 0 && (s.begins_with("...") || s.begins_with("---")))) + { + _indent(ilevel + 1); // indent the next line + ++ilevel; + } + size_t pos = 0; // tracks the last character that was already written + for(size_t i = 0; i < s.len; ++i) + { + const char curr = s.str[i]; + if(curr == '\n') + { + csubstr sub = s.range(pos, i); + _write(sub); // write everything up to (including) this newline + i = _write_escaped_newlines(s, i); + pos = i+1; + if(pos < s.len) + _indent(ilevel + 1); // indent the next line + } + } + // write remaining characters at the end of the string + if(pos < s.len) + _write(s.sub(pos)); +} + + +//----------------------------------------------------------------------------- + +namespace detail { +inline type_bits json_type_(type_bits ty) +{ + enum : type_bits { // NOLINT + ml_bits = (BLOCK|(STREAM & ~SEQ)), // remove SEQ from STREAM to test + sl_bits = (CONTAINER_STYLE & ~FLOW_SPC), + }; + if(ty & ml_bits) + { + ty &= ~BLOCK; + ty |= FLOW_ML1; + } + else if((ty & (SEQ|MAP)) && !(ty & sl_bits)) + { + ty |= FLOW_SL; + } + return ty; +} +} // namespace detail + + +template +void Emitter::_json_emit(id_type id) +{ + NodeType ty = m_tree->type(id); + // JSON does not have streams + if(C4_UNLIKELY(ty.is_stream() && m_opts.json_err_on_stream())) + _RYML_ERR_VISIT_(m_tree->callbacks(), m_tree, id, "found stream node"); + static_assert(STREAM & SEQ, "STREAM must be a SEQ"); + ty = detail::json_type_(ty); + if(ty.is_flow_mlx()) + { + _json_visit_ml(id, ty, 0); + _newl(); + } + else + { + _json_visit_sl(id, ty, 0); + } +} + +template +void Emitter::_json_visit_sl(id_type id, NodeType ty, id_type depth) +{ + if(C4_UNLIKELY(depth > m_opts.max_depth())) + _RYML_ERR_VISIT_(m_tree->callbacks(), m_tree, id, "max depth exceeded"); + if(ty.is_val()) + { + _json_writev(id, ty); + } + else if(ty.is_keyval()) + { + _json_writek(id, ty); + _write(": "); + _json_writev(id, ty); + } + else if(ty.is_container()) + { + ty = detail::json_type_(ty); + if(ty.has_key()) + { + _json_writek(id, ty); + _write(": "); + } + if(ty.is_seq()) + _write('['); + else if(ty.is_map()) + _write('{'); + + for(id_type child = m_tree->first_child(id); child != NONE; child = m_tree->next_sibling(child)) + { + if(child != m_tree->first_child(id)) + { + if((ty & FLOW_SPC) || m_opts.force_flow_spc()) + _write(", "); + else + _write(','); + } + _json_visit_sl(child, m_tree->type(child), depth+1); + } + + if(ty.is_seq()) + _write(']'); + else if(ty.is_map()) + _write('}'); + } // container +} + +template +void Emitter::_json_visit_ml(id_type id, NodeType ty, id_type depth) +{ + if(C4_UNLIKELY(depth > m_opts.max_depth())) + _RYML_ERR_VISIT_(m_tree->callbacks(), m_tree, id, "max depth exceeded"); + if(ty.is_val()) + { + _json_writev(id, ty); + } + else if(ty.is_keyval()) + { + _json_writek(id, ty); + _write(": "); + _json_writev(id, ty); + } + else if(ty.is_container()) + { + ty = detail::json_type_(ty); + if(ty.has_key()) + { + _json_writek(id, ty); + _write(": "); + } + if(ty.is_seq()) + _write('['); + else if(ty.is_map()) + _write('{'); + + if(m_tree->has_children(id)) + { + ++depth; + if(m_opts.indent_flow_ml()) ++m_ilevel; + _newl(); + _indent(m_ilevel); + for(id_type first = m_tree->first_child(id), child = first; + child != NONE; + child = m_tree->next_sibling(child)) + { + if(child != first) + { + _write(','); + const size_t maxcols = m_opts.max_cols(); + if((ty & FLOW_MLN) && (m_col+1 < maxcols)) + { + if((ty & FLOW_SPC) || m_opts.force_flow_spc()) + _write(' '); + } + else if((ty & FLOW_ML1) || (m_col+1 >= maxcols)) + { + _newl(); + _indent(m_ilevel); + } + } + NodeType chty = m_tree->type(child); + if(chty.is_flow_sl()) + _json_visit_sl(child, chty, depth); + else + _json_visit_ml(child, chty, depth); + } + if(m_opts.indent_flow_ml()) --m_ilevel; + --depth; + _newl(); + _indent(m_ilevel); + } + + if(ty.is_seq()) + _write(']'); + else if(ty.is_map()) + _write('}'); + } +} + +template +bool Emitter::_json_maybe_write_naninf(csubstr s) +{ + if(s == "nan" || s == ".nan" || s == ".NaN" || s == ".NAN") + { + _write("\".nan\""); + return true; + } + else if(s == "inf" || s == ".inf" || s == ".Inf" || s == ".INF" || s == "infinity") + { + _write("\".inf\""); + return true; + } + else if(s == "-inf" || s == "-.inf" || s == "-.Inf" || s == "-.INF" || s == "-infinity") + { + _write("\"-.inf\""); + return true; + } + return false; +} + +template +void Emitter::_json_writek(id_type id, NodeType ty) +{ + if(C4_UNLIKELY(ty.has_key_tag() && m_opts.json_err_on_tag())) + _RYML_ERR_VISIT_(m_tree->callbacks(), m_tree, id, "JSON does not have tags"); + if(C4_UNLIKELY(ty.has_key_anchor() && m_opts.json_err_on_anchor())) + _RYML_ERR_VISIT_(m_tree->callbacks(), m_tree, id, "JSON does not have anchors"); + csubstr key = m_tree->key(id); + if(key.len) + { + if(_json_maybe_write_naninf(key)) + ; + else + _json_write_scalar_dquo(key); + } + else + { + _write("\"\""); + } +} + +template +void Emitter::_json_writev(id_type id, NodeType ty) +{ + if(C4_UNLIKELY(ty.has_val_tag() && m_opts.json_err_on_tag())) + _RYML_ERR_VISIT_(m_tree->callbacks(), m_tree, id, "JSON does not have tags"); + if(C4_UNLIKELY(ty.has_val_anchor() && m_opts.json_err_on_anchor())) + _RYML_ERR_VISIT_(m_tree->callbacks(), m_tree, id, "JSON does not have anchors"); + csubstr val = m_tree->val(id); + if(val.len) + { + // use double quoted style if the style is marked quoted + bool dquoted = ((ty & VALQUO) + || (scalar_style_choose_json(val) & SCALAR_DQUO)); // choose the style + if(dquoted) + _json_write_scalar_dquo(val); + else if(_json_maybe_write_naninf(val)) + ; + else if(val.is_number()) + _json_write_number(val); + else + _write(val); + } + else + { + if(val.str || (ty & (VALQUO|VALTAG))) + _write("\"\""); + else + _write("null"); + } +} + + +template +void Emitter::_json_write_scalar_dquo(csubstr s) +{ + size_t pos = 0; + _write('"'); + for(size_t i = 0; i < s.len; ++i) + { + switch(s.str[i]) + { + case '"': + _write(s.range(pos, i)); + _write("\\\""); + pos = i + 1; + break; + case '\n': + _write(s.range(pos, i)); + _write("\\n"); + pos = i + 1; + break; + case '\t': + _write(s.range(pos, i)); + _write("\\t"); + pos = i + 1; + break; + case '\\': + _write(s.range(pos, i)); + _write("\\\\"); + pos = i + 1; + break; + case '\r': + _write(s.range(pos, i)); + _write("\\r"); + pos = i + 1; + break; + case '\b': + _write(s.range(pos, i)); + _write("\\b"); + pos = i + 1; + break; + case '\f': + _write(s.range(pos, i)); + _write("\\f"); + pos = i + 1; + break; + } + } + if(pos < s.len) + { + csubstr sub = s.sub(pos); + _write(sub); + } + _write('"'); +} + +template +void Emitter::_json_write_number(csubstr s) +{ + if(s.is_integer()) + { + _write(s); + } + else + { + if(s.begins_with('-') && s.len > 1) + { + csubstr rest = s.sub(1); + if(rest.begins_with('.')) + { + _write("-0"); + _write(rest); + } + else if(rest.ends_with('.')) + { + _write(s); + _write('0'); + } + else + { + _write(s); + } + } + else if(s.begins_with('.')) + { + _write('0'); + _write(s); + } + else if(s.ends_with('.')) + { + _write(s); + _write('0'); + } + else + { + _write(s); + } + } +} + +/** @endcond */ + +} // namespace yml +} // namespace c4 + +// NOLINTEND(modernize-avoid-c-style-cast) +C4_SUPPRESS_WARNING_GCC_CLANG_POP + +#endif /* _C4_YML_EMIT_DEF_HPP_ */ + + +// (end src/c4/yml/emit.def.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/filter_processor.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_YML_FILTER_PROCESSOR_HPP_ +#define _C4_YML_FILTER_PROCESSOR_HPP_ + +#ifndef _C4_YML_ERROR_HPP_ +#include "./error.hpp" +#endif + +#ifdef RYML_DBG +// amalgamate: removed include of +// c4/charconv.hpp +//#include "c4/charconv.hpp" +#if !defined(C4_CHARCONV_HPP_) && !defined(_C4_CHARCONV_HPP_) +#error "amalgamate: file c4/charconv.hpp must have been included at this point" +#endif /* C4_CHARCONV_HPP_ */ + +// amalgamate: removed include of +// c4/yml/detail/dbgprint.hpp +//#include "c4/yml/detail/dbgprint.hpp" +#if !defined(C4_YML_DETAIL_DBGPRINT_HPP_) && !defined(_C4_YML_DETAIL_DBGPRINT_HPP_) +#error "amalgamate: file c4/yml/detail/dbgprint.hpp must have been included at this point" +#endif /* C4_YML_DETAIL_DBGPRINT_HPP_ */ + +#endif + +namespace c4 { +namespace yml { + +/** @defgroup doc_filter_processors Scalar filter processors + * + * These are internal utilities used by @ref ParseEngine to parse the + * scalars; normally there is no reason for a user to be manually + * using these classes. + * + * @ingroup doc_parse */ +/** @{ */ + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** Abstracts the fact that a scalar filter result may not fit in the + * intended memory. */ +struct FilterResult +{ + C4_ALWAYS_INLINE bool valid() const noexcept { return str.str != nullptr; } + C4_ALWAYS_INLINE size_t required_len() const noexcept { return str.len; } + C4_ALWAYS_INLINE csubstr get() const { _RYML_ASSERT_BASIC(valid()); return str; } + csubstr str; +}; +/** Abstracts the fact that a scalar filter result may not fit in the + * intended memory. */ +struct FilterResultExtending +{ + C4_ALWAYS_INLINE bool valid() const noexcept { return str.str != nullptr; } + C4_ALWAYS_INLINE size_t required_len() const noexcept { return reqlen; } + C4_ALWAYS_INLINE csubstr get() const { _RYML_ASSERT_BASIC(valid()); return str; } + csubstr str; + size_t reqlen; +}; + + +//----------------------------------------------------------------------------- + +/** Filters an input string into a different output string */ +struct FilterProcessorSrcDst +{ + csubstr src; + substr dst; + size_t rpos; ///< read position + size_t wpos; ///< write position + + C4_ALWAYS_INLINE FilterProcessorSrcDst(csubstr src_, substr dst_) noexcept + : src(src_) + , dst(dst_) + , rpos(0) + , wpos(0) + { + _RYML_ASSERT_BASIC(!dst.overlaps(src)); + } + + C4_ALWAYS_INLINE void setwpos(size_t wpos_) noexcept { wpos = wpos_; } + C4_ALWAYS_INLINE void setpos(size_t rpos_, size_t wpos_) noexcept { rpos = rpos_; wpos = wpos_; } + C4_ALWAYS_INLINE void set_at_end() noexcept { skip(src.len - rpos); } + + C4_ALWAYS_INLINE bool has_more_chars() const noexcept { return rpos < src.len; } + C4_ALWAYS_INLINE bool has_more_chars(size_t maxpos) const noexcept { _RYML_ASSERT_BASIC(maxpos <= src.len); return rpos < maxpos; } + + C4_ALWAYS_INLINE csubstr rem() const noexcept { return src.sub(rpos); } + C4_ALWAYS_INLINE csubstr sofar() const noexcept { return csubstr(dst.str, wpos <= dst.len ? wpos : dst.len); } + C4_ALWAYS_INLINE FilterResult result() const noexcept + { + FilterResult ret; + ret.str.str = wpos <= dst.len ? dst.str : nullptr; + ret.str.len = wpos; + return ret; + } + + C4_ALWAYS_INLINE char curr() const noexcept { _RYML_ASSERT_BASIC(rpos < src.len); return src[rpos]; } + C4_ALWAYS_INLINE char next() const noexcept { return rpos+1 < src.len ? src[rpos+1] : '\0'; } + C4_ALWAYS_INLINE bool skipped_chars() const noexcept { return wpos != rpos; } + + C4_ALWAYS_INLINE void skip() noexcept { ++rpos; } + C4_ALWAYS_INLINE void skip(size_t num) noexcept { rpos += num; } + + C4_ALWAYS_INLINE void set_at(size_t pos, char c) noexcept // NOLINT(readability-make-member-function-const) + { + _RYML_ASSERT_BASIC(pos < wpos); + dst.str[pos] = c; + } + C4_ALWAYS_INLINE void set(char c) noexcept + { + if(wpos < dst.len) + dst.str[wpos] = c; + ++wpos; + } + C4_ALWAYS_INLINE void set(char c, size_t num) noexcept + { + _RYML_ASSERT_BASIC(num > 0); + if(wpos + num <= dst.len) + memset(dst.str + wpos, c, num); + wpos += num; + } + + C4_ALWAYS_INLINE void copy() noexcept + { + _RYML_ASSERT_BASIC(rpos < src.len); + if(wpos < dst.len) + dst.str[wpos] = src.str[rpos]; + ++wpos; + ++rpos; + } + C4_ALWAYS_INLINE void copy(size_t num) noexcept + { + _RYML_ASSERT_BASIC(num); + _RYML_ASSERT_BASIC(rpos+num <= src.len); + if(wpos + num <= dst.len) + memcpy(dst.str + wpos, src.str + rpos, num); + wpos += num; + rpos += num; + } + + C4_ALWAYS_INLINE void translate_esc(char c) noexcept + { + if(wpos < dst.len) + dst.str[wpos] = c; + ++wpos; + rpos += 2; + } + C4_ALWAYS_INLINE void translate_esc_bulk(const char *C4_RESTRICT s, size_t nw, size_t nr) noexcept + { + _RYML_ASSERT_BASIC(nw > 0); + _RYML_ASSERT_BASIC(nr > 0); + _RYML_ASSERT_BASIC(rpos+nr <= src.len); + if(wpos+nw <= dst.len) + memcpy(dst.str + wpos, s, nw); + wpos += nw; + rpos += 1 + nr; + } + C4_ALWAYS_INLINE void translate_esc_extending(const char *C4_RESTRICT s, size_t nw, size_t nr) noexcept + { + translate_esc_bulk(s, nw, nr); + } +}; + + +//----------------------------------------------------------------------------- +// filter in place + +// debugging scaffold +/** @cond dev */ +#if defined(RYML_DBG) && 0 +#define _c4dbgip(...) _c4dbgpf(__VA_ARGS__) +#else +#define _c4dbgip(...) +#endif +/** @endcond */ + +/** Filters in place. While the result may be larger than the source, + * any extending happens only at the end of the string. Consequently, + * it's impossible for characters to be left unfiltered. + * + * @see FilterProcessorInplaceMidExtending */ +struct FilterProcessorInplaceEndExtending +{ + substr src; ///< the subject string + size_t wcap; ///< write capacity - the capacity of the subject string's buffer + size_t rpos; ///< read position + size_t wpos; ///< write position + + C4_ALWAYS_INLINE FilterProcessorInplaceEndExtending(substr src_, size_t wcap_) noexcept + : src(src_) + , wcap(wcap_) + , rpos(0) + , wpos(0) + { + _RYML_ASSERT_BASIC(wcap >= src.len); + } + + C4_ALWAYS_INLINE void setwpos(size_t wpos_) noexcept { wpos = wpos_; } + C4_ALWAYS_INLINE void setpos(size_t rpos_, size_t wpos_) noexcept { rpos = rpos_; wpos = wpos_; } + C4_ALWAYS_INLINE void set_at_end() noexcept { skip(src.len - rpos); } + + C4_ALWAYS_INLINE bool has_more_chars() const noexcept { return rpos < src.len; } + C4_ALWAYS_INLINE bool has_more_chars(size_t maxpos) const noexcept { _RYML_ASSERT_BASIC(maxpos <= src.len); return rpos < maxpos; } + + C4_ALWAYS_INLINE FilterResult result() const noexcept + { + _c4dbgip("inplace: wpos={} wcap={} small={}", wpos, wcap, wpos > rpos); + FilterResult ret; + ret.str.str = (wpos <= wcap) ? src.str : nullptr; + ret.str.len = wpos; + return ret; + } + C4_ALWAYS_INLINE csubstr sofar() const noexcept { return csubstr(src.str, wpos <= wcap ? wpos : wcap); } + C4_ALWAYS_INLINE csubstr rem() const noexcept { return src.sub(rpos); } + + C4_ALWAYS_INLINE char curr() const noexcept { _RYML_ASSERT_BASIC(rpos < src.len); return src[rpos]; } + C4_ALWAYS_INLINE char next() const noexcept { return rpos+1 < src.len ? src[rpos+1] : '\0'; } + + C4_ALWAYS_INLINE void skip() noexcept { ++rpos; } + C4_ALWAYS_INLINE void skip(size_t num) noexcept { rpos += num; } + + void set_at(size_t pos, char c) noexcept + { + _RYML_ASSERT_BASIC(pos < wpos); + const size_t save = wpos; + wpos = pos; + set(c); + wpos = save; + } + void set(char c) noexcept + { + if(wpos < wcap) // respect write-capacity + src.str[wpos] = c; + ++wpos; + } + void set(char c, size_t num) noexcept + { + _RYML_ASSERT_BASIC(num); + if(wpos + num <= wcap) // respect write-capacity + memset(src.str + wpos, c, num); + wpos += num; + } + + void copy() noexcept + { + _RYML_ASSERT_BASIC(wpos <= rpos); + _RYML_ASSERT_BASIC(rpos < src.len); + if(wpos < wcap) // respect write-capacity + src.str[wpos] = src.str[rpos]; + ++rpos; + ++wpos; + } + void copy(size_t num) noexcept + { + _RYML_ASSERT_BASIC(num); + _RYML_ASSERT_BASIC(rpos+num <= src.len); + _RYML_ASSERT_BASIC(wpos <= rpos); + if(wpos + num <= wcap) // respect write-capacity + { + if(wpos + num <= rpos) // there is no overlap + memcpy(src.str + wpos, src.str + rpos, num); + else // there is overlap + memmove(src.str + wpos, src.str + rpos, num); + } + rpos += num; + wpos += num; + } + + void translate_esc(char c) noexcept + { + _RYML_ASSERT_BASIC(rpos + 2 <= src.len); + _RYML_ASSERT_BASIC(wpos <= rpos); + if(wpos < wcap) // respect write-capacity + src.str[wpos] = c; + rpos += 2; // add 1u to account for the escape character + ++wpos; + } + + void translate_esc_bulk(const char *C4_RESTRICT s, size_t nw, size_t nr) noexcept + { + _RYML_ASSERT_BASIC(nw > 0); + _RYML_ASSERT_BASIC(nr > 0); + _RYML_ASSERT_BASIC(nw <= nr + 1u); + _RYML_ASSERT_BASIC(rpos+nr <= src.len); + _RYML_ASSERT_BASIC(wpos <= rpos); + const size_t wpos_next = wpos + nw; + const size_t rpos_next = rpos + nr + 1u; // add 1u to account for the escape character + _RYML_ASSERT_BASIC(wpos_next <= rpos_next); + if(wpos_next <= wcap) + memcpy(src.str + wpos, s, nw); + rpos = rpos_next; + wpos = wpos_next; + } + + C4_ALWAYS_INLINE void translate_esc_extending(const char *C4_RESTRICT s, size_t nw, size_t nr) noexcept + { + translate_esc_bulk(s, nw, nr); + } +}; + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** Filters in place. The result may be larger than the source, and + * extending may happen anywhere. As a result some characters may be + * left unfiltered when there is no slack in the buffer and the + * write-position would overlap the read-position. Consequently, it's + * possible for characters to be left unfiltered. In YAML, this + * happens only with double-quoted strings, and only with a small + * number of escape sequences such as `\L` which is substituted by three + * bytes. These escape sequences cause a call to translate_esc_extending() + * which is the only entry point to this unfiltered situation. + * + * @see FilterProcessorInplaceMidExtending */ +struct FilterProcessorInplaceMidExtending +{ + substr src; ///< the subject string + size_t wcap; ///< write capacity - the capacity of the subject string's buffer + size_t rpos; ///< read position + size_t wpos; ///< write position + size_t maxcap; ///< the max capacity needed for filtering the string. This may be larger than the final string size. + bool unfiltered_chars; ///< number of characters that were not added to wpos from lack of capacity + + C4_ALWAYS_INLINE FilterProcessorInplaceMidExtending(substr src_, size_t wcap_) noexcept + : src(src_) + , wcap(wcap_) + , rpos(0) + , wpos(0) + , maxcap(src.len) + , unfiltered_chars(false) + { + _RYML_ASSERT_BASIC(wcap >= src.len); + } + + C4_ALWAYS_INLINE void setwpos(size_t wpos_) noexcept { wpos = wpos_; } + C4_ALWAYS_INLINE void setpos(size_t rpos_, size_t wpos_) noexcept { rpos = rpos_; wpos = wpos_; } + C4_ALWAYS_INLINE void set_at_end() noexcept { skip(src.len - rpos); } + + C4_ALWAYS_INLINE bool has_more_chars() const noexcept { return rpos < src.len; } + C4_ALWAYS_INLINE bool has_more_chars(size_t maxpos) const noexcept { _RYML_ASSERT_BASIC(maxpos <= src.len); return rpos < maxpos; } + + C4_ALWAYS_INLINE FilterResultExtending result() const noexcept + { + _c4dbgip("inplace: wpos={} wcap={} unfiltered={} maxcap={}", this->wpos, this->wcap, this->unfiltered_chars, this->maxcap); + FilterResultExtending ret; + ret.str.str = (wpos <= wcap && !unfiltered_chars) ? src.str : nullptr; + ret.str.len = wpos; + ret.reqlen = maxcap; + return ret; + } + C4_ALWAYS_INLINE csubstr sofar() const noexcept { return csubstr(src.str, wpos <= wcap ? wpos : wcap); } + C4_ALWAYS_INLINE csubstr rem() const noexcept { return src.sub(rpos); } + + C4_ALWAYS_INLINE char curr() const noexcept { _RYML_ASSERT_BASIC(rpos < src.len); return src[rpos]; } + C4_ALWAYS_INLINE char next() const noexcept { return rpos+1 < src.len ? src[rpos+1] : '\0'; } + + C4_ALWAYS_INLINE void skip() noexcept { ++rpos; } + C4_ALWAYS_INLINE void skip(size_t num) noexcept { rpos += num; } + + void set_at(size_t pos, char c) noexcept + { + _RYML_ASSERT_BASIC(pos < wpos); + const size_t save = wpos; + wpos = pos; + set(c); + wpos = save; + } + void set(char c) noexcept + { + if(wpos < wcap) // respect write-capacity + { + if((wpos <= rpos) && !unfiltered_chars) + src.str[wpos] = c; + } + else + { + _c4dbgip("inplace: add unwritten {}->{} maxcap={}->{}!", unfiltered_chars, true, maxcap, (wpos+1u > maxcap ? wpos+1u : maxcap)); + unfiltered_chars = true; + } + ++wpos; + maxcap = wpos > maxcap ? wpos : maxcap; + } + void set(char c, size_t num) noexcept + { + _RYML_ASSERT_BASIC(num); + if(wpos + num <= wcap) // respect write-capacity + { + if((wpos <= rpos) && !unfiltered_chars) + memset(src.str + wpos, c, num); + } + else + { + _c4dbgip("inplace: add unwritten {}->{} maxcap={}->{}!", unfiltered_chars, true, maxcap, (wpos+num > maxcap ? wpos+num : maxcap)); + unfiltered_chars = true; + } + wpos += num; + maxcap = wpos > maxcap ? wpos : maxcap; + } + + void copy() noexcept + { + _RYML_ASSERT_BASIC(rpos < src.len); + if(wpos < wcap) // respect write-capacity + { + if((wpos < rpos) && !unfiltered_chars) // write only if wpos is behind rpos + src.str[wpos] = src.str[rpos]; + } + else + { + _c4dbgip("inplace: add unwritten {}->{} (wpos={}!=rpos={})={} (wpos={}{}!", unfiltered_chars, true, wpos, rpos, wpos!=rpos, wpos, wcap, wpos maxcap ? wpos+1u : maxcap)); + unfiltered_chars = true; + } + ++rpos; + ++wpos; + maxcap = wpos > maxcap ? wpos : maxcap; + } + void copy(size_t num) noexcept + { + _RYML_ASSERT_BASIC(num); + _RYML_ASSERT_BASIC(rpos+num <= src.len); + if(wpos + num <= wcap) // respect write-capacity + { + if((wpos < rpos) && !unfiltered_chars) // write only if wpos is behind rpos + { + if(wpos + num <= rpos) // there is no overlap + memcpy(src.str + wpos, src.str + rpos, num); + else // there is overlap + memmove(src.str + wpos, src.str + rpos, num); + } + } + else + { + _c4dbgip("inplace: add unwritten {}->{} (wpos={}!=rpos={})={} (wpos={}{}!", unfiltered_chars, true, wpos, rpos, wpos!=rpos, wpos, wcap, wpos maxcap ? wpos : maxcap; + } + + void translate_esc(char c) noexcept + { + _RYML_ASSERT_BASIC(rpos + 2 <= src.len); + if(wpos < wcap) // respect write-capacity + { + if((wpos <= rpos) && !unfiltered_chars) + src.str[wpos] = c; + } + else + { + _c4dbgip("inplace: add unfiltered {}->{} maxcap={}->{}!", unfiltered_chars, true, maxcap, (wpos+1u > maxcap ? wpos+1u : maxcap)); + unfiltered_chars = true; + } + rpos += 2; + ++wpos; + maxcap = wpos > maxcap ? wpos : maxcap; + } + + C4_NO_INLINE void translate_esc_bulk(const char *C4_RESTRICT s, size_t nw, size_t nr) noexcept + { + _RYML_ASSERT_BASIC(nw > 0); + _RYML_ASSERT_BASIC(nr > 0); + _RYML_ASSERT_BASIC(nr+1u >= nw); + const size_t wpos_next = wpos + nw; + const size_t rpos_next = rpos + nr + 1u; // add 1u to account for the escape character + if(wpos_next <= wcap) // respect write-capacity + { + if((wpos <= rpos) && !unfiltered_chars) // write only if wpos is behind rpos + memcpy(src.str + wpos, s, nw); + } + else + { + _c4dbgip("inplace: add unwritten {}->{} (wpos={}!=rpos={})={} (wpos={}{}!", unfiltered_chars, true, wpos, rpos, wpos!=rpos, wpos, wcap, wpos maxcap ? wpos : maxcap; + } + + C4_NO_INLINE void translate_esc_extending(const char *C4_RESTRICT s, size_t nw, size_t nr) noexcept + { + _RYML_ASSERT_BASIC(nw > 0); + _RYML_ASSERT_BASIC(nr > 0); + _RYML_ASSERT_BASIC(rpos+nr <= src.len); + const size_t wpos_next = wpos + nw; + const size_t rpos_next = rpos + nr + 1u; // add 1u to account for the escape character + if(wpos_next <= rpos_next) // read and write do not overlap. just do a vanilla copy. + { + if((wpos_next <= wcap) && !unfiltered_chars) + memcpy(src.str + wpos, s, nw); + rpos = rpos_next; + wpos = wpos_next; + maxcap = wpos > maxcap ? wpos : maxcap; + } + else // there is overlap. move the (to-be-read) string to the right. + { + const size_t excess = wpos_next - rpos_next; + _RYML_ASSERT_BASIC(wpos_next > rpos_next); + if(src.len + excess <= wcap) // ensure we do not go past the end + { + _RYML_ASSERT_BASIC(rpos+nr+excess <= src.len); + if(wpos_next <= wcap) + { + if(!unfiltered_chars) + { + memmove(src.str + wpos_next, src.str + rpos_next, src.len - rpos_next); + memcpy(src.str + wpos, s, nw); + } + rpos = wpos_next; // wpos, not rpos + } + else + { + rpos = rpos_next; + //const size_t unw = nw > (nr + 1u) ? nw - (nr + 1u) : 0; + _c4dbgip("inplace: add unfiltered {}->{} maxcap={}->{}!", unfiltered_chars, true); + unfiltered_chars = true; + } + wpos = wpos_next; + // extend the string up to capacity + src.len += excess; + maxcap = wpos > maxcap ? wpos : maxcap; + } + else + { + //const size_t unw = nw > (nr + 1u) ? nw - (nr + 1u) : 0; + _RYML_ASSERT_BASIC(rpos_next <= src.len); + const size_t required_size = wpos_next + (src.len - rpos_next); + _c4dbgip("inplace: add unfiltered {}->{} maxcap={}->{}!", unfiltered_chars, true, maxcap, required_size > maxcap ? required_size : maxcap); + _RYML_ASSERT_BASIC(required_size > wcap); + unfiltered_chars = true; + maxcap = required_size > maxcap ? required_size : maxcap; + wpos = wpos_next; + rpos = rpos_next; + } + } + } +}; + +#undef _c4dbgip + + +/** @} */ + +} // namespace yml +} // namespace c4 + +#endif /* _C4_YML_FILTER_PROCESSOR_HPP_ */ + + +// (end src/c4/yml/filter_processor.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/parser_state.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_YML_PARSER_STATE_HPP_ +#define _C4_YML_PARSER_STATE_HPP_ + +#ifndef _C4_YML_ERROR_HPP_ +// amalgamate: removed include of +// c4/yml/error.hpp +//#include "c4/yml/error.hpp" +#if !defined(C4_YML_ERROR_HPP_) && !defined(_C4_YML_ERROR_HPP_) +#error "amalgamate: file c4/yml/error.hpp must have been included at this point" +#endif /* C4_YML_ERROR_HPP_ */ + +#endif + +// NOLINTBEGIN(hicpp-signed-bitwise) + +namespace c4 { +namespace yml { + +/** data type for @ref ParserState_e */ +using ParserFlag_t = int; + +/** Enumeration of the state flags for the parser */ +typedef enum : ParserFlag_t { // NOLINT + RTOP = 0x01 << 0, ///< reading at top level + RUNK = 0x01 << 1, ///< reading unknown state (when starting): must determine whether scalar, map or seq + RMAP = 0x01 << 2, ///< reading a map + RSEQ = 0x01 << 3, ///< reading a seq + RFLOW = 0x01 << 4, ///< reading is inside explicit flow chars: [] or {} + RBLCK = 0x01 << 5, ///< reading in block mode + QMRK = 0x01 << 6, ///< reading an explicit key (`? key`) + RKEY = 0x01 << 7, ///< reading a key + RVAL = 0x01 << 9, ///< reading a val + RKCL = 0x01 << 8, ///< reading the key colon (ie the : after the key in the map) + RNXT = 0x01 << 10, ///< read next sibling + SSCL = 0x01 << 11, ///< there's a stored scalar + QSCL = 0x01 << 12, ///< stored scalar was quoted + RSET = 0x01 << 13, ///< the (implicit) map being read is a !!set. @see https://yaml.org/type/set.html + RDOC = 0x01 << 14, ///< reading a document + NDOC = 0x01 << 15, ///< no document mode. a document has ended and another has not started yet. + USTY = 0x01 << 16, ///< reading in unknown style mode - must determine FLOW or BLCK + //! reading an implicit map nested in an explicit seq. + //! eg, {key: [key2: value2, key3: value3]} + //! is parsed as {key: [{key2: value2}, {key3: value3}]} + RSEQIMAP = 0x01 << 17, +} ParserState_e; + + +/** @cond dev */ +#ifdef RYML_DBG +namespace detail { +RYML_EXPORT csubstr _parser_flags_to_str(substr buf, ParserFlag_t flags); +} // namespace detail +#endif +/** @endcond */ + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** Helper to control the line contents while parsing a buffer */ +struct LineContents +{ + substr rem; ///< current line remainder, without newline characters + substr full; ///< full line, including newline characters `\n` and `\r` + size_t num_cols; ///< number of columns in the line, excluding newline + ///< characters (ie the initial size of rem) + size_t indentation; ///< number of spaces on the beginning of the line. + + LineContents() RYML_NOEXCEPT = default; + + void reset_with_next_line(substr buf, size_t start) RYML_NOEXCEPT + { + _RYML_ASSERT_BASIC(start <= buf.len); + size_t end = start; + // get the current line stripped of newline chars + while((end < buf.len) && (buf.str[end] != '\n')) + ++end; + if(end < buf.len) + { + _RYML_ASSERT_BASIC(buf[end] == '\n'); + full = buf.range(start, end + 1); + rem = buf.range(start, end); + } + else + { + // buffer ends without newline + full = rem = buf.sub(start); + } + size_t pos = rem.last_not_of('\r'); + rem.len = (pos != npos) ? pos + 1 : 0; + num_cols = rem.len; + _RYML_ASSERT_BASIC(rem.find('\r') == npos); + // TODO move this to the parser state + indentation = rem.first_not_of(' '); // find the first column where the character is not a space + } + + C4_ALWAYS_INLINE size_t current_col() const RYML_NOEXCEPT + { + _RYML_ASSERT_BASIC(rem.str >= full.str); + return static_cast(rem.str - full.str); + } + + C4_ALWAYS_INLINE size_t current_col(csubstr s) const RYML_NOEXCEPT + { + _RYML_ASSERT_BASIC(s.str >= full.str); + _RYML_ASSERT_BASIC(s.str <= rem.end()); + return static_cast(s.str - full.str); + } +}; +static_assert(std::is_standard_layout::value, "LineContents not standard"); + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +struct ParserState +{ + LineContents line_contents; + Location pos; + ParserFlag_t flags; + size_t indref; ///< the reference indentation in the current block scope + id_type level; + id_type node_id; ///< don't hold a pointer to the node as it will be relocated during tree resizes + size_t scalar_col; // the column where the scalar (or its quotes) begin + bool more_indented; + bool has_children; + + ParserState() = default; + + void start_parse(const char *file, id_type node_id_) + { + level = 0; + pos.name = to_csubstr(file); + pos.offset = 0; + pos.line = 1; + pos.col = 1; + node_id = node_id_; + more_indented = false; + scalar_col = 0; + indref = 0; + has_children = false; + } + + void reset_after_push() + { + node_id = NONE; + indref = npos; + more_indented = false; + ++level; + has_children = false; + } + + C4_ALWAYS_INLINE void reset_before_pop(ParserState const& to_pop) + { + pos = to_pop.pos; + line_contents = to_pop.line_contents; + } + +public: + + C4_ALWAYS_INLINE bool at_line_beginning() const noexcept + { + return line_contents.rem.str == line_contents.full.str; + } + C4_ALWAYS_INLINE bool at_first_token() const noexcept + { + _RYML_ASSERT_BASIC(line_contents.indentation != npos); + return pos.col == line_contents.indentation + 1; + } + C4_ALWAYS_INLINE bool indentation_eq() const noexcept + { + _RYML_ASSERT_BASIC(indref != npos); + return line_contents.indentation != npos + && line_contents.indentation == indref; + } + C4_ALWAYS_INLINE bool indentation_eq_extra() const noexcept + { + _RYML_ASSERT_BASIC(indref != npos); + return line_contents.indentation != npos + && line_contents.indentation == indref + 1u; + } + C4_ALWAYS_INLINE bool indentation_ge() const noexcept + { + _RYML_ASSERT_BASIC(indref != npos); + return line_contents.indentation != npos + && line_contents.indentation >= indref; + } + C4_ALWAYS_INLINE bool indentation_ge_extra() const noexcept + { + _RYML_ASSERT_BASIC(indref != npos); + return line_contents.indentation != npos + && line_contents.indentation >= indref + 1u; + } + C4_ALWAYS_INLINE bool indentation_gt() const noexcept + { + _RYML_ASSERT_BASIC(indref != npos); + return line_contents.indentation != npos + && line_contents.indentation > indref; + } + C4_ALWAYS_INLINE bool indentation_gt_extra() const noexcept + { + _RYML_ASSERT_BASIC(indref != npos); + return line_contents.indentation != npos + && line_contents.indentation > indref + 1u; + } + C4_ALWAYS_INLINE bool indentation_lt() const noexcept + { + _RYML_ASSERT_BASIC(indref != npos); + return line_contents.indentation != npos && line_contents.indentation < indref; + } + C4_ALWAYS_INLINE bool indentation_lt_extra() const noexcept + { + _RYML_ASSERT_BASIC(indref != npos); + return line_contents.indentation != npos + && line_contents.indentation < indref + 1u; + } +}; +static_assert(std::is_standard_layout::value, "ParserState not standard"); + + +} // namespace yml +} // namespace c4 + +// NOLINTEND(hicpp-signed-bitwise) + +#endif /* _C4_YML_PARSER_STATE_HPP_ */ + + +// (end src/c4/yml/parser_state.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/event_handler_stack.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_YML_EVENT_HANDLER_STACK_HPP_ +#define _C4_YML_EVENT_HANDLER_STACK_HPP_ + +#ifndef _C4_YML_DETAIL_STACK_HPP_ +// amalgamate: removed include of +// c4/yml/detail/stack.hpp +//#include "c4/yml/detail/stack.hpp" +#if !defined(C4_YML_DETAIL_STACK_HPP_) && !defined(_C4_YML_DETAIL_STACK_HPP_) +#error "amalgamate: file c4/yml/detail/stack.hpp must have been included at this point" +#endif /* C4_YML_DETAIL_STACK_HPP_ */ + +#endif + +#ifndef _C4_YML_NODE_TYPE_HPP_ +// amalgamate: removed include of +// c4/yml/node_type.hpp +//#include "c4/yml/node_type.hpp" +#if !defined(C4_YML_NODE_TYPE_HPP_) && !defined(_C4_YML_NODE_TYPE_HPP_) +#error "amalgamate: file c4/yml/node_type.hpp must have been included at this point" +#endif /* C4_YML_NODE_TYPE_HPP_ */ + +#endif + +#ifndef _C4_YML_DETAIL_DBGPRINT_HPP_ +// amalgamate: removed include of +// c4/yml/detail/dbgprint.hpp +//#include "c4/yml/detail/dbgprint.hpp" +#if !defined(C4_YML_DETAIL_DBGPRINT_HPP_) && !defined(_C4_YML_DETAIL_DBGPRINT_HPP_) +#error "amalgamate: file c4/yml/detail/dbgprint.hpp must have been included at this point" +#endif /* C4_YML_DETAIL_DBGPRINT_HPP_ */ + +#endif + +#ifndef _C4_YML_PARSER_STATE_HPP_ +// amalgamate: removed include of +// c4/yml/parser_state.hpp +//#include "c4/yml/parser_state.hpp" +#if !defined(C4_YML_PARSER_STATE_HPP_) && !defined(_C4_YML_PARSER_STATE_HPP_) +#error "amalgamate: file c4/yml/parser_state.hpp must have been included at this point" +#endif /* C4_YML_PARSER_STATE_HPP_ */ + +#endif + +#ifdef RYML_DBG +#ifndef _C4_YML_DETAIL_PRINT_HPP_ +// amalgamate: removed include of +// c4/yml/detail/print.hpp +//#include "c4/yml/detail/print.hpp" +#if !defined(C4_YML_DETAIL_PRINT_HPP_) && !defined(_C4_YML_DETAIL_PRINT_HPP_) +#error "amalgamate: file c4/yml/detail/print.hpp must have been included at this point" +#endif /* C4_YML_DETAIL_PRINT_HPP_ */ + +#endif +#endif + +// NOLINTBEGIN(hicpp-signed-bitwise) + +namespace c4 { +namespace yml { + +/** @addtogroup doc_event_handlers + * @{ */ + +/** Use this class a base of implementations of event handler to + * simplify the stack logic. */ +template +struct EventHandlerStack +{ + static_assert(std::is_base_of::value, + "ParserState must be a base of HandlerState"); + + using state = HandlerState; + +public: + + detail::stack m_stack; + state *C4_RESTRICT m_curr; ///< current stack level: top of the stack. cached here for easier access. + state *C4_RESTRICT m_parent; ///< parent of the current stack level. + substr m_src; + +protected: + + EventHandlerStack() : m_stack(), m_curr(), m_parent(), m_src() {} // NOLINT + EventHandlerStack(Callbacks const& cb) : m_stack(cb), m_curr(), m_parent(), m_src() {} // NOLINT + +protected: + + void _stack_start_parse(const char *filename, substr ymlsrc) + { + _RYML_ASSERT_BASIC_(m_stack.m_callbacks, m_curr != nullptr); + m_curr->start_parse(filename, m_curr->node_id); + m_src = ymlsrc; + } + + void _stack_finish_parse() + { + } + +protected: + + void _stack_reset_root() + { + m_stack.clear(); + m_stack.push({}); + m_parent = nullptr; + m_curr = &m_stack.top(); + } + + void _stack_reset_non_root() + { + m_stack.clear(); + m_stack.push({}); // parent + m_stack.push({}); // node + m_parent = &m_stack.top(1); + m_curr = &m_stack.top(); + } + + void _stack_push() + { + m_stack.push_top(); + m_parent = &m_stack.top(1); // don't use m_curr. watch out for relocations inside the prev push + m_curr = &m_stack.top(); + m_curr->reset_after_push(); + } + + void _stack_pop() + { + _RYML_ASSERT_BASIC_(m_stack.m_callbacks, m_parent); + _RYML_ASSERT_BASIC_(m_stack.m_callbacks, m_stack.size() > 1); + m_parent->reset_before_pop(*m_curr); + m_stack.pop(); + m_parent = m_stack.size() > 1 ? &m_stack.top(1) : nullptr; + m_curr = &m_stack.top(); + #ifdef RYML_DBG + if(m_parent) + _c4dbgpf("popped! top is now node={} (parent={})", m_curr->node_id, m_parent->node_id); + else + _c4dbgpf("popped! top is now node={} @ ROOT", m_curr->node_id); + #endif + } + +protected: + + // undefined below + #define _has_any_(bits) (static_cast(this)->template _has_any__()) + + // FIXME. Not happy about where these functions are. They should + // be defined and called by the parser, passing the bool result to + // begin_doc()/end_doc() as well as begin_doc_expl()/end_doc_expl(). + + bool _stack_should_push_on_begin_doc() const + { + const bool is_root = (m_stack.size() == 1u); + return is_root && (m_curr->has_children || _has_any_(DOC|VAL|MAP|SEQ)); + } + + bool _stack_should_pop_on_end_doc() const + { + const bool is_root = (m_stack.size() == 1u); + return !is_root && _has_any_(DOC); + } + + #undef _has_any_ + +}; + +/** @} */ + +} // namespace yml +} // namespace c4 + +// NOLINTEND(hicpp-signed-bitwise) + +#endif /* _C4_YML_EVENT_HANDLER_STACK_HPP_ */ + + +// (end src/c4/yml/event_handler_stack.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/event_handler_tree.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_YML_EVENT_HANDLER_TREE_HPP_ +#define _C4_YML_EVENT_HANDLER_TREE_HPP_ + +#ifndef _C4_YML_TREE_HPP_ +// amalgamate: removed include of +// c4/yml/tree.hpp +//#include "c4/yml/tree.hpp" +#if !defined(C4_YML_TREE_HPP_) && !defined(_C4_YML_TREE_HPP_) +#error "amalgamate: file c4/yml/tree.hpp must have been included at this point" +#endif /* C4_YML_TREE_HPP_ */ + +#endif + +#ifndef _C4_YML_EVENT_HANDLER_STACK_HPP_ +// amalgamate: removed include of +// c4/yml/event_handler_stack.hpp +//#include "c4/yml/event_handler_stack.hpp" +#if !defined(C4_YML_EVENT_HANDLER_STACK_HPP_) && !defined(_C4_YML_EVENT_HANDLER_STACK_HPP_) +#error "amalgamate: file c4/yml/event_handler_stack.hpp must have been included at this point" +#endif /* C4_YML_EVENT_HANDLER_STACK_HPP_ */ + +#endif + +C4_SUPPRESS_WARNING_GCC_PUSH +C4_SUPPRESS_WARNING_MSVC_PUSH +C4_SUPPRESS_WARNING_MSVC(4702) // unreachable code +#if defined(__GNUC__) && __GNUC__ >= 6 +C4_SUPPRESS_WARNING_GCC("-Wnull-dereference") +#endif + +// NOLINTBEGIN(hicpp-signed-bitwise) + +namespace c4 { +namespace yml { + +/** @addtogroup doc_event_handlers + * @{ */ + + +/** @cond dev */ +struct EventHandlerTreeState : public ParserState +{ + NodeData *tr_data; +}; +/** @endcond */ + + +/** The event handler to create a ryml @ref Tree. See the + * documentation for @ref doc_event_handlers, which has important + * notes about the event model used by rapidyaml. */ +struct EventHandlerTree : public EventHandlerStack +{ + + /** @name types + * @{ */ + + using state = EventHandlerTreeState; + enum { requires_strings_on_buffers = false }; + + /** @} */ + +public: + + /** @cond dev */ + Tree *C4_RESTRICT m_tree; + id_type m_curr_doc; + TagCache m_tag_cache; + + #ifdef RYML_DBG + #define _enable_(bits) _enable__(); _c4dbgpf("node[{}]: enable {}", m_curr->node_id, #bits) + #define _disable_(bits) _disable__(); _c4dbgpf("node[{}]: disable {}", m_curr->node_id, #bits) + #else + #define _enable_(bits) _enable__() + #define _disable_(bits) _disable__() + #endif + #define _has_any_(bits) _has_any__() + /** @endcond */ + +public: + + /** @name construction and resetting + * @{ */ + + EventHandlerTree() : EventHandlerStack(), m_tree(), m_curr_doc() {} + EventHandlerTree(Callbacks const& cb) : EventHandlerStack(cb), m_tree(), m_curr_doc() {} + EventHandlerTree(Tree *tree, id_type id) : EventHandlerStack(tree->callbacks()), m_tree(tree), m_curr_doc() + { + reset(tree, id); + } + + void reset(Tree *tree, id_type id) + { + if(C4_UNLIKELY(!tree)) + _RYML_ERR_BASIC_(m_stack.m_callbacks, "null tree"); + if(C4_UNLIKELY(id >= tree->capacity())) + _RYML_ERR_BASIC_(tree->callbacks(), "invalid node"); + if(C4_UNLIKELY(!tree->is_root(id))) + if(C4_UNLIKELY(tree->is_map(tree->parent(id)))) + if(C4_UNLIKELY(!tree->has_key(id))) + _RYML_ERR_BASIC_(tree->callbacks(), "destination node belongs to a map and has no key"); + m_tree = tree; + if(m_tree->is_root(id)) + { + _stack_reset_root(); + _reset_parser_state(m_curr, id, m_tree->root_id()); + } + else + { + _stack_reset_non_root(); + _reset_parser_state(m_parent, id, m_tree->parent(id)); + _reset_parser_state(m_curr, id, id); + } + m_curr_doc = m_tree->ancestor_doc(id); + m_tag_cache.clear(); + } + + Callbacks const& callbacks() const { return m_stack.m_callbacks; } + + C4_ALWAYS_INLINE TagDirectives& tag_directives() { return m_tree->m_tag_directives; } // NOLINT(readability-make-member-function-const) + C4_ALWAYS_INLINE TagCache &tag_cache() { return m_tag_cache; } + + /** @} */ + +public: + + /** @name parse events + * @{ */ + + void start_parse(const char* filename, substr ymlsrc) + { + _RYML_ASSERT_BASIC_(m_stack.m_callbacks, m_tree != nullptr); + this->_stack_start_parse(filename, ymlsrc); + } + + void finish_parse() + { + _RYML_ASSERT_BASIC_(m_stack.m_callbacks, m_tree != nullptr); + this->_stack_finish_parse(); + /* This pointer is temporary. Remember that: + * + * - this handler object may be held by the user + * - it may be used with a temporary tree inside the parse function + * - when the parse function returns the temporary tree, its address + * will change + * + * As a result, the user could try to read the tree from m_tree, and + * end up reading the stale temporary object. + * + * So it is better to clear it here; then the user will get an obvious + * segfault if reading from m_tree. */ + m_tree = nullptr; + } + + void cancel_parse() + { + m_tree = nullptr; + } + + /** @} */ + +public: + + /** @name YAML stream events */ + /** @{ */ + + C4_ALWAYS_INLINE void begin_stream() const noexcept { /* nothing to do */ } + + C4_ALWAYS_INLINE void end_stream() const noexcept { /* nothing to do */ } + + /** @} */ + +public: + + /** @name YAML document events */ + /** @{ */ + + /** implicit doc start (without ---) */ + void begin_doc() + { + _c4dbgp("begin_doc"); + if(_stack_should_push_on_begin_doc()) + { + _c4dbgp("push!"); + _set_root_as_stream(); + _push(); + _enable_(DOC); + } + m_curr_doc = m_curr->node_id; + } + /** implicit doc end (without ...) */ + void end_doc() + { + _c4dbgp("end_doc"); + m_curr_doc = m_tree->size(); + if(_stack_should_pop_on_end_doc()) + { + _remove_speculative(); + _c4dbgp("pop!"); + _pop(); + } + } + + /** explicit doc start, with --- */ + void begin_doc_expl() + { + _c4dbgp("begin_doc_expl"); + _RYML_ASSERT_VISIT_(m_stack.m_callbacks, m_tree->root_id() == m_curr->node_id, m_tree, m_curr->node_id); + if(m_tree->is_stream(m_tree->root_id())) //if(_should_push_on_begin_doc()) + { + _c4dbgp("push!"); + _push(); + } + else + { + _c4dbgp("ensure stream"); + _set_root_as_stream(); + const id_type root = m_tree->root_id(); + const id_type first = m_tree->first_child(root); + _RYML_ASSERT_VISIT_(m_stack.m_callbacks, m_tree->is_stream(root), m_tree, root); + _RYML_ASSERT_VISIT_(m_stack.m_callbacks, m_tree->num_children(root) == 1u, m_tree, root); + if(m_tree->is_container(first) || m_tree->is_val(first)) + { + _c4dbgp("push!"); + _push(); + #ifdef RYML_WITH_COMMENTS + m_tree->_p(root)->m_first_comment = NONE; + m_tree->_p(root)->m_last_comment = NONE; + #endif + } + else + { + _c4dbgp("tweak"); + _push(); + _remove_speculative(); + m_curr->node_id = m_tree->last_child(root); + m_curr->tr_data = m_tree->_p(m_curr->node_id); + } + } + _enable_(DOC); + m_curr_doc = m_curr->node_id; + } + /** explicit doc end, with ... */ + void end_doc_expl() + { + _c4dbgp("end_doc_expl"); + m_curr_doc = m_tree->size(); + _remove_speculative(); + if(_stack_should_pop_on_end_doc()) + { + _c4dbgp("pop!"); + _pop(); + } + } + + /** @} */ + +public: + + /** @name YAML map events */ + /** @{ */ + + C4_NORETURN void begin_map_key_flow() + { + _RYML_ERR_PARSE_(m_stack.m_callbacks, m_curr->pos, "ryml trees cannot handle containers as keys"); + } + C4_NORETURN void begin_map_key_block() + { + _RYML_ERR_PARSE_(m_stack.m_callbacks, m_curr->pos, "ryml trees cannot handle containers as keys"); + } + + void begin_map_val_flow() + { + _c4dbgpf("node[{}]: begin_map_val_flow", m_curr->node_id); + _RYML_CHECK_BASIC_(m_stack.m_callbacks, !_has_any_(VAL)); + _enable_(MAP|FLOW_SL); + _save_loc(); + _push(); + } + void begin_map_val_block() + { + _c4dbgpf("node[{}]: begin_map_val_block", m_curr->node_id); + _RYML_CHECK_BASIC_(m_stack.m_callbacks, !_has_any_(VAL)); + _enable_(MAP|BLOCK); + _save_loc(); + _push(); + } + + void end_map_block() + { + _c4dbgpf("node[{}]: end_map_block", m_parent->node_id, m_parent->pos.line, m_curr->pos.line); + _pop(); + } + + void end_map_flow(bool multiline, NodeType_e multiline_style=FLOW_ML1) + { + _c4dbgpf("node[{}]: end_map. multiline={} startline={} endline={}", m_parent->node_id, multiline, m_parent->pos.line, m_curr->pos.line); + _pop(); + if(multiline) + { + _disable_(FLOW_SL); + _enable__(multiline_style); + } + } + + /** @} */ + +public: + + /** @name YAML seq events */ + /** @{ */ + + C4_NORETURN void begin_seq_key_flow() + { + _RYML_ERR_PARSE_(m_stack.m_callbacks, m_curr->pos, "ryml trees cannot handle containers as keys"); + } + C4_NORETURN void begin_seq_key_block() + { + _RYML_ERR_PARSE_(m_stack.m_callbacks, m_curr->pos, "ryml trees cannot handle containers as keys"); + } + + void begin_seq_val_flow() + { + _c4dbgpf("node[{}]: begin_seq_val_flow", m_curr->node_id); + _RYML_CHECK_BASIC_(m_stack.m_callbacks, !_has_any_(VAL)); + _enable_(SEQ|FLOW_SL); + _save_loc(); + _push(); + } + void begin_seq_val_block() + { + _c4dbgpf("node[{}]: begin_seq_val_block", m_curr->node_id); + _RYML_CHECK_BASIC_(m_stack.m_callbacks, !_has_any_(VAL)); + _enable_(SEQ|BLOCK); + _save_loc(); + _push(); + } + + void end_seq_block() + { + _c4dbgpf("node[{}]: end_seq_block", m_parent->node_id, m_parent->pos.line, m_curr->pos.line); + _pop(); + } + + void end_seq_flow(bool multiline, NodeType_e multiline_style=FLOW_ML1) + { + _c4dbgpf("node[{}]: end_seq. multiline={} startline={} endline={}", m_parent->node_id, multiline, m_parent->pos.line, m_curr->pos.line); + _pop(); + if(multiline) + { + _disable_(FLOW_SL); + _enable__(multiline_style); + } + } + + /** @} */ + +public: + + /** @name YAML structure events */ + /** @{ */ + + void add_sibling() + { + _RYML_ASSERT_BASIC_(m_stack.m_callbacks, m_tree); + _RYML_ASSERT_BASIC_(m_stack.m_callbacks, m_parent); + _RYML_ASSERT_VISIT_(m_stack.m_callbacks, m_tree->has_children(m_parent->node_id), m_tree, m_parent->node_id); + NodeData const* const prev = m_tree->m_buf; // watchout against relocation of the tree nodes + _set_state_(m_curr, m_tree->_append_child__unprotected(m_parent->node_id)); + if(prev != m_tree->m_buf) + _refresh_after_relocation(); + _c4dbgpf("node[{}]: added sibling={} prev={}", m_parent->node_id, m_curr->node_id, m_tree->prev_sibling(m_curr->node_id)); + } + + /** reset the previous val as the first key of a new map, with flow style. + * + * See the documentation for @ref doc_event_handlers, which has + * important notes about this event. + */ + void actually_val_is_first_key_of_new_map_flow() + { + if(C4_UNLIKELY(m_tree->is_container(m_curr->node_id))) + _RYML_ERR_PARSE_(m_stack.m_callbacks, m_curr->pos, "ryml trees cannot handle containers as keys"); + _RYML_ASSERT_BASIC_(m_stack.m_callbacks, m_parent); + _RYML_ASSERT_VISIT_(m_stack.m_callbacks, m_tree->is_seq(m_parent->node_id), m_tree, m_parent->node_id); + _RYML_ASSERT_VISIT_(m_stack.m_callbacks, !m_tree->is_container(m_curr->node_id), m_tree, m_curr->node_id); + _RYML_ASSERT_VISIT_(m_stack.m_callbacks, !m_tree->has_key(m_curr->node_id), m_tree, m_curr->node_id); + const NodeData tmp = _val2key_(*m_curr->tr_data); + _disable_(_VALMASK|VAL_STYLE|VALNIL); + m_curr->tr_data->m_val = {}; + begin_map_val_flow(); + m_curr->tr_data->m_type = tmp.m_type; + m_curr->tr_data->m_key = tmp.m_key; + } + + /** like its flow counterpart, but this function can only be + * called after the end of a flow-val at root or doc level. + * + * See the documentation for @ref doc_event_handlers, which has + * important notes about this event. + */ + C4_NORETURN void actually_val_is_first_key_of_new_map_block() + { + _RYML_ERR_PARSE_(m_stack.m_callbacks, m_curr->pos, "ryml trees cannot handle containers as keys"); + } + + /** @} */ + +public: + + /** @name YAML scalar events */ + /** @{ */ + + + C4_ALWAYS_INLINE void set_key_scalar_plain_empty() noexcept + { + _c4dbgpf("node[{}]: set key scalar plain as empty", m_curr->node_id); + m_curr->tr_data->m_key.scalar = {}; + _enable_(KEY|KEY_PLAIN|KEYNIL); + } + C4_ALWAYS_INLINE void set_val_scalar_plain_empty() noexcept + { + _c4dbgpf("node[{}]: set val scalar plain as empty", m_curr->node_id); + m_curr->tr_data->m_val.scalar = {}; + _enable_(VAL|VAL_PLAIN|VALNIL); + } + + C4_ALWAYS_INLINE void set_key_scalar_plain(csubstr scalar) noexcept + { + _c4dbgpf("node[{}]: set key scalar plain: [{}]~~~{}~~~", m_curr->node_id, scalar.len, scalar); + m_curr->tr_data->m_key.scalar = scalar; + _enable_(KEY|KEY_PLAIN); + } + C4_ALWAYS_INLINE void set_val_scalar_plain(csubstr scalar) noexcept + { + _c4dbgpf("node[{}]: set val scalar plain: [{}]~~~{}~~~", m_curr->node_id, scalar.len, scalar); + m_curr->tr_data->m_val.scalar = scalar; + _enable_(VAL|VAL_PLAIN); + } + + + C4_ALWAYS_INLINE void set_key_scalar_dquoted(csubstr scalar) noexcept + { + _c4dbgpf("node[{}]: set key scalar dquot: [{}]~~~{}~~~", m_curr->node_id, scalar.len, scalar); + m_curr->tr_data->m_key.scalar = scalar; + _enable_(KEY|KEY_DQUO); + } + C4_ALWAYS_INLINE void set_val_scalar_dquoted(csubstr scalar) noexcept + { + _c4dbgpf("node[{}]: set val scalar dquot: [{}]~~~{}~~~", m_curr->node_id, scalar.len, scalar); + m_curr->tr_data->m_val.scalar = scalar; + _enable_(VAL|VAL_DQUO); + } + + + C4_ALWAYS_INLINE void set_key_scalar_squoted(csubstr scalar) noexcept + { + _c4dbgpf("node[{}]: set key scalar squot: [{}]~~~{}~~~", m_curr->node_id, scalar.len, scalar); + m_curr->tr_data->m_key.scalar = scalar; + _enable_(KEY|KEY_SQUO); + } + C4_ALWAYS_INLINE void set_val_scalar_squoted(csubstr scalar) noexcept + { + _c4dbgpf("node[{}]: set val scalar squot: [{}]~~~{}~~~", m_curr->node_id, scalar.len, scalar); + m_curr->tr_data->m_val.scalar = scalar; + _enable_(VAL|VAL_SQUO); + } + + + C4_ALWAYS_INLINE void set_key_scalar_literal(csubstr scalar) noexcept + { + _c4dbgpf("node[{}]: set key scalar literal: [{}]~~~{}~~~", m_curr->node_id, scalar.len, scalar); + m_curr->tr_data->m_key.scalar = scalar; + _enable_(KEY|KEY_LITERAL); + } + C4_ALWAYS_INLINE void set_val_scalar_literal(csubstr scalar) noexcept + { + _c4dbgpf("node[{}]: set val scalar literal: [{}]~~~{}~~~", m_curr->node_id, scalar.len, scalar); + m_curr->tr_data->m_val.scalar = scalar; + _enable_(VAL|VAL_LITERAL); + } + + + C4_ALWAYS_INLINE void set_key_scalar_folded(csubstr scalar) noexcept + { + _c4dbgpf("node[{}]: set key scalar folded: [{}]~~~{}~~~", m_curr->node_id, scalar.len, scalar); + m_curr->tr_data->m_key.scalar = scalar; + _enable_(KEY|KEY_FOLDED); + } + C4_ALWAYS_INLINE void set_val_scalar_folded(csubstr scalar) noexcept + { + _c4dbgpf("node[{}]: set val scalar folded: [{}]~~~{}~~~", m_curr->node_id, scalar.len, scalar); + m_curr->tr_data->m_val.scalar = scalar; + _enable_(VAL|VAL_FOLDED); + } + + + C4_ALWAYS_INLINE void mark_key_scalar_unfiltered() noexcept + { + _enable_(KEY_UNFILT); + } + C4_ALWAYS_INLINE void mark_val_scalar_unfiltered() noexcept + { + _enable_(VAL_UNFILT); + } + + /** @} */ + +public: + + /** @name YAML anchor/reference events */ + /** @{ */ + + void set_key_anchor(csubstr anchor) + { + _c4dbgpf("node[{}]: set key anchor: [{}]~~~{}~~~", m_curr->node_id, anchor.len, anchor); + _RYML_ASSERT_BASIC_(m_stack.m_callbacks, m_tree); + _RYML_ASSERT_BASIC_(m_stack.m_callbacks, !_has_any_(KEYREF)); + _RYML_ASSERT_PARSE_(m_tree->callbacks(), !anchor.begins_with('&'), m_curr->pos); + _enable_(KEYANCH); + m_curr->tr_data->m_key.anchor = anchor; + } + void set_val_anchor(csubstr anchor) + { + _c4dbgpf("node[{}]: set val anchor: [{}]~~~{}~~~", m_curr->node_id, anchor.len, anchor); + _RYML_ASSERT_BASIC_(m_stack.m_callbacks, m_tree); + _RYML_ASSERT_BASIC_(m_stack.m_callbacks, !_has_any_(VALREF)); + _RYML_ASSERT_PARSE_(m_tree->callbacks(), !anchor.begins_with('&'), m_curr->pos); + _enable_(VALANCH); + m_curr->tr_data->m_val.anchor = anchor; + } + + void set_key_ref(csubstr ref) + { + _c4dbgpf("node[{}]: set key ref: [{}]~~~{}~~~", m_curr->node_id, ref.len, ref); + _RYML_ASSERT_BASIC_(m_stack.m_callbacks, m_tree); + if(C4_UNLIKELY(_has_any_(KEYANCH))) + _RYML_ERR_PARSE_(m_tree->callbacks(), m_curr->pos, "key cannot have both anchor and ref"); + _RYML_ASSERT_PARSE_(m_tree->callbacks(), ref.begins_with('*'), m_curr->pos); + _enable_(KEY|KEYREF); + m_curr->tr_data->m_key.anchor = ref.sub(1); + m_curr->tr_data->m_key.scalar = ref; + } + void set_val_ref(csubstr ref) + { + _c4dbgpf("node[{}]: set val ref: [{}]~~~{}~~~", m_curr->node_id, ref.len, ref); + _RYML_ASSERT_BASIC_(m_stack.m_callbacks, m_tree); + if(C4_UNLIKELY(_has_any_(VALANCH))) + _RYML_ERR_PARSE_(m_tree->callbacks(), m_curr->pos, "val cannot have both anchor and ref"); + _RYML_ASSERT_PARSE_(m_tree->callbacks(), ref.begins_with('*'), m_curr->pos); + _enable_(VAL|VALREF); + m_curr->tr_data->m_val.anchor = ref.sub(1); + m_curr->tr_data->m_val.scalar = ref; + } + + /** @} */ + +public: + + /** @name YAML tag events */ + /** @{ */ + + void set_key_tag(csubstr tag) + { + _c4dbgpf("node[{}]: set key tag: [{}]~~~{}~~~", m_curr->node_id, tag.len, tag); + _enable_(KEYTAG); + m_curr->tr_data->m_key.tag = tag; + } + void set_val_tag(csubstr tag) + { + _c4dbgpf("node[{}]: set val tag: [{}]~~~{}~~~", m_curr->node_id, tag.len, tag); + _enable_(VALTAG); + m_curr->tr_data->m_val.tag = tag; + } + + /** @} */ + +public: + + /** @name YAML directive events */ + /** @{ */ + + void add_directive_yaml(csubstr yaml_version) // NOLINT(readability-convert-member-functions-to-static) + { + _c4dbgpf("%YAML directive! version={}", yaml_version); + (void)yaml_version; + } + + void add_directive_tag(csubstr handle, csubstr prefix) + { + _c4dbgpf("%TAG directive! handle={} prefix={} id={}", handle, prefix, m_curr_doc); + if(C4_UNLIKELY(!m_tree->m_tag_directives.add(handle, prefix, m_curr_doc))) + _RYML_ERR_PARSE_(m_stack.m_callbacks, m_curr->pos, "too many %TAG directives"); + } + + /** @} */ + +public: + + /** @name arena functions */ + /** @{ */ + + substr arena() + { + _RYML_ASSERT_BASIC_(m_stack.m_callbacks, m_tree); + return m_tree->m_arena.first(m_tree->m_arena_pos); + } + substr arena_rem() + { + _RYML_ASSERT_BASIC_(m_stack.m_callbacks, m_tree); + return m_tree->m_arena.sub(m_tree->m_arena_pos); + } + substr alloc_arena(size_t len) // NOLINT(readability-make-member-function-const) + { + return m_tree->alloc_arena(len); + } + + /** @} */ + +public: + + /** @cond dev */ + void _reset_parser_state(state* st, id_type parse_root, id_type node) + { + _RYML_ASSERT_BASIC_(m_stack.m_callbacks, m_tree); + _set_state_(st, node); + const NodeType type = m_tree->type(node); + #ifdef RYML_DBG + char flagbuf[80]; + _c4dbgpf("resetting state: initial flags={}", detail::_parser_flags_to_str(flagbuf, st->flags)); + #endif + if(type == NOTYPE) + { + _c4dbgpf("node[{}] is notype", node); + if(m_tree->is_root(parse_root)) + { + _c4dbgpf("node[{}] is root", node); + st->flags |= RUNK|RTOP; + } + else + { + _c4dbgpf("node[{}] is not root. setting USTY", node); + st->flags |= USTY; + } + } + else if(type.is_map()) + { + _c4dbgpf("node[{}] is map", node); + st->flags |= RMAP|USTY; + } + else if(type.is_seq()) + { + _c4dbgpf("node[{}] is map", node); + st->flags |= RSEQ|USTY; + } + else if(type.has_key()) + { + _c4dbgpf("node[{}] has key. setting USTY", node); + st->flags |= USTY; + } + else + { + _RYML_ERR_VISIT_(m_tree->callbacks(), m_tree, node, "cannot append to node"); + } + if(type.is_doc()) + { + _c4dbgpf("node[{}] is doc", node); + st->flags |= RDOC; + } + #ifdef RYML_DBG + _c4dbgpf("resetting state: final flags={}", detail::_parser_flags_to_str(flagbuf, st->flags)); + #endif + } + + /** push a new parent, add a child to the new parent, and set the + * child as the current node */ + void _push() + { + _stack_push(); + NodeData const* prev = m_tree->m_buf; // watch out against relocation of the tree nodes + m_curr->node_id = m_tree->_append_child__unprotected(m_parent->node_id); + m_curr->tr_data = m_tree->_p(m_curr->node_id); + if(prev != m_tree->m_buf) + _refresh_after_relocation(); + _c4dbgpf("pushed! level={}. top is now node={} (parent={})", m_curr->level, m_curr->node_id, m_parent ? m_parent->node_id : NONE); + } + /** end the current scope */ + void _pop() + { + _remove_speculative_with_parent(); + _stack_pop(); + } + +public: + + C4_ALWAYS_INLINE void _enable__(type_bits bits) noexcept + { + m_curr->tr_data->m_type.type = static_cast(m_curr->tr_data->m_type.type | bits); + } + template C4_HOT C4_ALWAYS_INLINE void _enable__() noexcept + { + m_curr->tr_data->m_type.type = static_cast(m_curr->tr_data->m_type.type | bits); + } + template C4_HOT C4_ALWAYS_INLINE void _disable__() noexcept + { + m_curr->tr_data->m_type.type = static_cast(m_curr->tr_data->m_type.type & (~bits)); + } + template C4_HOT C4_ALWAYS_INLINE bool _has_any__() const noexcept + { + return (m_curr->tr_data->m_type.type & bits) != 0; + } + +public: + + C4_ALWAYS_INLINE void _set_state_(state *C4_RESTRICT s, id_type id) const noexcept + { + s->node_id = id; + s->tr_data = m_tree->_p(id); + } + void _refresh_after_relocation() + { + _c4dbgp("tree: refreshing stack data after tree data relocation"); + for(auto &st : m_stack) + st.tr_data = m_tree->_p(st.node_id); + } + + void _set_root_as_stream() + { + _c4dbgp("set root as stream"); + _RYML_ASSERT_VISIT_(m_tree->callbacks(), m_tree->root_id() == 0u, m_tree, m_tree->root_id()); + _RYML_ASSERT_VISIT_(m_tree->callbacks(), m_curr->node_id == 0u, m_tree, m_curr->node_id); + m_tree->set_root_as_stream(); + _RYML_ASSERT_VISIT_(m_tree->callbacks(), m_tree->is_stream(m_tree->root_id()), m_tree, m_tree->root_id()); + _RYML_ASSERT_VISIT_(m_tree->callbacks(), m_tree->has_children(m_tree->root_id()), m_tree, m_tree->root_id()); + _RYML_ASSERT_VISIT_(m_tree->callbacks(), m_tree->is_doc(m_tree->first_child(m_tree->root_id())), m_tree, m_tree->root_id()); + _set_state_(m_curr, m_tree->root_id()); + } + + static NodeData _val2key_(NodeData const& C4_RESTRICT d) noexcept + { + NodeData r = d; + r.m_key = d.m_val; + r.m_val = {}; + r.m_type = d.m_type; + static_assert((_VALMASK >> 1u) == _KEYMASK, "required for this function to work"); + static_assert((VAL_STYLE >> 1u) == KEY_STYLE, "required for this function to work"); + r.m_type.type = ((d.m_type.type & (_VALMASK|VAL_STYLE)) >> 1u); + r.m_type.type = (r.m_type.type & ~(_VALMASK|VAL_STYLE)); + r.m_type.type = (r.m_type.type | KEY); + if(d.m_type.type & VALNIL) + r.m_type.type = (r.m_type.type | KEYNIL); + return r; + } + + void _remove_speculative() + { + _RYML_ASSERT_BASIC_(m_stack.m_callbacks, m_tree); + _RYML_ASSERT_BASIC_(m_tree->callbacks(), !m_tree->empty()); + const id_type last_added = m_tree->size() - 1; + const NodeData *C4_RESTRICT d = m_tree->_p(last_added); + if(d->m_parent != NONE && d->m_type == NOTYPE) + { + _c4dbgpf("remove speculative: currparent={} node={} parent(node)={}", m_parent->node_id, last_added, d->m_parent); + m_tree->remove(last_added); + --m_curr->node_id; + } + } + + void _remove_speculative_with_parent() + { + _RYML_ASSERT_BASIC_(m_stack.m_callbacks, m_tree); + _RYML_ASSERT_BASIC_(m_tree->callbacks(), !m_tree->empty()); + const id_type last_added = m_tree->size() - 1; + _RYML_ASSERT_VISIT_(m_tree->callbacks(), m_tree->has_parent(last_added), m_tree, last_added); + if(m_tree->_p(last_added)->m_type == NOTYPE) + { + _c4dbgpf("remove speculative node with parent. parent={} node={} parent(node)={}", m_parent->node_id, last_added, m_tree->parent(last_added)); + m_tree->remove(last_added); + --m_curr->node_id; + } + } + + C4_ALWAYS_INLINE void _save_loc() + { + _RYML_ASSERT_BASIC_(m_stack.m_callbacks, m_tree); + _RYML_ASSERT_BASIC_(m_tree->callbacks(), m_tree->_p(m_curr->node_id)->m_val.scalar.len == 0); + m_tree->_p(m_curr->node_id)->m_val.scalar.str = m_curr->line_contents.rem.str; + } + +#undef _enable_ +#undef _disable_ +#undef _has_any_ + + /** @endcond */ +}; + +/** @} */ + +} // namespace yml +} // namespace c4 + +// NOLINTEND(hicpp-signed-bitwise) +C4_SUPPRESS_WARNING_MSVC_POP +C4_SUPPRESS_WARNING_GCC_POP + +#endif /* _C4_YML_EVENT_HANDLER_TREE_HPP_ */ + + +// (end src/c4/yml/event_handler_tree.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/parse_engine.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_YML_PARSE_ENGINE_HPP_ +#define _C4_YML_PARSE_ENGINE_HPP_ + +#ifndef _C4_YML_PARSER_STATE_HPP_ +// amalgamate: removed include of +// c4/yml/parser_state.hpp +//#include "c4/yml/parser_state.hpp" +#if !defined(C4_YML_PARSER_STATE_HPP_) && !defined(_C4_YML_PARSER_STATE_HPP_) +#error "amalgamate: file c4/yml/parser_state.hpp must have been included at this point" +#endif /* C4_YML_PARSER_STATE_HPP_ */ + +#endif +#ifndef _C4_YML_PARSE_OPTIONS_HPP_ +// amalgamate: removed include of +// c4/yml/parse_options.hpp +//#include "c4/yml/parse_options.hpp" +#if !defined(C4_YML_PARSE_OPTIONS_HPP_) && !defined(_C4_YML_PARSE_OPTIONS_HPP_) +#error "amalgamate: file c4/yml/parse_options.hpp must have been included at this point" +#endif /* C4_YML_PARSE_OPTIONS_HPP_ */ + +#endif +#ifndef _C4_YML_FWD_HPP_ +// amalgamate: removed include of +// c4/yml/fwd.hpp +//#include "c4/yml/fwd.hpp" +#if !defined(C4_YML_FWD_HPP_) && !defined(_C4_YML_FWD_HPP_) +#error "amalgamate: file c4/yml/fwd.hpp must have been included at this point" +#endif /* C4_YML_FWD_HPP_ */ + +#endif + + +#if defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable: 4251/*needs to have dll-interface to be used by clients of struct*/) +#endif + +// NOLINTBEGIN(hicpp-signed-bitwise) + +namespace c4 { +namespace yml { + +/** @addtogroup doc_parse + * @{ */ + +/** @defgroup doc_event_handlers Event Handlers + * + * @brief rapidyaml implements its parsing logic with a two-level + * model, where a @ref ParseEngine object reads through the YAML + * source, and dispatches events to an EventHandler bound to the @ref + * ParseEngine. Because @ref ParseEngine is templated on the event + * handler, the binding uses static polymorphism, without any virtual + * functions. The actual handler object can be changed at run time, + * (but of course needs to be the type of the template parameter). + * This is thus a very efficient architecture, and further enables the + * user to provide his own custom handler if he wishes to bypass the + * rapidyaml @ref Tree. + * + * The following handlers are implemented in this project: + * + * - @ref EventHandlerTree is the handler responsible for creating the + * ryml @ref Tree . This is part of the library. + * + * - Extra handlers (not part of the library, but provided as extra classes): + * + * - @ref extra::EventHandlerInts parses YAML into a contiguous + * integer array representing the YAML structure. + * - [play.yaml.com](https://play.yaml.com/) + * - [matrix.yaml.info/](https://matrix.yaml.info/) + * - the CI of this project. + * + * + * ### Event model + * + * The event model used by the parse engine and event handlers follows + * very closely the event model in the [YAML test + * suite](https://github.com/yaml/yaml-test-suite). + * + * Consider for example this YAML, + * ```yaml + * {foo: bar,foo2: bar2} + * ``` + * which would produce these events in the test-suite parlance: + * ``` + * +STR + * +DOC + * +MAP {} + * =VAL :foo + * =VAL :bar + * =VAL :foo2 + * =VAL :bar2 + * -MAP + * -DOC + * -STR + * ``` + * + * For reference, the @ref ParseEngine object will produce this + * sequence of calls to its bound EventHandler: + * ```cpp + * handler.begin_stream(); + * handler.begin_doc(); + * handler.begin_map_val_flow(); + * handler.set_key_scalar_plain("foo"); + * handler.set_val_scalar_plain("bar"); + * handler.add_sibling(); + * handler.set_key_scalar_plain("foo2"); + * handler.set_val_scalar_plain("bar2"); + * handler.end_map(); + * handler.end_doc(); + * handler.end_stream(); + * ``` + * + * For many other examples of all areas of YAML and how ryml's parse + * model corresponds to the YAML standard model, refer to the [unit + * tests for the parse + * engine](https://github.com/biojppm/rapidyaml/tree/master/test/test_parse_engine.cpp). + * + * + * ### Special events + * + * Most of the parsing events adopted by rapidyaml in its event model + * are fairly obvious, but there are two less-obvious events requiring + * some explanation. + * + * These events exist to make it easier to parse some special YAML + * cases. They are called by the parser when a just-handled + * value/container is actually the first key of a new map: + * + * - `actually_val_is_first_key_of_new_map_flow()` (@ref EventHandlerTree::actually_val_is_first_key_of_new_map_flow() "see implementation in EventHandlerTree" / @ref EventHandlerInts::actually_val_is_first_key_of_new_map_flow() "see implementation in EventHandlerInts") + * - `actually_val_is_first_key_of_new_map_block()` (@ref EventHandlerTree::actually_val_is_first_key_of_new_map_block() "see implementation in EventHandlerTree" / @ref EventHandlerInts::actually_val_is_first_key_of_new_map_block() "see implementation in EventHandlerInts") + * + * For example, consider an implicit map inside a seq: `[a: b, c: + * d]` which is parsed as `[{a: b}, {c: d}]`. The standard event + * sequence for this YAML would be the following: + * ```cpp + * handler.begin_seq_val_flow(); + * handler.begin_map_val_flow(); + * handler.set_key_scalar_plain("a"); + * handler.set_val_scalar_plain("b"); + * handler.end_map(); + * handler.add_sibling(); + * handler.begin_map_val_flow(); + * handler.set_key_scalar_plain("c"); + * handler.set_val_scalar_plain("d"); + * handler.end_map(); + * handler.end_seq(); + * ``` + * The problem with this event sequence is that it forces the + * parser to delay setting the val scalar (in this case "a" and + * "c") until it knows whether the scalar is a key or a val. This + * would require the parser to store the scalar until this + * time. For instance, in the example above, the parser should + * delay setting "a" and "c", because they are in fact keys and + * not vals. Until then, the parser would have to store "a" and + * "c" in its internal state. The downside is that this complexity + * cost would apply even if there is no implicit map -- every val + * in a seq would have to be delayed until one of the + * disambiguating subsequent tokens `,-]:` is found. + * By calling this function, the parser can avoid this complexity, + * by preemptively setting the scalar as a val. Then a call to + * this function will create the map and rearrange the scalar as + * key. Now the cost applies only once: when a seqimap starts. So + * the following (easier and cheaper) event sequence below has the + * same effect as the event sequence above: + * ```cpp + * handler.begin_seq_val_flow(); + * handler.set_val_scalar_plain("notmap"); + * handler.set_val_scalar_plain("a"); // preemptively set "a" as val! + * handler.actually_as_new_map_key(); // create a map, move the "a" val as the key of the first child of the new map + * handler.set_val_scalar_plain("b"); // now "a" is a key and "b" the val + * handler.end_map(); + * handler.set_val_scalar_plain("c"); // "c" also as val! + * handler.actually_as_block_flow(); // likewise + * handler.set_val_scalar_plain("d"); // now "c" is a key and "b" the val + * handler.end_map(); + * handler.end_seq(); + * ``` + * This also applies to container keys (although ryml's tree + * cannot accomodate these): the parser can preemptively set a + * container as a val, and call this event to turn that container + * into a key. For example, consider this yaml: + * ```yaml + * [aa, bb]: [cc, dd] + * # ^ ^ ^ + * # | | | + * # (2) (1) (3) <- event sequence + * ``` + * The standard event sequence for this YAML would be the + * following: + * ```cpp + * handler.begin_map_val_block(); // (1) + * handler.begin_seq_key_flow(); // (2) + * handler.set_val_scalar_plain("aa"); + * handler.add_sibling(); + * handler.set_val_scalar_plain("bb"); + * handler.end_seq(); + * handler.begin_seq_val_flow(); // (3) + * handler.set_val_scalar_plain("cc"); + * handler.add_sibling(); + * handler.set_val_scalar_plain("dd"); + * handler.end_seq(); + * handler.end_map(); + * ``` + * The problem with the sequence above is that, reading from + * left-to-right, the parser can only detect the proper calls at + * (1) and (2) once it reaches (1) in the YAML source. So, the + * parser would have to buffer the entire event sequence starting + * from the beginning until it reaches (1). Using this function, + * the parser can do instead: + * ```cpp + * handler.begin_seq_val_flow(); // (2) -- preemptively as val! + * handler.set_val_scalar_plain("aa"); + * handler.add_sibling(); + * handler.set_val_scalar_plain("bb"); + * handler.end_seq(); + * handler.actually_as_new_map_key(); // (1) -- adjust when finding that the prev val was actually a key. + * handler.begin_seq_val_flow(); // (3) -- go on as before + * handler.set_val_scalar_plain("cc"); + * handler.add_sibling(); + * handler.set_val_scalar_plain("dd"); + * handler.end_seq(); + * handler.end_map(); + * ``` + */ + + +/** @cond dev */ +struct FilterResult; +struct FilterResultExtending; +typedef enum BlockChomp_ { // NOLINT + CHOMP_CLIP, //!< single newline at end (default) + CHOMP_STRIP, //!< no newline at end (-) + CHOMP_KEEP //!< all newlines from end (+) +} BlockChomp_e; +/** @endcond */ + + +/** Quickly inspect the source to estimate the number of nodes the + * resulting tree is likely to have. If a tree is empty before + * parsing, considerable time will be spent growing it, so calling + * this to reserve the tree size prior to parsing is likely to + * result in a time gain. We encourage using this method before + * parsing, but as always measure its impact in performance to + * obtain a good trade-off. + * + * @note since this method is meant for optimizing performance, it + * is approximate. The result may be actually smaller than the + * resulting number of nodes, notably if the YAML uses implicit + * maps as flow seq members as in `[these: are, individual: + * maps]`. */ +RYML_EXPORT id_type estimate_tree_capacity(csubstr src); // NOLINT(readability-redundant-declaration) + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** This is the main driver of parsing logic: it scans the YAML or + * JSON source for tokens, and emits the appropriate sequence of + * parsing events to its event handler. The parse engine itself has no + * special limitations, and *can* accomodate containers as keys; it is the + * event handler may introduce additional constraints. + * + * There are two implemented handlers (see @ref doc_event_handlers, + * which has important notes about the event model): + * + * - @ref EventHandlerTree is the handler responsible for creating the + * ryml @ref Tree + * + * - @ref extra::EventHandlerInts is the handler responsible for + * emitting integer-coded events. It is intended for implementing + * fully-conformant parsing in other programming languages + * (integration is currently under work for + * [YamlScript](https://github.com/yaml/yamlscript) and + * [go-yaml](https://github.com/yaml/go-yaml/)). It is not part of + * the library and is not installed. + * + */ +template +class ParseEngine +{ +public: + + using handler_type = EventHandler; + +public: + + /** @name construction and assignment */ + /** @{ */ + + ParseEngine(EventHandler *evt_handler, ParserOptions opts={}); + ~ParseEngine(); + + ParseEngine(ParseEngine &&) noexcept; + ParseEngine(ParseEngine const&); + ParseEngine& operator=(ParseEngine &&) noexcept; + ParseEngine& operator=(ParseEngine const&); + + /** @} */ + +public: + + /** @name modifiers */ + /** @{ */ + + /** Reserve a certain capacity for the parsing stack. + * This should be larger than the expected depth of the parsed + * YAML tree. + * + * The parsing stack is the only (potential) heap memory used + * directly by the parser. + * + * If the requested capacity is below the default + * stack size of 16, the memory is used directly in the parser + * object; otherwise it will be allocated from the heap. + * + * @note this reserves memory only for the parser itself; all the + * allocations for the parsed tree will go through the tree's + * allocator (when different). + * + * @note for maximum efficiency, the tree and the arena can (and + * should) also be reserved. */ + void reserve_stack(id_type capacity) + { + _RYML_ASSERT_BASIC(m_evt_handler); + m_evt_handler->m_stack.reserve(capacity); + } + + /** Reserve a certain capacity for the array used to track node + * locations in the source buffer. */ + void reserve_locations(size_t num_source_lines) + { + _resize_locations(num_source_lines); + } + + /** @} */ + +public: + + /** @name getters */ + /** @{ */ + + /** Get the options used to build this parser object. */ + ParserOptions const& options() const { return m_options; } + + /** Get the current callbacks in the parser. */ + Callbacks const& callbacks() const { _RYML_ASSERT_BASIC(m_evt_handler); return m_evt_handler->m_stack.m_callbacks; } + + /** Get the name of the latest file parsed by this object. */ + csubstr filename() const { return m_evt_handler->m_curr ? m_evt_handler->m_curr->pos.name : csubstr{}; } + + /** Get the latest YAML buffer parsed by this object. */ + csubstr source() const { return m_evt_handler ? m_evt_handler->m_src : csubstr{}; } + + /** Get the encoding of the latest YAML buffer parsed by this object. + * If no encoding was specified, UTF8 is assumed as per the YAML standard. */ + Encoding_e encoding() const { return m_encoding != NOBOM ? m_encoding : UTF8; } + + id_type stack_capacity() const { _RYML_ASSERT_BASIC(m_evt_handler); return m_evt_handler->m_stack.capacity(); } + size_t locations_capacity() const { return m_newline_offsets_capacity; } + + /** @} */ + +public: + + /** @name parse methods */ + /** @{ */ + + /** parse YAML in place, emitting events to the current handler */ + void parse_in_place_ev(csubstr filename, substr src); + + /** parse JSON in place, emitting events to the current handler */ + void parse_json_in_place_ev(csubstr filename, substr src); + + /** @} */ + +public: + + /** @name locations */ + /** @{ */ + + /** Get the string starting at a particular location, to the end + * of the parsed source buffer. */ + csubstr location_contents(Location const& loc) const; + + /** Given a pointer to a buffer position, get the location. + * @param[in] val must be pointing to somewhere in the source + * buffer that was last parsed by this object. */ + Location val_location(const char *val) const; + + /** @} */ + +public: + + /** @name scalar filtering */ + /** @{*/ + + /** filter a plain scalar */ + FilterResult filter_scalar_plain(csubstr scalar, substr dst, size_t indentation); + /** filter a plain scalar in place */ + FilterResult filter_scalar_plain_in_place(substr scalar, size_t cap, size_t indentation); + + /** filter a single-quoted scalar */ + FilterResult filter_scalar_squoted(csubstr scalar, substr dst); + /** filter a single-quoted scalar in place */ + FilterResult filter_scalar_squoted_in_place(substr scalar, size_t cap); + + /** filter a double-quoted scalar */ + FilterResult filter_scalar_dquoted(csubstr scalar, substr dst); + /** filter a double-quoted scalar in place */ + FilterResultExtending filter_scalar_dquoted_in_place(substr scalar, size_t cap); + + /** filter a block-literal scalar */ + FilterResult filter_scalar_block_literal(csubstr scalar, substr dst, size_t indentation, BlockChomp_e chomp); + /** filter a block-literal scalar in place */ + FilterResult filter_scalar_block_literal_in_place(substr scalar, size_t cap, size_t indentation, BlockChomp_e chomp); + + /** filter a block-folded scalar */ + FilterResult filter_scalar_block_folded(csubstr scalar, substr dst, size_t indentation, BlockChomp_e chomp); + /** filter a block-folded scalar in place */ + FilterResult filter_scalar_block_folded_in_place(substr scalar, size_t cap, size_t indentation, BlockChomp_e chomp); + + /** @} */ + +private: + + struct ScannedScalar + { + substr scalar; + bool needs_filter; + }; + + struct ScannedBlock + { + substr scalar; + size_t indentation; + BlockChomp_e chomp; + }; + +private: + + bool _is_doc_begin(csubstr s); + bool _is_doc_end(csubstr s); + + bool _scan_scalar_plain_blck(ScannedScalar *C4_RESTRICT sc, size_t indentation); + bool _scan_scalar_plain_seq_flow(ScannedScalar *C4_RESTRICT sc); + bool _scan_scalar_plain_seq_blck(ScannedScalar *C4_RESTRICT sc); + bool _scan_scalar_plain_map_flow(ScannedScalar *C4_RESTRICT sc); + bool _scan_scalar_plain_map_blck(ScannedScalar *C4_RESTRICT sc); + bool _scan_scalar_map_json(ScannedScalar *C4_RESTRICT sc); + bool _scan_scalar_seq_json(ScannedScalar *C4_RESTRICT sc); + bool _scan_scalar_plain_unk(ScannedScalar *C4_RESTRICT sc); + bool _is_valid_start_scalar_plain_flow(csubstr s); + bool _is_valid_start_scalar_plain_flow_check_block_token(csubstr s); + bool _is_valid_start_scalar_plain_flow_check_qmrk(csubstr s); + bool _scan_scalar_plain_handle_newline(csubstr s, size_t offs); + void _check_valid_newline_in_quoted_scalar(); + + ScannedScalar _scan_scalar_squot(); + ScannedScalar _scan_scalar_dquot(); + + void _scan_block(ScannedBlock *C4_RESTRICT sb, size_t indref); + csubstr _scan_anchor(); + csubstr _scan_ref_seq(); + csubstr _scan_ref_map(); + csubstr _scan_tag(); + csubstr _scan_tag(csubstr *orig); + +public: // exposed for testing + + /** @cond dev */ + csubstr _filter_scalar_plain(substr s, size_t indentation); + csubstr _filter_scalar_squot(substr s); + csubstr _filter_scalar_dquot(substr s); + csubstr _filter_scalar_literal(substr s, size_t indentation, BlockChomp_e chomp); + csubstr _filter_scalar_folded(substr s, size_t indentation, BlockChomp_e chomp); + csubstr _move_scalar_left_and_add_newline(substr s); + + csubstr _maybe_filter_key_scalar_plain(ScannedScalar const& sc, size_t indendation); + csubstr _maybe_filter_val_scalar_plain(ScannedScalar const& sc, size_t indendation); + csubstr _maybe_filter_key_scalar_squot(ScannedScalar const& sc); + csubstr _maybe_filter_val_scalar_squot(ScannedScalar const& sc); + csubstr _maybe_filter_key_scalar_dquot(ScannedScalar const& sc); + csubstr _maybe_filter_val_scalar_dquot(ScannedScalar const& sc); + csubstr _maybe_filter_key_scalar_literal(ScannedBlock const& sb); + csubstr _maybe_filter_val_scalar_literal(ScannedBlock const& sb); + csubstr _maybe_filter_key_scalar_folded(ScannedBlock const& sb); + csubstr _maybe_filter_val_scalar_folded(ScannedBlock const& sb); + /** @endcond */ + +private: + + void _handle_map_block(); + bool _handle_map_block_qmrk(); + bool _handle_map_block_rkcl(); + void _handle_seq_block(); + void _handle_map_flow(); + void _handle_seq_flow(); + void _handle_seq_imap(); + void _handle_map_json(); + void _handle_seq_json(); + + void _handle_unk(); + void _handle_unk_json(); + + void _handle_usty(); + + void _handle_flow_skip_whitespace(); + void _handle_flow_line_beginning(); + + size_t _handle_unk_check_left_tokens(size_t realindent, size_t col, bool skip_annotations=true); + void _handle_unk_get_first_non_pending_token_pos(csubstr s, size_t *indent, size_t *first_non_token_pos); + void _handle_unk_begin_doc(); + + size_t _handle_block_skip_leading_whitespace(); + C4_ALWAYS_INLINE + size_t _handle_block_get_whitespace_mark() const noexcept { return m_evt_handler->m_curr->pos.offset; } + void _handle_block_check_leading_tabs(size_t prev_mark) { return _handle_block_check_leading_tabs(prev_mark, m_evt_handler->m_curr->pos.offset); } + void _handle_block_check_leading_tabs(size_t start_mark, size_t end_mark); + + void _end_map_flow(); + void _end_seq_flow(); + void _end_map_blck(); + void _end_seq_blck(); + void _end2_map(); + void _end2_seq(); + void _end_flow_container(size_t orig_indent, bool multiline); + void _flow_container_was_a_key(size_t orig_indent); + + void _begin2_doc(); + void _begin2_doc_expl(); + void _end2_doc(); + void _end2_doc_expl(); + void _check_doc_end_tokens() const; + + void _maybe_begin_doc(); + void _maybe_end_doc(); + + void _start_doc_suddenly(); + void _end_doc_suddenly(); + void _end_doc_suddenly__pop(); + void _check_trailing_doc_token(); + void _end_stream(); + + void _set_indentation(size_t indentation) noexcept; + void _save_indentation(); + void _mark_seqflow_val_end() noexcept; + void _handle_indentation_pop_from_block_seq(); + void _handle_indentation_pop_from_block_map(); + void _handle_indentation_pop(ParserState const* dst); + + void _maybe_skip_comment(); + void _maybe_skip_comment_strict(); + void _skip_comment(); + void _maybe_skip_whitespace_tokens(); + void _maybe_skipchars(char c); + template + void _skipchars(const char (&chars)[N]); + bool _maybe_scan_following_colon() noexcept; + +public: + + /** @cond dev */ + template auto _filter_plain(FilterProcessor &C4_RESTRICT proc, size_t indentation) -> decltype(proc.result()); + template auto _filter_squoted(FilterProcessor &C4_RESTRICT proc) -> decltype(proc.result()); + template auto _filter_dquoted(FilterProcessor &C4_RESTRICT proc) -> decltype(proc.result()); + template auto _filter_block_literal(FilterProcessor &C4_RESTRICT proc, size_t indentation, BlockChomp_e chomp) -> decltype(proc.result()); + template auto _filter_block_folded(FilterProcessor &C4_RESTRICT proc, size_t indentation, BlockChomp_e chomp) -> decltype(proc.result()); + /** @endcond */ + +public: + + /** @cond dev */ + template void _filter_nl_plain(FilterProcessor &C4_RESTRICT proc, size_t indentation); + template void _filter_nl_squoted(FilterProcessor &C4_RESTRICT proc); + template void _filter_nl_dquoted(FilterProcessor &C4_RESTRICT proc); + + template bool _filter_ws_handle_to_first_non_space(FilterProcessor &C4_RESTRICT proc); + template void _filter_ws_copy_trailing(FilterProcessor &C4_RESTRICT proc); + template void _filter_ws_skip_trailing(FilterProcessor &C4_RESTRICT proc); + + template void _filter_dquoted_backslash(FilterProcessor &C4_RESTRICT proc); + template void _filter_dquoted_backslash_decode(FilterProcessor &C4_RESTRICT proc, size_t sz); + + template void _filter_chomp(FilterProcessor &C4_RESTRICT proc, BlockChomp_e chomp, size_t indentation); + template size_t _handle_all_whitespace(FilterProcessor &C4_RESTRICT proc, BlockChomp_e chomp); + template size_t _extend_to_chomp(FilterProcessor &C4_RESTRICT proc, size_t contents_len); + template void _filter_block_indentation(FilterProcessor &C4_RESTRICT proc, size_t indentation); + template void _filter_block_folded_newlines(FilterProcessor &C4_RESTRICT proc, size_t indentation, size_t len); + template size_t _filter_block_folded_newlines_compress(FilterProcessor &C4_RESTRICT proc, size_t num_newl, size_t wpos_at_first_newl); + template void _filter_block_folded_newlines_leading(FilterProcessor &C4_RESTRICT proc, size_t indentation, size_t len); + template void _filter_block_folded_indented_block(FilterProcessor &C4_RESTRICT proc, size_t indentation, size_t len, size_t curr_indentation) noexcept; + + substr _alloc_arena(size_t len, substr *relocated=nullptr); + substr _alloc_arena(size_t len, csubstr *relocated) { return _alloc_arena(len, reinterpret_cast(relocated)); } // NOLINT + + /** @endcond */ + +private: + + void _line_progressed(size_t ahead); + void _line_ended(); + void _line_ended_undo(); + + bool _finished_file() const; + bool _finished_line() const; + + void _scan_line(); + substr _peek_next_line(size_t pos=npos) const; + + void _relocate_arena(csubstr prev_arena, substr next_arena, substr *other_string=nullptr); + +private: + + C4_ALWAYS_INLINE substr _buf() const noexcept { return m_evt_handler->m_src; } + + C4_ALWAYS_INLINE bool has_all(ParserFlag_t f) const noexcept { return (m_evt_handler->m_curr->flags & f) == f; } + C4_ALWAYS_INLINE bool has_any(ParserFlag_t f) const noexcept { return (m_evt_handler->m_curr->flags & f) != 0; } + C4_ALWAYS_INLINE bool has_none(ParserFlag_t f) const noexcept { return (m_evt_handler->m_curr->flags & f) == 0; } + static C4_ALWAYS_INLINE bool has_all(ParserFlag_t f, ParserState const* C4_RESTRICT s) noexcept { return (s->flags & f) == f; } + static C4_ALWAYS_INLINE bool has_any(ParserFlag_t f, ParserState const* C4_RESTRICT s) noexcept { return (s->flags & f) != 0; } + static C4_ALWAYS_INLINE bool has_none(ParserFlag_t f, ParserState const* C4_RESTRICT s) noexcept { return (s->flags & f) == 0; } + + #ifndef RYML_DBG + C4_ALWAYS_INLINE void add_flags(ParserFlag_t on) noexcept { m_evt_handler->m_curr->flags |= on; } + C4_ALWAYS_INLINE void addrem_flags(ParserFlag_t on, ParserFlag_t off) noexcept { m_evt_handler->m_curr->flags &= ~off; m_evt_handler->m_curr->flags |= on; } + C4_ALWAYS_INLINE void rem_flags(ParserFlag_t off) noexcept { m_evt_handler->m_curr->flags &= ~off; } + #else + C4_ALWAYS_INLINE void add_flags(ParserFlag_t on); + C4_ALWAYS_INLINE void addrem_flags(ParserFlag_t on, ParserFlag_t off); + C4_ALWAYS_INLINE void rem_flags(ParserFlag_t off); + #endif + +private: + + void _prepare_locations(); + void _resize_locations(size_t sz); + bool _locations_dirty() const; + +private: + + void _reset(); + void _free(); + void _clr(); + + template C4_NORETURN C4_NO_INLINE void _err(Location const& cpploc, const char *fmt, Args const& ...args) const; + template C4_NORETURN C4_NO_INLINE void _err(Location const& cpploc, Location const& ymlloc, const char *fmt, Args const& ...args) const; + #ifdef RYML_DBG + template C4_NO_INLINE void _dbg(csubstr fmt, Args const& ...args) const; + template C4_NO_INLINE void _fmt_msg(DumpFn &&dumpfn) const; + C4_NO_INLINE void _print_state_stack() const; + C4_NO_INLINE void _print_state_stack(substr buf) const; + #endif + +private: + + /** store pending tag or anchor/ref annotations */ + struct Annotation + { + struct Entry + { + csubstr str; + size_t indentation; + size_t line; + csubstr orig; + }; + Entry annotations[2]; + uint8_t num_entries; + }; + + void _handle_colon(); + void _add_annotation(Annotation *C4_RESTRICT dst, csubstr str, size_t indentation, size_t line); + void _add_annotation(Annotation *C4_RESTRICT dst, csubstr str, size_t indentation, size_t line, csubstr orig); + void _add_annotation(Annotation *C4_RESTRICT dst, csubstr str); + C4_ALWAYS_INLINE void _clear_annotations(Annotation *C4_RESTRICT dst) noexcept { dst->num_entries = 0; } + bool _annotations_require_key_container() const; + bool _handle_annotations_before_unexpected_flow_token_rkey(); + void _handle_annotations_before_blck_key_scalar(); + void _handle_annotations_before_blck_val_scalar(); + void _handle_annotations_before_start_mapblck(size_t current_line); + void _handle_annotations_before_start_mapblck_as_key(); + void _handle_annotations_and_indentation_after_start_mapblck(size_t key_indentation, size_t key_line); + size_t _select_indentation_from_annotations(size_t val_indentation, size_t val_line); + uint32_t _get_annotations_same_line(csubstr token_soup, csubstr * first, csubstr * second) const; + void _handle_keyref(csubstr alias); + void _handle_valref(csubstr alias); + csubstr _resolve_tag(csubstr tag); + void _handle_directive(csubstr rem); + bool _validate_directive_yaml(csubstr *C4_RESTRICT directive, csubstr *C4_RESTRICT version) const; + bool _validate_directive_tag(csubstr *C4_RESTRICT directive, csubstr *C4_RESTRICT handle, csubstr *C4_RESTRICT prefix) const; + bool _handle_bom(); + void _handle_bom(Encoding_e enc); + +private: + + ParserOptions m_options; + +public: + + /** @cond dev */ + EventHandler *C4_RESTRICT m_evt_handler; // NOLINT + /** @endcond */ + +private: + + Annotation m_pending_anchors; + Annotation m_pending_tags; + + bool m_has_directives_yaml; + bool m_has_directives; + bool m_doc_empty; + size_t m_prev_colon; + size_t m_prev_val_end; + +private: + + size_t m_bom_len; + size_t m_bom_line; + Encoding_e m_encoding; + +private: + + size_t *m_newline_offsets; + size_t m_newline_offsets_size; + size_t m_newline_offsets_capacity; + +public: + + // deprecated methods + + /** @cond dev */ + RYML_DEPRECATED("filter arena no longer needed") size_t filter_arena_capacity() const { return 0u; } // LCOV_EXCL_LINE + RYML_DEPRECATED("filter arena no longer needed") void reserve_filter_arena(size_t) {} // LCOV_EXCL_LINE + + template RYML_DEPRECATED("removed, deliberately undefined. use the function in parse.hpp.") typename std::enable_if::type parse_in_place(csubstr filename, substr yaml, Tree *t, size_t node_id); + template RYML_DEPRECATED("removed, deliberately undefined. use the function in parse.hpp.") typename std::enable_if::type parse_in_place( substr yaml, Tree *t, size_t node_id); + template RYML_DEPRECATED("removed, deliberately undefined. use the function in parse.hpp.") typename std::enable_if::type parse_in_place(csubstr filename, substr yaml, Tree *t ); + template RYML_DEPRECATED("removed, deliberately undefined. use the function in parse.hpp.") typename std::enable_if::type parse_in_place( substr yaml, Tree *t ); + template RYML_DEPRECATED("removed, deliberately undefined. use the function in parse.hpp.") typename std::enable_if::type parse_in_place(csubstr filename, substr yaml, NodeRef node ); + template RYML_DEPRECATED("removed, deliberately undefined. use the function in parse.hpp.") typename std::enable_if::type parse_in_place( substr yaml, NodeRef node ); + template RYML_DEPRECATED("removed, deliberately undefined. use the function in parse.hpp.") typename std::enable_if::type parse_in_place(csubstr filename, substr yaml ); + template RYML_DEPRECATED("removed, deliberately undefined. use the function in parse.hpp.") typename std::enable_if::type parse_in_place( substr yaml ); + template RYML_DEPRECATED("removed, deliberately undefined. use the function in parse.hpp.") typename std::enable_if::type parse_in_arena(csubstr filename, csubstr yaml, Tree *t, size_t node_id); + template RYML_DEPRECATED("removed, deliberately undefined. use the function in parse.hpp.") typename std::enable_if::type parse_in_arena( csubstr yaml, Tree *t, size_t node_id); + template RYML_DEPRECATED("removed, deliberately undefined. use the function in parse.hpp.") typename std::enable_if::type parse_in_arena(csubstr filename, csubstr yaml, Tree *t ); + template RYML_DEPRECATED("removed, deliberately undefined. use the function in parse.hpp.") typename std::enable_if::type parse_in_arena( csubstr yaml, Tree *t ); + template RYML_DEPRECATED("removed, deliberately undefined. use the function in parse.hpp.") typename std::enable_if::type parse_in_arena(csubstr filename, csubstr yaml, NodeRef node ); + template RYML_DEPRECATED("removed, deliberately undefined. use the function in parse.hpp.") typename std::enable_if::type parse_in_arena( csubstr yaml, NodeRef node ); + template RYML_DEPRECATED("removed, deliberately undefined. use the function in parse.hpp.") typename std::enable_if::type parse_in_arena(csubstr filename, csubstr yaml ); + template RYML_DEPRECATED("removed, deliberately undefined. use the function in parse.hpp.") typename std::enable_if::type parse_in_arena( csubstr yaml ); + template RYML_DEPRECATED("removed, deliberately undefined. use the csubstr version in parse.hpp.") typename std::enable_if::type parse_in_arena(csubstr filename, substr yaml, Tree *t, size_t node_id); + template RYML_DEPRECATED("removed, deliberately undefined. use the csubstr version in parse.hpp.") typename std::enable_if::type parse_in_arena( substr yaml, Tree *t, size_t node_id); + template RYML_DEPRECATED("removed, deliberately undefined. use the csubstr version in parse.hpp.") typename std::enable_if::type parse_in_arena(csubstr filename, substr yaml, Tree *t ); + template RYML_DEPRECATED("removed, deliberately undefined. use the csubstr version in parse.hpp.") typename std::enable_if::type parse_in_arena( substr yaml, Tree *t ); + template RYML_DEPRECATED("removed, deliberately undefined. use the csubstr version in parse.hpp.") typename std::enable_if::type parse_in_arena(csubstr filename, substr yaml, NodeRef node ); + template RYML_DEPRECATED("removed, deliberately undefined. use the csubstr version in parse.hpp.") typename std::enable_if::type parse_in_arena( substr yaml, NodeRef node ); + template RYML_DEPRECATED("removed, deliberately undefined. use the csubstr version in parse.hpp.") typename std::enable_if::type parse_in_arena(csubstr filename, substr yaml ); + template RYML_DEPRECATED("removed, deliberately undefined. use the csubstr version in parse.hpp.") typename std::enable_if::type parse_in_arena( substr yaml ); + + template + RYML_DEPRECATED("moved to Tree::location(Parser const&). deliberately undefined here.") + auto location(Tree const&, id_type node) const -> typename std::enable_if::type; + + template + RYML_DEPRECATED("moved to ConstNodeRef::location(Parser const&), deliberately undefined here.") + auto location(ConstNodeRef const&) const -> typename std::enable_if::type; + /** @endcond */ + +}; + + +/** @} */ + +} // namespace yml +} // namespace c4 + +// NOLINTEND(hicpp-signed-bitwise) + +#if defined(_MSC_VER) +# pragma warning(pop) +#endif + +#endif /* _C4_YML_PARSE_ENGINE_HPP_ */ + + +// (end src/c4/yml/parse_engine.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/preprocess.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_YML_PREPROCESS_HPP_ +#define _C4_YML_PREPROCESS_HPP_ + +/** @file preprocess.hpp Functions for preprocessing YAML prior to parsing. */ + +#ifndef _C4_YML_COMMON_HPP_ +#include "./common.hpp" +#endif +// amalgamate: removed include of +// c4/substr.hpp +//#include +#if !defined(C4_SUBSTR_HPP_) && !defined(_C4_SUBSTR_HPP_) +#error "amalgamate: file c4/substr.hpp must have been included at this point" +#endif /* C4_SUBSTR_HPP_ */ + + + +namespace c4 { +namespace yml { + +/** @addtogroup doc_preprocessors + * @{ + */ + +/** @cond dev */ +namespace detail { +using Preprocessor = size_t(csubstr, substr); +template +substr preprocess_into_container(csubstr input, CharContainer *out) +{ + // try to write once. the preprocessor will stop writing at the end of + // the container, but will process all the input to determine the + // required container size. + size_t sz = PP(input, to_substr(*out)); + // if the container size is not enough, resize, and run again in the + // resized container + if(sz > out->size()) + { + out->resize(sz); + sz = PP(input, to_substr(*out)); + } + return to_substr(*out).first(sz); +} +} // namespace detail +/** @endcond */ + + +//----------------------------------------------------------------------------- + +/** @defgroup doc_preprocess_rxmap preprocess_rxmap + * + * @brief Convert flow-type relaxed maps (with implicit bools) into strict YAML + * flow map: + * + * @code{.yaml} + * {a, b, c, d: [e, f], g: {a, b}} + * # is converted into this: + * {a: 1, b: 1, c: 1, d: [e, f], g: {a, b}} + * @endcode + + * @note this is NOT recursive - conversion happens only in the top-level map + * @param rxmap A relaxed map + * @param buf output buffer + * @param out output container + * + * @{ + */ + +/** Write into a given output buffer. This function is safe to call with + * empty or small buffers; it won't write beyond the end of the buffer. + * + * @return the number of characters required for output + */ +RYML_EXPORT size_t preprocess_rxmap(csubstr rxmap, substr buf); + + +/** Write into an existing container. It is resized to contained the output. + * @return a substr of the container + * @overload preprocess_rxmap */ +template +substr preprocess_rxmap(csubstr rxmap, CharContainer *out) +{ + return detail::preprocess_into_container(rxmap, out); +} + + +/** Create a container with the result. + * @overload preprocess_rxmap */ +template +CharContainer preprocess_rxmap(csubstr rxmap) +{ + CharContainer out; + preprocess_rxmap(rxmap, &out); + return out; +} + +/** @} */ // preprocess_rxmap +/** @} */ // group + +} // namespace yml +} // namespace c4 + +#endif /* _C4_YML_PREPROCESS_HPP_ */ + + +// (end src/c4/yml/preprocess.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/reference_resolver.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_YML_REFERENCE_RESOLVER_HPP_ +#define _C4_YML_REFERENCE_RESOLVER_HPP_ + +// amalgamate: removed include of +// c4/yml/tree.hpp +//#include "c4/yml/tree.hpp" +#if !defined(C4_YML_TREE_HPP_) && !defined(_C4_YML_TREE_HPP_) +#error "amalgamate: file c4/yml/tree.hpp must have been included at this point" +#endif /* C4_YML_TREE_HPP_ */ + +// amalgamate: removed include of +// c4/yml/detail/stack.hpp +//#include "c4/yml/detail/stack.hpp" +#if !defined(C4_YML_DETAIL_STACK_HPP_) && !defined(_C4_YML_DETAIL_STACK_HPP_) +#error "amalgamate: file c4/yml/detail/stack.hpp must have been included at this point" +#endif /* C4_YML_DETAIL_STACK_HPP_ */ + + + +namespace c4 { +namespace yml { + +/** @addtogroup doc_ref_utils + * @{ + */ + +/** Reusable object to resolve references/aliases in a @ref Tree. */ +struct ReferenceResolver +{ + ReferenceResolver() = default; + + /** Resolve references: for each reference, look for a matching + * anchor, and copy its contents to the ref node. + * + * @p tree the subject tree + * + * @p clear_anchors whether to clear existing anchors after + * resolving + * + * This method first does a full traversal of the tree to gather + * all anchors and references in a separate collection, then it + * goes through that collection to locate the names, which it does + * by obeying the YAML standard diktat that "an alias node refers + * to the most recent node in the serialization having the + * specified anchor" + * + * So, depending on the number of anchor/alias nodes, this is a + * potentially expensive operation, with a best-case linear + * complexity (from the initial traversal). This potential cost is + * one of the reasons for requiring an explicit call. + * + * The @ref Tree has an `Tree::resolve()` overload set forwarding + * here. Previously this operation was done there, using a + * discarded object; using this separate class offers opportunity + * for reuse of the object. + * + * @warning resolving references opens an attack vector when the + * data is malicious or severely malformed, as the tree can expand + * exponentially. See for example the [Billion Laughs + * Attack](https://en.wikipedia.org/wiki/Billion_laughs_attack). + * + */ + RYML_EXPORT void resolve(Tree *tree, bool clear_anchors=true); + +public: + + /** @cond dev */ + + struct RefData + { + NodeType type; + id_type node; + id_type prev_anchor; + id_type target; + id_type parent_ref; + id_type parent_ref_sibling; + }; + + void reset_(Tree *t_); + void resolve_(); + void gather_anchors_and_refs_(); + void gather_anchors_and_refs__(id_type n); + id_type count_anchors_and_refs_(id_type n); + + id_type lookup_(RefData const* C4_RESTRICT ra); + + Tree *C4_RESTRICT m_tree; + /** We're using this stack purely as an array. */ + detail::stack m_refs; + + /** @endcond */ +}; + +/** @} */ + +} // namespace ryml +} // namespace c4 + + +#endif // _C4_YML_REFERENCE_RESOLVER_HPP_ + + +// (end src/c4/yml/reference_resolver.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/parse.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_YML_PARSE_HPP_ +#define _C4_YML_PARSE_HPP_ + +#ifndef _C4_YML_COMMON_HPP_ +// amalgamate: removed include of +// c4/yml/common.hpp +//#include "c4/yml/common.hpp" +#if !defined(C4_YML_COMMON_HPP_) && !defined(_C4_YML_COMMON_HPP_) +#error "amalgamate: file c4/yml/common.hpp must have been included at this point" +#endif /* C4_YML_COMMON_HPP_ */ + +#endif +#ifndef _C4_YML_PARSE_OPTIONS_HPP_ +// amalgamate: removed include of +// c4/yml/parse_options.hpp +//#include "c4/yml/parse_options.hpp" +#if !defined(C4_YML_PARSE_OPTIONS_HPP_) && !defined(_C4_YML_PARSE_OPTIONS_HPP_) +#error "amalgamate: file c4/yml/parse_options.hpp must have been included at this point" +#endif /* C4_YML_PARSE_OPTIONS_HPP_ */ + +#endif + +namespace c4 { +namespace yml { + +class Tree; +class NodeRef; +template class ParseEngine; +struct EventHandlerTree; +RYML_EXPORT id_type estimate_tree_capacity(csubstr src); // NOLINT + + +/** @addtogroup doc_parse + * @{ */ + +/** This is the main ryml parser, where the parser events are handled + * to create a ryml tree (see @ref doc_event_handlers). + * + * @warning This class cannot parse YAML which has container + * keys. This is not a limitation of the @ref ParseEngine itself, but of the + * @ref EventHandlerTree, which is present because the @ref Tree does + * not accept containers as keys. However, the @ref ParseEngine *can* + * parse container keys; consult its documentation for more details. + * + * @see @ref ParserOptions + * @see @ref ParseEngine + * @see @ref EventHandlerTree + * @see @ref doc_event_handlers + * */ +using Parser = ParseEngine; + + +//----------------------------------------------------------------------------- + +/** @defgroup doc_parse_in_place__with_existing_parser Parse in place with existing parser + * + * @brief parse a mutable YAML source buffer (re)using an existing + * parser. Scalars requiring filtering are mutated in place (except in + * the rare cases where the filtered scalar is longer than the + * original scalar, or where filtering was disabled before the + * call). These overloads accept an existing parser object, and + * provide the opportunity to use special parser options. + * + * @see ParserOptions + * + * @{ + */ + +// this is vertically aligned to highlight the parameter differences. + +RYML_EXPORT void parse_in_place(Parser *parser, csubstr filename, substr yaml, Tree *t, id_type node_id); /**< (1) parse YAML into an existing tree node. + * + * The filename will be used in any error messages + * arising during the parse. The callbacks in the + * tree are kept, and used to allocate + * the tree members, if any allocation is required. */ +RYML_EXPORT void parse_in_place(Parser *parser, substr yaml, Tree *t, id_type node_id); /**< (2) like (1) but no filename will be reported */ +RYML_EXPORT void parse_in_place(Parser *parser, csubstr filename, substr yaml, Tree *t ); /**< (3) parse YAML into the root node of an existing tree. + * + * The filename will be used in any error messages + * arising during the parse. The callbacks in the + * tree are kept, and used to allocate + * the tree members, if any allocation is required. */ +RYML_EXPORT void parse_in_place(Parser *parser, substr yaml, Tree *t ); /**< (4) like (3) but no filename will be reported */ +RYML_EXPORT void parse_in_place(Parser *parser, csubstr filename, substr yaml, NodeRef node ); /**< (5) like (1) but the node is given as a NodeRef */ +RYML_EXPORT void parse_in_place(Parser *parser, substr yaml, NodeRef node ); /**< (6) like (5) but no filename will be reported */ +RYML_EXPORT Tree parse_in_place(Parser *parser, csubstr filename, substr yaml ); /**< (7) create a new tree, and parse YAML into its root node. + * + * The filename will be used in any error messages + * arising during the parse. The tree is created with + * the callbacks currently in the parser. + */ +RYML_EXPORT Tree parse_in_place(Parser *parser, substr yaml ); /**< (8) like (7) but no filename will be reported */ + + +// this is vertically aligned to highlight the parameter differences. +RYML_EXPORT void parse_json_in_place(Parser *parser, csubstr filename, substr json, Tree *t, id_type node_id); ///< (1) parse JSON into an existing tree node. The filename will be used in any error messages arising during the parse. +RYML_EXPORT void parse_json_in_place(Parser *parser, substr json, Tree *t, id_type node_id); ///< (2) like (1) but no filename will be reported +RYML_EXPORT void parse_json_in_place(Parser *parser, csubstr filename, substr json, Tree *t ); ///< (3) parse JSON into an existing tree, into its root node. +RYML_EXPORT void parse_json_in_place(Parser *parser, substr json, Tree *t ); ///< (4) like (3) but no filename will be reported +RYML_EXPORT void parse_json_in_place(Parser *parser, csubstr filename, substr json, NodeRef node ); ///< (5) like (1) but the node is given as a NodeRef +RYML_EXPORT void parse_json_in_place(Parser *parser, substr json, NodeRef node ); ///< (6) like (5) but no filename will be reported +RYML_EXPORT Tree parse_json_in_place(Parser *parser, csubstr filename, substr json ); ///< (7) create a new tree, and parse JSON into its root node. +RYML_EXPORT Tree parse_json_in_place(Parser *parser, substr json ); ///< (8) like (7) but no filename will be reported + +/** @} */ + + +//----------------------------------------------------------------------------- + +/** @defgroup doc_parse_in_place___with_temporary_parser Parse in place with temporary parser + * + * @brief parse a mutable YAML source buffer. Scalars requiring + * filtering are mutated in place (except in the rare cases where the + * filtered scalar is longer than the original scalar). + * + * @note These freestanding functions use a temporary parser object, + * and are convenience functions to enable the user to easily parse + * YAML without the need to explicitly instantiate a parser and event + * handler. Note that some properties (notably node locations in the + * original source code) are only available through the parser + * class. If you need access to any of these properties, use + * the appropriate overload from @ref doc_parse_in_place__with_existing_parser + * + * @{ + */ + +// this is vertically aligned to highlight the parameter differences. +RYML_EXPORT void parse_in_place(csubstr filename, substr yaml, Tree *t, id_type node_id, ParserOptions const& opts={}); ///< (1) parse YAML into an existing tree node. The filename will be used in any error messages arising during the parse. +RYML_EXPORT void parse_in_place( substr yaml, Tree *t, id_type node_id, ParserOptions const& opts={}); ///< (2) like (1) but no filename will be reported +RYML_EXPORT void parse_in_place(csubstr filename, substr yaml, Tree *t , ParserOptions const& opts={}); ///< (3) parse YAML into an existing tree, into its root node. +RYML_EXPORT void parse_in_place( substr yaml, Tree *t , ParserOptions const& opts={}); ///< (4) like (3) but no filename will be reported +RYML_EXPORT void parse_in_place(csubstr filename, substr yaml, NodeRef node , ParserOptions const& opts={}); ///< (5) like (1) but the node is given as a NodeRef +RYML_EXPORT void parse_in_place( substr yaml, NodeRef node , ParserOptions const& opts={}); ///< (6) like (5) but no filename will be reported +RYML_EXPORT Tree parse_in_place(csubstr filename, substr yaml , ParserOptions const& opts={}); ///< (7) create a new tree, and parse YAML into its root node. +RYML_EXPORT Tree parse_in_place( substr yaml , ParserOptions const& opts={}); ///< (8) like (7) but no filename will be reported + +// this is vertically aligned to highlight the parameter differences. +RYML_EXPORT void parse_json_in_place(csubstr filename, substr json, Tree *t, id_type node_id, ParserOptions const& opts={}); ///< (1) parse JSON into an existing tree node. The filename will be used in any error messages arising during the parse. +RYML_EXPORT void parse_json_in_place( substr json, Tree *t, id_type node_id, ParserOptions const& opts={}); ///< (2) like (1) but no filename will be reported +RYML_EXPORT void parse_json_in_place(csubstr filename, substr json, Tree *t , ParserOptions const& opts={}); ///< (3) parse JSON into an existing tree, into its root node. +RYML_EXPORT void parse_json_in_place( substr json, Tree *t , ParserOptions const& opts={}); ///< (4) like (3) but no filename will be reported +RYML_EXPORT void parse_json_in_place(csubstr filename, substr json, NodeRef node , ParserOptions const& opts={}); ///< (5) like (1) but the node is given as a NodeRef +RYML_EXPORT void parse_json_in_place( substr json, NodeRef node , ParserOptions const& opts={}); ///< (6) like (5) but no filename will be reported +RYML_EXPORT Tree parse_json_in_place(csubstr filename, substr json , ParserOptions const& opts={}); ///< (7) create a new tree, and parse JSON into its root node. +RYML_EXPORT Tree parse_json_in_place( substr json , ParserOptions const& opts={}); ///< (8) like (7) but no filename will be reported + +/** @} */ + + +//----------------------------------------------------------------------------- + + +/** @defgroup doc_parse_in_arena__with_existing_parser Parse in arena with existing parser + * + * @brief parse a read-only (immutable) YAML source buffer. This is + * achieved by first copying the contents of the buffer to the tree's + * arena, and then calling @ref parse_in_arena() . All the resulting + * scalars will be filtered in the arena. These overloads accept an + * existing parser object, and provide the opportunity to use special + * parser options. + * + * @see ParserOptions + * + * + * @note These freestanding functions use a temporary parser object, + * and are convenience functions to easily parse YAML without the need + * to instantiate a separate parser. Note that some properties + * (notably node locations in the original source code) are only + * available through the parser class. If you need access to any of + * these properties, use the appropriate overload from @ref + * doc_parse_in_arena__with_existing_parser + * + * @warning overloads receiving a substr YAML buffer are intentionally + * left undefined, such that calling parse_in_arena() with a substr + * will cause a linker error. This is to prevent an accidental copy of + * the source buffer to the tree's arena, because substr (which is + * mutable) is implicitly convertible to csubstr (which is + * immutable). If you really intend to parse a mutable buffer in the + * tree's arena, convert it first to immutable by assigning the substr + * to a csubstr prior to calling parse_in_arena(). This is not needed + * for parse_in_place() because csubstr is not implicitly convertible + * to substr. To be clear: + * ```c++ + * substr mutable_buffer = ...; + * parser.parse_in_arena(mutable_buffer); // linker error + * + * csubstr immutable_buffer = mutable_buffer; // convert first to csubstr + * parser.parse_in_arena(immutable_buffer); // ok + * ``` + * + * @{ + */ + +#define RYML_DONT_PARSE_SUBSTR_IN_ARENA "" \ + "Do not pass a (mutable) substr to parse_in_arena(); " \ + "if you have a substr, it should be parsed in place. " \ + "Consider using parse_in_place() instead, or convert " \ + "the buffer to csubstr prior to calling. This function " \ + " is deliberately left undefined, so that calling it " \ + "will cause a linker error." + +// this is vertically aligned to highlight the parameter differences. +RYML_EXPORT void parse_in_arena(Parser *parser, csubstr filename, csubstr yaml, Tree *t, id_type node_id); ///< (1) parse YAML into an existing tree node. The filename will be used in any error messages arising during the parse. +RYML_EXPORT void parse_in_arena(Parser *parser, csubstr yaml, Tree *t, id_type node_id); ///< (2) like (1) but no filename will be reported +RYML_EXPORT void parse_in_arena(Parser *parser, csubstr filename, csubstr yaml, Tree *t ); ///< (3) parse YAML into an existing tree, into its root node. +RYML_EXPORT void parse_in_arena(Parser *parser, csubstr yaml, Tree *t ); ///< (4) like (3) but no filename will be reported +RYML_EXPORT void parse_in_arena(Parser *parser, csubstr filename, csubstr yaml, NodeRef node ); ///< (5) like (1) but the node is given as a NodeRef +RYML_EXPORT void parse_in_arena(Parser *parser, csubstr yaml, NodeRef node ); ///< (6) like (5) but no filename will be reported +RYML_EXPORT Tree parse_in_arena(Parser *parser, csubstr filename, csubstr yaml ); ///< (7) create a new tree, and parse YAML into its root node. +RYML_EXPORT Tree parse_in_arena(Parser *parser, csubstr yaml ); ///< (8) like (7) but no filename will be reported + +// this is vertically aligned to highlight the parameter differences. +RYML_EXPORT void parse_json_in_arena(Parser *parser, csubstr filename, csubstr json, Tree *t, id_type node_id); ///< (1) parse JSON into an existing tree node. The filename will be used in any error messages arising during the parse. +RYML_EXPORT void parse_json_in_arena(Parser *parser, csubstr json, Tree *t, id_type node_id); ///< (2) like (1) but no filename will be reported +RYML_EXPORT void parse_json_in_arena(Parser *parser, csubstr filename, csubstr json, Tree *t ); ///< (3) parse JSON into an existing tree, into its root node. +RYML_EXPORT void parse_json_in_arena(Parser *parser, csubstr json, Tree *t ); ///< (4) like (3) but no filename will be reported +RYML_EXPORT void parse_json_in_arena(Parser *parser, csubstr filename, csubstr json, NodeRef node ); ///< (5) like (1) but the node is given as a NodeRef +RYML_EXPORT void parse_json_in_arena(Parser *parser, csubstr json, NodeRef node ); ///< (6) like (5) but no filename will be reported +RYML_EXPORT Tree parse_json_in_arena(Parser *parser, csubstr filename, csubstr json ); ///< (7) create a new tree, and parse JSON into its root node. +RYML_EXPORT Tree parse_json_in_arena(Parser *parser, csubstr json ); ///< (8) like (7) but no filename will be reported + +/* READ THE DEPRECATION NOTE! + * + * All of the functions below are intentionally left undefined, to + * prevent them being used. + * + */ +/** @cond dev */ +RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena(Parser *parser, substr yaml, Tree *t, id_type node_id); +RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena(Parser *parser, csubstr filename, substr yaml, Tree *t, id_type node_id); +RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena(Parser *parser, substr yaml, Tree *t ); +RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena(Parser *parser, csubstr filename, substr yaml, Tree *t ); +RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena(Parser *parser, substr yaml, NodeRef node ); +RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena(Parser *parser, csubstr filename, substr yaml, NodeRef node ); +RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) Tree parse_in_arena(Parser *parser, substr yaml ); +RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) Tree parse_in_arena(Parser *parser, csubstr filename, substr yaml ); +RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_json_in_arena(Parser *parser, substr json, Tree *t, id_type node_id); +RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_json_in_arena(Parser *parser, csubstr filename, substr json, Tree *t, id_type node_id); +RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_json_in_arena(Parser *parser, substr json, Tree *t ); +RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_json_in_arena(Parser *parser, csubstr filename, substr json, Tree *t ); +RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_json_in_arena(Parser *parser, substr json, NodeRef node ); +RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_json_in_arena(Parser *parser, csubstr filename, substr json, NodeRef node ); +RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) Tree parse_json_in_arena(Parser *parser, substr json ); +RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) Tree parse_json_in_arena(Parser *parser, csubstr filename, substr json ); +/** @endcond */ + +/** @} */ + + +//----------------------------------------------------------------------------- + + +/** @defgroup doc_parse_in_arena__with_temporary_parser Parse in arena with temporary parser + * + * @brief parse a read-only (immutable) YAML source buffer. This is + * achieved by first copying the contents of the buffer to the tree's + * arena, and then calling @ref parse_in_arena() . + * + * @note These freestanding functions use a temporary parser object, + * and are convenience functions to easily one-off parse YAML without + * the need to instantiate a separate parser. Note that some + * properties (notably node locations in the original source code) are + * only available through the parser class. If you need access to any + * of these properties, use the appropriate overload from @ref + * doc_parse_in_arena__with_existing_parser + * + * @warning overloads receiving a substr YAML buffer are intentionally + * left undefined, such that calling parse_in_arena() with a substr + * will cause a linker error. This is to prevent an accidental copy of + * the source buffer to the tree's arena, because substr (which is + * mutable) is implicitly convertible to csubstr (which is + * immutable). If you really intend to parse a mutable buffer in the + * tree's arena, convert it first to immutable by assigning the substr + * to a csubstr prior to calling parse_in_arena(). This is not needed + * for parse_in_place() because csubstr is not implicitly convertible + * to substr. To be clear: + * ```c++ + * substr mutable_buffer = ...; + * parser.parse_in_arena(mutable_buffer); // linker error + * + * csubstr immutable_buffer = mutable_buffer; // convert first to csubstr + * parser.parse_in_arena(immutable_buffer); // ok now + * ``` + * + * @{ + */ + +// this is vertically aligned to highlight the parameter differences. +RYML_EXPORT void parse_in_arena(csubstr filename, csubstr yaml, Tree *t, id_type node_id, ParserOptions const& opts={}); ///< (1) parse YAML into an existing tree node. The filename will be used in any error messages arising during the parse. +RYML_EXPORT void parse_in_arena( csubstr yaml, Tree *t, id_type node_id, ParserOptions const& opts={}); ///< (2) like (1) but no filename will be reported +RYML_EXPORT void parse_in_arena(csubstr filename, csubstr yaml, Tree *t , ParserOptions const& opts={}); ///< (3) parse YAML into an existing tree, into its root node. +RYML_EXPORT void parse_in_arena( csubstr yaml, Tree *t , ParserOptions const& opts={}); ///< (4) like (3) but no filename will be reported +RYML_EXPORT void parse_in_arena(csubstr filename, csubstr yaml, NodeRef node , ParserOptions const& opts={}); ///< (5) like (1) but the node is given as a NodeRef +RYML_EXPORT void parse_in_arena( csubstr yaml, NodeRef node , ParserOptions const& opts={}); ///< (6) like (5) but no filename will be reported +RYML_EXPORT Tree parse_in_arena(csubstr filename, csubstr yaml , ParserOptions const& opts={}); ///< (7) create a new tree, and parse YAML into its root node. +RYML_EXPORT Tree parse_in_arena( csubstr yaml , ParserOptions const& opts={}); ///< (8) like (7) but no filename will be reported + +// this is vertically aligned to highlight the parameter differences. +RYML_EXPORT void parse_json_in_arena(csubstr filename, csubstr json, Tree *t, id_type node_id, ParserOptions const& opts={}); ///< (1) parse JSON into an existing tree node. The filename will be used in any error messages arising during the parse. +RYML_EXPORT void parse_json_in_arena( csubstr json, Tree *t, id_type node_id, ParserOptions const& opts={}); ///< (2) like (1) but no filename will be reported +RYML_EXPORT void parse_json_in_arena(csubstr filename, csubstr json, Tree *t , ParserOptions const& opts={}); ///< (3) parse JSON into an existing tree, into its root node. +RYML_EXPORT void parse_json_in_arena( csubstr json, Tree *t , ParserOptions const& opts={}); ///< (4) like (3) but no filename will be reported +RYML_EXPORT void parse_json_in_arena(csubstr filename, csubstr json, NodeRef node , ParserOptions const& opts={}); ///< (5) like (1) but the node is given as a NodeRef +RYML_EXPORT void parse_json_in_arena( csubstr json, NodeRef node , ParserOptions const& opts={}); ///< (6) like (5) but no filename will be reported +RYML_EXPORT Tree parse_json_in_arena(csubstr filename, csubstr json , ParserOptions const& opts={}); ///< (7) create a new tree, and parse JSON into its root node. +RYML_EXPORT Tree parse_json_in_arena( csubstr json , ParserOptions const& opts={}); ///< (8) like (7) but no filename will be reported + + +/* READ THE DEPRECATION NOTE! + * + * All of the functions below are intentionally left undefined, to + * prevent them being used. + */ +/** @cond dev */ +RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena( substr yaml, Tree *t, id_type node_id); +RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena(csubstr filename, substr yaml, Tree *t, id_type node_id); +RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena( substr yaml, Tree *t ); +RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena(csubstr filename, substr yaml, Tree *t ); +RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena( substr yaml, NodeRef node ); +RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena(csubstr filename, substr yaml, NodeRef node ); +RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) Tree parse_in_arena( substr yaml ); +RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) Tree parse_in_arena(csubstr filename, substr yaml ); +RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_json_in_arena( substr json, Tree *t, id_type node_id); +RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_json_in_arena(csubstr filename, substr json, Tree *t, id_type node_id); +RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_json_in_arena( substr json, Tree *t ); +RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_json_in_arena(csubstr filename, substr json, Tree *t ); +RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_json_in_arena( substr json, NodeRef node ); +RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_json_in_arena(csubstr filename, substr json, NodeRef node ); +RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) Tree parse_json_in_arena( substr json ); +RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) Tree parse_json_in_arena(csubstr filename, substr json ); +/** @endcond */ + +/** @} */ +/** @} */ + +} // namespace yml +} // namespace c4 + +#endif /* _C4_YML_PARSE_HPP_ */ + + +// (end src/c4/yml/parse.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/std/map.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_YML_STD_MAP_HPP_ +#define _C4_YML_STD_MAP_HPP_ + +/** @file map.hpp write/read std::map to/from a YAML tree. */ + +// amalgamate: removed include of +// c4/yml/node.hpp +//#include "c4/yml/node.hpp" +#if !defined(C4_YML_NODE_HPP_) && !defined(_C4_YML_NODE_HPP_) +#error "amalgamate: file c4/yml/node.hpp must have been included at this point" +#endif /* C4_YML_NODE_HPP_ */ + +#include + +namespace c4 { +namespace yml { + +// std::map requires child nodes in the data +// tree hierarchy (a MAP node in ryml parlance). +// So it should be serialized via write()/read(). + +template +void write(c4::yml::NodeRef *n, std::map const& m) +{ + *n |= c4::yml::MAP; + for(auto const& C4_RESTRICT p : m) + { + auto ch = n->append_child(); + ch << c4::yml::key(p.first); + ch << p.second; + } +} + +/** read the node members, assigning into the existing map. If a key + * is already present in the map, then its value will be + * move-assigned. */ +template +bool read(c4::yml::ConstNodeRef const& n, std::map * m) +{ + for(auto const& C4_RESTRICT ch : n) + { + K k{}; + ch >> c4::yml::key(k); + ch >> (*m)[k]; + } + return true; +} + +} // namespace yml +} // namespace c4 + +#endif // _C4_YML_STD_MAP_HPP_ + + +// (end src/c4/yml/std/map.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/std/string.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef C4_YML_STD_STRING_HPP_ +#define C4_YML_STD_STRING_HPP_ + +/** @file string.hpp substring conversions for/from std::string */ + +// everything we need is implemented here: +// amalgamate: removed include of +// c4/std/string.hpp +//#include +#if !defined(C4_STD_STRING_HPP_) && !defined(_C4_STD_STRING_HPP_) +#error "amalgamate: file c4/std/string.hpp must have been included at this point" +#endif /* C4_STD_STRING_HPP_ */ + + +#endif // C4_YML_STD_STRING_HPP_ + + +// (end src/c4/yml/std/string.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/std/vector.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_YML_STD_VECTOR_HPP_ +#define _C4_YML_STD_VECTOR_HPP_ + +// amalgamate: removed include of +// c4/yml/node.hpp +//#include "c4/yml/node.hpp" +#if !defined(C4_YML_NODE_HPP_) && !defined(_C4_YML_NODE_HPP_) +#error "amalgamate: file c4/yml/node.hpp must have been included at this point" +#endif /* C4_YML_NODE_HPP_ */ + +// amalgamate: removed include of +// c4/std/vector.hpp +//#include +#if !defined(C4_STD_VECTOR_HPP_) && !defined(_C4_STD_VECTOR_HPP_) +#error "amalgamate: file c4/std/vector.hpp must have been included at this point" +#endif /* C4_STD_VECTOR_HPP_ */ + +//included above: +//#include + +namespace c4 { +namespace yml { + +// vector is a sequence-like type, and it requires child nodes +// in the data tree hierarchy (a SEQ node in ryml parlance). +// So it should be serialized via write()/read(). + + +template +void write(c4::yml::NodeRef *n, std::vector const& vec) +{ + *n |= c4::yml::SEQ; + for(V const& v : vec) + n->append_child() << v; +} + +/** read the node members, overwriting existing vector entries. */ +template +bool read(c4::yml::ConstNodeRef const& n, std::vector *vec) +{ + C4_SUPPRESS_WARNING_GCC_WITH_PUSH("-Wuseless-cast") + vec->resize(static_cast(n.num_children())); + C4_SUPPRESS_WARNING_GCC_POP + size_t pos = 0; + for(ConstNodeRef const child : n) + child >> (*vec)[pos++]; + return true; +} + +/** read the node members, overwriting existing vector entries. + * specialization: std::vector uses std::vector::reference as + * the return value of its operator[]. */ +template +bool read(c4::yml::ConstNodeRef const& n, std::vector *vec) +{ + C4_SUPPRESS_WARNING_GCC_WITH_PUSH("-Wuseless-cast") + vec->resize(static_cast(n.num_children())); + C4_SUPPRESS_WARNING_GCC_POP + size_t pos = 0; + bool tmp = {}; + for(ConstNodeRef const child : n) + { + child >> tmp; + (*vec)[pos++] = tmp; + } + return true; +} + +} // namespace yml +} // namespace c4 + +#endif // _C4_YML_STD_VECTOR_HPP_ + + +// (end src/c4/yml/std/vector.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/std/std.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_YML_STD_STD_HPP_ +#define _C4_YML_STD_STD_HPP_ + +/** @file std.hpp includes all rapidyaml-std interop files */ + +// bring all of c4core interop +// amalgamate: removed include of +// c4/std/std.hpp +//#include "c4/std/std.hpp" +#if !defined(C4_STD_STD_HPP_) && !defined(_C4_STD_STD_HPP_) +#error "amalgamate: file c4/std/std.hpp must have been included at this point" +#endif /* C4_STD_STD_HPP_ */ + + +// bring all of rapidyaml interop +// amalgamate: removed include of +// c4/yml/std/string.hpp +//#include "c4/yml/std/string.hpp" +#if !defined(C4_YML_STD_STRING_HPP_) && !defined(_C4_YML_STD_STRING_HPP_) +#error "amalgamate: file c4/yml/std/string.hpp must have been included at this point" +#endif /* C4_YML_STD_STRING_HPP_ */ + +// amalgamate: removed include of +// c4/yml/std/vector.hpp +//#include "c4/yml/std/vector.hpp" +#if !defined(C4_YML_STD_VECTOR_HPP_) && !defined(_C4_YML_STD_VECTOR_HPP_) +#error "amalgamate: file c4/yml/std/vector.hpp must have been included at this point" +#endif /* C4_YML_STD_VECTOR_HPP_ */ + +// amalgamate: removed include of +// c4/yml/std/map.hpp +//#include "c4/yml/std/map.hpp" +#if !defined(C4_YML_STD_MAP_HPP_) && !defined(_C4_YML_STD_MAP_HPP_) +#error "amalgamate: file c4/yml/std/map.hpp must have been included at this point" +#endif /* C4_YML_STD_MAP_HPP_ */ + + +#endif // _C4_YML_STD_STD_HPP_ + + +// (end src/c4/yml/std/std.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/version.cpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifdef RYML_SINGLE_HDR_DEFINE_NOW +// amalgamate: removed include of +// c4/yml/version.hpp +//#include "c4/yml/version.hpp" +#if !defined(C4_YML_VERSION_HPP_) && !defined(_C4_YML_VERSION_HPP_) +#error "amalgamate: file c4/yml/version.hpp must have been included at this point" +#endif /* C4_YML_VERSION_HPP_ */ + + +namespace c4 { +namespace yml { + +csubstr version() +{ + return RYML_VERSION; +} + +int version_major() +{ + return RYML_VERSION_MAJOR; +} + +int version_minor() +{ + return RYML_VERSION_MINOR; +} + +int version_patch() +{ + return RYML_VERSION_PATCH; +} + +} // namespace yml +} // namespace c4 + +#endif /* RYML_SINGLE_HDR_DEFINE_NOW */ + + +// (end src/c4/yml/version.cpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/common.cpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifdef RYML_SINGLE_HDR_DEFINE_NOW +// amalgamate: removed include of +// c4/yml/common.hpp +//#include "c4/yml/common.hpp" +#if !defined(C4_YML_COMMON_HPP_) && !defined(_C4_YML_COMMON_HPP_) +#error "amalgamate: file c4/yml/common.hpp must have been included at this point" +#endif /* C4_YML_COMMON_HPP_ */ + +// amalgamate: removed include of +// c4/yml/error.hpp +//#include "c4/yml/error.hpp" +#if !defined(C4_YML_ERROR_HPP_) && !defined(_C4_YML_ERROR_HPP_) +#error "amalgamate: file c4/yml/error.hpp must have been included at this point" +#endif /* C4_YML_ERROR_HPP_ */ + +// amalgamate: removed include of +// c4/yml/error.def.hpp +//#include "c4/yml/error.def.hpp" +#if !defined(C4_YML_ERROR_DEF_HPP_) && !defined(_C4_YML_ERROR_DEF_HPP_) +#error "amalgamate: file c4/yml/error.def.hpp must have been included at this point" +#endif /* C4_YML_ERROR_DEF_HPP_ */ + + +#ifndef RYML_NO_DEFAULT_CALLBACKS +//included above: +//# include +//included above: +//# include +#endif // RYML_NO_DEFAULT_CALLBACKS +#ifdef _RYML_EXCEPTIONS +# include +#endif + + +namespace c4 { +namespace yml { + +C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wold-style-cast") +C4_SUPPRESS_WARNING_MSVC_WITH_PUSH(4702/*unreachable code*/) // on the call to the unreachable macro + +namespace { +Callbacks s_default_callbacks; + +#ifndef RYML_NO_DEFAULT_CALLBACKS + +C4_NO_INLINE void dump2stderr(csubstr s) +{ + // using fwrite() is more portable than using fprintf("%.*s") which + // is not available in some embedded platforms + if(s.len) + fwrite(s.str, 1, s.len, stderr); // NOLINT +} +C4_NO_INLINE void endmsg() +{ + fputc('\n', stderr); // NOLINT + fflush(stderr); // NOLINT +} + +[[noreturn]] C4_NO_INLINE void error_basic_impl(csubstr msg, ErrorDataBasic const& errdata, void * /*user_data*/) +{ + err_basic_format(dump2stderr, msg, errdata); + endmsg(); + #ifdef _RYML_WITH_EXCEPTIONS + throw ExceptionBasic(msg, errdata); + #else + abort(); // LCOV_EXCL_LINE + #endif +} + +[[noreturn]] C4_NO_INLINE void error_parse_impl(csubstr msg, ErrorDataParse const& errdata, void * /*user_data*/) +{ + err_parse_format(dump2stderr, msg, errdata); + endmsg(); + #ifdef _RYML_WITH_EXCEPTIONS + throw ExceptionParse(msg, errdata); + #else + abort(); // LCOV_EXCL_LINE + #endif +} + +[[noreturn]] C4_NO_INLINE void error_visit_impl(csubstr msg, ErrorDataVisit const& errdata, void * /*user_data*/) +{ + err_visit_format(dump2stderr, msg, errdata); + endmsg(); + #ifdef _RYML_WITH_EXCEPTIONS + throw ExceptionVisit(msg, errdata); + #else + abort(); // LCOV_EXCL_LINE + #endif +} + +void* allocate_impl(size_t length, void * /*hint*/, void * /*user_data*/) +{ + void *mem = ::malloc(length); + if(mem == nullptr) + error_basic_impl("could not allocate memory", ErrorDataBasic{RYML_LOC_HERE()}, nullptr); // LCOV_EXCL_LINE + return mem; +} + +void free_impl(void *mem, size_t /*length*/, void * /*user_data*/) +{ + ::free(mem); +} + +#endif // RYML_NO_DEFAULT_CALLBACKS + +} // anon namespace + + +void set_callbacks(Callbacks const& c) +{ + s_default_callbacks = c; +} + +Callbacks const& get_callbacks() +{ + return s_default_callbacks; +} + +void reset_callbacks() +{ + set_callbacks(Callbacks()); +} + + +Callbacks::Callbacks() noexcept + : + m_user_data(nullptr), + #ifndef RYML_NO_DEFAULT_CALLBACKS + m_allocate(allocate_impl), + m_free(free_impl), + m_error_basic(error_basic_impl), + m_error_parse(error_parse_impl), + m_error_visit(error_visit_impl) + #else + m_allocate(nullptr), + m_free(nullptr), + m_error_basic(nullptr), + m_error_parse(nullptr), + m_error_visit(nullptr) + #endif +{ +} + +Callbacks::Callbacks(void *user_data, pfn_allocate alloc_, pfn_free free_, pfn_error_basic error_basic_) + : + m_user_data(user_data), + #ifndef RYML_NO_DEFAULT_CALLBACKS + m_allocate(alloc_ ? alloc_ : allocate_impl), + m_free(free_ ? free_ : free_impl), + m_error_basic(error_basic_ ? error_basic_ : error_basic_impl), + m_error_parse(error_parse_impl), + m_error_visit(error_visit_impl) + #else + m_allocate(alloc_), + m_free(free_), + m_error_basic(error_basic_), + m_error_parse(nullptr), + m_error_visit(nullptr) + #endif +{ +} + + +Callbacks& Callbacks::set_user_data(void* user_data) +{ + m_user_data = user_data; + return *this; +} + +Callbacks& Callbacks::set_allocate(pfn_allocate allocate) +{ + m_allocate = allocate; + #ifndef RYML_NO_DEFAULT_CALLBACKS + m_allocate = m_allocate ? m_allocate : allocate_impl; + #endif + return *this; +} + +Callbacks& Callbacks::set_free(pfn_free free) +{ + m_free = free; + #ifndef RYML_NO_DEFAULT_CALLBACKS + m_free = m_free ? m_free : free_impl; + #endif + return *this; +} + +Callbacks& Callbacks::set_error_basic(pfn_error_basic error_basic) +{ + m_error_basic = error_basic; + #ifndef RYML_NO_DEFAULT_CALLBACKS + m_error_basic = m_error_basic ? m_error_basic : error_basic_impl; + #endif + return *this; +} + +Callbacks& Callbacks::set_error_parse(pfn_error_parse error_parse) +{ + m_error_parse = error_parse; + #ifndef RYML_NO_DEFAULT_CALLBACKS + m_error_parse = m_error_parse ? m_error_parse : error_parse_impl; + #endif + return *this; +} + +Callbacks& Callbacks::set_error_visit(pfn_error_visit error_visit) +{ + m_error_visit = error_visit; + #ifndef RYML_NO_DEFAULT_CALLBACKS + m_error_visit = m_error_visit ? m_error_visit : error_visit_impl; + #endif + return *this; +} + + +C4_NORETURN C4_NO_INLINE void err_basic(ErrorDataBasic const& errdata, const char* msg) +{ + err_basic(get_callbacks(), errdata, msg); + C4_UNREACHABLE_AFTER_ERR(); +} +C4_NORETURN C4_NO_INLINE void err_basic(Callbacks const& callbacks, ErrorDataBasic const& errdata, const char* msg_) +{ + csubstr msg = to_csubstr(msg_); + callbacks.m_error_basic(msg, errdata, callbacks.m_user_data); + abort(); // the call above should not return, so force it here in case it does // LCOV_EXCL_LINE + C4_UNREACHABLE_AFTER_ERR(); +} + + +C4_NORETURN C4_NO_INLINE void err_parse(ErrorDataParse const& errdata, const char *msg) +{ + err_parse(get_callbacks(), errdata, msg); + C4_UNREACHABLE_AFTER_ERR(); +} +C4_NORETURN C4_NO_INLINE void err_parse(Callbacks const& callbacks, ErrorDataParse const& errdata, const char *msg_) +{ + csubstr msg = to_csubstr(msg_); + if(callbacks.m_error_parse) + callbacks.m_error_parse(msg, errdata, callbacks.m_user_data); + // fall to basic error if there is no parse handler set + else if(callbacks.m_error_basic) + callbacks.m_error_basic(msg, errdata.ymlloc, callbacks.m_user_data); + abort(); // the call above should not return, so force it here in case it does // LCOV_EXCL_LINE + C4_UNREACHABLE_AFTER_ERR(); +} + + +C4_NORETURN C4_NO_INLINE void err_visit(ErrorDataVisit const& errdata, const char *msg) +{ + err_visit(get_callbacks(), errdata, msg); + C4_UNREACHABLE_AFTER_ERR(); +} +C4_NORETURN C4_NO_INLINE void err_visit(Callbacks const& callbacks, ErrorDataVisit const& errdata, const char *msg_) +{ + csubstr msg = to_csubstr(msg_); + if(callbacks.m_error_visit) + callbacks.m_error_visit(msg, errdata, callbacks.m_user_data); + // fall to basic error if there is no visit handler set + else if(callbacks.m_error_basic) + callbacks.m_error_basic(msg, errdata.cpploc, callbacks.m_user_data); + abort(); // the call above should not return, so force it here in case it does // LCOV_EXCL_LINE + C4_UNREACHABLE_AFTER_ERR(); +} + + + +#ifdef _RYML_WITH_EXCEPTIONS +ExceptionBasic::ExceptionBasic(csubstr msg_, ErrorDataBasic const& errdata_) noexcept + : errdata_basic(errdata_) + , msg() +{ + msg[0] = '\0'; + if(msg_.len) + { + if(msg_.len >= sizeof(msg)) + { + static_assert(sizeof(msg) > 6u, "message buffer too small"); + msg_.len = sizeof(msg) - 6u; + msg[msg_.len ] = '['; + msg[msg_.len + 1u] = '.'; + msg[msg_.len + 2u] = '.'; + msg[msg_.len + 3u] = '.'; + msg[msg_.len + 4u] = ']'; + msg[msg_.len + 5u] = '\0'; + } + memcpy(msg, msg_.str, msg_.len); + } +} +ExceptionParse::ExceptionParse(csubstr msg_, ErrorDataParse const& errdata_) noexcept + : ExceptionBasic(msg_, {errdata_.ymlloc}) + , errdata_parse(errdata_) +{ +} +ExceptionVisit::ExceptionVisit(csubstr msg_, ErrorDataVisit const& errdata_) noexcept + : ExceptionBasic(msg_, {errdata_.cpploc}) + , errdata_visit(errdata_) +{ +} +#endif // _RYML_WITH_EXCEPTIONS + + +namespace detail { +RYML_EXPORT csubstr _get_text_region(csubstr text, size_t pos, size_t num_lines_before, size_t num_lines_after) +{ + if(pos > text.len) + return text.last(0); + size_t before = text.first(pos).last_of('\n'); + size_t before_count = 0; + while((before != npos) && (++before_count <= num_lines_before)) + { + if(before == 0) + break; + before = text.first(--before).last_of('\n'); + } + if(before < text.len || before == npos) + ++before; + size_t after = text.first_of('\n', pos); + size_t after_count = 0; + while((after != npos) && (++after_count <= num_lines_after)) + { + ++after; + if(after >= text.len) + break; + after = text.first_of('\n', after); + } + return before <= after ? text.range(before, after) : text.first(0); +} +} // namespace detail + +C4_SUPPRESS_WARNING_MSVC_POP +C4_SUPPRESS_WARNING_GCC_CLANG_POP + +} // namespace yml +} // namespace c4 + +#endif /* RYML_SINGLE_HDR_DEFINE_NOW */ + + +// (end src/c4/yml/common.cpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/node_type.cpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifdef RYML_SINGLE_HDR_DEFINE_NOW +#ifndef _C4_YML_NODE_TYPE_HPP_ +// amalgamate: removed include of +// c4/yml/node_type.hpp +//#include "c4/yml/node_type.hpp" +#if !defined(C4_YML_NODE_TYPE_HPP_) && !defined(_C4_YML_NODE_TYPE_HPP_) +#error "amalgamate: file c4/yml/node_type.hpp must have been included at this point" +#endif /* C4_YML_NODE_TYPE_HPP_ */ + +#endif +#ifndef _C4_YML_ERROR_HPP_ +// amalgamate: removed include of +// c4/yml/error.hpp +//#include "c4/yml/error.hpp" +#if !defined(C4_YML_ERROR_HPP_) && !defined(_C4_YML_ERROR_HPP_) +#error "amalgamate: file c4/yml/error.hpp must have been included at this point" +#endif /* C4_YML_ERROR_HPP_ */ + +#endif + + +namespace c4 { +namespace yml { + +const char* NodeType::type_str(NodeType_e ty) noexcept +{ + switch(ty & _TYMASK) + { + case KEYVAL: + return "KEYVAL"; + case KEY: + return "KEY"; + case VAL: + return "VAL"; + case MAP: + return "MAP"; + case SEQ: + return "SEQ"; + case KEYMAP: + return "KEYMAP"; + case KEYSEQ: + return "KEYSEQ"; + case DOCSEQ: + return "DOCSEQ"; + case DOCMAP: + return "DOCMAP"; + case DOCVAL: + return "DOCVAL"; + case DOC: + return "DOC"; + case STREAM: + return "STREAM"; + case NOTYPE: + return "NOTYPE"; + default: + if((ty & KEYVAL) == KEYVAL) + return "KEYVAL***"; + if((ty & KEYMAP) == KEYMAP) + return "KEYMAP***"; + if((ty & KEYSEQ) == KEYSEQ) + return "KEYSEQ***"; + if((ty & DOCSEQ) == DOCSEQ) + return "DOCSEQ***"; + if((ty & DOCMAP) == DOCMAP) + return "DOCMAP***"; + if((ty & DOCVAL) == DOCVAL) + return "DOCVAL***"; + if(ty & KEY) + return "KEY***"; + if(ty & VAL) + return "VAL***"; + if(ty & MAP) + return "MAP***"; + if(ty & SEQ) + return "SEQ***"; + if(ty & DOC) + return "DOC***"; + return "(unk)"; + } +} + +size_t NodeType::type_str(substr buf, NodeType_e flags) noexcept +{ + size_t pos = 0; + bool gotone = false; + + #define _prflag(fl, txt) \ + do { \ + if((flags & (fl)) == (fl)) \ + { \ + if(gotone) \ + { \ + if(pos + 1 < buf.len) \ + buf[pos] = '|'; \ + ++pos; \ + } \ + csubstr fltxt = txt; \ + if(pos + fltxt.len <= buf.len) \ + memcpy(buf.str + pos, fltxt.str, fltxt.len); \ + pos += fltxt.len; \ + gotone = true; \ + flags = (flags & ~(fl)); /*remove the flag*/ \ + } \ + } while(0) + + _prflag(STREAM, "STREAM"); + _prflag(DOC, "DOC"); + // key properties + _prflag(KEY, "KEY"); + _prflag(KEYNIL, "KNIL"); + _prflag(KEYTAG, "KTAG"); + _prflag(KEYANCH, "KANCH"); + _prflag(KEYREF, "KREF"); + _prflag(KEY_LITERAL, "KLITERAL"); + _prflag(KEY_FOLDED, "KFOLDED"); + _prflag(KEY_SQUO, "KSQUO"); + _prflag(KEY_DQUO, "KDQUO"); + _prflag(KEY_PLAIN, "KPLAIN"); + _prflag(KEY_UNFILT, "KUNFILT"); + // val properties + _prflag(VAL, "VAL"); + _prflag(VALNIL, "VNIL"); + _prflag(VALTAG, "VTAG"); + _prflag(VALANCH, "VANCH"); + _prflag(VALREF, "VREF"); + _prflag(VAL_UNFILT, "VUNFILT"); + _prflag(VAL_LITERAL, "VLITERAL"); + _prflag(VAL_FOLDED, "VFOLDED"); + _prflag(VAL_SQUO, "VSQUO"); + _prflag(VAL_DQUO, "VDQUO"); + _prflag(VAL_PLAIN, "VPLAIN"); + _prflag(VAL_UNFILT, "VUNFILT"); + // container properties + _prflag(MAP, "MAP"); + _prflag(SEQ, "SEQ"); + _prflag(FLOW_SL, "FLOWSL"); + _prflag(FLOW_ML1, "FLOWML1"); + _prflag(FLOW_MLN, "FLOWMLN"); + _prflag(FLOW_SPC, "FLOWSPC"); + _prflag(BLOCK, "BLCK"); + if(pos == 0) + _prflag(NOTYPE, "NOTYPE"); + + #undef _prflag + + return pos; +} + + +//----------------------------------------------------------------------------- + +// see https://www.yaml.info/learn/quote.html#noplain +bool scalar_style_query_squo(csubstr s) noexcept +{ + // cannot have leading whitespace after a newline + for(size_t i = 0; i < s.len; ++i) + { + if(s.str[i] == '\n' && i + 1 < s.len) + { + char next = s.str[i + 1]; + if(next == ' ' || next == '\t') + return false; + } + } + return true; +} + +namespace { +bool _is_wsnl(char c) noexcept +{ + return c == ' ' || c == '\n' || c == '\t' || c == '\r'; +} +bool _is_valid_bulk(csubstr s, size_t i) +{ + C4_ASSERT(i >= 1 && i+1 < s.len); + C4_ASSERT(s.str[i] == ':' || s.str[i] == '#'); + switch(s.str[i]) + { + case ':': return !_is_wsnl(s.str[i+1]); + case '#': return !_is_wsnl(s.str[i-1]); + } + C4_UNREACHABLE(); // LCOV_EXCL_LINE +} +} // namespace +// see https://www.yaml.info/learn/quote.html#noplain +bool scalar_style_query_plain_flow(csubstr s) noexcept +{ + if(!s.len) + return !s.str; + // first + switch(s.str[0]) + { + case ' ': case '\n': case '\t': case '\r': + case '!': case '&': case '*': case ',': + case '"': case '\'': case '|': case '>': + case '{': case '}': case '[': case ']': + case '#': case '`': case '%': case '@': + return false; + case '-': case ':': case '?': + if(s.len == 1 || (s.str[1] == ' ' || s.str[1] == '\t')) + return false; + break; + } + // bulk + for(size_t i = 1; i + 1 < s.len; ++i) + { + switch(s.str[i]) + { + case ',': case '{': case '}': case '[': case ']': + return false; + case ':': case '#': + if(!_is_valid_bulk(s, i)) + return false; + break; + } + } + // last + if(s.len > 1) + { + switch(s.back()) + { + case ' ': case '\n': case '\t': case '\r': + case ',': + case '{': case '}': + case '[': case ']': + case '#': + case ':': + return false; + } + } + return true; +} + +bool scalar_style_query_plain_block(csubstr s) noexcept +{ + if(!s.len) + return !s.str; + // first + switch(s.str[0]) + { + case ' ': case '\n': case '\t': case '\r': + case '!': case '&': case '*': case ',': + case '"': case '\'': case '|': case '>': + case '{': case '}': case '[': case ']': + case '#': case '`': case '%': case '@': + return false; + case '-': case ':': case '?': + if (s.len == 1 || (s.str[1] == ' ' || s.str[1] == '\t')) + return false; + break; + } + // bulk + for(size_t i = 1; i + 1 < s.len; ++i) + { + switch(s.str[i]) + { + case ':': case '#': + if(!_is_valid_bulk(s, i)) + return false; + break; + } + } + // last + if(s.len > 1) + { + switch(s.back()) + { + case ' ': case '\n': case '\t': case '\r': + case '#': + case ':': + return false; + } + } + return true; +} + +NodeType_e scalar_style_choose_flow(csubstr s) noexcept +{ + if(s.len) + { + if(scalar_style_query_plain_flow(s)) + return SCALAR_PLAIN; + else if(scalar_style_query_squo(s)) + return SCALAR_SQUO; + return SCALAR_DQUO; + } + return s.str ? SCALAR_SQUO : SCALAR_PLAIN; +} + +NodeType_e scalar_style_choose_block(csubstr s) noexcept +{ + if(s.len) + { + if(scalar_style_query_plain_block(s)) + return SCALAR_PLAIN; + _RYML_ASSERT_BASIC(scalar_style_query_squo(s) + && "if this assertion fires, please submit an issue!"); + return SCALAR_SQUO; + } + return s.str ? SCALAR_SQUO : SCALAR_PLAIN; +} + + +bool scalar_is_null(csubstr s) noexcept +{ + return s.str == nullptr || + (s.len == 1 && (s.str[0] == '~')) || + (s.len == 4 && ((0 == memcmp("null", s.str, 4)) + || (0 == memcmp("Null", s.str, 4)) + || (0 == memcmp("NULL", s.str, 4)))); +} + + +//----------------------------------------------------------------------------- + +namespace { + +#define rest_is(c1, c2) ((s.str[1] == (c1)) && (s.str[2] == (c2))) +bool is_inf_or_nan(csubstr s) noexcept +{ + _RYML_ASSERT_BASIC(!s.begins_with("-.")); + _RYML_ASSERT_BASIC(!s.begins_with("+.")); + _RYML_ASSERT_BASIC(!s.begins_with(".")); + _RYML_ASSERT_BASIC(s.len == 3); + switch(s.str[0]) + { + case 'i': return rest_is('n', 'f'); + case 'I': return rest_is('n', 'f') || rest_is('N', 'F'); + case 'n': return rest_is('a', 'n'); + case 'N': return rest_is('a', 'n') || rest_is('A', 'N') || rest_is('a', 'N'); + } + return false; +} +bool is_inf(csubstr s) noexcept +{ + _RYML_ASSERT_BASIC(!s.begins_with("-.")); + _RYML_ASSERT_BASIC(!s.begins_with("+.")); + _RYML_ASSERT_BASIC(!s.begins_with(".")); + _RYML_ASSERT_BASIC(s.len == 3); + switch(s.str[0]) + { + case 'i': return rest_is('n', 'f'); + case 'I': return rest_is('n', 'f') || rest_is('N', 'F'); + } + return false; +} +#undef rest_is + +bool json_is_plain_number(csubstr s) noexcept +{ + return s.is_number() + && + ( + // quote integral numbers if they have a leading 0 + // https://github.com/biojppm/rapidyaml/issues/291 + (!(s.len > 1 && s.begins_with('0'))) + // do not quote reals with leading 0 + // https://github.com/biojppm/rapidyaml/issues/313 + || (s.find('.') != csubstr::npos) + ); +} +bool json_is_special_scalar(csubstr s) noexcept +{ + if(s.len == 4) + return 0 == memcmp("true", s.str, 4) + || 0 == memcmp("null", s.str, 4) + || (s[0] == '.' && is_inf_or_nan(s.sub(1))); + else if(s.len == 5) + return 0 == memcmp("false", s.str, 5) + || ((s[0] == '-' || s[0] == '+') && s[1] == '.' && is_inf(s.sub(2))); + return false; +} +} // namespace +NodeType_e scalar_style_choose_json(csubstr s) noexcept +{ + // do not quote numbers or special scalars + return json_is_plain_number(s) || json_is_special_scalar(s) ? SCALAR_PLAIN : SCALAR_DQUO; +} + +} // namespace yml +} // namespace c4 + +#endif /* RYML_SINGLE_HDR_DEFINE_NOW */ + + +// (end src/c4/yml/node_type.cpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/tag.cpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifdef RYML_SINGLE_HDR_DEFINE_NOW +// amalgamate: removed include of +// c4/yml/tag.hpp +//#include "c4/yml/tag.hpp" +#if !defined(C4_YML_TAG_HPP_) && !defined(_C4_YML_TAG_HPP_) +#error "amalgamate: file c4/yml/tag.hpp must have been included at this point" +#endif /* C4_YML_TAG_HPP_ */ + +// amalgamate: removed include of +// c4/yml/error.hpp +//#include "c4/yml/error.hpp" +#if !defined(C4_YML_ERROR_HPP_) && !defined(_C4_YML_ERROR_HPP_) +#error "amalgamate: file c4/yml/error.hpp must have been included at this point" +#endif /* C4_YML_ERROR_HPP_ */ + +// amalgamate: removed include of +// c4/yml/detail/dbgprint.hpp +//#include "c4/yml/detail/dbgprint.hpp" +#if !defined(C4_YML_DETAIL_DBGPRINT_HPP_) && !defined(_C4_YML_DETAIL_DBGPRINT_HPP_) +#error "amalgamate: file c4/yml/detail/dbgprint.hpp must have been included at this point" +#endif /* C4_YML_DETAIL_DBGPRINT_HPP_ */ + + + +namespace c4 { +namespace yml { + +bool is_custom_tag(csubstr tag) +{ + if((tag.len > 2) && (tag.str[0] == '!')) + { + size_t pos = tag.find('!', 1); + return pos != npos && pos > 1 && tag.str[1] != '<'; + } + return false; +} + +csubstr normalize_tag(csubstr tag) +{ + YamlTag_e t = to_tag(tag); + if(t != TAG_NONE) + return from_tag(t); + if(tag.begins_with("!<")) + tag = tag.sub(1); + if(tag.begins_with("'; + result = output.first(len); + } + else + { + result.str = nullptr; + result.len = len; + } + } + return result; +} + +YamlTag_e to_tag(csubstr tag) +{ + if(tag.begins_with("!<")) + tag = tag.sub(1); + if(tag.begins_with("!!")) + { + tag = tag.sub(2); + } + else if(tag.begins_with('!')) + { + return TAG_NONE; + } + else + { + csubstr pfx = ""}; + case TAG_OMAP: + return {""}; + case TAG_PAIRS: + return {""}; + case TAG_SET: + return {""}; + case TAG_SEQ: + return {""}; + case TAG_BINARY: + return {""}; + case TAG_BOOL: + return {""}; + case TAG_FLOAT: + return {""}; + case TAG_INT: + return {""}; + case TAG_MERGE: + return {""}; + case TAG_NULL: + return {""}; + case TAG_STR: + return {""}; + case TAG_TIMESTAMP: + return {""}; + case TAG_VALUE: + return {""}; + case TAG_YAML: + return {""}; + case TAG_NONE: + default: + return {""}; + } +} + +csubstr from_tag(YamlTag_e tag) +{ + switch(tag) + { + case TAG_MAP: + return {"!!map"}; + case TAG_OMAP: + return {"!!omap"}; + case TAG_PAIRS: + return {"!!pairs"}; + case TAG_SET: + return {"!!set"}; + case TAG_SEQ: + return {"!!seq"}; + case TAG_BINARY: + return {"!!binary"}; + case TAG_BOOL: + return {"!!bool"}; + case TAG_FLOAT: + return {"!!float"}; + case TAG_INT: + return {"!!int"}; + case TAG_MERGE: + return {"!!merge"}; + case TAG_NULL: + return {"!!null"}; + case TAG_STR: + return {"!!str"}; + case TAG_TIMESTAMP: + return {"!!timestamp"}; + case TAG_VALUE: + return {"!!value"}; + case TAG_YAML: + return {"!!yaml"}; + case TAG_NONE: + default: + return {""}; + } +} + +bool is_valid_tag_handle(csubstr handle) +{ + if(handle.begins_with('!') && handle.ends_with('!')) + { + _c4dbgpf("handle={}", _prs(handle, true)); + csubstr trimmed = handle.sub(1); + if(trimmed.ends_with('!')) + trimmed = trimmed.offs(0, 1); + _c4dbgpf("handle_trimmed={}", _prs(trimmed, true)); + // https://yaml.org/spec/1.2.2/#rule-ns-word-char + for(char c : trimmed) + { + bool ok = (c >= '0' && c <= '9') + || (c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || c == '-'; + if(!ok) + { + _c4dbgpf("invalid handle character: '{}'", _c4prc(c)); + return false; + } + } + return true; + } + return false; +} + +namespace { +bool is_valid_tag_char(char c) +{ + // https://yaml.org/spec/1.2.2/#691-node-tags + bool ok = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); + if(!ok) + { + switch(c) + { + case '-': + case '#': + case ';': + case '/': + case '?': + case ':': + case '@': + case '&': + case '=': + case '+': + case '$': + case '_': + case '.': + case '~': + case '*': + case '\'': + case '(': + case ')': + case '%': + break; + default: + return false; + } + } + return true; +} +bool read_hex_char(csubstr suffix, size_t pos, char *out) +{ + // must be succeeded by 2 hex digits + if(pos + 3 > suffix.len) + return false; + suffix = suffix.range(pos + 1, pos + 3); + uint8_t val = 0; + if(C4_UNLIKELY(!read_hex(suffix, &val) || val > 127)) + return false; + *out = static_cast(val); + return true; +} +} // namespace + + +size_t transform_tag(substr output, csubstr handle, csubstr prefix, csubstr tag, + Callbacks const& callbacks, Location const& ymlloc, + bool with_brackets) +{ + _RYML_ASSERT_BASIC_(callbacks, tag.len >= handle.len); + _RYML_ASSERT_BASIC_(callbacks, !output.overlaps(tag)); + _RYML_ASSERT_BASIC_(callbacks, prefix.len > 0); + csubstr rest = tag.sub(handle.len); + _c4dbgpf("%TAG: rest={}", _prs(rest)); + size_t rpos = 0, wpos = 0; + auto appendstr = [&](csubstr s) { + if(s.len && wpos + s.len <= output.len) + memcpy(output.str + wpos, s.str, s.len); + wpos += s.len; + }; + auto appendchar = [&](char c) { + if(wpos < output.len) + output.str[wpos] = c; + ++wpos; + }; + if(with_brackets) + appendchar('<'); + appendstr(prefix); + const char *errmsg = nullptr; + for(size_t pos = 0; pos < rest.len; ++pos) + { + char c = rest.str[pos]; + if(C4_LIKELY(is_valid_tag_char(c))) + { + if(c != '%') + continue; + else if(read_hex_char(rest, pos, &c)) + { + appendstr(rest.range(rpos, pos)); + appendchar(c); + pos += 2; + rpos = pos + 1; + continue; + } + } + errmsg = "invalid tag"; + goto err; // NOLINT + } + appendstr(rest.sub(rpos)); + if(with_brackets) + appendchar('>'); + return wpos; +err: + if(ymlloc) + { + _RYML_ERR_PARSE_(callbacks, ymlloc, errmsg); + } + else + { + _RYML_ERR_BASIC_(callbacks, errmsg); + } +} + + +//----------------------------------------------------------------------------- + +id_type TagDirectives::size() const noexcept +{ + // this assumes we have a very small number of tag directives + id_type i = 0; + for(; i < RYML_MAX_TAG_DIRECTIVES; ++i) + if(m_directives[i].handle.empty()) + break; + return i; +} + +TagDirective const* TagDirectives::add(csubstr handle, csubstr prefix, id_type doc_id) noexcept +{ + id_type pos = size(); + TagDirective *C4_RESTRICT td = nullptr; + if(pos < RYML_MAX_TAG_DIRECTIVES) + { + td = &m_directives[pos]; + td->handle = handle; + td->prefix = prefix; + td->doc_id = doc_id; + _c4dbgpf("tagd[{}]: added! handle={} prefix={} doc={}", pos, td->handle, td->prefix, td->doc_id); + } + return td; +} + +void TagDirectives::clear() noexcept +{ + for(TagDirective &td : m_directives) + { + td.handle = {}; + td.prefix = {}; + td.doc_id = NONE; + } +} + +TagDirectiveRange TagDirectives::lookup_range(id_type doc_id) const noexcept +{ + TagDirective const* first = nullptr; + TagDirective const* last = nullptr; + for(id_type i = 0; i < RYML_MAX_TAG_DIRECTIVES; ++i) + { + TagDirective const& C4_RESTRICT td = m_directives[i]; + if(doc_id == td.doc_id) + { + first = m_directives + i; + break; + } + else if(td.handle.empty()) + { + break; + } + } + if(first) + { + last = m_directives + RYML_MAX_TAG_DIRECTIVES; + for(TagDirective const* C4_RESTRICT td = first; td < last; ++td) + { + if(doc_id != td->doc_id || td->handle.empty()) + { + last = td; + break; + } + } + } + else + { + first = last = m_directives; + } + return TagDirectiveRange{first, last}; +} + +TagDirective const* TagDirectives::lookup(csubstr tag, id_type doc_id) const noexcept +{ + _c4dbgpf("tagd: searching for {}, doc_id={}", _prs(tag), doc_id); + for(id_type i = 0; i < RYML_MAX_TAG_DIRECTIVES; ++i) + { + TagDirective const& C4_RESTRICT td = m_directives[i]; + if(td.handle.empty()) + { + continue; + } + _c4dbgpf("tagd[{}]: handle={} prefix={} doc_id={}", i, td.handle, td.prefix, td.doc_id); + if(tag.begins_with(td.handle)) + { + if(td.handle == '!' && ( + tag.begins_with("!!") + || tag.begins_with('<') + || tag.begins_with("!<") + || is_custom_tag(tag))) + continue; + _c4dbgpf("tagd[{}]: matches handle!", i); + if(doc_id == td.doc_id) + { + _c4dbgpf("tagd[{}]: matches doc={}!", i, doc_id); + return &td; + } + } + } + return nullptr; +} + +csubstr TagDirectives::resolve(substr buf, size_t *bufsz, csubstr tag, id_type id, Location const& ymlloc, Callbacks const& callbacks, bool with_brackets) const +{ + _RYML_ASSERT_BASIC_(callbacks, !buf.overlaps(tag)); + TagDirective const* C4_RESTRICT td = lookup(tag, id); + *bufsz = 0; + csubstr handle, prefix, ret; + const char *errmsg = nullptr; + size_t len; + if(td) + { + handle = td->handle; + prefix = td->prefix; + } + else + { + _c4dbgp("tagd: no directive found"); + if(tag.begins_with('<')) + { + _c4dbgp("tagd: already resolved"); + if(C4_UNLIKELY(!tag.ends_with('>'))) + { + errmsg = "malformed tag"; + goto err; // NOLINT + } + return tag; + } + else if(tag.begins_with("!<")) + { + _c4dbgp("tagd: already resolved"); + if(C4_UNLIKELY(!tag.ends_with('>'))) + { + errmsg = "malformed tag"; + goto err; // NOLINT + } + return tag.sub(1); + } + else if(tag.begins_with("!!")) + { + _c4dbgp("tagd: !!"); + YamlTag_e tagenum = to_tag(tag); + if(tagenum != TAG_NONE) + { + _c4dbgpf("tagd: standard tag: {} -> {}", tag, from_tag_long(tagenum)); + tag = from_tag_long(tagenum); + return with_brackets ? tag : tag.offs(1, 1); + } + handle = "!!"; + prefix = "tag:yaml.org,2002:"; + } + else if(C4_UNLIKELY(is_custom_tag(tag))) + { + _c4dbgp("tagd: custom_tag"); + _c4dbgpf("tag '{}' at id={}: no matching directive was found", tag, id); + errmsg = "tag without matching directive"; + goto err; // NOLINT + } + else + { + _c4dbgp("tagd: !"); + handle = prefix = "!"; + } + } + len = transform_tag(buf, handle, prefix, tag, callbacks, ymlloc, with_brackets); + *bufsz = len; + if(len <= buf.len) + { + ret = buf.first(len); + } + else + { + _c4dbgp("tagd: not enough room"); + ret.str = nullptr; + ret.len = len; + } + return ret; +err: + if(ymlloc) + { + _RYML_ERR_PARSE_(callbacks, ymlloc, errmsg); + } + else + { + _RYML_ERR_BASIC_(callbacks, errmsg); + } +} + + +//----------------------------------------------------------------------------- +TagCache::LookupResult TagCache::find(csubstr tag, id_type doc_id, id_type linear_threshold) const noexcept +{ + LookupResult ret = {}; + id_type sz = m_entries.size(); + if(sz < linear_threshold) // do a linear search on small size + { + for(size_t i = 0; i < sz; ++i) + { + Entry const& C4_RESTRICT e = m_entries[i]; + if(e.tag == tag && e.doc_id == doc_id) + { + ret.resolved = e.resolved; + ret.pos = i; + return ret; + } + else if(e.tag > tag || ((e.tag == tag) && e.doc_id > doc_id)) + { + ret.pos = i; + return ret; + } + } + ret.pos = sz; + } + else // do a binary search on larger size + { + id_type first = 0; + id_type count = sz; + while(count) + { + id_type halfsz = count / id_type(2); + id_type mid = first + halfsz; + _RYML_ASSERT_BASIC_(m_entries.m_callbacks, mid < sz); + Entry const& C4_RESTRICT e = m_entries[mid]; + if(e.tag < tag || (e.tag == tag && e.doc_id < doc_id)) + { + first = mid + 1; + _RYML_ASSERT_BASIC_(m_entries.m_callbacks, count >= halfsz + 1); + count -= halfsz + 1; + } + else + { + count = halfsz; + } + } + ret.pos = first; + if(first < sz) + { + Entry const& C4_RESTRICT e = m_entries[first]; + if(e.tag == tag && e.doc_id == doc_id) + { + ret.resolved = m_entries[first].resolved; + } + } + } + return ret; +} + +void TagCache::add(csubstr tag, csubstr resolved, id_type doc_id, const_iterator pos) RYML_NOEXCEPT +{ + const id_type sz = m_entries.size(); + _RYML_ASSERT_BASIC_(m_entries.m_callbacks, pos <= sz); + _RYML_ASSERT_BASIC_(m_entries.m_callbacks, pos == sz || tag < m_entries[pos].tag || (tag == m_entries[pos].tag && doc_id < m_entries[pos].doc_id)); + m_entries.resize(sz + 1); + if(pos < sz) + memmove(m_entries.m_stack + pos + 1, m_entries.m_stack + pos, (sz - pos) * sizeof(Entry)); + m_entries.m_stack[pos].tag = tag; + m_entries.m_stack[pos].resolved = resolved; + m_entries.m_stack[pos].doc_id = doc_id; + _c4dbgpf("tagcache: add entry @pos={}: docid={} {} -> {}", pos, doc_id, tag, _maybe_null_str(resolved)); +} + +} // namespace yml +} // namespace c4 + +#endif /* RYML_SINGLE_HDR_DEFINE_NOW */ + + +// (end src/c4/yml/tag.cpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/parse_engine.def.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_YML_PARSE_ENGINE_DEF_HPP_ +#define _C4_YML_PARSE_ENGINE_DEF_HPP_ + +#ifndef _C4_YML_PARSE_ENGINE_HPP_ +// amalgamate: removed include of +// c4/yml/parse_engine.hpp +//#include "c4/yml/parse_engine.hpp" +#if !defined(C4_YML_PARSE_ENGINE_HPP_) && !defined(_C4_YML_PARSE_ENGINE_HPP_) +#error "amalgamate: file c4/yml/parse_engine.hpp must have been included at this point" +#endif /* C4_YML_PARSE_ENGINE_HPP_ */ + +#endif +#ifndef _C4_CHARCONV_HPP_ +// amalgamate: removed include of +// c4/charconv.hpp +//#include "c4/charconv.hpp" +#if !defined(C4_CHARCONV_HPP_) && !defined(_C4_CHARCONV_HPP_) +#error "amalgamate: file c4/charconv.hpp must have been included at this point" +#endif /* C4_CHARCONV_HPP_ */ + +#endif +#ifndef C4_UTF_HPP_ +// amalgamate: removed include of +// c4/utf.hpp +//#include "c4/utf.hpp" +#if !defined(C4_UTF_HPP_) && !defined(_C4_UTF_HPP_) +#error "amalgamate: file c4/utf.hpp must have been included at this point" +#endif /* C4_UTF_HPP_ */ + +#endif +#ifndef _C4_YML_FILTER_PROCESSOR_HPP_ +// amalgamate: removed include of +// c4/yml/filter_processor.hpp +//#include "c4/yml/filter_processor.hpp" +#if !defined(C4_YML_FILTER_PROCESSOR_HPP_) && !defined(_C4_YML_FILTER_PROCESSOR_HPP_) +#error "amalgamate: file c4/yml/filter_processor.hpp must have been included at this point" +#endif /* C4_YML_FILTER_PROCESSOR_HPP_ */ + +#endif +#ifndef _C4_YML_TAG_HPP_ +// amalgamate: removed include of +// c4/yml/tag.hpp +//#include "c4/yml/tag.hpp" +#if !defined(C4_YML_TAG_HPP_) && !defined(_C4_YML_TAG_HPP_) +#error "amalgamate: file c4/yml/tag.hpp must have been included at this point" +#endif /* C4_YML_TAG_HPP_ */ + +#endif +#ifndef _C4_YML_NODE_TYPE_HPP_ +// amalgamate: removed include of +// c4/yml/node_type.hpp +//#include "c4/yml/node_type.hpp" +#if !defined(C4_YML_NODE_TYPE_HPP_) && !defined(_C4_YML_NODE_TYPE_HPP_) +#error "amalgamate: file c4/yml/node_type.hpp must have been included at this point" +#endif /* C4_YML_NODE_TYPE_HPP_ */ + +#endif + +#ifndef _C4_YML_DETAIL_DBGPRINT_HPP_ +// amalgamate: removed include of +// c4/yml/detail/dbgprint.hpp +//#include "c4/yml/detail/dbgprint.hpp" +#if !defined(C4_YML_DETAIL_DBGPRINT_HPP_) && !defined(_C4_YML_DETAIL_DBGPRINT_HPP_) +#error "amalgamate: file c4/yml/detail/dbgprint.hpp must have been included at this point" +#endif /* C4_YML_DETAIL_DBGPRINT_HPP_ */ + +#endif + +#ifdef RYML_DBG +#ifndef C4_DUMP_HPP_ +// amalgamate: removed include of +// c4/dump.hpp +//#include +#if !defined(C4_DUMP_HPP_) && !defined(_C4_DUMP_HPP_) +#error "amalgamate: file c4/dump.hpp must have been included at this point" +#endif /* C4_DUMP_HPP_ */ + +#endif +#define _c4err(...) \ + do { RYML_DEBUG_BREAK(); this->_err(RYML_LOC_HERE(), __VA_ARGS__); } while(0) +#else +#define _c4err(...) \ + this->_err(RYML_LOC_HERE(), __VA_ARGS__) +#endif +#define _c4assert(...) \ + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, __VA_ARGS__, m_evt_handler->m_curr->pos) + + +#if defined(RYML_WITH_TAB_TOKENS) +#define _RYML_WITH_TAB_TOKENS(...) __VA_ARGS__ +#define _RYML_WITHOUT_TAB_TOKENS(...) +#define _RYML_WITH_OR_WITHOUT_TAB_TOKENS(with, without) with +#else +#define _RYML_WITH_TAB_TOKENS(...) +#define _RYML_WITHOUT_TAB_TOKENS(...) __VA_ARGS__ +#define _RYML_WITH_OR_WITHOUT_TAB_TOKENS(with, without) without +#endif + +// helper to export cases to the YAML test suite +#ifndef RYML_SAVE_TEST_YAML +#define _RYML_SAVE_TEST_YAML(filename, src) +#define _RYML_SAVE_TEST_JSON(filename, src) +#else +#define _RYML_SAVE_TEST_YAML(filename, src) c4::yml::ryml_save_test_yaml(filename, src) +#define _RYML_SAVE_TEST_JSON(filename, src) c4::yml::ryml_save_test_json(filename, src) +namespace c4 { +namespace yml { +void ryml_save_test_yaml(csubstr filename, csubstr src); +void ryml_save_test_json(csubstr filename, csubstr src); +} // namespace yml +} // namespace c4 +#endif + + +// scaffold: +#define _c4dbgnextline() \ + do { \ + _c4dbgq("\n-----------"); \ + _c4dbgt("handling line={}, offset={}B", \ + m_evt_handler->m_curr->pos.line, \ + m_evt_handler->m_curr->pos.offset); \ + } while(0) + + +C4_SUPPRESS_WARNING_MSVC_PUSH +C4_SUPPRESS_WARNING_MSVC(4296) // expression is always 'boolean_value' +C4_SUPPRESS_WARNING_MSVC(4702) // unreachable code +C4_SUPPRESS_WARNING_GCC_CLANG_PUSH +C4_SUPPRESS_WARNING_GCC_CLANG("-Wtype-limits") // to remove a warning on an assertion that a size_t >= 0. Later on, this size_t will turn into a template argument, and then it can become < 0. +C4_SUPPRESS_WARNING_GCC_CLANG("-Wformat-nonliteral") +C4_SUPPRESS_WARNING_GCC_CLANG("-Wold-style-cast") +#if defined(__GNUC__) && (__GNUC__ >= 6) +C4_SUPPRESS_WARNING_GCC("-Wnull-dereference") +#endif +#if defined(__GNUC__) && (__GNUC__ >= 7) +C4_SUPPRESS_WARNING_GCC("-Wduplicated-branches") +#endif + +// NOLINTBEGIN(hicpp-signed-bitwise,cppcoreguidelines-avoid-goto,hicpp-avoid-goto,hicpp-multiway-paths-covered,modernize-avoid-c-style-cast) + +namespace c4 { +namespace yml { + +namespace { // NOLINT + +C4_HOT C4_ALWAYS_INLINE void _set_first(substr &C4_RESTRICT subject, size_t pos) noexcept +{ + // avoids reassigning the ptr in substr + subject.len = pos != npos ? pos : subject.len; +} +C4_HOT C4_ALWAYS_INLINE void _set_first(csubstr &C4_RESTRICT subject, size_t pos) noexcept +{ + // avoids reassigning the ptr in substr + subject.len = pos != npos ? pos : subject.len; +} +C4_HOT C4_ALWAYS_INLINE void _set_first_strict(substr &C4_RESTRICT subject, size_t pos) RYML_NOEXCEPT +{ + // avoids reassigning the ptr in substr + _RYML_ASSERT_BASIC(pos != npos); // LCOV_EXCL_LINE + subject.len = pos; +} +C4_HOT C4_ALWAYS_INLINE void _set_first_strict(csubstr &C4_RESTRICT subject, size_t pos) RYML_NOEXCEPT +{ + // avoids reassigning the ptr in substr + _RYML_ASSERT_BASIC(pos != npos); // LCOV_EXCL_LINE + subject.len = pos; +} + +C4_HOT C4_ALWAYS_INLINE bool _is_blck_token(csubstr s) RYML_NOEXCEPT +{ + _RYML_ASSERT_BASIC(s.len > 0); + _RYML_ASSERT_BASIC(s.str[0] == '-' || s.str[0] == ':' || s.str[0] == '?'); + return ((s.len == 1) || ((s.str[1] == ' ') _RYML_WITH_TAB_TOKENS( || (s.str[1] == '\t')))); +} + +C4_HOT C4_ALWAYS_INLINE bool _is_blck_seq_token_maybe(csubstr const& C4_RESTRICT s) noexcept +{ + return ((s.len >= 1) && (s.str[0] == '-') && ((s.len == 1) || ((s.str[1] == ' ') _RYML_WITH_TAB_TOKENS( || (s.str[1] == '\t'))))); +} + +inline bool _is_doc_begin_token(csubstr s) RYML_NOEXCEPT +{ + _RYML_ASSERT_BASIC(s.begins_with('-')); + _RYML_ASSERT_BASIC(!s.ends_with("\n")); + _RYML_ASSERT_BASIC(!s.ends_with("\r")); + return (s.len >= 3 && s.str[1] == '-' && s.str[2] == '-') + && (s.len == 3 || (s.str[3] == ' ' _RYML_WITH_TAB_TOKENS(|| s.str[3] == '\t'))); +} + +inline bool _is_doc_end_token(csubstr s) RYML_NOEXCEPT +{ + _RYML_ASSERT_BASIC(s.begins_with('.')); + _RYML_ASSERT_BASIC(!s.ends_with("\n")); + _RYML_ASSERT_BASIC(!s.ends_with("\r")); + return (s.len >= 3 && s.str[1] == '.' && s.str[2] == '.') + && (s.len == 3 || (s.str[3] == ' ' _RYML_WITH_TAB_TOKENS(|| s.str[3] == '\t'))); +} + +inline bool _is_doc_token(csubstr s) noexcept +{ + if(s.len >= 3) + { + switch(s.str[0]) + { + case '-': + //return _is_doc_begin_token(s); // this was failing with gcc -O2 + return (s.str[1] == '-' && s.str[2] == '-') + && (s.len == 3 || (s.str[3] == ' ' _RYML_WITH_TAB_TOKENS(|| s.str[3] == '\t'))); + case '.': + //return _is_doc_end_token(s); // this was failing with gcc -O2 + return (s.str[1] == '.' && s.str[2] == '.') + && (s.len == 3 || (s.str[3] == ' ' _RYML_WITH_TAB_TOKENS(|| s.str[3] == '\t'))); + } + } + return false; +} + +inline size_t _begins_with_special_json_scalar(csubstr s) RYML_NOEXCEPT +{ + _RYML_ASSERT_BASIC(s.len); + switch(s.str[0]) + { + case 'f': + return s.begins_with("false") ? 5u : 0u; + case 't': + return s.begins_with("true") ? 4u : 0u; + case 'n': + return s.begins_with("null") ? 4u : 0u; + } + return 0u; +} + + +//----------------------------------------------------------------------------- + +C4_ALWAYS_INLINE size_t _extend_from_combined_newline(char nl, char following) +{ + return (nl == '\n' && following == '\r') || (nl == '\r' && following == '\n'); +} + +//! look for the next newline chars, and jump to the right of those +inline substr _from_next_line(substr rem) +{ + size_t nlpos = rem.first_of("\r\n"); + if(nlpos == csubstr::npos) + return {}; + const char nl = rem[nlpos]; + rem = rem.right_of(nlpos); + if(rem.empty()) + return {}; + if(_extend_from_combined_newline(nl, rem.front())) + rem = rem.sub(1); + return rem; +} + + +//----------------------------------------------------------------------------- + +inline size_t _count_following_newlines(csubstr r, size_t *C4_RESTRICT i) +{ + _RYML_ASSERT_BASIC(r[*i] == '\n'); + size_t numnl_following = 0; + ++(*i); + for( ; *i < r.len; ++(*i)) + { + if(r.str[*i] == '\n') + ++numnl_following; + // skip leading whitespace + else if(r.str[*i] == ' ' || r.str[*i] == '\t' || r.str[*i] == '\r') + ; + else + break; + } + return numnl_following; +} + +/** @p i is set to the first non whitespace character after the line + * @return the number of empty lines after the initial position */ +inline size_t _count_following_newlines(csubstr r, size_t *C4_RESTRICT i, size_t indentation) +{ + _RYML_ASSERT_BASIC(r[*i] == '\n'); + size_t numnl_following = 0; + ++(*i); + if(indentation == 0) + { + for( ; *i < r.len; ++(*i)) + { + const char c = r.str[*i]; + if(c == '\n') + ++numnl_following; + // skip leading whitespace + else if(c != ' ' && c != '\t' && c != '\r') + break; + } + } + else + { + for( ; *i < r.len; ++(*i)) + { + char c = r.str[*i]; + if(c == '\n') + { + ++numnl_following; + // skip the indentation after the newline + size_t stop = *i + indentation; + for( ; *i < r.len; ++(*i)) + { + c = r.str[*i]; + if(c != ' ' && c != '\r') + break; + _RYML_ASSERT_BASIC(*i < stop); // LCOV_EXCL_LINE + } + C4_UNUSED(stop); + } + // skip leading whitespace + else if(c != ' ' && c != '\t' && c != '\r') + { + break; + } + } + } + return numnl_following; +} + +} // anon namespace + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +template +ParseEngine::~ParseEngine() +{ + _free(); + _clr(); +} + +template +ParseEngine::ParseEngine(EventHandler *evt_handler, ParserOptions opts) + : m_options(opts) + , m_evt_handler(evt_handler) + , m_pending_anchors() + , m_pending_tags() + , m_has_directives_yaml(false) + , m_has_directives(false) + , m_doc_empty(true) + , m_prev_colon(npos) + , m_prev_val_end(npos) + , m_encoding(NOBOM) + , m_newline_offsets() + , m_newline_offsets_size(0) + , m_newline_offsets_capacity(0) +{ + _RYML_CHECK_BASIC(evt_handler); +} + +template +ParseEngine::ParseEngine(ParseEngine &&that) noexcept + : m_options(that.m_options) + , m_evt_handler(that.m_evt_handler) + , m_pending_anchors(that.m_pending_anchors) + , m_pending_tags(that.m_pending_tags) + , m_has_directives_yaml(that.m_has_directives_yaml) + , m_has_directives(that.m_has_directives) + , m_doc_empty(that.m_doc_empty) + , m_prev_colon(npos) + , m_prev_val_end(npos) + , m_encoding(NOBOM) + , m_newline_offsets(that.m_newline_offsets) + , m_newline_offsets_size(that.m_newline_offsets_size) + , m_newline_offsets_capacity(that.m_newline_offsets_capacity) +{ + that._clr(); +} + +template +ParseEngine::ParseEngine(ParseEngine const& that) + : m_options(that.m_options) + , m_evt_handler(that.m_evt_handler) + , m_pending_anchors(that.m_pending_anchors) + , m_pending_tags(that.m_pending_tags) + , m_has_directives_yaml(that.m_has_directives_yaml) + , m_has_directives(that.m_has_directives) + , m_doc_empty(that.m_doc_empty) + , m_prev_colon(npos) + , m_prev_val_end(npos) + , m_encoding(NOBOM) + , m_newline_offsets() + , m_newline_offsets_size() + , m_newline_offsets_capacity() +{ + if(that.m_newline_offsets_capacity) + { + _resize_locations(that.m_newline_offsets_capacity); + _RYML_CHECK_BASIC_(m_evt_handler->m_stack.m_callbacks, m_newline_offsets_capacity == that.m_newline_offsets_capacity); + memcpy(m_newline_offsets, that.m_newline_offsets, that.m_newline_offsets_size * sizeof(size_t)); + m_newline_offsets_size = that.m_newline_offsets_size; + } +} + +template +ParseEngine& ParseEngine::operator=(ParseEngine &&that) noexcept +{ + _free(); + m_options = (that.m_options); + m_evt_handler = that.m_evt_handler; + m_pending_anchors = that.m_pending_anchors; + m_pending_tags = that.m_pending_tags; + m_has_directives_yaml = that.m_has_directives_yaml; + m_has_directives = that.m_has_directives; + m_doc_empty = that.m_doc_empty; + m_prev_colon = that.m_prev_colon; + m_prev_val_end = that.m_prev_val_end; + m_encoding = that.m_encoding; + m_newline_offsets = (that.m_newline_offsets); + m_newline_offsets_size = (that.m_newline_offsets_size); + m_newline_offsets_capacity = (that.m_newline_offsets_capacity); + that._clr(); + return *this; +} + +template +ParseEngine& ParseEngine::operator=(ParseEngine const& that) +{ + if(&that != this) + { + _free(); + m_options = (that.m_options); + m_evt_handler = that.m_evt_handler; + m_pending_anchors = that.m_pending_anchors; + m_pending_tags = that.m_pending_tags; + m_has_directives_yaml = that.m_has_directives_yaml; + m_has_directives = that.m_has_directives; + m_doc_empty = that.m_doc_empty; + m_prev_colon = that.m_prev_colon; + m_prev_val_end = that.m_prev_val_end; + m_encoding = that.m_encoding; + if(that.m_newline_offsets_capacity > m_newline_offsets_capacity) + _resize_locations(that.m_newline_offsets_capacity); + _RYML_CHECK_BASIC_(m_evt_handler->m_stack.m_callbacks, m_newline_offsets_capacity >= that.m_newline_offsets_capacity); + _RYML_CHECK_BASIC_(m_evt_handler->m_stack.m_callbacks, m_newline_offsets_capacity >= that.m_newline_offsets_size); + memcpy(m_newline_offsets, that.m_newline_offsets, that.m_newline_offsets_size * sizeof(size_t)); + m_newline_offsets_size = that.m_newline_offsets_size; + } + return *this; +} + +template +void ParseEngine::_clr() +{ + m_options = {}; + m_evt_handler = {}; + m_pending_anchors = {}; + m_pending_tags = {}; + m_has_directives_yaml = false; + m_has_directives = false; + m_doc_empty = true; + m_prev_colon = npos; + m_prev_val_end = npos; + m_encoding = NOBOM; + m_newline_offsets = {}; + m_newline_offsets_size = {}; + m_newline_offsets_capacity = {}; +} + +template +void ParseEngine::_free() +{ + if(m_newline_offsets) + { + _RYML_CB_FREE(m_evt_handler->m_stack.m_callbacks, m_newline_offsets, size_t, m_newline_offsets_capacity); + m_newline_offsets = nullptr; + m_newline_offsets_size = 0u; + m_newline_offsets_capacity = 0u; + } +} + + +//----------------------------------------------------------------------------- + +template +void ParseEngine::_reset() +{ + m_pending_anchors = {}; + m_pending_tags = {}; + m_has_directives_yaml = false; + m_has_directives = false; + m_doc_empty = true; + m_prev_colon = npos; + m_prev_val_end = npos; + m_bom_len = 0; + m_encoding = NOBOM; + m_bom_line = 0; + if(m_options.locations()) + { + _prepare_locations(); + } +} + + +//----------------------------------------------------------------------------- + +template +void ParseEngine::_relocate_arena(csubstr prev_arena, substr next_arena, substr *other) +{ + _c4dbgp("relocate to new arena"); + const char *pb = prev_arena.str; + const char *pe = prev_arena.str + prev_arena.len; + #define _ryml_relocate(s) \ + if((s).str >= pb && (s).str <= pe) \ + { \ + (s).str = next_arena.str + ((s).str - pb); \ + } \ + ((void)0) + for(ParserState &st : m_evt_handler->m_stack) + { + _ryml_relocate(st.line_contents.rem); + _ryml_relocate(st.line_contents.full); + } + _ryml_relocate(m_evt_handler->m_src); + for(size_t i = 0; i < m_pending_tags.num_entries; ++i) + { + _ryml_relocate(m_pending_tags.annotations[i].str); // LCOV_EXCL_LINE + _ryml_relocate(m_pending_tags.annotations[i].orig); // LCOV_EXCL_LINE + } + for(size_t i = 0; i < m_pending_anchors.num_entries; ++i) + { + _ryml_relocate(m_pending_anchors.annotations[i].str); + _ryml_relocate(m_pending_anchors.annotations[i].orig); + } + { + TagDirectives &tds = m_evt_handler->tag_directives(); + for(size_t i = 0, sz = tds.size(); i < sz; ++i) + { + _ryml_relocate(tds.m_directives[i].handle); + _ryml_relocate(tds.m_directives[i].prefix); + } + } + { + TagCache &tch = m_evt_handler->tag_cache(); + for(id_type i = 0, sz = tch.m_entries.size(); i < sz; ++i) + { + _ryml_relocate(tch.m_entries[i].tag); + _ryml_relocate(tch.m_entries[i].resolved); + } + } + if(other) + { + _ryml_relocate(*other); + } + #undef _ryml_relocate +} + +/** @cond dev */ +template +substr ParseEngine::_alloc_arena(size_t len, substr *other) +{ + csubstr prev = m_evt_handler->arena(); + substr out = m_evt_handler->alloc_arena(len); + substr curr = m_evt_handler->arena(); + if(curr.str != prev.str) + _relocate_arena(prev, curr, other); + return out; +} +/** @endcond */ + + +//----------------------------------------------------------------------------- + +#ifdef RYML_DBG +template +template +C4_NO_INLINE void ParseEngine::_fmt_msg(DumpFn &&dumpfn) const +{ + ParserState const *const C4_RESTRICT st = m_evt_handler->m_curr; + LineContents const& C4_RESTRICT lc = st->line_contents; + csubstr contents = lc.full.first(lc.num_cols); + if(contents.len) + { + // print the yaml src line + size_t offs = 3u + to_chars(substr{}, st->pos.line) + to_chars(substr{}, st->pos.col); + csubstr m_file = m_evt_handler->m_curr->pos.name; + if(m_file.len) + { + _dbg_dump(std::forward(dumpfn), "{}:", m_file); + offs += m_file.len + 1; + } + _dbg_dump(std::forward(dumpfn), "{}:{}: ", st->pos.line, st->pos.col); + csubstr maybe_full_content = (contents.len < 80u ? contents : contents.first(80u)); + csubstr maybe_ellipsis = (contents.len < 80u ? csubstr{} : csubstr("...")); + _dbg_dump(std::forward(dumpfn), "{}{} (size={})\n", escaped_scalar(maybe_full_content, /*escape*/true), maybe_ellipsis, contents.len); + // highlight the remaining portion of the previous line + size_t firstcol = (size_t)(lc.rem.str - lc.full.str); + size_t lastcol = firstcol + lc.rem.len; + size_t firstcol_adj = adjust_pos_with_escapes(lc.full, firstcol); + size_t len = adjust_pos_with_escapes(lc.rem, lc.rem.len); + for(size_t i = 0; i < offs + firstcol_adj; ++i) + std::forward(dumpfn)(" "); + std::forward(dumpfn)("^"); + for(size_t i = 1, e = (len < 80u ? len : 80u); i < e; ++i) + std::forward(dumpfn)("~"); + _dbg_dump(std::forward(dumpfn), "{} (cols {}-{})\n", maybe_ellipsis, firstcol+1, lastcol+1); + } + else + { + std::forward(dumpfn)("\n"); + } + // next line: print the state flags + { + char flagbuf_[128]; + _dbg_dump(std::forward(dumpfn), "top state: {}\n", detail::_parser_flags_to_str(flagbuf_, m_evt_handler->m_curr->flags)); + } +} + +template +void ParseEngine::_print_state_stack(substr buf) const +{ + if(_dbg_enabled()) + { + for(ParserState const& s : m_evt_handler->m_stack) + _dbg_printf("state[{}]: ind={} node={} flags={}\n", s.level, s.indref, s.node_id, detail::_parser_flags_to_str(buf, s.flags)); + } +} + +template +void ParseEngine::_print_state_stack() const +{ + char buf[128]; + _print_state_stack(buf); +} +#endif + + +//----------------------------------------------------------------------------- + +template +template +C4_NORETURN C4_NO_INLINE void ParseEngine::_err(Location const& cpploc, Location const& ymlloc, const char* fmt, Args const& ...args) const +{ + m_evt_handler->cancel_parse(); + err_parse(m_evt_handler->m_stack.m_callbacks, ErrorDataParse{cpploc, ymlloc}, fmt, args...); +} + +template +template +C4_NORETURN C4_NO_INLINE void ParseEngine::_err(Location const& cpploc, const char *fmt, Args const& ...args) const +{ + m_evt_handler->cancel_parse(); + err_parse(m_evt_handler->m_stack.m_callbacks, ErrorDataParse{cpploc, m_evt_handler->m_curr->pos}, fmt, args...); +} + + +//----------------------------------------------------------------------------- +#ifdef RYML_DBG +template +template +void ParseEngine::_dbg(csubstr fmt, Args const& ...args) const +{ + if(_dbg_enabled()) + { + _dbg_printf(fmt, args...); + _dbg_dumper("\n"); + _fmt_msg(_dbg_dumper); + } +} +#endif + + +//----------------------------------------------------------------------------- +template +bool ParseEngine::_finished_file() const +{ + bool ret = m_evt_handler->m_curr->pos.offset >= _buf().len; + if(ret) + { + _c4dbgp("finished file!!!"); + } + return ret; +} + +template +C4_HOT C4_ALWAYS_INLINE bool ParseEngine::_finished_line() const // LCOV_EXCL_LINE +{ + return m_evt_handler->m_curr->line_contents.rem.empty(); +} + + +//----------------------------------------------------------------------------- + +template +void ParseEngine::_maybe_skip_whitespace_tokens() +{ + if(m_evt_handler->m_curr->line_contents.rem.len && (m_evt_handler->m_curr->line_contents.rem.str[0] == ' ' _RYML_WITH_TAB_TOKENS(|| m_evt_handler->m_curr->line_contents.rem.str[0] == '\t'))) + { + size_t pos = m_evt_handler->m_curr->line_contents.rem.first_not_of(_RYML_WITH_OR_WITHOUT_TAB_TOKENS(" \t", ' ')); + if(pos == npos) + pos = m_evt_handler->m_curr->line_contents.rem.len; // maybe the line is just all whitespace + _c4dbgpf("skip {} whitespace characters", pos); + _line_progressed(pos); + } +} + +template +void ParseEngine::_maybe_skipchars(char c) +{ + if(m_evt_handler->m_curr->line_contents.rem.len && m_evt_handler->m_curr->line_contents.rem.str[0] == c) + { + size_t pos = m_evt_handler->m_curr->line_contents.rem.first_not_of(c); + if(pos == npos) + pos = m_evt_handler->m_curr->line_contents.rem.len; // maybe the line is just all c + _c4dbgpf("skip {}x'{}'", pos, _c4prc(c)); + _line_progressed(pos); + } +} + +template +template +void ParseEngine::_skipchars(const char (&chars)[N]) +{ + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->line_contents.rem.begins_with_any(chars), m_evt_handler->m_curr->pos); + size_t pos = m_evt_handler->m_curr->line_contents.rem.first_not_of(chars); + if(pos == npos) + pos = m_evt_handler->m_curr->line_contents.rem.len; // maybe the line is just whitespace + _c4dbgpf("skip {} characters", pos); + _line_progressed(pos); +} + +template +void ParseEngine::_skip_comment() +{ + LineContents const& C4_RESTRICT lc = m_evt_handler->m_curr->line_contents; + const size_t col = m_evt_handler->m_curr->pos.col - 1u; + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, lc.rem.begins_with('#'), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, lc.rem.is_sub(lc.full), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->pos.col >= 1, m_evt_handler->m_curr->pos); // 1-based + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, col == ((size_t)(lc.rem.str - lc.full.str)), m_evt_handler->m_curr->pos); + // raise an error if the comment is not preceded by whitespace + if(lc.rem.str != lc.full.str) // not at line beginning + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, col > 0, m_evt_handler->m_curr->pos); + const char prev = lc.full.str[col - 1u]; + if(C4_UNLIKELY(prev != ' ' && prev != '\t')) + _c4err("comment not preceded by whitespace"); + } + _c4dbgpf("comment was '{}'", m_evt_handler->m_curr->line_contents.rem); + _line_progressed(m_evt_handler->m_curr->line_contents.rem.len); +} + +template +void ParseEngine::_maybe_skip_comment_strict() +{ + size_t pos = m_evt_handler->m_curr->line_contents.rem.first_not_of(" \t"); + if(pos != npos) + { + if('#' == m_evt_handler->m_curr->line_contents.rem[pos]) + { + _line_progressed(pos); + _skip_comment(); + } + } +} + +template +void ParseEngine::_maybe_skip_comment() +{ + size_t pos = m_evt_handler->m_curr->line_contents.rem.first_not_of(" \t"); + if(pos != npos) + { + if('#' == m_evt_handler->m_curr->line_contents.rem[pos]) + { + _line_progressed(pos); + _skip_comment(); + } + } + else + { + _line_progressed(m_evt_handler->m_curr->line_contents.rem.len); + } +} + +template +bool ParseEngine::_maybe_scan_following_colon() noexcept +{ + size_t pos = m_evt_handler->m_curr->line_contents.rem.first_not_of(" \t"); + if(pos != npos) + { + if(':' == m_evt_handler->m_curr->line_contents.rem[pos]) + { + // bump pos to skip the colon as well, and check the colon + // is followed by space or tab + if(++pos < m_evt_handler->m_curr->line_contents.rem.len) + { + const char next = m_evt_handler->m_curr->line_contents.rem.str[pos]; + if(next == ' ' _RYML_WITH_TAB_TOKENS(|| next == '\t')) + ++pos; + else + return false; + } + _line_progressed(pos); + return true; + } + } + else + { + _line_progressed(m_evt_handler->m_curr->line_contents.rem.len); + } + return false; +} + + +//----------------------------------------------------------------------------- + +template +csubstr ParseEngine::_scan_anchor() +{ + csubstr s = m_evt_handler->m_curr->line_contents.rem; + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, s.begins_with('&'), m_evt_handler->m_curr->pos); + csubstr anchor = s.range(1, s.first_of(" ,]}\t")); + _line_progressed(1u + anchor.len); + _maybe_skipchars(' '); + return anchor; +} + +template +csubstr ParseEngine::_scan_ref_seq() +{ + csubstr s = m_evt_handler->m_curr->line_contents.rem; + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, s.begins_with('*'), m_evt_handler->m_curr->pos); + _set_first(s, s.first_of(" ,]\t")); + _line_progressed(s.len); + return s; +} + +template +csubstr ParseEngine::_scan_ref_map() +{ + csubstr s = m_evt_handler->m_curr->line_contents.rem; + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, s.begins_with('*'), m_evt_handler->m_curr->pos); + _set_first(s, s.first_of(" ,}\t")); + _line_progressed(s.len); + return s; +} + +template +csubstr ParseEngine::_scan_tag() +{ + csubstr t = m_evt_handler->m_curr->line_contents.rem; + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, t.begins_with('!'), m_evt_handler->m_curr->pos); + if(!t.begins_with("!<")) + { + _c4dbgp("begins with '!'"); + _set_first(t, t.first_of(" ,]}\t")); + if(C4_UNLIKELY(t.first_of("[{") != npos)) + _c4err("invalid tag"); + _line_progressed(t.len); + if(m_options.resolve_tags_all() || (m_options.resolve_tags() && is_custom_tag(t))) + t = _resolve_tag(t); + } + else + { + _c4dbgp("begins with '!<'"); + size_t pos = t.find('>'); + if(C4_UNLIKELY(pos == npos)) + _c4err("invalid tag"); + _set_first_strict(t, pos+1); + _line_progressed(t.len); + t = t.sub(1); + } + _maybe_skip_whitespace_tokens(); + return t; +} + +template +csubstr ParseEngine::_scan_tag(csubstr *orig) +{ + csubstr t = m_evt_handler->m_curr->line_contents.rem; + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, t.begins_with('!'), m_evt_handler->m_curr->pos); + if(!t.begins_with("!<")) + { + _c4dbgp("begins with '!'"); + _set_first(t, t.first_of(" ,\t")); + if(C4_UNLIKELY(t.first_of("[{") != npos)) + _c4err("invalid tag"); + _line_progressed(t.len); + *orig = t; + if(m_options.resolve_tags_all() || (m_options.resolve_tags() && is_custom_tag(t))) + t = _resolve_tag(t); + } + else + { + _c4dbgp("begins with '!<'"); + size_t pos = t.find('>'); + if(C4_UNLIKELY(pos == npos)) + _c4err("invalid tag"); + _set_first_strict(t, pos+1); + _line_progressed(t.len); + *orig = t; + t = t.sub(1); + } + _maybe_skip_whitespace_tokens(); + return t; +} + + +//----------------------------------------------------------------------------- + +template +bool ParseEngine::_is_valid_start_scalar_plain_flow_check_block_token(csubstr s) +{ + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, s.len > 0, m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, s.begins_with_any(":-"), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, s.count('\n') == 0, m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, s.count('\r') == 0, m_evt_handler->m_curr->pos); + if(s.len > 1) + { + switch(s.str[1]) + { + case ' ': + case ',': + case '}': + case ']': + case '\t': + if(s.str[0] == ':') + { + _c4dbgpf("not a scalar: found non-scalar token '{}{}'", s.str[0], s.str[1]); + return false; + } + else + { + _c4err("invalid scalar"); + } + break; + case '{': + case '[': + _c4err("invalid token \":{}\"", _c4prc(s.str[1])); + break; + default: + break; + } + } + else + { + if(s.str[0] == '-') + _c4err("invalid scalar"); + return false; + } + return true; +} + +template +bool ParseEngine::_is_valid_start_scalar_plain_flow_check_qmrk(csubstr s) +{ + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, s.len > 0, m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, s[0] == '?', m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, s.count('\n') == 0, m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, s.count('\r') == 0, m_evt_handler->m_curr->pos); + if(s.len > 1) + { + switch(s.str[1]) + { + case ' ': + case '\t': + _c4dbgpf("not a scalar: found non-scalar token '?{}'", _c4prc(s.str[1])); + return false; + case '{': + case '}': + case '[': + case ']': + _c4err("invalid token \"?{}\"", _c4prc(s.str[1])); + break; + default: + break; + } + } + else + { + return false; + } + return true; +} + + +template +bool ParseEngine::_is_valid_start_scalar_plain_flow(csubstr s) +{ + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, !s.empty(), m_evt_handler->m_curr->pos); + // it's not a scalar if it starts with any of these characters: + switch(s.str[0]) + { + // these are all legal tokens which mean no scalar is starting: + case '[': + case ']': + case '{': + case '}': + case '&': + case '*': + case '!': + case '|': + case '>': + case '#': + case ',': + _c4dbgpf("not a scalar: found non-scalar token '{}'", _c4prc(s.str[0])); + return false; + // '-' and ':' are illegal at the beginning if not followed by a scalar character + case '-': + case ':': + _c4dbgpf("suspicious token='{}' len={}", _c4prc(s.str[0]), s.len); + return _is_valid_start_scalar_plain_flow_check_block_token(s); + case '?': + _c4dbgpf("qmrk='{}' len={}", _c4prc(s.str[0]), s.len); + return _is_valid_start_scalar_plain_flow_check_qmrk(s); + // everything else is a legal starting character + default: + return true; + } +} + + +template +bool ParseEngine::_scan_scalar_plain_handle_newline(csubstr s, size_t offs) +{ + _c4dbgpf("newl[PLAIN]: found '\\n'. offs={} line={} sofar={}", offs, m_evt_handler->m_curr->pos.line, _prs(s.first(offs), true)); + if(s.len > offs + 1) + { + _c4dbgp("newl[PLAIN]: buffer continues"); + csubstr next_line = s.sub(offs + 1); + size_t next_line_indentation = next_line.first_not_of(' '); + if(next_line_indentation != npos) + { + _c4dbgpf("newl[PLAIN]: line={} indentation={} indref={}", m_evt_handler->m_curr->pos.line + 1, next_line_indentation, m_evt_handler->m_curr->indref); + next_line = next_line.first(next_line.first_of("\n\r")); + _c4dbgpf("newl[PLAIN]: has indentation. next_line={}", _prs(next_line)); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, next_line_indentation <= next_line.len, m_evt_handler->m_curr->pos); + if(C4_LIKELY(next_line_indentation >= m_evt_handler->m_curr->indref)) + { + _c4dbgp("newl[PLAIN]: larger indentation"); + next_line = next_line.sub(next_line_indentation); + } + else if(C4_UNLIKELY(next_line.len && next_line.triml(' ').len)) + { + _c4dbgp("newl[PLAIN]: err, smaller indentation"); + _line_progressed(m_evt_handler->m_curr->line_contents.rem.len); + _line_ended(); + _scan_line(); + if(m_evt_handler->m_curr->line_contents.indentation != npos) + _line_progressed(m_evt_handler->m_curr->line_contents.indentation); + _c4err("parse error"); // cannot reduce indentation here + } + _c4dbgpf("newl[PLAIN]: next_line.len={}", next_line.len); + if(next_line.len) + { + size_t fno = next_line.first_not_of(" \t"); + if(fno != csubstr::npos) + { + _c4assert(fno < next_line.len); + switch(next_line.str[fno]) + { + case ',': case ']': case '#': + _c4dbgpf("newl[PLAIN]: found terminating character beginning next line: '{}'", next_line.str[fno]); + return false; + case ':': // cannot be succeeded by whitespace + _c4dbgp("newl[PLAIN]: found :"); + if(fno + 1 == next_line.len || _is_blck_token(next_line.sub(fno))) + { + _c4dbgpf("newl[PLAIN]: found terminating character beginning next line: '{}'", next_line.str[fno]); + return false; + } + break; + } + } + } + } + } + _line_progressed(m_evt_handler->m_curr->line_contents.rem.len); + _line_ended(); + _scan_line(); + return true; +} + +template +bool ParseEngine::_scan_scalar_plain_seq_flow(ScannedScalar *C4_RESTRICT sc) +{ + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RMAP), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RBLCK), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RSEQ|RSEQIMAP), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RFLOW), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RVAL), m_evt_handler->m_curr->pos); + + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, !m_evt_handler->m_curr->line_contents.rem.begins_with(' '), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, !m_evt_handler->m_curr->line_contents.rem.begins_with('\n'), m_evt_handler->m_curr->pos); + + if(!m_evt_handler->m_curr->line_contents.rem.len || !_is_valid_start_scalar_plain_flow(m_evt_handler->m_curr->line_contents.rem)) + return false; + + substr s = _buf().sub(m_evt_handler->m_curr->pos.offset); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, s.begins_with(m_evt_handler->m_curr->line_contents.rem), m_evt_handler->m_curr->pos); + + _c4dbgp("scanning seqflow scalar..."); + + bool needs_filter = false; + size_t col = 0; // zero-based column + size_t offs = 0; // offset + for( ; offs < s.len; ++offs, ++col) + { + const char c = s.str[offs]; + switch(c) + { + case ',': + case ']': + _c4dbgpf("found terminating character at {}: '{}'", offs, c); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, offs > 0, m_evt_handler->m_curr->pos); + goto ended_scalar; + case '\n': + _c4dbgpf("found '\\n' at col={}", col); + if(!_scan_scalar_plain_handle_newline(s, offs)) + goto ended_scalar; + col = (size_t)-1; // so that col is 0 in the next loop iteration + needs_filter = true; + break; + case '\r': + --col; // don't count \r when calling _line_progressed() + needs_filter = true; + break; + case ':': + _c4dbgp("found suspicious ':'"); + if(s.len > offs + 1) + { + char next = s.str[offs + 1]; + _c4dbgpf("next char is '{}'", _c4prc(next)); + if(next == '\r') + { + csubstr after = s.sub(offs + 1).triml('\r'); + if(after.len) + { + next = after.str[0]; + _c4dbgpf("skip \\r to '{}'", _c4prc(next)); + } + } + // no else here. + if(next == ' ' _RYML_WITH_TAB_TOKENS(|| next == '\t') || next == ',' || next == '\n' || next == ']') + { + _c4dbgp("map starting!"); + goto ended_scalar; + } + else + { + _c4dbgp("':' nothing to see here"); + } + } + else + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, s.len == offs + 1, m_evt_handler->m_curr->pos); + _line_progressed(col); + _c4err("missing termination: '{}'", c); // noreturn + } + break; + case '#': + { + _c4dbgp("found suspicious '#'"); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, offs > 0, m_evt_handler->m_curr->pos); + char prev = s.str[offs - 1]; + if(prev == ' ' _RYML_WITH_TAB_TOKENS(|| prev == '\t')) + { + _c4dbgpf("found terminating character at {}: '{}'", offs, c); + goto ended_scalar; + } + } + break; + case '[': + case '{': + case '}': + _line_progressed(col); // advance to report the proper position in the error + _c4err("invalid character: '{}'", c); // noreturn + case '-': + case '.': + _c4dbgpf("doc token character: '{}', offs={}", c, offs); + if(offs == 0 && m_evt_handler->m_curr->at_line_beginning()) + { + _c4dbgp("at line beginning"); + if(s.len >= 3 && s.str[1] == c && s.str[2] == c) + { + _c4err("parse error"); // no return + } + } + break; + default: + ; + } + } + +ended_scalar: + + _line_progressed(col); + _set_first(s, offs); + sc->scalar = s.trimr(_RYML_WITH_OR_WITHOUT_TAB_TOKENS(" \t", ' ')); + sc->needs_filter = needs_filter; + + _c4prscalar("scanned plain scalar", sc->scalar, /*keep_newlines*/true); + + return true; +} + +template +bool ParseEngine::_scan_scalar_plain_map_flow(ScannedScalar *C4_RESTRICT sc) +{ + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RSEQ) || has_any(RSEQIMAP), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RBLCK), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RMAP|RSEQIMAP), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RFLOW), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RKEY|RVAL|QMRK), m_evt_handler->m_curr->pos); + + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, !m_evt_handler->m_curr->line_contents.rem.begins_with(' '), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, !m_evt_handler->m_curr->line_contents.rem.begins_with('\n'), m_evt_handler->m_curr->pos); + + if(!m_evt_handler->m_curr->line_contents.rem.len || !_is_valid_start_scalar_plain_flow(m_evt_handler->m_curr->line_contents.rem)) + return false; + + substr s = _buf().sub(m_evt_handler->m_curr->pos.offset); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, s.begins_with(m_evt_handler->m_curr->line_contents.rem), m_evt_handler->m_curr->pos); + + _c4dbgp("scanning mapflow scalar..."); + + bool needs_filter = false; + size_t col = 0; // zero-based column + size_t offs = 0; // offset + for( ; offs < s.len; ++offs, ++col) + { + const char c = s.str[offs]; + switch(c) + { + case ',': + case '}': + _c4dbgpf("found terminating character at {}: '{}'", offs, c); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, offs > 0, m_evt_handler->m_curr->pos); + goto ended_scalar; + case '\n': + _c4dbgpf("found '\\n' at col={}", col); + if(!_scan_scalar_plain_handle_newline(s, offs)) + goto ended_scalar; + col = (size_t)-1; // so that col is 0 in the next loop iteration + needs_filter = true; + break; + case '\r': + --col; // don't count \r when calling _line_progressed() + needs_filter = true; + break; + case ':': + _c4dbgpf("found ':'", c); + if(s.len == offs+1) + break; + { + const char next = s.str[offs+1]; + _c4dbgpf("next='{}'", c); + if(next == ' ' || next == ',' || next == '}' || next == '\n' || next == '\r' _RYML_WITH_TAB_TOKENS(|| next == '\t')) + { + _c4dbgpf("found terminating character: '{}'", c); + goto ended_scalar; + } + } + break; + case '{': + case '[': + _line_progressed(col); + _c4err("invalid character: '{}'", c); // noreturn + break; + case ']': + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RSEQIMAP), m_evt_handler->m_curr->pos); + goto ended_scalar; + default: + ; + } + } + +ended_scalar: + + _line_progressed(col); + s = s.first(offs); + sc->scalar = s.trimr(_RYML_WITH_OR_WITHOUT_TAB_TOKENS(" \t", ' ')); + sc->needs_filter = needs_filter; + + _c4prscalar("scanned plain scalar", sc->scalar, /*keep_newlines*/true); + + return sc->scalar.len > 0u; +} + +template +bool ParseEngine::_scan_scalar_seq_json(ScannedScalar *C4_RESTRICT sc) +{ + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RMAP), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RBLCK), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RSEQ), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RFLOW), m_evt_handler->m_curr->pos); + + substr s = m_evt_handler->m_curr->line_contents.rem; + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, !s.begins_with(' '), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, s.len > 0, m_evt_handler->m_curr->pos); + + _c4dbgp("seq_json: scanning scalar..."); + + switch(s.str[0]) + { + case ']': + case '{': + case ',': + _c4dbgp("seq_json: not a scalar."); + return false; + } + + { + const size_t len = _begins_with_special_json_scalar(s); + if(len) + { + char c = s.len > len ? s.str[len] : ','; + if(c == ',' || c == ']' || c == ' ' || c == '\n' || c == '\t' || c == '\r') + { + sc->scalar = s.first(len); + sc->needs_filter = false; + _c4dbgpf("seq_json: special scalar: '{}'", sc->scalar); + _line_progressed(len); + return true; + } + else + { + return false; + } + } + } + + // must be a number or special scalar + size_t i = 0; + for( ; i < s.len; ++i) + { + const char c = s.str[i]; + switch(c) + { + case ',': + case ']': + case ' ': + case '\t': + _c4dbgpf("seq_json: found terminating character: '{}'", c); + goto ended_scalar; + default: + ; + } + } + +ended_scalar: + + if(C4_LIKELY(i > 0)) + { + _line_progressed(i); + sc->scalar = s.first(i); + sc->needs_filter = false; + _c4dbgpf("seq_json: scalar was {}", _prs(sc->scalar, /*escape*/true)); + } + + return true; +} + +template +bool ParseEngine::_scan_scalar_map_json(ScannedScalar *C4_RESTRICT sc) +{ + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RSEQ), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RBLCK), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RMAP), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RFLOW), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RKEY|RVAL), m_evt_handler->m_curr->pos); + + substr s = m_evt_handler->m_curr->line_contents.rem; + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, !s.begins_with(' '), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, s.len > 0, m_evt_handler->m_curr->pos); + + _c4dbgp("scanning scalar..."); + + { + const size_t len = _begins_with_special_json_scalar(s); + if(len) + { + char c = s.len > len ? s.str[len] : ','; + _c4dbgpf("begins with special scalar: {} next='{}'", s.first(len), _c4prc(c)); + if(c == ',' || c == '}' || c == ' ' || c == '\n' || c == '\t' || c == '\r') + { + sc->scalar = s.first(len); + sc->needs_filter = false; + _c4dbgpf("special json scalar: '{}'", _prs(sc->scalar)); + _line_progressed(len); + return true; + } + else + { + return false; + } + } + } + + // must be a number + size_t i = 0; + for( ; i < s.len; ++i) + { + const char c = s.str[i]; + switch(c) + { + case ',': + case '}': + case ' ': + case '\t': + _c4dbgpf("found terminating character: '{}'", c); + goto ended_scalar; + default: + ; + } + } + +ended_scalar: + + if(C4_LIKELY(i > 0)) + { + _line_progressed(i); + sc->scalar = s.first(i); + sc->needs_filter = false; + _c4dbgpf("scalar was {}", _prs(sc->scalar)); + return true; + } + + return false; +} + +template +bool ParseEngine::_is_doc_begin(csubstr s) +{ + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, s[0] == '-', m_evt_handler->m_curr->pos); + return (m_evt_handler->m_curr->line_contents.indentation == 0u && m_evt_handler->m_curr->at_line_beginning() && _is_doc_begin_token(s)); +} + +template +bool ParseEngine::_is_doc_end(csubstr s) +{ + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, s[0] == '.', m_evt_handler->m_curr->pos); + return (m_evt_handler->m_curr->line_contents.indentation == 0u && m_evt_handler->m_curr->at_line_beginning() && _is_doc_end_token(s)); +} + +template +bool ParseEngine::_scan_scalar_plain_blck(ScannedScalar *C4_RESTRICT sc, size_t indentation) +{ + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RFLOW), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RSEQIMAP), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RBLCK|RUNK|USTY), m_evt_handler->m_curr->pos); + + substr s = m_evt_handler->m_curr->line_contents.rem; + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, !s.begins_with(' '), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, s.len > 0, m_evt_handler->m_curr->pos); + + switch(s.str[0]) + { + case '-': + if(_is_blck_token(s)) + { + return false; + } + else if(_is_doc_begin(s)) + { + _c4dbgp("token is doc start"); + return false; + } + break; + case ':': + case '?': + if(_is_blck_token(s)) + return false; + break; + case '[': + case '{': + case '&': + case '*': + case '!': + case '\t': + case ',': + case '%': + return false; + case '.': + if(_is_doc_end(s)) + { + _c4dbgp("token is doc end"); + return false; + } + break; + } + + _c4dbgpf("plain scalar! indentation={}", indentation); + + const size_t start_offset = m_evt_handler->m_curr->pos.offset; + const size_t start_line = m_evt_handler->m_curr->pos.line; + + bool needs_filter = false; + while(true) + { + _c4dbgpf("plain scalar line: {}", _prs(s)); + for(size_t i = 0; i < s.len; ++i) + { + const char curr = s.str[i]; + //_c4dbgpf("[{}]='{}'", i, _c4prc(curr)); + switch(curr) + { + case ':': + _c4dbgpf("[{}]: got suspicious ':'", i); + // are there more characters? + if((i + 1 == s.len) || ((s.str[i+1] == ' ') _RYML_WITH_TAB_TOKENS( || (s.str[i+1] == '\t')))) + { + _c4dbgpf("followed by '{}'", i+1 == s.len ? csubstr("\\n") : _c4prc(s.str[i+1])); + _line_progressed(i); + // ': ' is accepted only on the first line + if(C4_LIKELY(m_evt_handler->m_curr->pos.line == start_line)) + { + _c4dbgp("start line. scalar ends here"); + goto ended_scalar; + } + else + { + _c4err("multiline scalars cannot be used as implicit keys"); + } + } + else + { + size_t j = i; + while(j + 1 < s.len && s.str[j+1] == ':') + { + _c4dbgp("skip colon"); + ++j; + } + i = j > i ? j-1 : i; + _c4dbgp("nothing to see here"); + } + break; + case '#': + _c4dbgp("got suspicious '#'"); + if(!i || (s.str[i-1] == ' ' || s.str[i-1] == '\t')) + { + _c4dbgp("comment! scalar ends here"); + _line_progressed(i); + goto ended_scalar; + } + else + { + _c4dbgp("nothing to see here"); + } + break; + } + } + _line_progressed(s.len); + csubstr next_peeked = _peek_next_line(m_evt_handler->m_curr->pos.offset); + next_peeked = next_peeked.trimr("\n\r"); + const size_t next_indentation = next_peeked.first_not_of(' '); + _c4dbgpf("indentation curr={} next={}", indentation, next_indentation); + if(next_indentation < indentation) + { + _c4dbgp("smaller indentation! scalar ended"); + goto ended_scalar; + } + else if(next_indentation == 0 && next_peeked.len > 0) + { + const char first = next_peeked.str[0]; + switch(first) + { + case '-': + _c4dbgpf("doc begin? peeked={}", _prs(next_peeked, size_t(3))); + if(_is_doc_begin_token(next_peeked)) + { + _c4dbgp("doc begin! scalar ended"); + goto ended_scalar; + } + break; + case '.': + _c4dbgpf("doc end? peeked={}", _prs(next_peeked, size_t(3))); + if(_is_doc_end_token(next_peeked)) + { + _c4dbgp("doc end! scalar ended"); + goto ended_scalar; + } + break; + } + } + // load with next line + _c4dbgp("next line!"); + if(!_finished_file()) + { + _c4dbgp("next line!"); + _line_ended(); + _scan_line(); + } + else + { + _c4dbgp("file finished!"); + goto ended_scalar; + } + s = m_evt_handler->m_curr->line_contents.rem; + needs_filter = true; + } + +ended_scalar: + + sc->scalar = _buf().range(start_offset, m_evt_handler->m_curr->pos.offset).trimr(" \n\r\t"); + sc->needs_filter = needs_filter; + + _c4dbgpf("scalar was {}", _prs(sc->scalar)); + + return true; +} + +template +C4_ALWAYS_INLINE bool ParseEngine::_scan_scalar_plain_seq_blck(ScannedScalar *C4_RESTRICT sc) // LCOV_EXCL_LINE +{ + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RMAP), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RFLOW), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RSEQIMAP), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RSEQ), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RBLCK), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RVAL), m_evt_handler->m_curr->pos); + return _scan_scalar_plain_blck(sc, m_evt_handler->m_curr->indref + 1u); +} + +template +C4_ALWAYS_INLINE bool ParseEngine::_scan_scalar_plain_map_blck(ScannedScalar *C4_RESTRICT sc) // LCOV_EXCL_LINE +{ + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RSEQ), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RFLOW), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RMAP), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RBLCK), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RKEY|RVAL|QMRK), m_evt_handler->m_curr->pos); + return _scan_scalar_plain_blck(sc, m_evt_handler->m_curr->indref + 1u); +} + +template +C4_ALWAYS_INLINE bool ParseEngine::_scan_scalar_plain_unk(ScannedScalar *C4_RESTRICT sc) // LCOV_EXCL_LINE +{ + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RUNK|USTY), m_evt_handler->m_curr->pos); + return _scan_scalar_plain_blck(sc, m_evt_handler->m_curr->indref); +} + + +//----------------------------------------------------------------------------- + +template +substr ParseEngine::_peek_next_line(size_t pos) const +{ + substr rem{}; // declare here because of the goto + size_t nlpos{}; // declare here because of the goto + pos = pos == npos ? m_evt_handler->m_curr->pos.offset : pos; + if(pos >= _buf().len) + goto next_is_empty; + + // look for the next newline chars, and jump to the right of those + rem = _from_next_line(_buf().sub(pos)); + if(rem.empty()) + goto next_is_empty; + + // now get everything up to and including the following newline chars + nlpos = rem.first_of("\r\n"); + if((nlpos != csubstr::npos) && (nlpos + 1 < rem.len)) + nlpos += _extend_from_combined_newline(rem[nlpos], rem[nlpos+1]); + rem = rem.left_of(nlpos, /*include_pos*/true); + + _c4dbgpf("peek next line @ {}: (len={})'{}'", pos, rem.len, rem.trimr("\r\n")); + return rem; + +next_is_empty: + _c4dbgpf("peek next line @ {}: (len=0)''", pos); + return rem; +} + +//----------------------------------------------------------------------------- + +template +void ParseEngine::_scan_line() +{ + if(C4_LIKELY(m_evt_handler->m_curr->pos.offset < _buf().len)) + m_evt_handler->m_curr->line_contents.reset_with_next_line(_buf(), m_evt_handler->m_curr->pos.offset); + else + m_evt_handler->m_curr->line_contents.reset_with_next_line(_buf().last(0), 0); +} + +template +void ParseEngine::_line_progressed(size_t ahead) +{ + _c4dbgpf("line[{}] ({} cols) progressed by {}: col {}-->{} offset {}-->{}", + m_evt_handler->m_curr->pos.line, + m_evt_handler->m_curr->line_contents.full.len, + ahead, m_evt_handler->m_curr->pos.col, + m_evt_handler->m_curr->pos.col+ahead, + m_evt_handler->m_curr->pos.offset, + m_evt_handler->m_curr->pos.offset+ahead); + m_evt_handler->m_curr->pos.offset += ahead; + m_evt_handler->m_curr->pos.col += ahead; + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->pos.col <= m_evt_handler->m_curr->line_contents.num_cols+1, m_evt_handler->m_curr->pos); + m_evt_handler->m_curr->line_contents.rem = m_evt_handler->m_curr->line_contents.rem.sub(ahead); +} + +template +void ParseEngine::_line_ended() +{ + _c4dbgpf("line[{}] ({} cols) ended! offset {}-->{} / col {}-->{}", + m_evt_handler->m_curr->pos.line, + m_evt_handler->m_curr->line_contents.full.len, + m_evt_handler->m_curr->pos.offset, m_evt_handler->m_curr->pos.offset + m_evt_handler->m_curr->line_contents.full.len - m_evt_handler->m_curr->line_contents.num_cols, + m_evt_handler->m_curr->pos.col, 1); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->pos.col == m_evt_handler->m_curr->line_contents.num_cols + 1, m_evt_handler->m_curr->pos); + m_evt_handler->m_curr->pos.offset += m_evt_handler->m_curr->line_contents.full.len - m_evt_handler->m_curr->line_contents.num_cols; + ++m_evt_handler->m_curr->pos.line; + m_evt_handler->m_curr->pos.col = 1; +} + +template +void ParseEngine::_line_ended_undo() +{ + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->pos.col == 1u, m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->pos.line > 0u, m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->pos.offset >= m_evt_handler->m_curr->line_contents.full.len - m_evt_handler->m_curr->line_contents.num_cols, m_evt_handler->m_curr->pos); + const size_t delta = m_evt_handler->m_curr->line_contents.full.len - m_evt_handler->m_curr->line_contents.num_cols; + _c4dbgpf("line[{}] undo ended! line {}-->{}, offset {}-->{}", m_evt_handler->m_curr->pos.line, m_evt_handler->m_curr->pos.line, m_evt_handler->m_curr->pos.line - 1, m_evt_handler->m_curr->pos.offset, m_evt_handler->m_curr->pos.offset - delta); + m_evt_handler->m_curr->pos.offset -= delta; + --m_evt_handler->m_curr->pos.line; + m_evt_handler->m_curr->pos.col = m_evt_handler->m_curr->line_contents.num_cols + 1u; + // don't forget to undo also the changes to the remainder of the line + //_RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->pos.offset >= _buf().len || _buf()[m_evt_handler->m_curr->pos.offset] == '\n' || _buf()[m_evt_handler->m_curr->pos.offset] == '\r', m_evt_handler->m_curr->pos); + m_evt_handler->m_curr->line_contents.rem = _buf().sub(m_evt_handler->m_curr->pos.offset, 0); +} + + +//----------------------------------------------------------------------------- +template +void ParseEngine::_set_indentation(size_t indentation) noexcept +{ + m_evt_handler->m_curr->indref = indentation; + _c4dbgpf("state[{}]: saving indentation: {}", m_evt_handler->m_curr->level, m_evt_handler->m_curr->indref); +} + +template +void ParseEngine::_save_indentation() +{ + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->line_contents.rem.is_sub(m_evt_handler->m_curr->line_contents.full), m_evt_handler->m_curr->pos); + m_evt_handler->m_curr->indref = m_evt_handler->m_curr->line_contents.current_col(); + _c4dbgpf("state[{}]: saving indentation: {}", m_evt_handler->m_curr->level, m_evt_handler->m_curr->indref); +} + +template +void ParseEngine::_mark_seqflow_val_end() noexcept +{ + _c4dbgpf("SEQFLOW. mark val end at line={}", m_evt_handler->m_curr->pos.line); + m_prev_val_end = m_evt_handler->m_curr->pos.line; +} + + +//----------------------------------------------------------------------------- + +template +void ParseEngine::_flow_container_was_a_key(size_t orig_indent) +{ + _c4dbgpf("flow container is followed by colon! orig_indent={}", orig_indent); + m_evt_handler->actually_val_is_first_key_of_new_map_block(); + addrem_flags(RMAP|RVAL|RBLCK, RKCL|RSEQ|RUNK); + _set_indentation(orig_indent); + _maybe_skip_whitespace_tokens(); +} + +template +void ParseEngine::_end_flow_container(size_t orig_indent, bool multiline) +{ + // this is called AFTER ending the flow container, + // so now we're at the parent container's scope + if(has_all(RMAP|RBLCK) && has_none(RKCL|RVAL|RNXT)) + { + _c4dbgp("flow container: end as vanilla block map key!"); + if(C4_UNLIKELY(multiline)) + _c4err("multiline key is invalid"); + if(C4_UNLIKELY(!_maybe_scan_following_colon())) + _c4err("could not find ':' colon after key"); + _maybe_skip_whitespace_tokens(); + addrem_flags(RVAL, RKEY|RKCL|RNXT); + } + else if(has_none(RFLOW)) + { + _c4dbgp("end_flow_container: now not in flow!"); + if(has_any(RUNK|RSEQ|RKCL) && _maybe_scan_following_colon()) + { + if(C4_UNLIKELY(multiline)) + _c4err("multiline key is invalid"); + _flow_container_was_a_key(orig_indent); + } + else + { + _c4dbgp("end_flow_container: end map as key!"); + } + } + else if(has_any(RSEQ)) + { + _c4dbgp("end_flow_container: now in a flow seq"); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RFLOW), m_evt_handler->m_curr->pos); + _mark_seqflow_val_end(); + } +} + +template +void ParseEngine::_end_map_flow() +{ + bool multiline = m_evt_handler->m_parent->pos.line < m_evt_handler->m_curr->pos.line; + size_t orig_indent = m_evt_handler->m_curr->indref; + _c4dbgpf("mapflow: end, multiline={}", multiline); + m_evt_handler->end_map_flow(multiline && m_options.detect_flow_ml(), m_options.flow_ml_style().type); + _end_flow_container(orig_indent, multiline); +} + +template +void ParseEngine::_end_seq_flow() +{ + bool multiline = m_evt_handler->m_parent->pos.line < m_evt_handler->m_curr->pos.line; + size_t orig_indent = m_evt_handler->m_curr->indref; + _c4dbgpf("seqflow: end, multiline={}", multiline); + m_evt_handler->end_seq_flow(multiline && m_options.detect_flow_ml(), m_options.flow_ml_style().type); + _end_flow_container(orig_indent, multiline); +} + +template +void ParseEngine::_end_map_blck() +{ + _c4dbgp("mapblck: end"); + if(has_any(RKCL|RVAL)) + { + _c4dbgp("mapblck: set missing val"); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->set_val_scalar_plain_empty(); + } + else if(has_any(QMRK)) + { + _c4dbgp("mapblck: set missing keyval"); + _handle_annotations_before_blck_key_scalar(); + m_evt_handler->set_key_scalar_plain_empty(); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->set_val_scalar_plain_empty(); + } + m_evt_handler->end_map_block(); +} + +template +void ParseEngine::_end_seq_blck() +{ + if(has_any(RVAL)) + { + _c4dbgp("seqblck: set missing val"); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->set_val_scalar_plain_empty(); + } + m_evt_handler->end_seq_block(); +} + +template +void ParseEngine::_end2_map() +{ + _c4dbgp("map: end"); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RMAP), m_evt_handler->m_curr->pos); + if(has_any(RBLCK)) + { + _end_map_blck(); + } + else + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RFLOW), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(USTY), m_evt_handler->m_curr->pos); + m_evt_handler->_pop(); + } +} + +template +void ParseEngine::_end2_seq() +{ + _c4dbgp("seq: end"); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RSEQ), m_evt_handler->m_curr->pos); + if(has_any(RBLCK)) + { + _end_seq_blck(); + } + else + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RFLOW), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(USTY), m_evt_handler->m_curr->pos); + m_evt_handler->_pop(); + } +} + +template +void ParseEngine::_begin2_doc() +{ + _c4dbgp("begin_doc"); + m_has_directives_yaml = false; + m_has_directives = false; + m_doc_empty = true; + add_flags(RDOC); + m_evt_handler->begin_doc(); + m_evt_handler->m_curr->indref = 0; // ? +} + +template +void ParseEngine::_begin2_doc_expl() +{ + _c4dbgp("begin_doc_expl"); + m_has_directives_yaml = false; + m_has_directives = false; + m_doc_empty = true; + add_flags(RDOC); + m_evt_handler->begin_doc_expl(); + m_evt_handler->m_curr->indref = 0; // ? +} + +template +void ParseEngine::_end2_doc() +{ + _c4dbgp("doc: end"); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RDOC), m_evt_handler->m_curr->pos); + if(m_doc_empty || (m_pending_tags.num_entries || m_pending_anchors.num_entries)) + { + _c4dbgp("doc was empty; add empty val"); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->set_val_scalar_plain_empty(); + } + m_evt_handler->end_doc(); + m_bom_len = 0; +} + +template +void ParseEngine::_end2_doc_expl() +{ + _c4dbgp("doc: end"); + if(m_doc_empty || (m_pending_tags.num_entries || m_pending_anchors.num_entries)) + { + _c4dbgp("doc: no children; add empty val"); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->set_val_scalar_plain_empty(); + } + m_evt_handler->end_doc_expl(); + m_bom_len = 0; +} + +template +void ParseEngine::_maybe_begin_doc() +{ + if(has_none(RDOC)) + { + _c4dbgp("doc must be started"); + _begin2_doc(); + } +} +template +void ParseEngine::_maybe_end_doc() +{ + if(has_any(RDOC)) + { + _c4dbgp("doc must be finished"); + _end2_doc(); + } + else if(m_doc_empty && (m_pending_tags.num_entries || m_pending_anchors.num_entries)) + { + _c4dbgp("no doc to finish, but pending annotations"); + m_evt_handler->begin_doc(); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->set_val_scalar_plain_empty(); + m_evt_handler->end_doc(); + } +} + +template +void ParseEngine::_end_doc_suddenly__pop() +{ + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_stack.size() >= 1, m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_stack[0].flags & RDOC, m_evt_handler->m_curr->pos); + _c4dbgp("root is RDOC"); + if(m_evt_handler->m_curr->level != 0) + _handle_indentation_pop(&m_evt_handler->m_stack[0]); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RDOC), m_evt_handler->m_curr->pos); +} + +/** Check whether the current parse tokens are trailing on the + * previous doc, and raise an error if they are */ +template +void ParseEngine::_check_trailing_doc_token() +{ + const bool is_root = (m_evt_handler->m_stack.size() == 1u); + const bool isndoc = (m_evt_handler->m_curr->flags & NDOC) != 0; + const bool suspicious = m_evt_handler->template _has_any__(); + _c4dbgpf("target={} isroot={} suspicious={} ndoc={}", m_evt_handler->m_curr->node_id, is_root, suspicious, isndoc); + if((is_root || m_evt_handler->template _has_any__()) && suspicious && !isndoc) + _c4err("parse error"); +} + +template +void ParseEngine::_end_doc_suddenly() +{ + _c4dbgp("end doc suddenly"); + _end_doc_suddenly__pop(); + _end2_doc_expl(); + addrem_flags(RUNK|RTOP|NDOC, RMAP|RSEQ|RDOC); +} + +template +void ParseEngine::_check_doc_end_tokens() const +{ + csubstr rem = m_evt_handler->m_curr->line_contents.rem; + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, !rem.begins_with_any(". \t"), m_evt_handler->m_curr->pos); + if(C4_UNLIKELY(rem.len && !rem.begins_with('#'))) + { + _c4err("parse error"); + } +} + +template +void ParseEngine::_start_doc_suddenly() +{ + _c4dbgp("start doc suddenly"); + _end_doc_suddenly__pop(); + _end2_doc(); + _begin2_doc_expl(); +} + +template +void ParseEngine::_end_stream() +{ + _c4dbgpf("end_stream, level={} node_id={}", m_evt_handler->m_curr->level, m_evt_handler->m_curr->node_id); + if(C4_UNLIKELY(has_all(RSEQ|RFLOW))) + _c4err("missing terminating ]"); + else if(C4_UNLIKELY(has_all(RMAP|RFLOW))) + _c4err("missing terminating }"); + if(m_evt_handler->m_stack.size() > 1) + _handle_indentation_pop(m_evt_handler->m_stack.begin()); + if(has_all(RDOC)) + { + _end2_doc(); + } + else if(has_all(RTOP|RUNK)) + { + if(m_pending_anchors.num_entries || m_pending_tags.num_entries) + { + if(m_doc_empty) + { + m_evt_handler->begin_doc(); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->set_val_scalar_plain_empty(); + m_evt_handler->end_doc(); + } + } + } + m_evt_handler->end_stream(); + if(C4_UNLIKELY(m_has_directives)) + _c4err("directives cannot be used without a document"); +} + + +template +void ParseEngine::_handle_indentation_pop(ParserState const* popto) +{ + _c4dbgpf("popping {} level{}: from level {}(@ind={}) to level {}(@ind={})", m_evt_handler->m_curr->level - popto->level, (((m_evt_handler->m_curr->level - popto->level) > 1) ? "s" : ""), m_evt_handler->m_curr->level, m_evt_handler->m_curr->indref, popto->level, popto->indref); + while(m_evt_handler->m_curr != popto) + { + if(has_any(RSEQ)) + { + _c4dbgpf("popping seq at level {} (indentation={},addr={})", m_evt_handler->m_curr->level, m_evt_handler->m_curr->indref, m_evt_handler->m_curr); + _end2_seq(); + } + else if(has_any(RMAP)) + { + _c4dbgpf("popping map at level {} (indentation={},addr={})", m_evt_handler->m_curr->level, m_evt_handler->m_curr->indref, m_evt_handler->m_curr); + _end2_map(); + } + else + { + break; + } + } + _c4dbgpf("current level is {} (indentation={})", m_evt_handler->m_curr->level, m_evt_handler->m_curr->indref); +} + +template +void ParseEngine::_handle_indentation_pop_from_block_seq() +{ + // search the stack frame to jump to based on its indentation + using state_type = typename EventHandler::state; + state_type const* popto = nullptr; + auto &stack = m_evt_handler->m_stack; + _RYML_ASSERT_PARSE_(stack.m_callbacks, stack.is_contiguous(), m_evt_handler->m_curr->pos); // this search relies on the stack being contiguous + _RYML_ASSERT_PARSE_(stack.m_callbacks, m_evt_handler->m_curr >= stack.begin() && m_evt_handler->m_curr < stack.end(), m_evt_handler->m_curr->pos); + const size_t ind = m_evt_handler->m_curr->line_contents.indentation; + #ifdef RYML_DBG + _print_state_stack(); + #endif + for(state_type const* s = m_evt_handler->m_curr-1; s >= stack.begin(); --s) + { + _c4dbgpf("searching for state with indentation {}. curr={} (level={},node={})", ind, s->indref, s->level, s->node_id); + if(s->indref == ind) + { + _c4dbgpf("gotit!!! level={} node={}", s->level, s->node_id); + popto = s; + break; + } + } + if(!popto || popto >= m_evt_handler->m_curr || popto->level >= m_evt_handler->m_curr->level) + { + _c4err("parse error: incorrect indentation?"); + } + _handle_indentation_pop(popto); +} + +template +void ParseEngine::_handle_indentation_pop_from_block_map() +{ + // search the stack frame to jump to based on its indentation + using state_type = typename EventHandler::state; + auto &stack = m_evt_handler->m_stack; + _RYML_ASSERT_PARSE_(stack.m_callbacks, stack.is_contiguous(), m_evt_handler->m_curr->pos); // this search relies on the stack being contiguous + _RYML_ASSERT_PARSE_(stack.m_callbacks, m_evt_handler->m_curr >= stack.begin() && m_evt_handler->m_curr < stack.end(), m_evt_handler->m_curr->pos); + const size_t ind = m_evt_handler->m_curr->line_contents.indentation; + state_type const* popto = nullptr; + #ifdef RYML_DBG + char flagbuf_[128]; + _print_state_stack(flagbuf_); + #endif + for(state_type const* s = m_evt_handler->m_curr-1; s > stack.begin(); --s) // never go to the stack bottom. that's the root + { + _c4dbgpf("searching for state with indentation {}. current: ind={},level={},node={},flags={}", ind, s->indref, s->level, s->node_id, detail::_parser_flags_to_str(flagbuf_, s->flags)); + if(s->indref < ind) + { + break; + } + else if(s->indref == ind) + { + _c4dbgpf("same indentation!!! level={} node={}", s->level, s->node_id); + if(popto && has_any(RTOP, s) && has_none(RMAP|RSEQ, s)) + { + break; + } + popto = s; + if(has_all(RSEQ|RBLCK, s)) + { + csubstr rem = m_evt_handler->m_curr->line_contents.rem; + const size_t first = rem.first_not_of(' '); + _RYML_ASSERT_PARSE_(stack.m_callbacks, first == ind || first == npos, m_evt_handler->m_curr->pos); + rem = rem.right_of(first, true); + _c4dbgpf("indentless? rem='{}' first={}", rem, first); + if(rem.begins_with('-') && _is_blck_token(rem)) + { + _c4dbgp("parent was indentless seq"); + break; + } + } + } + } + if(!popto || popto >= m_evt_handler->m_curr || popto->level >= m_evt_handler->m_curr->level) + { + _c4err("parse error: incorrect indentation?"); + } + _handle_indentation_pop(popto); +} + + +//----------------------------------------------------------------------------- +template +void ParseEngine::_check_valid_newline_in_quoted_scalar() +{ + if(C4_UNLIKELY(has_all(RMAP|RBLCK|RKEY))) + { + _c4err("multiline quoted keys are invalid"); + } + else // check contextual indentation + { + const size_t minindent = m_evt_handler->m_curr->indref + ((has_any(RMAP|RSEQ) && has_any(RBLCK))); + _c4dbgpf("indent={} vs minindent={} indref={}", m_evt_handler->m_curr->line_contents.indentation, minindent, m_evt_handler->m_curr->indref); + if(m_evt_handler->m_curr->line_contents.indentation < minindent) + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, + m_evt_handler->m_curr->line_contents.indentation == m_evt_handler->m_curr->line_contents.rem.first_not_of(' '), + m_evt_handler->m_curr->pos); + csubstr trimmed = m_evt_handler->m_curr->line_contents.rem.sub(m_evt_handler->m_curr->line_contents.indentation); + _c4dbgpf("trimmed.len={} line={}", trimmed.len, _prs(m_evt_handler->m_curr->line_contents.rem, true)); + if(C4_UNLIKELY(!!trimmed.len)) + { + _c4err("bad indentation"); + } + } + } +} + + +//----------------------------------------------------------------------------- +template +typename ParseEngine::ScannedScalar ParseEngine::_scan_scalar_squot() +{ + // quoted scalars can spread over multiple lines! + // nice explanation here: http://yaml-multiline.info/ + + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, _buf().sub(m_evt_handler->m_curr->pos.offset).begins_with('\''), m_evt_handler->m_curr->pos); + + // a span to the end of the file, skipping the opening quote + substr s = _buf().sub(m_evt_handler->m_curr->pos.offset + 1); + _line_progressed(1); // advance over the opening quote + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, !m_evt_handler->m_curr->at_line_beginning(), m_evt_handler->m_curr->pos); + + bool needs_filter = false; + size_t pos = npos; // find the pos of the matching quote + while( ! _finished_file()) + { + const csubstr line = m_evt_handler->m_curr->line_contents.rem; + _c4dbgpf("scanning single quoted scalar @ line[{}]: {}", m_evt_handler->m_curr->pos.line, _prs(line)); + if(C4_UNLIKELY(m_evt_handler->m_curr->at_line_beginning() && _is_doc_token(line))) + _c4err("token can not appear at line begin"); + for(size_t i = 0; i < line.len; ++i) + { + const char curr = line.str[i]; + if(curr == '\'') // single quotes are escaped with two single quotes + { + const char next = i+1 < line.len ? line.str[i+1] : '~'; + if(next != '\'') // so just look for the first quote + { // without another after it + _line_progressed(i + 1); // progress beyond the quote + pos = i + (size_t)(line.str - s.str); // set pos to before the quote + goto found_close; + } + else + { + needs_filter = true; // needs filter to remove escaped quotes + ++i; // skip the escaped quote + } + } + } + + needs_filter = true; + _line_progressed(line.len); + _line_ended(); + _scan_line(); + _check_valid_newline_in_quoted_scalar(); + } + + _c4err("reached end of file while looking for closing quote"); + +found_close: + + _c4dbgpf("found closing quote at: {}", pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, pos != npos, m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, pos >= 0, m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, s.end() >= _buf().begin() && s.end() <= _buf().end(), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, s.end() == _buf().end() || *s.end() == '\'', m_evt_handler->m_curr->pos); + _set_first_strict(s, pos); + + _c4prscalar("scanned squoted scalar", s, /*keep_newlines*/true); + + return ScannedScalar { s, needs_filter }; +} + + +//----------------------------------------------------------------------------- +template +typename ParseEngine::ScannedScalar ParseEngine::_scan_scalar_dquot() +{ + // quoted scalars can spread over multiple lines! + // nice explanation here: http://yaml-multiline.info/ + + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, _buf().sub(m_evt_handler->m_curr->pos.offset).begins_with('"'), m_evt_handler->m_curr->pos); + + // a span to the end of the file, skipping the opening quote + substr s = _buf().sub(m_evt_handler->m_curr->pos.offset + 1); + _line_progressed(1); // advance over the opening quote + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, !m_evt_handler->m_curr->at_line_beginning(), m_evt_handler->m_curr->pos); + + bool needs_filter = false; + size_t pos = npos; // find the pos of the matching quote + while( ! _finished_file()) + { + #if defined(__GNUC__) && (/*__GNUC__ == 12 || */__GNUC__ == 13) + C4_DONT_OPTIMIZE(m_evt_handler->m_curr->line_contents.rem); // prevent hoisting + #endif + csubstr rem = m_evt_handler->m_curr->line_contents.rem; + _c4dbgpf("scanning double quoted scalar @ line[{}]: line='{}'", m_evt_handler->m_curr->pos.line, rem); + if(C4_UNLIKELY(m_evt_handler->m_curr->at_line_beginning() && _is_doc_token(rem))) + _c4err("token can not appear at line begin"); + for(size_t i = 0; i < rem.len; ++i) + { + const char curr = rem.str[i]; + // every \ is an escape + if(curr == '\\') + { + const char next = i+1 < rem.len ? rem.str[i+1] : '~'; + needs_filter = true; + if(next == '"' || next == '\\') + ++i; + } + else if(curr == '"') + { + _line_progressed(i + 1); // progress beyond the quote + pos = i + (size_t)(rem.str - s.str); // set pos to before the quote + goto found_close; + } + } + + // leading whitespace also needs filtering + needs_filter = true; + _line_progressed(rem.len); + _line_ended(); + _scan_line(); + _check_valid_newline_in_quoted_scalar(); + } + + _c4err("reached end of file while looking for closing quote"); + +found_close: + + _c4dbgpf("found closing quote at: {}", pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, pos != npos, m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, pos >= 0, m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, s.end() >= _buf().begin() && s.end() <= _buf().end(), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, s.end() == _buf().end() || *s.end() == '"', m_evt_handler->m_curr->pos); + _set_first_strict(s, pos); + + _c4prscalar("scanned dquoted scalar", s, /*keep_newlines*/true); + + return ScannedScalar{s, needs_filter}; +} + + +//----------------------------------------------------------------------------- +template +void ParseEngine::_scan_block(ScannedBlock *C4_RESTRICT sb, size_t indref) +{ + _c4dbgpf("blck: indref={}", indref); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, indref != npos, m_evt_handler->m_curr->pos); + + // nice explanation here: http://yaml-multiline.info/ + csubstr s = m_evt_handler->m_curr->line_contents.rem; + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, s.begins_with('|') || s.begins_with('>'), m_evt_handler->m_curr->pos); + + _c4dbgpf("blck: specs={}", _prs(s)); + + // parse the spec + BlockChomp_e chomp = CHOMP_CLIP; // default to clip unless + or - are used + size_t indentation = npos; // have to find out if no spec is given + if(s.len > 1) + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, s.begins_with_any("|>"), m_evt_handler->m_curr->pos); + csubstr t = s.sub(1); + _c4dbgpf("blck: spec is multichar: {}", _prs(t)); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, t.len >= 1, m_evt_handler->m_curr->pos); + size_t pos = t.first_of("-+"); + _c4dbgpf("blck: spec chomp char: pos={}", pos); + if(pos != npos) + { + _c4dbgpf("blck: spec chomp char: {}", _c4prc(t[pos])); + if(t[pos] == '-') + { + _c4dbgp("blck: chomp=STRIP"); + chomp = CHOMP_STRIP; + } + else if(t[pos] == '+') + { + _c4dbgp("blck: chomp=KEEP"); + chomp = CHOMP_KEEP; + } + if(pos == 0) + t = t.sub(1); + else + t = t.first(pos); + _c4dbgpf("blck: spec is now: {}", _prs(t)); + } + // from here to the end, only digits are considered + pos = t.first_not_of("0123456789"); + csubstr rest = t.first(pos); + if( ! rest.empty()) + { + _c4dbgpf("blck: parse indentation digits: {}", _prs(rest)); + if(C4_UNLIKELY(rest.len > 1)) + _c4err("parse error: invalid indentation"); + if(C4_UNLIKELY( ! c4::atou(rest, &indentation))) + _c4err("parse error: could not read indentation as decimal"); // LCOV_EXCL_LINE + if(C4_UNLIKELY( ! indentation)) + _c4err("parse error: null indentation"); + _c4dbgpf("blck: indentation specified: {}. add {} from curr state -> {}", indentation, m_evt_handler->m_curr->indref, indentation+indref); + indentation += m_evt_handler->m_curr->indref; + } + else + { + rest = t.triml(" \t"); + _c4dbgpf("blck: digits empty. t={} trimmed={} iscomm={} t.iscomm={}", _prs(t), _prs(rest), rest.begins_with('#'), t.begins_with('#')); + if(C4_UNLIKELY(rest.len && (rest.str[0] != '#' || t.str[0] == '#'))) + _c4err("parse error: invalid token"); + } + } + + _c4dbgpf("blck: style={} chomp={} indentation={}", s.begins_with('>') ? "fold" : "literal", chomp==CHOMP_CLIP ? "clip" : (chomp==CHOMP_STRIP ? "strip" : "keep"), indentation); + + // finish the current line + _line_progressed(s.len); + _line_ended(); + _scan_line(); + + // start with a zero-length block, already pointing at the right place + substr raw_block(_buf().data() + m_evt_handler->m_curr->pos.offset, size_t(0)); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, raw_block.begin() == m_evt_handler->m_curr->line_contents.full.str, m_evt_handler->m_curr->pos); + + // read every full line into a raw block, + // from which newlines are to be stripped as needed. + // + // If no explicit indentation was given, pick it from the first + // non-empty line. See + // https://yaml.org/spec/1.2.2/#8111-block-indentation-indicator + size_t num_lines = 0; + size_t first = m_evt_handler->m_curr->pos.line; + size_t provisional_indentation = npos; + LineContents lc; + while(( ! _finished_file())) + { + // peek next line, but do not advance immediately + lc.reset_with_next_line(_buf(), m_evt_handler->m_curr->pos.offset); + #if defined(__GNUC__) && (__GNUC__ == 12 || __GNUC__ == 13) + C4_DONT_OPTIMIZE(lc.rem); + #endif + _c4dbgpf("blck: peeking at {}", _prs(lc.rem.trimr("\r\n"), true)); + // evaluate termination conditions + if(indentation != npos) + { + _c4dbgpf("blck: indentation={}", indentation); + // stop when the line is deindented and not empty + if(lc.indentation < indentation && ( ! lc.rem.trim(" \t").empty())) + { + if(raw_block.len) + { + _c4dbgpf("blck: indentation decreased ref={} thisline={}", indentation, lc.indentation); + } + else + { + _c4err("indentation decreased without any scalar"); + } + break; + } + else if(indentation == 0) + { + _c4dbgpf("blck: noindent. lc.rem={}", _prs(lc.rem)); + if(_is_doc_token(lc.rem)) + { + _c4dbgp("blck: stop. indentation=0 and doc ended"); + break; + } + } + } + else + { + const size_t fns = lc.rem.first_not_of(' '); + _c4dbgpf("blck: indentation ref not set. firstnonws={}", fns); + if(fns != npos) // non-empty line + { + _c4dbgpf("blck: line not empty. indref={} indprov={} indentation={}", indref, provisional_indentation, lc.indentation); + if(C4_UNLIKELY(lc.full.begins_with('\t'))) + _c4err("parse error"); + if(provisional_indentation == npos) + { + if(lc.indentation < indref) + { + _c4dbgpf("blck: block terminated indentation={} < indref={}", lc.indentation, indref); + if(raw_block.len == 0) + { + _c4dbgp("blck: was empty, undo next line"); + _line_ended_undo(); + } + break; + } + else if(lc.indentation == m_evt_handler->m_curr->indref) + { + if(has_any(RSEQ|RMAP)) + { + _c4dbgpf("blck: block terminated. reading container and indentation={}==indref={}", lc.indentation, m_evt_handler->m_curr->indref); + break; + } + } + _c4dbgpf("blck: set indentation ref from this line: ref={}", lc.indentation); + indentation = lc.indentation; + } + else + { + if(lc.indentation >= provisional_indentation) + { + _c4dbgpf("blck: set indentation ref from provisional indentation: provisional_ref={}, thisline={}", provisional_indentation, lc.indentation); + //indentation = provisional_indentation ? provisional_indentation : lc.indentation; + indentation = lc.indentation; + } + else + { + if(lc.indentation >= indref) + _c4err("parse error: first non-empty block line should have at least the original indentation"); + _c4dbgp("blck: finished"); + break; + } + } + } + else // empty line + { + _c4dbgpf("blck: line empty or {} spaces. line_indentation={} prov_indentation={}", lc.rem.len, lc.indentation, provisional_indentation); + if(provisional_indentation != npos) + { + if(lc.rem.len >= provisional_indentation) + { + _c4dbgpf("blck: increase provisional_ref {} -> {}", provisional_indentation, lc.rem.len); + provisional_indentation = lc.rem.len; + } + } + else + { + provisional_indentation = lc.indentation ? lc.indentation : has_any(RSEQ|RVAL); + _c4dbgpf("blck: initialize provisional_ref={}", provisional_indentation); + if(provisional_indentation == npos) + { + provisional_indentation = lc.rem.len ? lc.rem.len : has_any(RSEQ|RVAL); + _c4dbgpf("blck: initialize provisional_ref={}", provisional_indentation); + } + if(provisional_indentation < indref) + { + provisional_indentation = indref; + _c4dbgpf("blck: initialize provisional_ref={}", provisional_indentation); + } + } + } + } + // advance now that we know the folded scalar continues + m_evt_handler->m_curr->line_contents = lc; + _c4dbgpf("blck: append '{}'", m_evt_handler->m_curr->line_contents.rem); + raw_block.len += m_evt_handler->m_curr->line_contents.full.len; + _line_progressed(m_evt_handler->m_curr->line_contents.rem.len); + _line_ended(); + ++num_lines; + } + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->pos.line == (first + num_lines) || (raw_block.len == 0), m_evt_handler->m_curr->pos); + C4_UNUSED(num_lines); + C4_UNUSED(first); + + if(indentation == npos) + { + _c4dbgpf("blck: set indentation from provisional: {}", provisional_indentation); + indentation = provisional_indentation; + } + + if(num_lines) + _line_ended_undo(); + + _c4prscalar("scanned block", raw_block, /*keep_newlines*/true); + + sb->scalar = raw_block; + sb->indentation = indentation; + sb->chomp = chomp; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +/** @cond dev */ + +// a debugging scaffold: +#if 0 +#define _c4dbgfws(fmt, ...) _c4dbgpf("filt_ws[{}->{}]: " fmt, proc.rpos, proc.wpos, __VA_ARGS__) +#else +#define _c4dbgfws(...) +#endif + +template +template +bool ParseEngine::_filter_ws_handle_to_first_non_space(FilterProcessor &proc) +{ + _c4dbgfws("found whitespace '{}'", _c4prc(proc.curr())); + _RYML_ASSERT_PARSE_(this->callbacks(), proc.curr() == ' ' || proc.curr() == '\t', m_evt_handler->m_curr->pos); + + const size_t first_pos = proc.rpos > 0 ? proc.src.first_not_of(" \t", proc.rpos) : proc.src.first_not_of(' ', proc.rpos); + if(first_pos != npos) + { + const char first_char = proc.src[first_pos]; + _c4dbgfws("firstnonws='{}'@{}", _c4prc(first_char), first_pos); + if(first_char == '\n' || first_char == '\r') // skip trailing whitespace + { + _c4dbgfws("whitespace is trailing on line", ""); + proc.skip(first_pos - proc.rpos); + } + else // a legit whitespace + { + proc.copy(); + _c4dbgfws("legit whitespace. sofar={}", _prs(proc.sofar())); + } + return true; + } + _c4dbgfws("whitespace is trailing on line", ""); + return false; +} + +template +template +void ParseEngine::_filter_ws_copy_trailing(FilterProcessor &proc) +{ + if(!_filter_ws_handle_to_first_non_space(proc)) + { + _c4dbgfws("... everything else is trailing whitespace - copy {} chars", proc.src.len - proc.rpos); + proc.copy(proc.src.len - proc.rpos); + } +} + +template +template +void ParseEngine::_filter_ws_skip_trailing(FilterProcessor &proc) +{ + if(!_filter_ws_handle_to_first_non_space(proc)) + { + _c4dbgfws("... everything else is trailing whitespace - skip {} chars", proc.src.len - proc.rpos); + proc.skip(proc.src.len - proc.rpos); + } +} + +#undef _c4dbgfws + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +/* plain scalars */ + +// a debugging scaffold: +#if 0 +#define _c4dbgfps(fmt, ...) _c4dbgpf("filt_plain[{}->{}]: " fmt, proc.rpos, proc.wpos, __VA_ARGS__) +#else +#define _c4dbgfps(fmt, ...) +#endif + +template +template +void ParseEngine::_filter_nl_plain(FilterProcessor &C4_RESTRICT proc, size_t indentation) +{ + _RYML_ASSERT_PARSE_(this->callbacks(), proc.curr() == '\n', m_evt_handler->m_curr->pos); + + _c4dbgfps("found newline. sofar={}", _prs(proc.sofar())); + size_t ii = proc.rpos; + const size_t numnl_following = _count_following_newlines(proc.src, &ii, indentation); + if(numnl_following) + { + proc.set('\n', numnl_following); + _c4dbgfps("{} consecutive (empty) lines {}. totalws={}", 1+numnl_following, ii < proc.src.len ? "in the middle" : "at the end", proc.rpos-ii); + } + else + { + const size_t ret = proc.src.first_not_of(" \t", proc.rpos+1); + if(ret != npos) + { + proc.set(' '); + _c4dbgfps("single newline. convert to space. ret={}/{}. sofar={}", ii, proc.src.len, _prs(proc.sofar())); + } + else + { + _c4dbgfps("last newline, everything else is whitespace. ii={}/{}", ii, proc.src.len); + ii = proc.src.len; + } + } + proc.rpos = ii; +} + +template +template +auto ParseEngine::_filter_plain(FilterProcessor &C4_RESTRICT proc, size_t indentation) -> decltype(proc.result()) +{ + _RYML_ASSERT_PARSE_(this->callbacks(), indentation != npos, m_evt_handler->m_curr->pos); + _c4dbgfps("before={}", _prs(proc.src)); + + while(proc.has_more_chars()) + { + const char curr = proc.curr(); + _c4dbgfps("'{}', sofar={}", _c4prc(curr), _prs(proc.sofar())); + switch(curr) + { + case ' ': + _RYML_WITH_TAB_TOKENS(case '\t':) + _c4dbgfps("whitespace", curr); + _filter_ws_skip_trailing(proc); + break; + case '\n': + _c4dbgfps("newline", curr); + _filter_nl_plain(proc, /*indentation*/indentation); + break; + case '\r': // skip \r --- https://stackoverflow.com/questions/1885900 + _c4dbgfps("carriage return, ignore", curr); + proc.skip(); + break; + default: + proc.copy(); + break; + } + } + + _c4dbgfps("after={}", _prs(proc.sofar())); + + return proc.result(); +} + +#undef _c4dbgfps + + +template +FilterResult ParseEngine::filter_scalar_plain(csubstr scalar, substr dst, size_t indentation) +{ + FilterProcessorSrcDst proc(scalar, dst); + return _filter_plain(proc, indentation); +} + +template +FilterResult ParseEngine::filter_scalar_plain_in_place(substr dst, size_t cap, size_t indentation) +{ + FilterProcessorInplaceEndExtending proc(dst, cap); + return _filter_plain(proc, indentation); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +/* single quoted */ + +// a debugging scaffold: +#if 0 +#define _c4dbgfsq(fmt, ...) _c4dbgpf("filt_squo[{}->{}]: " fmt, proc.rpos, proc.wpos, __VA_ARGS__) +#else +#define _c4dbgfsq(fmt, ...) +#endif + +template +template +void ParseEngine::_filter_nl_squoted(FilterProcessor &C4_RESTRICT proc) +{ + _RYML_ASSERT_PARSE_(this->callbacks(), proc.curr() == '\n', m_evt_handler->m_curr->pos); + + _c4dbgfsq("found newline. sofar={}", _prs(proc.sofar())); + size_t ii = proc.rpos; + const size_t numnl_following = _count_following_newlines(proc.src, &ii); + if(numnl_following) + { + proc.set('\n', numnl_following); + _c4dbgfsq("{} consecutive (empty) lines {}. totalws={}", 1+numnl_following, ii < proc.src.len ? "in the middle" : "at the end", proc.rpos-ii); + } + else + { + const size_t ret = proc.src.first_not_of(" \t", proc.rpos+1); + if(ret != npos) + { + proc.set(' '); + _c4dbgfsq("single newline. convert to space. ret={}/{}. sofar={}", ii, proc.src.len, _prs(proc.sofar())); + } + else + { + proc.set(' '); + _c4dbgfsq("single newline. convert to space. ii={}/{}. sofar={}", ii, proc.src.len, _prs(proc.sofar())); + } + } + proc.rpos = ii; +} + +template +template +auto ParseEngine::_filter_squoted(FilterProcessor &C4_RESTRICT proc) -> decltype(proc.result()) +{ + _c4dbgfsq("before={}", _prs(proc.src)); + + // from the YAML spec for double-quoted scalars: + // https://yaml.org/spec/1.2-old/spec.html#style/flow/single-quoted + while(proc.has_more_chars()) + { + const char curr = proc.curr(); + _c4dbgfsq("'{}', sofar={}", _c4prc(curr), _prs(proc.sofar())); + switch(curr) + { + case ' ': + case '\t': + _c4dbgfsq("whitespace", curr); + _filter_ws_copy_trailing(proc); + break; + case '\n': + _c4dbgfsq("newline", curr); + _filter_nl_squoted(proc); + break; + case '\r': // skip \r --- https://stackoverflow.com/questions/1885900 + _c4dbgfsq("skip cr", curr); + proc.skip(); + break; + case '\'': + _c4dbgfsq("squote", curr); + if(proc.next() == '\'') + { + _c4dbgfsq("two consecutive squotes", curr); + proc.skip(); + proc.copy(); + } + else + { + _c4err("filter error"); + } + break; + default: + proc.copy(); + break; + } + } + + _c4dbgfsq(": #filteredchars={} after={}", proc.src.len-proc.sofar().len, _prs(proc.sofar())); + + return proc.result(); +} + +#undef _c4dbgfsq + +template +FilterResult ParseEngine::filter_scalar_squoted(csubstr scalar, substr dst) +{ + FilterProcessorSrcDst proc(scalar, dst); + return _filter_squoted(proc); +} + +template +FilterResult ParseEngine::filter_scalar_squoted_in_place(substr dst, size_t cap) +{ + FilterProcessorInplaceEndExtending proc(dst, cap); + return _filter_squoted(proc); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +/* double quoted */ + +// a debugging scaffold: +#if 0 +#define _c4dbgfdq(fmt, ...) _c4dbgpf("filt_dquo[{}->{}]: " fmt, proc.rpos, proc.wpos, __VA_ARGS__) +#else +#define _c4dbgfdq(...) +#endif + +template +template +void ParseEngine::_filter_nl_dquoted(FilterProcessor &C4_RESTRICT proc) +{ + _RYML_ASSERT_PARSE_(this->callbacks(), proc.curr() == '\n', m_evt_handler->m_curr->pos); + + _c4dbgfdq("found newline. sofar={}", _prs(proc.sofar())); + size_t ii = proc.rpos; + const size_t numnl_following = _count_following_newlines(proc.src, &ii); + if(numnl_following) + { + proc.set('\n', numnl_following); + _c4dbgfdq("{} consecutive (empty) lines {}. totalws={}", 1+numnl_following, ii < proc.src.len ? "in the middle" : "at the end", proc.rpos-ii); + } + else + { + const size_t ret = proc.src.first_not_of(" \t", proc.rpos+1); + if(ret != npos) + { + proc.set(' '); + _c4dbgfdq("single newline. convert to space. ret={}/{}. sofar={}", ii, proc.src.len, _prs(proc.sofar())); + } + else + { + proc.set(' '); + _c4dbgfdq("single newline. convert to space. ii={}/{}. sofar={}", ii, proc.src.len, _prs(proc.sofar())); + } + if(ii < proc.src.len && proc.src.str[ii] == '\\') + { + _c4dbgfdq("backslash at [{}]", ii); + const char next = ii+1 < proc.src.len ? proc.src.str[ii+1] : '\0'; + if(next == ' ' || next == '\t') + { + _c4dbgfdq("extend skip to backslash", ""); + ++ii; + } + } + } + proc.rpos = ii; +} + +template +template +void ParseEngine::_filter_dquoted_backslash_decode(FilterProcessor &C4_RESTRICT proc, size_t sz) +{ + const size_t szp1 = sz + 1u; + if(C4_UNLIKELY(proc.rpos + szp1 >= proc.src.len)) + _c4err("codepoint requires {} hex digits. scalar pos={}", sz, proc.rpos); + char readbuf[8]; + csubstr codepoint = proc.src.sub(proc.rpos + 2u, sz); + _c4dbgfdq("utf8 ~~~{}~~~ rpos={} rem=~~~{}~~~", codepoint, proc.rpos, proc.src.sub(proc.rpos)); + uint32_t codepoint_val = {}; + if(C4_UNLIKELY(!read_hex(codepoint, &codepoint_val))) + _c4err("failed to parse codepoint. scalar pos={}", proc.rpos); + const size_t numbytes = decode_code_point((uint8_t*)readbuf, sizeof(readbuf), codepoint_val); + if(C4_UNLIKELY(numbytes == 0)) + _c4err("failed to decode code point={}", proc.rpos); + _RYML_ASSERT_PARSE_(callbacks(), numbytes <= 4, m_evt_handler->m_curr->pos); + proc.translate_esc_bulk(readbuf, numbytes, /*nread*/szp1); + _c4dbgfdq("utf8 after rpos={} rem=~~~{}~~~", proc.rpos, proc.src.sub(proc.rpos)); +} + +template +template +void ParseEngine::_filter_dquoted_backslash(FilterProcessor &C4_RESTRICT proc) +{ + char next = proc.next(); + _c4dbgfdq("backslash, next='{}'", _c4prc(next)); + if(next == '\r') + { + if(proc.rpos+2 < proc.src.len && proc.src.str[proc.rpos+2] == '\n') + { + proc.skip(); // newline escaped with \ -- skip both (add only one as i is loop-incremented) + next = '\n'; + _c4dbgfdq("[{}]: was \\r\\n, now next='\\n'", proc.rpos); + } + } + + if(next == '\n') + { + size_t ii = proc.rpos + 2; + for( ; ii < proc.src.len; ++ii) + { + // skip leading whitespace + if(proc.src.str[ii] == ' ' || proc.src.str[ii] == '\t') + ; + else + break; + } + proc.skip(ii - proc.rpos); + } + else if(next == '"' || next == '/' || next == ' ' || next == '\t') + { + // escapes for json compatibility + proc.translate_esc(next); + _c4dbgfdq("here, used '{}'", _c4prc(next)); + } + else if(next == '\r') + { + proc.skip(); + } + else if(next == 'n') + { + proc.translate_esc('\n'); + } + else if(next == 'r') + { + proc.translate_esc('\r'); + } + else if(next == 't') + { + proc.translate_esc('\t'); + } + else if(next == '\\') + { + proc.translate_esc('\\'); + } + else if(next == 'x') // 2-digit Unicode escape (\xXX), code point 0x00–0xFF + { + _filter_dquoted_backslash_decode(proc, 2u); + } + else if(next == 'u') // 4-digit Unicode escape (\uXXXX), code point 0x0000–0xFFFF + { + _filter_dquoted_backslash_decode(proc, 4u); + } + else if(next == 'U') // 8-digit Unicode escape (\UXXXXXXXX), full 32-bit code point + { + _filter_dquoted_backslash_decode(proc, 8u); + } + // https://yaml.org/spec/1.2.2/#rule-c-ns-esc-char + else if(next == '0') + { + proc.translate_esc('\0'); + } + else if(next == 'b') // backspace + { + proc.translate_esc('\b'); + } + else if(next == 'f') // form feed + { + proc.translate_esc('\f'); + } + else if(next == 'a') // bell character + { + proc.translate_esc('\a'); + } + else if(next == 'v') // vertical tab + { + proc.translate_esc('\v'); + } + else if(next == 'e') // escape character + { + proc.translate_esc('\x1b'); + } + else if(next == '_') // unicode non breaking space \u00a0 + { + // https://www.compart.com/en/unicode/U+00a0 + const char payload[] = { + _RYML_CHCONST(-0x3e, 0xc2), + _RYML_CHCONST(-0x60, 0xa0), + }; + proc.translate_esc_bulk(payload, /*nwrite*/2, /*nread*/1); + } + else if(next == 'N') // unicode next line \u0085 + { + // https://www.compart.com/en/unicode/U+0085 + const char payload[] = { + _RYML_CHCONST(-0x3e, 0xc2), + _RYML_CHCONST(-0x7b, 0x85), + }; + proc.translate_esc_bulk(payload, /*nwrite*/2, /*nread*/1); + } + else if(next == 'L') // unicode line separator \u2028 + { + // https://www.utf8-chartable.de/unicode-utf8-table.pl?start=8192&number=1024&names=-&utf8=0x&unicodeinhtml=hex + const char payload[] = { + _RYML_CHCONST(-0x1e, 0xe2), + _RYML_CHCONST(-0x80, 0x80), + _RYML_CHCONST(-0x58, 0xa8), + }; + proc.translate_esc_extending(payload, /*nwrite*/3, /*nread*/1); + } + else if(next == 'P') // unicode paragraph separator \u2029 + { + // https://www.utf8-chartable.de/unicode-utf8-table.pl?start=8192&number=1024&names=-&utf8=0x&unicodeinhtml=hex + const char payload[] = { + _RYML_CHCONST(-0x1e, 0xe2), + _RYML_CHCONST(-0x80, 0x80), + _RYML_CHCONST(-0x57, 0xa9), + }; + proc.translate_esc_extending(payload, /*nwrite*/3, /*nread*/1); + } + else if(next == '\0') + { + proc.skip(); + } + else + { + _c4err("unknown character '{}' after '\\' pos={}", _c4prc(next), proc.rpos); + } + _c4dbgfdq("backslash...sofar={}", _prs(proc.sofar())); +} + + +template +template +auto ParseEngine::_filter_dquoted(FilterProcessor &C4_RESTRICT proc) -> decltype(proc.result()) +{ + _c4dbgfdq("before={}", _prs(proc.src)); + // from the YAML spec for double-quoted scalars: + // https://yaml.org/spec/1.2-old/spec.html#style/flow/double-quoted + while(proc.has_more_chars()) + { + const char curr = proc.curr(); + _c4dbgfdq("'{}' sofar={}", _c4prc(curr), _prs(proc.sofar())); + switch(curr) + { + case ' ': + case '\t': + { + _c4dbgfdq("whitespace", curr); + _filter_ws_copy_trailing(proc); + break; + } + case '\n': + { + _c4dbgfdq("newline", curr); + _filter_nl_dquoted(proc); + break; + } + case '\r': // skip \r --- https://stackoverflow.com/questions/1885900 + { + _c4dbgfdq("carriage return, ignore", curr); + proc.skip(); + break; + } + case '\\': + { + _filter_dquoted_backslash(proc); + break; + } + default: + { + proc.copy(); + break; + } + } + } + _c4dbgfdq("after={}", _prs(proc.sofar())); + return proc.result(); +} + +#undef _c4dbgfdq + + +template +FilterResult ParseEngine::filter_scalar_dquoted(csubstr scalar, substr dst) +{ + FilterProcessorSrcDst proc(scalar, dst); + return _filter_dquoted(proc); +} + +template +FilterResultExtending ParseEngine::filter_scalar_dquoted_in_place(substr dst, size_t cap) +{ + FilterProcessorInplaceMidExtending proc(dst, cap); + return _filter_dquoted(proc); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// block filtering helpers + +C4_NO_INLINE inline size_t _find_last_newline_and_larger_indentation(csubstr s, size_t indentation) noexcept +{ + if(indentation + 1 > s.len) + return npos; + for(size_t i = s.len-indentation-1; i != size_t(-1); --i) + { + if(s.str[i] == '\n') + { + csubstr rem = s.sub(i + 1); + size_t first = rem.first_not_of(' '); + first = (first != npos) ? first : rem.len; + if(first > indentation) + return i; + } + } + return npos; +} + +template +template +void ParseEngine::_filter_chomp(FilterProcessor &C4_RESTRICT proc, BlockChomp_e chomp, size_t indentation) +{ + _RYML_ASSERT_PARSE_(this->callbacks(), chomp == CHOMP_CLIP || chomp == CHOMP_KEEP || chomp == CHOMP_STRIP, m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(this->callbacks(), proc.rem().first_not_of(" \n\r") == npos, m_evt_handler->m_curr->pos); + + // a debugging scaffold: + #if 0 + #define _c4dbgchomp(fmt, ...) _c4dbgpf("chomp[{}->{}]: " fmt, proc.rpos, proc.wpos, __VA_ARGS__) + #else + #define _c4dbgchomp(...) + #endif + + // advance to the last line having spaces beyond the indentation + { + size_t last = _find_last_newline_and_larger_indentation(proc.rem(), indentation); + if(last != npos) + { + _c4dbgchomp("found newline and larger indentation. last={}", last); + last = proc.rpos + last + size_t(1) + indentation; // last started at to-be-read. + _RYML_ASSERT_PARSE_(this->callbacks(), last <= proc.src.len, m_evt_handler->m_curr->pos); + // remove indentation spaces, copy the rest + while((proc.rpos < last) && proc.has_more_chars()) + { + const char curr = proc.curr(); + _c4dbgchomp("curr='{}'", _c4prc(curr)); + switch(curr) + { + case '\n': + { + _c4dbgchomp("newline! remlen={}", proc.rem().len); + proc.copy(); + // are there spaces after the newline? + csubstr at_next_line = proc.rem(); + if(at_next_line.begins_with(' ')) + { + _c4dbgchomp("next line begins with spaces. indentation={}", indentation); + // there are spaces. + size_t first_non_space = at_next_line.first_not_of(' '); + _c4dbgchomp("first_non_space={}", first_non_space); + if(first_non_space == npos) + { + _c4dbgchomp("{} spaces, to the end", at_next_line.len); + first_non_space = at_next_line.len; + } + if(first_non_space <= indentation) + { + _c4dbgchomp("skip spaces={}<=indentation={}", first_non_space, indentation); + proc.skip(first_non_space); + } + else + { + _c4dbgchomp("skip indentation={}{}]: " fmt, proc.rpos, proc.wpos, __VA_ARGS__) +#else +#define _c4dbgfb(...) +#endif + +template +template +void ParseEngine::_filter_block_indentation(FilterProcessor &C4_RESTRICT proc, size_t indentation) +{ + csubstr rem = proc.rem(); // remaining + if(rem.len) + { + size_t first = rem.first_not_of(' '); + if(first != npos) + { + _c4dbgfb("{} spaces follow before next nonws character", first); + if(first < indentation) + { + _c4dbgfb("skip {}<{} spaces from indentation", first, indentation); + proc.skip(first); + } + else + { + _c4dbgfb("skip {} spaces from indentation", indentation); + proc.skip(indentation); + } + } + #ifdef RYML_NO_COVERAGE__TO_BE_DELETED + else + { + _c4dbgfb("all spaces to the end: {} spaces", first); + first = rem.len; + if(first) + { + if(first < indentation) + { + _c4dbgfb("skip everything", first); + proc.skip(proc.src.len - proc.rpos); + } + else + { + _c4dbgfb("skip {} spaces from indentation", indentation); + proc.skip(indentation); + } + } + } + #endif + } +} + +template +template +size_t ParseEngine::_handle_all_whitespace(FilterProcessor &C4_RESTRICT proc, BlockChomp_e chomp) +{ + csubstr contents = proc.src.trimr(" \n\r"); + _c4dbgfb("ws: contents_len={} wslen={}", contents.len, proc.src.len-contents.len); + if(!contents.len) + { + _c4dbgfb("ws: all whitespace: len={}", proc.src.len); + if(chomp == CHOMP_KEEP && proc.src.len) + { + _c4dbgfb("ws: chomp=KEEP all {} newlines", proc.src.count('\n')); + while(proc.has_more_chars()) + { + const char curr = proc.curr(); + if(curr == '\n') + proc.copy(); + else + proc.skip(); + } + if(!proc.wpos) + { + proc.set('\n'); + } + } + } + return contents.len; +} + +template +template +size_t ParseEngine::_extend_to_chomp(FilterProcessor &C4_RESTRICT proc, size_t contents_len) +{ + _c4dbgfb("contents_len={}", contents_len); + + _RYML_ASSERT_PARSE_(this->callbacks(), contents_len > 0u, m_evt_handler->m_curr->pos); + + // extend contents to just before the first newline at the end, + // in case it is preceded by spaces + size_t firstnewl = proc.src.first_of('\n', contents_len); + if(firstnewl != npos) + { + contents_len = firstnewl; + _c4dbgfb("contents_len={} <--- firstnewl={}", contents_len, firstnewl); + } + else + { + contents_len = proc.src.len; + _c4dbgfb("contents_len={} <--- src.len={}", contents_len, proc.src.len); + } + + return contents_len; +} + +#undef _c4dbgfb + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +// a debugging scaffold: +#if 0 +#define _c4dbgfbl(fmt, ...) _c4dbgpf("filt_block_lit[{}->{}]: " fmt, proc.rpos, proc.wpos, __VA_ARGS__) +#else +#define _c4dbgfbl(...) +#endif + +template +template +auto ParseEngine::_filter_block_literal(FilterProcessor &C4_RESTRICT proc, size_t indentation, BlockChomp_e chomp) -> decltype(proc.result()) +{ + _c4dbgfbl("indentation={} before={}", indentation, _prs(proc.src)); + + size_t contents_len = _handle_all_whitespace(proc, chomp); + if(!contents_len) + return proc.result(); + + contents_len = _extend_to_chomp(proc, contents_len); + + _c4dbgfbl("to filter={}", _prs(proc.src.first(contents_len))); + + _filter_block_indentation(proc, indentation); + + // now filter the bulk + while(proc.has_more_chars(/*maxpos*/contents_len)) + { + const char curr = proc.curr(); + _c4dbgfbl("'{}' sofar={}", _c4prc(curr), _prs(proc.sofar())); + switch(curr) + { + case '\n': + { + _c4dbgfbl("found newline. skip indentation on the next line", curr); + proc.copy(); // copy the newline + _filter_block_indentation(proc, indentation); + break; + } + case '\r': + proc.skip(); + break; + default: + proc.copy(); + break; + } + } + + _c4dbgfbl("before chomp: #tochomp={} sofar={}", proc.rem().len, _prs(proc.sofar())); + + _filter_chomp(proc, chomp, indentation); + + _c4dbgfbl("final={}", _prs(proc.sofar())); + + return proc.result(); +} + +#undef _c4dbgfbl + +template +FilterResult ParseEngine::filter_scalar_block_literal(csubstr scalar, substr dst, size_t indentation, BlockChomp_e chomp) +{ + FilterProcessorSrcDst proc(scalar, dst); + return _filter_block_literal(proc, indentation, chomp); +} + +template +FilterResult ParseEngine::filter_scalar_block_literal_in_place(substr scalar, size_t cap, size_t indentation, BlockChomp_e chomp) +{ + FilterProcessorInplaceEndExtending proc(scalar, cap); + return _filter_block_literal(proc, indentation, chomp); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +// a debugging scaffold: +#if 0 +#define _c4dbgfbf(fmt, ...) _c4dbgpf("filt_block_folded[{}->{}]: " fmt, proc.rpos, proc.wpos, __VA_ARGS__) +#else +#define _c4dbgfbf(...) +#endif + + +template +template +void ParseEngine::_filter_block_folded_newlines_leading(FilterProcessor &C4_RESTRICT proc, size_t indentation, size_t len) +{ + _filter_block_indentation(proc, indentation); + while(proc.has_more_chars(len)) + { + const char curr = proc.curr(); + _c4dbgfbf("'{}' sofar={}", _c4prc(curr), _prs(proc.sofar())); + switch(curr) + { + case '\n': + _c4dbgfbf("newline.", curr); + proc.copy(); + _filter_block_indentation(proc, indentation); + break; + case '\r': + proc.skip(); + break; + case ' ': + case '\t': + { + size_t first = proc.rem().first_not_of(" \t"); + _c4dbgfbf("space. first={}", first); + if(first == npos) + first = proc.rem().len; + _c4dbgfbf("... indentation increased to {}", first); + _filter_block_folded_indented_block(proc, indentation, len, first); + break; + } + default: + _c4dbgfbf("newl leading: not space, not newline. stop.", 0); + return; + } + } +} + +template +template +size_t ParseEngine::_filter_block_folded_newlines_compress(FilterProcessor &C4_RESTRICT proc, size_t num_newl, size_t wpos_at_first_newl) +{ + switch(num_newl) + { + case 1u: + _c4dbgfbf("... this is the first newline. turn into space. wpos={}", proc.wpos); + wpos_at_first_newl = proc.wpos; + proc.skip(); + proc.set(' '); + break; + case 2u: + _c4dbgfbf("... this is the second newline. prev space (at wpos={}) must be newline", wpos_at_first_newl); + _RYML_ASSERT_PARSE_(this->callbacks(), wpos_at_first_newl != npos, m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(this->callbacks(), proc.sofar()[wpos_at_first_newl] == ' ', m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(this->callbacks(), wpos_at_first_newl + 1u == proc.wpos, m_evt_handler->m_curr->pos); + proc.skip(); + proc.set_at(wpos_at_first_newl, '\n'); + _RYML_ASSERT_PARSE_(this->callbacks(), proc.sofar()[wpos_at_first_newl] == '\n', m_evt_handler->m_curr->pos); + break; + default: + _c4dbgfbf("... subsequent newline (num_newl={}). copy", num_newl); + proc.copy(); + break; + } + return wpos_at_first_newl; +} + +template +template +void ParseEngine::_filter_block_folded_newlines(FilterProcessor &C4_RESTRICT proc, size_t indentation, size_t len) +{ + _RYML_ASSERT_PARSE_(this->callbacks(), proc.curr() == '\n', m_evt_handler->m_curr->pos); + size_t num_newl = 0; + size_t wpos_at_first_newl = npos; + while(proc.has_more_chars(len)) + { + const char curr = proc.curr(); + _c4dbgfbf("'{}' sofar={}", _c4prc(curr), _prs(proc.sofar())); + switch(curr) + { + case '\n': + { + _c4dbgfbf("newline. sofar={}", num_newl); + // NOTE: vs2022-32bit-release builds were giving wrong + // results in this block, if it was written as either + // as a switch(num_newl) or its equivalent if-form. + // + // For this reason, we're using a dedicated function + // (**_compress), which seems to work around the issue. + // + // The manifested problem was that somewhere between the + // assignment to curr and this point, proc.wpos (the + // write-position of the processor) jumped to npos, which + // made the write wrap-around! To make things worse, + // enabling prints via _c4dbgpf() and _c4dbgfbf() made the + // problem go away! + // + // The only way to make the problem appear with prints + // enabled was by disabling all prints in this function + // (including in the block which was moved to the compress + // function) and then selectively enabling only some of + // those prints. + // + // This may be due to some bug in the cl-x86 optimizer; or + // it may be triggered by some UB which may be + // inadvertedly present in this function or in the filter + // processor. This is despite our best efforts to weed out + // any such UB problem: neither clang-tidy nor none of the + // sanitizers, or gcc's -fanalyzer pointed to any problems + // in this code. + // + // In the end, moving this block to a separate function + // was the only way to bury the problem. But it may + // resurface again, as The Undead, rising to from the + // grave to haunt us with his terrible presence. + // + // We may have to revisit this. With a stake, and lots of + // garlic. + wpos_at_first_newl = _filter_block_folded_newlines_compress(proc, ++num_newl, wpos_at_first_newl); + _filter_block_indentation(proc, indentation); + break; + } + case ' ': + case '\t': + { + size_t first = proc.rem().first_not_of(" \t"); + _c4dbgfbf("space. first={}", first); + if(first == npos) + first = proc.rem().len; + _c4dbgfbf("... indentation increased to {}", first); + if(num_newl) + { + _c4dbgfbf("... prev space (at wpos={}) must be newline", wpos_at_first_newl); + proc.set_at(wpos_at_first_newl, '\n'); + } + if(num_newl > 1u) + { + _c4dbgfbf("... add missing newline", wpos_at_first_newl); + proc.set('\n'); + } + _filter_block_folded_indented_block(proc, indentation, len, first); + num_newl = 0; + wpos_at_first_newl = npos; + break; + } + case '\r': + proc.skip(); + break; + default: + _c4dbgfbf("not space, not newline. stop.", 0); + return; + } + } +} + + +template +template +void ParseEngine::_filter_block_folded_indented_block(FilterProcessor &C4_RESTRICT proc, size_t indentation, size_t len, size_t curr_indentation) noexcept +{ + _RYML_ASSERT_PARSE_(this->callbacks(), (proc.rem().first_not_of(" \t") == curr_indentation) || (proc.rem().first_not_of(" \t") == npos), m_evt_handler->m_curr->pos); + if(curr_indentation) + proc.copy(curr_indentation); + while(proc.has_more_chars(len)) + { + const char curr = proc.curr(); + _c4dbgfbf("'{}' sofar={}", _c4prc(curr), _prs(proc.sofar())); + switch(curr) + { + case '\n': + { + proc.copy(); + _filter_block_indentation(proc, indentation); + csubstr rem = proc.rem(); + const size_t first = rem.first_not_of(' '); + _c4dbgfbf("newline. firstns={}", first); + if(first == 0) + { + const char c = rem[first]; + _c4dbgfbf("firstns={}='{}'", first, _c4prc(c)); + if(c != '\n' && c != '\r') + { + _c4dbgfbf("done with indented block", first); + goto endloop; + } + } + else if(first != npos) + { + proc.copy(first); + _c4dbgfbf("copy all {} spaces", first); + } + break; + } + break; + case '\r': + proc.skip(); + break; + default: + proc.copy(); + break; + } + } + endloop: + return; +} + + +template +template +auto ParseEngine::_filter_block_folded(FilterProcessor &C4_RESTRICT proc, size_t indentation, BlockChomp_e chomp) -> decltype(proc.result()) +{ + _c4dbgfbf("indentation={} before={}", indentation, _prs(proc.src)); + + size_t contents_len = _handle_all_whitespace(proc, chomp); + if(!contents_len) + return proc.result(); + + contents_len = _extend_to_chomp(proc, contents_len); + + _c4dbgfbf("to filter={}", _prs(proc.src.first(contents_len))); + + _filter_block_folded_newlines_leading(proc, indentation, contents_len); + + // now filter the bulk + while(proc.has_more_chars(/*maxpos*/contents_len)) + { + const char curr = proc.curr(); + _c4dbgfbf("'{}' sofar={}", _c4prc(curr), _prs(proc.sofar())); + switch(curr) + { + case '\n': + { + _c4dbgfbf("found newline", curr); + _filter_block_folded_newlines(proc, indentation, contents_len); + break; + } + case '\r': + proc.skip(); + break; + default: + proc.copy(); + break; + } + } + + _c4dbgfbf("before chomp: #tochomp={} sofar={}", proc.rem().len, _prs(proc.sofar())); + + _filter_chomp(proc, chomp, indentation); + + _c4dbgfbf("final={}", proc.sofar().len, _prs(proc.sofar())); + + return proc.result(); +} + +#undef _c4dbgfbf + +template +FilterResult ParseEngine::filter_scalar_block_folded(csubstr scalar, substr dst, size_t indentation, BlockChomp_e chomp) +{ + FilterProcessorSrcDst proc(scalar, dst); + return _filter_block_folded(proc, indentation, chomp); +} + +template +FilterResult ParseEngine::filter_scalar_block_folded_in_place(substr scalar, size_t cap, size_t indentation, BlockChomp_e chomp) +{ + FilterProcessorInplaceEndExtending proc(scalar, cap); + return _filter_block_folded(proc, indentation, chomp); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +template +csubstr ParseEngine::_filter_scalar_plain(substr s, size_t indentation) +{ + _c4dbgpf("filtering plain scalar: s={}", _prs(s)); + FilterResult r = this->filter_scalar_plain_in_place(s, s.len, indentation); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, r.valid(), m_evt_handler->m_curr->pos); + _c4dbgpf("filtering plain scalar: success! s={}", _prs(r.get())); + return r.get(); +} + +//----------------------------------------------------------------------------- + +template +csubstr ParseEngine::_filter_scalar_squot(substr s) +{ + _c4dbgpf("filtering squo scalar: s={}", _prs(s)); + FilterResult r = this->filter_scalar_squoted_in_place(s, s.len); + _RYML_ASSERT_PARSE_(this->callbacks(), r.valid(), m_evt_handler->m_curr->pos); + _c4dbgpf("filtering squo scalar: success! s={}", _prs(r.get())); + return r.get(); +} + + +//----------------------------------------------------------------------------- + +template +csubstr ParseEngine::_filter_scalar_dquot(substr s) +{ + _c4dbgpf("filtering dquo scalar: s={}", _prs(s)); + FilterResultExtending r = this->filter_scalar_dquoted_in_place(s, s.len); + if(C4_LIKELY(r.valid())) + { + _c4dbgpf("filtering dquo scalar: success! s={}", _prs(r.get())); + return r.get(); + } + else + { + const size_t len = r.required_len(); + _c4dbgpf("filtering dquo scalar: not enough space: needs {}, have {}", len, s.len); + substr dst = _alloc_arena(len, &s); + _c4dbgpf("filtering dquo scalar: dst.len={}", dst.len); + if(dst.str) + { + _RYML_ASSERT_PARSE_(this->callbacks(), dst.len == len, m_evt_handler->m_curr->pos); + FilterResult rsd = this->filter_scalar_dquoted(s, dst); + _c4dbgpf("filtering dquo scalar: ... result now needs {} was {}", rsd.required_len(), len); + _RYML_ASSERT_PARSE_(this->callbacks(), rsd.required_len() <= len, m_evt_handler->m_curr->pos); // may be smaller! + _RYML_CHECK_PARSE_(m_evt_handler->m_stack.m_callbacks, rsd.valid(), m_evt_handler->m_curr->pos); + _c4dbgpf("filtering dquo scalar: success! s={}", _prs(rsd.get())); + return rsd.get(); + } + return dst; + } +} + + +//----------------------------------------------------------------------------- + +template +csubstr ParseEngine::_move_scalar_left_and_add_newline(substr s) +{ + if(s.is_sub(_buf())) + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, s.str > _buf().str, m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, s.str-1 >= _buf().str, m_evt_handler->m_curr->pos); + if(s.len) + memmove(s.str - 1, s.str, s.len); + --s.str; + s.str[s.len] = '\n'; + ++s.len; + return s; + } + else + { + substr dst = _alloc_arena(s.len + 1, &s); + if(s.len) + memcpy(dst.str, s.str, s.len); + dst[s.len] = '\n'; + return dst; + } +} + +template +csubstr ParseEngine::_filter_scalar_literal(substr s, size_t indentation, BlockChomp_e chomp) +{ + _c4dbgpf("filtering block literal scalar: s={}", _prs(s)); + FilterResult r = this->filter_scalar_block_literal_in_place(s, s.len, indentation, chomp); + csubstr result; + if(C4_LIKELY(r.valid())) + { + result = r.get(); + } + else + { + _c4dbgpf("filtering block literal scalar: not enough space: needs {}, have {}", r.required_len(), s.len); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, r.required_len() == s.len + 1, m_evt_handler->m_curr->pos); + // this can only happen when adding a single newline in clip mode. + // so we shift left the scalar by one place + result = _move_scalar_left_and_add_newline(s); + } + _c4dbgpf("filtering block literal scalar: success! s={}", _prs(result)); + return result; +} + + +//----------------------------------------------------------------------------- +template +csubstr ParseEngine::_filter_scalar_folded(substr s, size_t indentation, BlockChomp_e chomp) +{ + _c4dbgpf("filtering block folded scalar: s={}", _prs(s)); + FilterResult r = this->filter_scalar_block_folded_in_place(s, s.len, indentation, chomp); + csubstr result; + if(C4_LIKELY(r.valid())) + { + result = r.get(); + } + else + { + _c4dbgpf("filtering block folded scalar: not enough space: needs {}, have {}", r.required_len(), s.len); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, r.required_len() == s.len + 1, m_evt_handler->m_curr->pos); + // this can only happen when adding a single newline in clip mode. + // so we shift left the scalar by one place + result = _move_scalar_left_and_add_newline(s); + } + _c4dbgpf("filtering block folded scalar: success! s={}", _prs(result)); + return result; +} + + +//----------------------------------------------------------------------------- + +template +csubstr ParseEngine::_maybe_filter_key_scalar_plain(ScannedScalar const& C4_RESTRICT sc, size_t indentation) +{ + if(sc.needs_filter) + { + if(m_options.scalar_filtering()) + { + return _filter_scalar_plain(sc.scalar, indentation); + } + else + { + _c4dbgp("plain scalar left unfiltered"); + m_evt_handler->mark_key_scalar_unfiltered(); + } + } + else + { + _c4dbgp("plain scalar doesn't need filtering"); + } + return sc.scalar; +} + +template +csubstr ParseEngine::_maybe_filter_val_scalar_plain(ScannedScalar const& C4_RESTRICT sc, size_t indentation) +{ + if(sc.needs_filter) + { + if(m_options.scalar_filtering()) + { + return _filter_scalar_plain(sc.scalar, indentation); + } + else + { + _c4dbgp("plain scalar left unfiltered"); + m_evt_handler->mark_val_scalar_unfiltered(); + } + } + else + { + _c4dbgp("plain scalar doesn't need filtering"); + } + return sc.scalar; +} + + +//----------------------------------------------------------------------------- + +template +csubstr ParseEngine::_maybe_filter_key_scalar_squot(ScannedScalar const& C4_RESTRICT sc) +{ + if(sc.needs_filter) + { + if(m_options.scalar_filtering()) + { + return _filter_scalar_squot(sc.scalar); + } + else + { + _c4dbgp("squo key scalar left unfiltered"); + m_evt_handler->mark_key_scalar_unfiltered(); + } + } + else + { + _c4dbgp("squo key scalar doesn't need filtering"); + } + return sc.scalar; +} + +template +csubstr ParseEngine::_maybe_filter_val_scalar_squot(ScannedScalar const& C4_RESTRICT sc) +{ + if(sc.needs_filter) + { + if(m_options.scalar_filtering()) + { + return _filter_scalar_squot(sc.scalar); + } + else + { + _c4dbgp("squo val scalar left unfiltered"); + m_evt_handler->mark_val_scalar_unfiltered(); + } + } + else + { + _c4dbgp("squo val scalar doesn't need filtering"); + } + return sc.scalar; +} + + +//----------------------------------------------------------------------------- + +template +csubstr ParseEngine::_maybe_filter_key_scalar_dquot(ScannedScalar const& C4_RESTRICT sc) +{ + if(sc.needs_filter) + { + if(m_options.scalar_filtering()) + { + return _filter_scalar_dquot(sc.scalar); + } + else + { + _c4dbgp("dquo scalar left unfiltered"); + m_evt_handler->mark_key_scalar_unfiltered(); + } + } + else + { + _c4dbgp("dquo scalar doesn't need filtering"); + } + return sc.scalar; +} + +template +csubstr ParseEngine::_maybe_filter_val_scalar_dquot(ScannedScalar const& C4_RESTRICT sc) +{ + if(sc.needs_filter) + { + if(m_options.scalar_filtering()) + { + return _filter_scalar_dquot(sc.scalar); + } + else + { + _c4dbgp("dquo scalar left unfiltered"); + m_evt_handler->mark_val_scalar_unfiltered(); + } + } + else + { + _c4dbgp("dquo scalar doesn't need filtering"); + } + return sc.scalar; +} + + +//----------------------------------------------------------------------------- + +template +csubstr ParseEngine::_maybe_filter_key_scalar_literal(ScannedBlock const& C4_RESTRICT sb) +{ + if(m_options.scalar_filtering()) + { + return _filter_scalar_literal(sb.scalar, sb.indentation, sb.chomp); + } + else + { + _c4dbgp("literal scalar left unfiltered"); + m_evt_handler->mark_key_scalar_unfiltered(); + } + return sb.scalar; +} + +template +csubstr ParseEngine::_maybe_filter_val_scalar_literal(ScannedBlock const& C4_RESTRICT sb) +{ + if(m_options.scalar_filtering()) + { + return _filter_scalar_literal(sb.scalar, sb.indentation, sb.chomp); + } + else + { + _c4dbgp("literal scalar left unfiltered"); + m_evt_handler->mark_val_scalar_unfiltered(); + } + return sb.scalar; +} + + +//----------------------------------------------------------------------------- + +template +csubstr ParseEngine::_maybe_filter_key_scalar_folded(ScannedBlock const& C4_RESTRICT sb) +{ + if(m_options.scalar_filtering()) + { + return _filter_scalar_folded(sb.scalar, sb.indentation, sb.chomp); + } + else + { + _c4dbgp("folded scalar left unfiltered"); + m_evt_handler->mark_key_scalar_unfiltered(); + } + return sb.scalar; +} + +template +csubstr ParseEngine::_maybe_filter_val_scalar_folded(ScannedBlock const& C4_RESTRICT sb) +{ + if(m_options.scalar_filtering()) + { + return _filter_scalar_folded(sb.scalar, sb.indentation, sb.chomp); + } + else + { + _c4dbgp("folded scalar left unfiltered"); + m_evt_handler->mark_val_scalar_unfiltered(); + } + return sb.scalar; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +#ifdef RYML_DBG // !!! <---------------------------------- + +template +void ParseEngine::add_flags(ParserFlag_t on) +{ + ParserState *s = m_evt_handler->m_curr; + char buf1_[64], buf2_[64], buf3_[64]; + csubstr buf1 = detail::_parser_flags_to_str(buf1_, on); + csubstr buf2 = detail::_parser_flags_to_str(buf2_, s->flags); + csubstr buf3 = detail::_parser_flags_to_str(buf3_, s->flags|on); + _c4dbgpf("state[{}]: add {}: before={} after={}", s->level, buf1, buf2, buf3); + s->flags |= on; +} + +template +void ParseEngine::addrem_flags(ParserFlag_t on, ParserFlag_t off) +{ + ParserState *s = m_evt_handler->m_curr; + char buf1_[64], buf2_[64], buf3_[64], buf4_[64]; + csubstr buf1 = detail::_parser_flags_to_str(buf1_, on); + csubstr buf2 = detail::_parser_flags_to_str(buf2_, off); + csubstr buf3 = detail::_parser_flags_to_str(buf3_, s->flags); + csubstr buf4 = detail::_parser_flags_to_str(buf4_, (~off)&((s->flags|on))); + _c4dbgpf("state[{}]: add {} / rem {}: before={} after={}", s->level, buf1, buf2, buf3, buf4); + _RYML_ASSERT_BASIC((on & off) == ParserFlag_t(0)); + s->flags &= ~off; + s->flags |= on; +} + +template +void ParseEngine::rem_flags(ParserFlag_t off) +{ + ParserState *s = m_evt_handler->m_curr; + char buf1_[64], buf2_[64], buf3_[64]; + csubstr buf1 = detail::_parser_flags_to_str(buf1_, off); + csubstr buf2 = detail::_parser_flags_to_str(buf2_, s->flags); + csubstr buf3 = detail::_parser_flags_to_str(buf3_, s->flags&(~off)); + _c4dbgpf("state[{}]: rem {}: before={} after={}", s->level, buf1, buf2, buf3); + s->flags &= ~off; +} + +inline C4_NO_INLINE csubstr detail::_parser_flags_to_str(substr buf, ParserFlag_t flags) +{ + size_t pos = 0; + bool gotone = false; + + #define _prflag(fl) \ + if((flags & fl) == (fl)) \ + { \ + if(gotone) \ + { \ + if(pos + 1 < buf.len) \ + buf[pos] = '|'; \ + ++pos; \ + } \ + csubstr fltxt = #fl; \ + if(pos + fltxt.len <= buf.len) \ + memcpy(buf.str + pos, fltxt.str, fltxt.len); \ + pos += fltxt.len; \ + gotone = true; \ + } + + _prflag(RTOP); + _prflag(RUNK); + _prflag(RMAP); + _prflag(RSEQ); + _prflag(RFLOW); + _prflag(RBLCK); + _prflag(QMRK); + _prflag(RKEY); + _prflag(RVAL); + _prflag(RKCL); + _prflag(RNXT); + _prflag(SSCL); + _prflag(QSCL); + _prflag(RSET); + _prflag(RDOC); + _prflag(NDOC); + _prflag(USTY); + _prflag(RSEQIMAP); + + #undef _prflag + + if(pos == 0) + if(buf.len > 0) + buf[pos++] = '0'; + + _RYML_CHECK_BASIC(pos <= buf.len); + + return buf.first(pos); +} + +#endif // RYML_DBG !!! <---------------------------------- + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +template +csubstr ParseEngine::location_contents(Location const& loc) const +{ + _RYML_ASSERT_BASIC_(m_evt_handler->m_stack.m_callbacks, loc.offset < _buf().len); + return _buf().sub(loc.offset); +} + +template +Location ParseEngine::val_location(const char *val) const +{ + if(C4_UNLIKELY(val == nullptr)) + return {m_evt_handler->m_curr->pos.name, 0, 0, 0}; + _RYML_CHECK_BASIC_(m_evt_handler->m_stack.m_callbacks, m_options.locations()); + // NOTE: if any of these checks fails, the parser needs to be + // instantiated with locations enabled. + _RYML_ASSERT_BASIC_(m_evt_handler->m_stack.m_callbacks, m_options.locations()); + _RYML_ASSERT_BASIC_(m_evt_handler->m_stack.m_callbacks, !_locations_dirty()); + _RYML_ASSERT_BASIC_(m_evt_handler->m_stack.m_callbacks, m_newline_offsets != nullptr); + _RYML_ASSERT_BASIC_(m_evt_handler->m_stack.m_callbacks, m_newline_offsets_size > 0); + // NOTE: the pointer needs to belong to the buffer that was used to parse. + csubstr src = _buf(); + _RYML_CHECK_BASIC_(m_evt_handler->m_stack.m_callbacks, val != nullptr || src.str == nullptr); + _RYML_CHECK_BASIC_(m_evt_handler->m_stack.m_callbacks, (val >= src.begin() && val <= src.end()) || (src.str == nullptr && val == nullptr)); + // ok. search the first stored newline after the given ptr + using lineptr_type = size_t const* C4_RESTRICT; + lineptr_type lineptr = nullptr; + size_t offset = (size_t)(val - src.begin()); + if(m_newline_offsets_size < RYML_LOCATIONS_SMALL_THRESHOLD) + { + // just do a linear search if the size is small. + for(lineptr_type curr = m_newline_offsets, last = m_newline_offsets + m_newline_offsets_size; curr < last; ++curr) + { + if(*curr > offset) + { + lineptr = curr; + break; + } + } + } + else + { + // do a bisection search if the size is not small. + // + // We could use std::lower_bound but this is simple enough and + // spares the costly include of . + size_t count = m_newline_offsets_size; + lineptr = m_newline_offsets; + while(count) + { + size_t step = count >> 1; + lineptr_type it = lineptr + step; + if(*it < offset) + { + lineptr = ++it; + count -= step + 1; + } + else + { + count = step; + } + } + } + _RYML_ASSERT_BASIC_(m_evt_handler->m_stack.m_callbacks, lineptr >= m_newline_offsets); + _RYML_ASSERT_BASIC_(m_evt_handler->m_stack.m_callbacks, lineptr <= m_newline_offsets + m_newline_offsets_size); + _RYML_ASSERT_BASIC_(m_evt_handler->m_stack.m_callbacks, *lineptr > offset); + Location loc; + loc.name = m_evt_handler->m_curr->pos.name; + loc.offset = offset; + loc.line = (size_t)(lineptr - m_newline_offsets); + if(lineptr > m_newline_offsets) + loc.col = (offset - *(lineptr-1) - 1u); + else + loc.col = offset; + return loc; +} + +template +void ParseEngine::_prepare_locations() +{ + csubstr src = _buf(); + size_t numnewlines = 1u + src.count('\n'); + _resize_locations(numnewlines); + m_newline_offsets_size = 0; + for(size_t i = 0; i < src.len; i++) + if(src.str[i] == '\n') + m_newline_offsets[m_newline_offsets_size++] = i; // NOLINT + m_newline_offsets[m_newline_offsets_size++] = src.len; // NOLINT + _RYML_ASSERT_BASIC_(m_evt_handler->m_stack.m_callbacks, m_newline_offsets_size == numnewlines); +} + +template +void ParseEngine::_resize_locations(size_t numnewlines) +{ + numnewlines = numnewlines >= 16 ? numnewlines : 16; + if(numnewlines > m_newline_offsets_capacity) + { + if(m_newline_offsets) + _RYML_CB_FREE(m_evt_handler->m_stack.m_callbacks, m_newline_offsets, size_t, m_newline_offsets_capacity); + m_newline_offsets = _RYML_CB_ALLOC_HINT(m_evt_handler->m_stack.m_callbacks, size_t, numnewlines, m_newline_offsets); + m_newline_offsets_capacity = numnewlines; + } +} + +template +bool ParseEngine::_locations_dirty() const +{ + return !m_newline_offsets_size; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +template +void ParseEngine::_handle_flow_skip_whitespace() +{ + // don't assign to csubstr rem: otherwise, gcc12,13,14 -O3 -m32 misbuilds + if(m_evt_handler->m_curr->line_contents.rem.len > 0) + { + if(m_evt_handler->m_curr->line_contents.rem.str[0] == ' ' || m_evt_handler->m_curr->line_contents.rem.str[0] == '\t') + { + _c4dbgpf("starts with whitespace: '{}'", _c4prc(m_evt_handler->m_curr->line_contents.rem.str[0])); + _skipchars(" \t"); + } + // comments + if(m_evt_handler->m_curr->line_contents.rem.begins_with('#')) + { + _c4dbgpf("it's a comment: {}", m_evt_handler->m_curr->line_contents.rem); + _line_progressed(m_evt_handler->m_curr->line_contents.rem.len); + } + } +} + + +template +void ParseEngine::_handle_flow_line_beginning() +{ + _c4dbgpf("flow: indref={} indentation={}", m_evt_handler->m_curr->indref, m_evt_handler->m_curr->line_contents.indentation); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->at_line_beginning(), m_evt_handler->m_curr->pos); + if(C4_UNLIKELY(m_evt_handler->m_curr->indentation_lt())) + { + csubstr trimmed = m_evt_handler->m_curr->line_contents.rem.sub(m_evt_handler->m_curr->line_contents.indentation); + _c4dbgpf("flow: after indentation={}", _prs(trimmed)); + if(trimmed.len && trimmed.triml(" \t").len) + { + _line_progressed(m_evt_handler->m_curr->line_contents.indentation); + _c4err("bad indentation"); + } + } +} + +template +size_t ParseEngine::_handle_block_skip_leading_whitespace() +{ + const size_t mark = m_evt_handler->m_curr->pos.offset; + const size_t firstpos = m_evt_handler->m_curr->line_contents.rem.first_not_of(" \t"); + _c4dbgpf("block: mark={} firstpos={}", mark, firstpos); + if(firstpos != npos) + { + _c4dbgp("block: non empty line"); + _line_progressed(firstpos); + return mark; + } + else + { + _c4dbgp("block: rest of line is whitespace"); + _line_progressed(m_evt_handler->m_curr->line_contents.rem.len); + return npos; + } +} + +template +void ParseEngine::_handle_block_check_leading_tabs(size_t start_mark, size_t end_mark) +{ + _c4dbgpf("block: start_mark={} end_mark={}", start_mark, end_mark); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, end_mark >= start_mark, m_evt_handler->m_curr->pos); + if(end_mark != start_mark) + { + csubstr leading = _buf().range(start_mark, end_mark); + _c4dbgpf("block: leading[{}-{}]={}", start_mark, end_mark, _prs(leading, true)); + size_t pos = leading.find('\t'); + if(pos != npos) + { + size_t fno = leading.first_not_of(" \t"); + if(fno == npos || pos < fno) + _c4err("invalid tab character to the left"); + } + (void)leading; + } +} + + +//----------------------------------------------------------------------------- + + +template +void ParseEngine::_handle_colon() +{ + size_t curr = m_evt_handler->m_curr->pos.line; + if(C4_UNLIKELY(m_prev_colon != npos && curr == m_prev_colon)) + { + _c4dbgpf("colon: prevline={} currline={}", m_prev_colon, curr); + _c4err("two colons on same line"); + } + _c4dbgpf("colon: set prevline={}->{}", m_prev_colon, curr); + m_prev_colon = curr; +} + +template +void ParseEngine::_add_annotation(Annotation *C4_RESTRICT dst, csubstr str) +{ + _c4dbgpf("store annotation[{}]: {}", dst->num_entries, _prs(str)); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, dst->num_entries < C4_COUNTOF(dst->annotations), m_evt_handler->m_curr->pos); // NOLINT(bugprone-sizeof-expression) + dst->annotations[dst->num_entries].str = str; + dst->annotations[dst->num_entries].indentation = {}; + dst->annotations[dst->num_entries].line = {}; + dst->annotations[dst->num_entries].orig = {}; + ++dst->num_entries; +} + +template +void ParseEngine::_add_annotation(Annotation *C4_RESTRICT dst, csubstr str, size_t indentation, size_t line) +{ + _c4dbgpf("store annotation[{}]: '{}' indentation={} line={}", dst->num_entries, _maybe_null_str(str), indentation, line); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, dst->num_entries < C4_COUNTOF(dst->annotations), m_evt_handler->m_curr->pos); // NOLINT(bugprone-sizeof-expression) + if(C4_UNLIKELY(dst->num_entries && dst->annotations[0].line == line)) + { + _c4err("parse error"); + } + dst->annotations[dst->num_entries].str = str; + dst->annotations[dst->num_entries].indentation = indentation; + dst->annotations[dst->num_entries].line = line; + dst->annotations[dst->num_entries].orig = {}; + ++dst->num_entries; +} + +template +void ParseEngine::_add_annotation(Annotation *C4_RESTRICT dst, csubstr str, size_t indentation, size_t line, csubstr orig) +{ + _c4dbgpf("store annotation[{}]: '{}'->'{}' indentation={} line={}", dst->num_entries, orig, _maybe_null_str(str), indentation, line); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, dst->num_entries < C4_COUNTOF(dst->annotations), m_evt_handler->m_curr->pos); // NOLINT(bugprone-sizeof-expression) + if(C4_UNLIKELY(dst->num_entries && dst->annotations[0].line == line)) + { + _c4err("parse error"); + } + dst->annotations[dst->num_entries].str = str; + dst->annotations[dst->num_entries].indentation = indentation; + dst->annotations[dst->num_entries].line = line; + dst->annotations[dst->num_entries].orig = orig; + ++dst->num_entries; +} + +template +bool ParseEngine::_annotations_require_key_container() const +{ + return m_pending_tags.num_entries > 1 || m_pending_anchors.num_entries > 1; +} + +template +bool ParseEngine::_handle_annotations_before_unexpected_flow_token_rkey() +{ + if(!(m_pending_tags.num_entries | m_pending_anchors.num_entries)) + return false; + _c4dbgpf("handle_annotations_before_unexpected_flow_comma_rkey, node={}", m_evt_handler->m_curr->node_id); + if(m_pending_tags.num_entries) + { + _c4dbgpf("handle_annotations_before_unexpected_flow_comma_rkey, #tags={}", m_pending_tags.num_entries); + if(C4_LIKELY(m_pending_tags.num_entries == 1)) + { + m_evt_handler->set_key_tag(m_pending_tags.annotations[0].str); + _clear_annotations(&m_pending_tags); + } + else + { + _c4err("too many tags"); + } + } + if(m_pending_anchors.num_entries) + { + _c4dbgpf("handle_annotations_before_unexpected_flow_comma, #anchors={}", m_pending_tags.num_entries); + if(C4_LIKELY(m_pending_anchors.num_entries == 1)) + { + m_evt_handler->set_key_anchor(m_pending_anchors.annotations[0].str); + _clear_annotations(&m_pending_anchors); + } + else + { + _c4err("too many anchors"); + } + } + m_evt_handler->set_key_scalar_plain_empty(); + m_evt_handler->set_val_scalar_plain_empty(); + return true; +} + +template +void ParseEngine::_handle_annotations_before_blck_key_scalar() +{ + _c4dbgpf("annotations_before_blck_key_scalar, node={}", m_evt_handler->m_curr->node_id); + if(m_pending_tags.num_entries) + { + _c4dbgpf("annotations_before_blck_key_scalar, #tags={}", m_pending_tags.num_entries); + if(C4_LIKELY(m_pending_tags.num_entries == 1)) + { + m_evt_handler->set_key_tag(m_pending_tags.annotations[0].str); + _clear_annotations(&m_pending_tags); + } + else + { + _c4err("too many tags"); // LCOV_EXCL_LINE + } + } + if(m_pending_anchors.num_entries) + { + _c4dbgpf("annotations_before_blck_key_scalar, #anchors={}", m_pending_anchors.num_entries); + if(C4_LIKELY(m_pending_anchors.num_entries == 1)) + { + m_evt_handler->set_key_anchor(m_pending_anchors.annotations[0].str); + _clear_annotations(&m_pending_anchors); + } + else + { + _c4err("too many anchors"); // LCOV_EXCL_LINE + } + } +} + +template +void ParseEngine::_handle_annotations_before_blck_val_scalar() +{ + _c4dbgpf("annotations_before_blck_val_scalar, node={}", m_evt_handler->m_curr->node_id); + if(m_pending_tags.num_entries) + { + _c4dbgpf("annotations_before_blck_val_scalar, #tags={}", m_pending_tags.num_entries); + if(C4_LIKELY(m_pending_tags.num_entries == 1)) + { + m_evt_handler->set_val_tag(m_pending_tags.annotations[0].str); + _clear_annotations(&m_pending_tags); + } + else + { + _c4err("too many tags"); + } + } + if(m_pending_anchors.num_entries) + { + _c4dbgpf("annotations_before_blck_val_scalar, #anchors={}", m_pending_anchors.num_entries); + if(C4_LIKELY(m_pending_anchors.num_entries == 1)) + { + m_evt_handler->set_val_anchor(m_pending_anchors.annotations[0].str); + _clear_annotations(&m_pending_anchors); + } + else + { + _c4err("too many anchors"); + } + } +} + +template +void ParseEngine::_handle_annotations_before_start_mapblck(size_t current_line) +{ + _c4dbgpf("annotations_before_start_mapblck, current_line={}", current_line); + if(m_pending_tags.num_entries == 2) + { + _c4dbgp("2 tags, setting entry 0"); + m_evt_handler->set_val_tag(m_pending_tags.annotations[0].str); + } + else if(m_pending_tags.num_entries == 1) + { + _c4dbgpf("1 tag. line={}, curr={}", m_pending_tags.annotations[0].line, current_line); + if(m_pending_tags.annotations[0].line < current_line) + { + _c4dbgp("...tag is for the map. setting it."); + m_evt_handler->set_val_tag(m_pending_tags.annotations[0].str); + _clear_annotations(&m_pending_tags); + } + } + // + if(m_pending_anchors.num_entries == 2) + { + _c4dbgp("2 anchors, setting entry 0"); + m_evt_handler->set_val_anchor(m_pending_anchors.annotations[0].str); + } + else if(m_pending_anchors.num_entries == 1) + { + _c4dbgpf("1 anchor. line={}, curr={}", m_pending_anchors.annotations[0].line, current_line); + if(m_pending_anchors.annotations[0].line < current_line) + { + _c4dbgp("...anchor is for the map. setting it."); + m_evt_handler->set_val_anchor(m_pending_anchors.annotations[0].str); + _clear_annotations(&m_pending_anchors); + } + } +} + +template +void ParseEngine::_handle_annotations_before_start_mapblck_as_key() +{ + _c4dbgp("annotations_before_start_mapblck_as_key"); + switch(m_pending_tags.num_entries) + { + case 1u: + _c4dbgpf("annotations_after_start_mapblck_as_key: 1 tag={} line={} currline=", _prs(m_pending_tags.annotations[0].str), m_pending_tags.annotations[0].line, m_evt_handler->m_curr->pos.line); + if(m_pending_tags.annotations[0].line != m_evt_handler->m_curr->pos.line) + { + _c4dbgp("annotations_after_start_mapblck_as_key: is map tag"); + m_evt_handler->set_key_tag(m_pending_tags.annotations[0].str); + _clear_annotations(&m_pending_tags); + } + break; + case 2u: + _c4dbgpf("annotations_after_start_mapblck_as_key: 2 tags: {} -> {}", _prs(m_pending_tags.annotations[0].str), _prs(m_pending_tags.annotations[1].str)); + m_evt_handler->set_key_tag(m_pending_tags.annotations[0].str); + break; + } + switch(m_pending_anchors.num_entries) + { + case 1u: + _c4dbgpf("annotations_after_start_mapblck_as_key: 1 anchor={} line={} currline=", m_pending_anchors.annotations[0].str, m_pending_anchors.annotations[0].line, m_evt_handler->m_curr->pos.line); + if(m_pending_anchors.annotations[0].line != m_evt_handler->m_curr->pos.line) + { + _c4dbgp("annotations_after_start_mapblck_as_key: is map anchor"); + m_evt_handler->set_key_anchor(m_pending_anchors.annotations[0].str); + _clear_annotations(&m_pending_anchors); + } + break; + case 2u: + _c4dbgpf("annotations_after_start_mapblck_as_key: 2 anchors: {} -> {}", m_pending_anchors.annotations[0].str, m_pending_anchors.annotations[1].str); + m_evt_handler->set_key_anchor(m_pending_anchors.annotations[0].str); + break; + } +} + +template +void ParseEngine::_handle_annotations_and_indentation_after_start_mapblck(size_t key_indentation, size_t key_line) +{ + _c4dbgp("annotations_after_start_mapblck"); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, m_pending_tags.num_entries <= 2, m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, m_pending_anchors.num_entries <= 2, m_evt_handler->m_curr->pos); + if(m_pending_anchors.num_entries || m_pending_tags.num_entries) + { + key_indentation = _select_indentation_from_annotations(key_indentation, key_line); + switch(m_pending_tags.num_entries) + { + case 1u: + _c4dbgpf("annotations_after_start_mapblck: 1 tag: {}", _prs(m_pending_tags.annotations[0].str)); + m_evt_handler->set_key_tag(m_pending_tags.annotations[0].str); + _clear_annotations(&m_pending_tags); + break; + case 2u: + _c4dbgpf("annotations_after_start_mapblck: 2 tags: {} -> {}", _prs(m_pending_tags.annotations[0].str), _prs(m_pending_tags.annotations[1].str)); + m_evt_handler->set_key_tag(m_pending_tags.annotations[1].str); + _clear_annotations(&m_pending_tags); + break; + } + switch(m_pending_anchors.num_entries) + { + case 1u: + _c4dbgpf("annotations_after_start_mapblck: 1 anchors: {} -> {}", m_pending_anchors.annotations[0].str); + m_evt_handler->set_key_anchor(m_pending_anchors.annotations[0].str); + _clear_annotations(&m_pending_anchors); + break; + case 2u: + _c4dbgpf("annotations_after_start_mapblck: 2 anchors: {} -> {}", m_pending_anchors.annotations[0].str, m_pending_anchors.annotations[1].str); + m_evt_handler->set_key_anchor(m_pending_anchors.annotations[1].str); + _clear_annotations(&m_pending_anchors); + break; + } + } + _set_indentation(key_indentation); +} + +template +size_t ParseEngine::_select_indentation_from_annotations(size_t val_indentation, size_t val_line) +{ + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, m_pending_tags.num_entries | m_pending_anchors.num_entries, m_evt_handler->m_curr->pos); + // select the left-most annotation on the max line + auto const *C4_RESTRICT curr = m_pending_anchors.num_entries ? &m_pending_anchors.annotations[0] : &m_pending_tags.annotations[0]; + for(size_t i = 0; i < m_pending_anchors.num_entries; ++i) + { + auto const& C4_RESTRICT ann = m_pending_anchors.annotations[i]; + if(ann.line > curr->line) + curr = &ann; + else if(ann.indentation < curr->indentation) + curr = &ann; + } + for(size_t j = 0; j < m_pending_tags.num_entries; ++j) + { + auto const& C4_RESTRICT ann = m_pending_tags.annotations[j]; + if(ann.line > curr->line) + curr = &ann; + else if(ann.indentation < curr->indentation) + curr = &ann; + } + return curr->line < val_line ? val_indentation : curr->indentation; +} + +template +void ParseEngine::_handle_keyref(csubstr alias) +{ + if(C4_LIKELY(!(m_pending_anchors.num_entries | m_pending_tags.num_entries))) + m_evt_handler->set_key_ref(alias); + else + _c4err("aliases cannot have anchors or tags"); +} + +template +void ParseEngine::_handle_valref(csubstr alias) +{ + if(C4_LIKELY(!(m_pending_anchors.num_entries | m_pending_tags.num_entries))) + m_evt_handler->set_val_ref(alias); + else + _c4err("aliases cannot have anchors or tags"); +} + +template +csubstr ParseEngine::_resolve_tag(csubstr tag) +{ + _c4dbgpf("resolving tag: {} curr_doc={}", _prs(tag), m_evt_handler->m_curr_doc); + _c4assert(tag.is_sub(_buf())); + TagCache::LookupResult ret = m_evt_handler->tag_cache().find(tag, m_evt_handler->m_curr_doc); + if(ret) + { + _c4dbgpf("resolving tag: found in cache[{}]: {}", ret.pos, _prs(ret.resolved)); + return ret.resolved; + } + _c4dbgpf("resolving tag: not in cache: {} curr_doc={}", _prs(tag), m_evt_handler->m_curr_doc); + size_t bufsz = 0; + substr buf = m_evt_handler->arena_rem(); + TagDirectives const& C4_RESTRICT tds = m_evt_handler->tag_directives(); + csubstr ttag = tds.resolve(buf, &bufsz, tag, m_evt_handler->m_curr_doc, + m_evt_handler->m_curr->pos, + m_evt_handler->m_stack.m_callbacks); + _c4dbgpf("resolving tag: bufsz={} ttag.len={} !!ttag.str={}", bufsz, ttag.len, !!ttag.str); + _c4assert((bufsz > buf.len) == (!ttag.str)); + _c4assert(!!bufsz == (ttag.len == bufsz)); + // try again if the arena size was not enough + if(!ttag.str) + { + _c4dbgpf("tag requires arena, but it was small. arena.len={} arena.slack={} tag.required={}", m_evt_handler->arena_rem().len, m_evt_handler->arena().len, ttag.len); + _c4assert(ttag.len == bufsz); + buf = _alloc_arena(bufsz, &tag); + if(buf.str) // the alloc may fail eg with the ints handler + { + ttag = tds.resolve(buf, &bufsz, tag, m_evt_handler->m_curr_doc, + m_evt_handler->m_curr->pos, + m_evt_handler->m_stack.m_callbacks); + } + _c4assert(ttag.len == bufsz); + _c4assert(!ttag.str || ttag.is_sub(m_evt_handler->arena())); + } + else if(bufsz) // if we succeeded writing into the arena, grow it as needed + { + _c4dbgp("tag required arena. update size"); + _c4assert(ttag.len == bufsz); + _c4assert(ttag.is_sub(buf)); + (void)_alloc_arena(bufsz); + } + C4_SUPPRESS_WARNING_MSVC_WITH_PUSH(4127) // conditional expression is constant + if C4_IF_CONSTEXPR (EventHandler::requires_strings_on_buffers) // NOLINT + { + _c4dbgpf("handler requires tags in buffers. !!ttag.str={} in_arena={} in_src={}", !!ttag.str, ttag.is_sub(m_evt_handler->arena()), ttag.is_sub(_buf())); + // is the resolved tag not in any of those buffers? + if(ttag.str && !ttag.is_sub(m_evt_handler->arena()) && !ttag.is_sub(_buf())) + { + _c4dbgpf("copying resolved tag to arena: slack={} required={}", m_evt_handler->arena_rem().len, ttag.len); + buf = _alloc_arena(ttag.len, &tag); + if(buf.str) // the alloc may fail eg with the ints handler + memcpy(buf.str, ttag.str, ttag.len); + ttag.str = buf.str; // keep the current len! + _c4assert(!ttag.str || ttag.is_sub(m_evt_handler->arena())); + } + } + C4_SUPPRESS_WARNING_MSVC_POP + _c4dbgpf("resolved tag: {} --> [{}]~~~{}~~~", _prs(tag), ttag.len, _maybe_null_str(ttag)); + _c4assert(ttag.len > 0); + // cache the hard-earned result! + m_evt_handler->tag_cache().add(tag, ttag, m_evt_handler->m_curr_doc, ret.pos); + return ttag; +} + +template +bool ParseEngine::_validate_directive_yaml(csubstr *C4_RESTRICT directive, csubstr *C4_RESTRICT version) const +{ + _c4assert(directive->begins_with("%YAML")); + size_t version_start = directive->first_not_of(" \t", 5); + if(version_start != npos) + { + csubstr digits = "0123456789"; + size_t major_end = directive->first_not_of(digits, version_start); + if(major_end != npos && directive->str[major_end] == '.') // single dot + { + size_t minor_end = directive->first_not_of(digits, major_end + 1); + if(minor_end == npos) + minor_end = directive->len; + _set_first_strict(*directive, minor_end); + *version = directive->range(version_start, minor_end); + _c4dbgpf("%YAML: version={} full={}", *version, _prs(*directive, true)); + return true; + } + } + return false; +} + +template +bool ParseEngine::_validate_directive_tag(csubstr *C4_RESTRICT directive, csubstr *C4_RESTRICT handle, csubstr *C4_RESTRICT prefix) const +{ + _c4assert(directive->begins_with("%TAG")); + csubstr whitespace = " \t"; + size_t handle_start = directive->first_not_of(whitespace, 4); + if(handle_start != npos && directive->str[handle_start] == '!') + { + size_t handle_end = directive->first_of(whitespace, handle_start); + if(handle_end != npos) + { + size_t prefix_start = directive->first_not_of(whitespace, handle_end); + if(prefix_start != npos) + { + size_t prefix_end = directive->first_of(whitespace, prefix_start); + if(prefix_end == npos) + prefix_end = directive->len; + _set_first_strict(*directive, prefix_end); + *handle = directive->range(handle_start, handle_end); + *prefix = directive->range(prefix_start, prefix_end); + _c4dbgpf("%TAG: handle={} prefix={} full={}", *handle, *prefix, _prs(*directive, true)); + if(is_valid_tag_handle(*handle)) + return true; + } + } + } + return false; +} + +template +void ParseEngine::_handle_directive(csubstr directive) +{ + _c4dbgpf("handle_directive: rem={}", _prs(directive, true)); + _c4assert(m_evt_handler->m_curr->line_contents.rem.begins_with('%')); + _c4assert(directive.str == m_evt_handler->m_curr->line_contents.rem.str); + const char *err = nullptr; + csubstr rem; + size_t pos; + auto isdirective = [](csubstr str, csubstr dir) { + if(str.begins_with(dir)) + { + csubstr rest = str.sub(dir.len); + return (!rest.len || rest.str[0] == ' ' || rest.str[0] == '\t'); + } + return false; + }; + if(isdirective(directive, "%TAG")) + { + csubstr handle; + csubstr prefix; + if(C4_UNLIKELY(!_validate_directive_tag(&directive, &handle, &prefix))) + { + err = "invalid %TAG directive"; + goto directive_error; // NOLINT + } + m_evt_handler->add_directive_tag(handle, prefix); + } + else if(isdirective(directive, "%YAML")) + { + csubstr version; + if(C4_UNLIKELY(!_validate_directive_yaml(&directive, &version))) + { + err = "invalid %YAML directive"; + goto directive_error; // NOLINT + } + if(C4_UNLIKELY(m_has_directives_yaml)) + { + err = "multiple %YAML directives"; + goto directive_error; // NOLINT + } + m_has_directives_yaml = true; + m_evt_handler->add_directive_yaml(version); + } + m_has_directives = true; + rem = m_evt_handler->m_curr->line_contents.rem; + pos = rem.first_not_of(" \t", directive.len); + pos = pos != npos ? pos : rem.len; + _line_progressed(pos); + rem = rem.sub(pos); + _c4dbgpf("handle_directive: rest={}", _prs(rem)); + if(C4_UNLIKELY(rem.len && !rem.begins_with('#'))) + { + err = "invalid tokens after directive"; + goto directive_error; // NOLINT + } +directive_error: + if(C4_UNLIKELY(err != nullptr)) + _c4err(err); +} + +template +bool ParseEngine::_handle_bom() +{ + const csubstr rem = m_evt_handler->m_curr->line_contents.rem; + if(rem.len) + { + const csubstr rest = rem.sub(1); + // https://yaml.org/spec/1.2.2/#52-character-encodings + #define _rymlisascii(c) ((c) > '\0' && (c) <= '\x7f') // is the character ASCII? + if(rem.begins_with(csubstr{"\x00\x00\xfe\xff", 4}) || (rem.begins_with(csubstr{"\x00\x00\x00", 3}) && rem.len >= 4u && _rymlisascii(rem.str[3]))) + { + _c4dbgp("byte order mark: UTF32BE"); + _handle_bom(UTF32BE); + _line_progressed(4); + m_bom_len = 4; + return true; + } + else if(rem.begins_with(csubstr{"\xff\xfe\x00\x00", 4}) || (rest.begins_with(csubstr{"\x00\x00\x00", 3}) && rem.len >= 4u && _rymlisascii(rem.str[0]))) + { + _c4dbgp("byte order mark: UTF32LE"); + _handle_bom(UTF32LE); + _line_progressed(4); + m_bom_len = 4; + return true; + } + else if(rem.begins_with("\xfe\xff") || (rem.begins_with('\x00') && rem.len >= 2u && _rymlisascii(rem.str[1]))) + { + _c4dbgp("byte order mark: UTF16BE"); + _handle_bom(UTF16BE); + _line_progressed(2); + m_bom_len = 2; + return true; + } + else if(rem.begins_with("\xff\xfe") || (rest.begins_with('\x00') && rem.len >= 2u && _rymlisascii(rem.str[0]))) + { + _c4dbgp("byte order mark: UTF16LE"); + _handle_bom(UTF16LE); + _line_progressed(2); + m_bom_len = 2; + return true; + } + else if(rem.begins_with("\xef\xbb\xbf")) + { + _c4dbgp("byte order mark: UTF8"); + _handle_bom(UTF8); + _line_progressed(3); + m_bom_len = 3; + return true; + } + #undef _rymlisascii + } + return false; +} + +template +void ParseEngine::_handle_bom(Encoding_e enc) +{ + if(m_encoding == NOBOM) + { + if(enc == UTF8 || /*beginning of file*/(m_evt_handler->m_curr->line_contents.rem.str == _buf().str)) + m_encoding = enc; + else + _c4err("non-UTF8 byte order mark can appear only at the beginning of the file"); + } + else if(enc != m_encoding) + { + _c4err("byte order mark can only be set once"); + } +} + + +//----------------------------------------------------------------------------- + +template +void ParseEngine::_handle_seq_json() +{ +seqjson_start: + _c4dbgpf("handle2_seq_json: node_id={} level={} indentation={}", m_evt_handler->m_curr->node_id, m_evt_handler->m_curr->level, m_evt_handler->m_curr->indref); + + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RKEY), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_all(RSEQ), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_all(RFLOW), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RVAL|RNXT), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_all(RVAL) != has_all(RNXT), m_evt_handler->m_curr->pos); + + _handle_flow_skip_whitespace(); + csubstr rem = m_evt_handler->m_curr->line_contents.rem; + if(!rem.len) + goto seqjson_again; + + if(has_any(RVAL)) + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RNXT), m_evt_handler->m_curr->pos); + const char first = rem.str[0]; + _c4dbgpf("mapjson[RVAL]: '{}'", first); + switch(first) + { + case '"': + { + _c4dbgp("seqjson[RVAL]: scanning double-quoted scalar"); + ScannedScalar sc = _scan_scalar_dquot(); + csubstr maybe_filtered = _maybe_filter_val_scalar_dquot(sc); + m_evt_handler->set_val_scalar_dquoted(maybe_filtered); + addrem_flags(RNXT, RVAL); + break; + } + case '[': + { + _c4dbgp("seqjson[RVAL]: start child seqjson"); + addrem_flags(RNXT, RVAL); + m_evt_handler->begin_seq_val_flow(); + addrem_flags(RVAL, RNXT); + _line_progressed(1); + break; + } + case '{': + { + _c4dbgp("seqjson[RVAL]: start child mapjson"); + addrem_flags(RNXT, RVAL); + m_evt_handler->begin_map_val_flow(); + addrem_flags(RMAP|RKEY, RSEQ|RVAL|RNXT); + _line_progressed(1); + goto seqjson_finish; + } + case ']': // this happens on a trailing comma like ", ]" + { + _c4dbgp("seqjson[RVAL]: end!"); + rem_flags(RSEQ); + _end_seq_flow(); + _line_progressed(1); + if(!has_all(RSEQ|RFLOW)) + goto seqjson_finish; + break; + } + default: + { + ScannedScalar sc; + if(_scan_scalar_seq_json(&sc)) + { + _c4dbgp("seqjson[RVAL]: it's a plain scalar."); + csubstr maybe_filtered = _maybe_filter_val_scalar_plain(sc, m_evt_handler->m_curr->indref); + m_evt_handler->set_val_scalar_plain(maybe_filtered); + addrem_flags(RNXT, RVAL); + } + else + { + _c4err("parse error"); + } + } + } + } + else // RNXT + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RNXT), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RVAL), m_evt_handler->m_curr->pos); + const char first = rem.str[0]; + _c4dbgpf("mapjson[RNXT]: '{}'", first); + switch(first) + { + case ',': + { + _c4dbgp("seqjson[RNXT]: expect next val"); + addrem_flags(RVAL, RNXT); + m_evt_handler->add_sibling(); + _line_progressed(1); + break; + } + case ']': + { + _c4dbgp("seqjson[RNXT]: end!"); + _end_seq_flow(); + _line_progressed(1); + goto seqjson_finish; + } + default: + _c4err("parse error"); + } + } + + seqjson_again: + _c4dbgt("seqjson: go again", 0); + if(_finished_line()) + { + if(C4_LIKELY(!_finished_file())) + { + _line_ended(); + _scan_line(); + _c4dbgnextline(); + } + else + { + _c4err("missing terminating ]"); + } + } + goto seqjson_start; + + seqjson_finish: + _c4dbgp("seqjson: finish"); +} + + +//----------------------------------------------------------------------------- + +template +void ParseEngine::_handle_map_json() +{ +mapjson_start: + _c4dbgpf("handle2_map_json: node_id={} level={} indentation={}", m_evt_handler->m_curr->node_id, m_evt_handler->m_curr->level, m_evt_handler->m_curr->indref); + + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_all(RMAP), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_all(RFLOW), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(QMRK), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RKEY|RKCL|RVAL|RNXT), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, 1 == (has_any(RKEY) + has_any(RKCL) + has_any(RVAL) + has_any(RNXT)), m_evt_handler->m_curr->pos); + + _handle_flow_skip_whitespace(); + csubstr rem = m_evt_handler->m_curr->line_contents.rem; + if(!rem.len) + goto mapjson_again; + + if(has_any(RKEY)) + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RKCL), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RVAL), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RNXT), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(QMRK), m_evt_handler->m_curr->pos); + const char first = rem.str[0]; + _c4dbgpf("mapjson[RKEY]: '{}'", first); + switch(first) + { + case '"': + { + _c4dbgp("mapjson[RKEY]: scanning double-quoted scalar"); + ScannedScalar sc = _scan_scalar_dquot(); + csubstr maybe_filtered = _maybe_filter_key_scalar_dquot(sc); + m_evt_handler->set_key_scalar_dquoted(maybe_filtered); + addrem_flags(RKCL, RKEY); + break; + } + case '}': // this happens on a trailing comma like ", }" + { + _c4dbgp("mapjson[RKEY]: end!"); + _end_map_flow(); + _line_progressed(1); + goto mapjson_finish; + } + default: + _c4err("parse error"); + } + } + else if(has_any(RVAL)) + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RKEY), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RKCL), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RNXT), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(QMRK), m_evt_handler->m_curr->pos); + const char first = rem.str[0]; + _c4dbgpf("mapjson[RVAL]: '{}'", first); + switch(first) + { + case '"': + { + _c4dbgp("mapjson[RVAL]: scanning double-quoted scalar"); + ScannedScalar sc = _scan_scalar_dquot(); + csubstr maybe_filtered = _maybe_filter_val_scalar_dquot(sc); + m_evt_handler->set_val_scalar_dquoted(maybe_filtered); + addrem_flags(RNXT, RVAL); + break; + } + case '[': + { + _c4dbgp("mapjson[RVAL]: start val seqjson"); + addrem_flags(RNXT, RVAL); + m_evt_handler->begin_seq_val_flow(); + _set_indentation(m_evt_handler->m_parent->indref); + addrem_flags(RSEQ|RVAL, RMAP|RNXT); + _line_progressed(1); + goto mapjson_finish; + } + case '{': + { + _c4dbgp("mapjson[RVAL]: start val mapjson"); + addrem_flags(RNXT, RVAL); + m_evt_handler->begin_map_val_flow(); + _set_indentation(m_evt_handler->m_parent->indref); + addrem_flags(RKEY, RNXT); + _line_progressed(1); + // keep going in this function + break; + } + default: + { + ScannedScalar sc; + if(_scan_scalar_map_json(&sc)) + { + _c4dbgp("mapjson[RVAL]: plain scalar."); + csubstr maybe_filtered = _maybe_filter_val_scalar_plain(sc, m_evt_handler->m_curr->indref); + m_evt_handler->set_val_scalar_plain(maybe_filtered); + addrem_flags(RNXT, RVAL); + } + else + { + _c4err("parse error"); + } + break; + } + } + } + else if(has_any(RKCL)) // read the key colon + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RKEY), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RVAL), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RNXT), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(QMRK), m_evt_handler->m_curr->pos); + const char first = rem.str[0]; + _c4dbgpf("mapjson[RKCL]: '{}'", first); + if(first == ':') + { + _c4dbgp("mapjson[RKCL]: found the colon"); + addrem_flags(RVAL, RKCL); + _line_progressed(1); + } + else + { + _c4err("parse error"); + } + } + else if(has_any(RNXT)) + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RKEY), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RKCL), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RVAL), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(QMRK), m_evt_handler->m_curr->pos); + _c4dbgpf("mapjson[RNXT]: '{}'", rem.str[0]); + if(rem.begins_with(',')) + { + _c4dbgp("mapjson[RNXT]: expect next keyval"); + m_evt_handler->add_sibling(); + addrem_flags(RKEY, RNXT); + _line_progressed(1); + } + else if(rem.begins_with('}')) + { + _c4dbgp("mapjson[RNXT]: end!"); + _end_map_flow(); + _line_progressed(1); + goto mapjson_finish; + } + else + { + _c4err("parse error"); // LCOV_EXCL_LINE + } + } + + mapjson_again: + _c4dbgt("mapjson: go again", 0); + if(_finished_line()) + { + if(C4_LIKELY(!_finished_file())) + { + _line_ended(); + _scan_line(); + _c4dbgnextline(); + } + else + { + _c4err("missing terminating }"); + } + } + goto mapjson_start; + + mapjson_finish: + _c4dbgp("mapjson: finish"); +} + + +//----------------------------------------------------------------------------- + +template +void ParseEngine::_handle_seq_imap() +{ +seqimap_start: + _c4dbgpf("handle2_seq_imap: node_id={} level={} indref={}", m_evt_handler->m_curr->node_id, m_evt_handler->m_curr->level, m_evt_handler->m_curr->indref); + + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_all(RSEQIMAP), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RKEY), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RVAL|RNXT|QMRK|RKCL), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, 1 == has_all(RVAL) + has_all(RNXT) + has_all(QMRK) + has_all(RKCL), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_stack.size() >= 3, m_evt_handler->m_curr->pos); + + _handle_flow_skip_whitespace(); + csubstr rem = m_evt_handler->m_curr->line_contents.rem; + if(!rem.len) + goto seqimap_again; + + if(has_any(RVAL)) + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RVAL), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RNXT), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(QMRK), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RKCL), m_evt_handler->m_curr->pos); + const char first = rem.str[0]; + _c4dbgpf("seqimap[RVAL]: '{}'", _c4prc(first)); + ScannedScalar sc; + if(first == '\'') + { + _c4dbgp("seqimap[RVAL]: scanning single-quoted scalar"); + sc = _scan_scalar_squot(); + csubstr maybe_filtered = _maybe_filter_val_scalar_squot(sc); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->set_val_scalar_squoted(maybe_filtered); + _end_map_flow(); + goto seqimap_finish; + } + else if(first == '"') + { + _c4dbgp("seqimap[RVAL]: scanning double-quoted scalar"); + sc = _scan_scalar_dquot(); + csubstr maybe_filtered = _maybe_filter_val_scalar_dquot(sc); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->set_val_scalar_dquoted(maybe_filtered); + _end_map_flow(); + goto seqimap_finish; + } + // block scalars (ie | and >) cannot appear in flow containers + else if(_scan_scalar_plain_map_flow(&sc)) + { + _c4dbgp("seqimap[RVAL]: it's a scalar."); + csubstr maybe_filtered = _maybe_filter_val_scalar_plain(sc, m_evt_handler->m_curr->indref); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->set_val_scalar_plain(maybe_filtered); + _end_map_flow(); + goto seqimap_finish; + } + else if(first == '[') + { + _c4dbgp("seqimap[RVAL]: start child seqflow"); + addrem_flags(RNXT, RVAL); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->begin_seq_val_flow(); + addrem_flags(RVAL, RNXT|RSEQIMAP); + _set_indentation(m_evt_handler->m_parent->indref); + _line_progressed(1); + goto seqimap_finish; + } + else if(first == '{') + { + _c4dbgp("seqimap[RVAL]: start child mapflow"); + addrem_flags(RNXT, RVAL); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->begin_map_val_flow(); + addrem_flags(RMAP|RKEY, RSEQ|RVAL|RSEQIMAP|RNXT); + _set_indentation(m_evt_handler->m_parent->indref); + _line_progressed(1); + goto seqimap_finish; + } + else if(first == ',' || first == ']') + { + _c4dbgp("seqimap[RVAL]: finish without val."); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->set_val_scalar_plain_empty(); + _end_map_flow(); + goto seqimap_finish; + } + else if(first == '*') + { + csubstr ref = _scan_ref_seq(); + _c4dbgpf("seqimap[RVAL]: ref! {}", _prs(ref)); + _handle_valref(ref); + addrem_flags(RNXT, RVAL); + } + else if(first == '&') + { + csubstr anchor = _scan_anchor(); + _c4dbgpf("seqimap[RVAL]: anchor! {}", _prs(anchor)); + _add_annotation(&m_pending_anchors, anchor); + } + else if(first == '!') + { + csubstr tag = _scan_tag(); + _c4dbgpf("seqimap[RVAL]: tag! {}", _prs(tag)); + _add_annotation(&m_pending_tags, tag); + } + else + { + _c4err("parse error"); // LCOV_EXCL_LINE + } + } + else if(has_any(RNXT)) + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RNXT), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RVAL), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(QMRK), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RKCL), m_evt_handler->m_curr->pos); + const char first = rem.str[0]; + _c4dbgpf("seqimap[RNXT]: '{}'", _c4prc(first)); + if(first == ',' || first == ']') + { + // we may get here because a map or a seq started and we + // return later + _c4dbgp("seqimap: done"); + _end_map_flow(); + goto seqimap_finish; + } + else + { + _c4err("parse error"); // LCOV_EXCL_LINE + } + } + else if(has_any(QMRK)) + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(QMRK), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RVAL), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RNXT), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RKCL), m_evt_handler->m_curr->pos); + const char first = rem.str[0]; + _c4dbgpf("seqimap[QMRK]: '{}'", _c4prc(first)); + ScannedScalar sc; + if(first == '\'') + { + _c4dbgp("seqimap[QMRK]: scanning single-quoted scalar"); + sc = _scan_scalar_squot(); + csubstr maybe_filtered = _maybe_filter_key_scalar_squot(sc); + m_evt_handler->set_key_scalar_squoted(maybe_filtered); + addrem_flags(RKCL, QMRK); + goto seqimap_again; + } + else if(first == '"') + { + _c4dbgp("seqimap[QMRK]: scanning double-quoted scalar"); + sc = _scan_scalar_dquot(); + csubstr maybe_filtered = _maybe_filter_key_scalar_dquot(sc); + m_evt_handler->set_key_scalar_dquoted(maybe_filtered); + addrem_flags(RKCL, QMRK); + goto seqimap_again; + } + // block scalars (ie | and >) cannot appear in flow containers + else if(_scan_scalar_plain_map_flow(&sc)) + { + _c4dbgp("seqimap[QMRK]: it's a scalar."); + csubstr maybe_filtered = _maybe_filter_key_scalar_plain(sc, m_evt_handler->m_curr->indref); + m_evt_handler->set_key_scalar_plain(maybe_filtered); + addrem_flags(RKCL, QMRK); + goto seqimap_again; + } + else if(first == '[') + { + _c4dbgp("seqimap[QMRK]: start child seqflow"); + addrem_flags(RKCL, QMRK); + m_evt_handler->begin_seq_key_flow(); + addrem_flags(RSEQ|RVAL, RKCL|RSEQIMAP); + _set_indentation(m_evt_handler->m_parent->indref); + _line_progressed(1); + goto seqimap_finish; + } + else if(first == '{') + { + _c4dbgp("seqimap[QMRK]: start child mapflow"); + addrem_flags(RKCL, QMRK); + m_evt_handler->begin_map_key_flow(); + addrem_flags(RMAP|RKEY, RSEQ|RKCL|RSEQIMAP); + _set_indentation(m_evt_handler->m_parent->indref); + _line_progressed(1); + goto seqimap_finish; + } + else if(first == ',' || first == ']') + { + _c4dbgp("seqimap[QMRK]: finish without key."); + m_evt_handler->set_key_scalar_plain_empty(); + m_evt_handler->set_val_scalar_plain_empty(); + _end_map_flow(); + goto seqimap_finish; + } + else if(first == '&') + { + csubstr anchor = _scan_anchor(); + _c4dbgp("seqimap[QMRK]: anchor!"); + m_evt_handler->set_key_anchor(anchor); + } + else if(first == '*') + { + csubstr ref = _scan_ref_seq(); + _c4dbgp("seqimap[QMRK]: ref!"); + _handle_keyref(ref); + addrem_flags(RKCL, QMRK); + } + else + { + _c4err("parse error"); // LCOV_EXCL_LINE + } + } + else if(has_any(RKCL)) + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RVAL), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RNXT), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(QMRK), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RKCL), m_evt_handler->m_curr->pos); + const char first = rem.str[0]; + _c4dbgpf("seqimap[RKCL]: '{}'", _c4prc(first)); + if(first == ':') + { + _c4dbgp("seqimap[RKCL]: found ':'"); + addrem_flags(RVAL, RKCL); + _line_progressed(1); + goto seqimap_again; + } + else if(first == ',' || first == ']') + { + _c4dbgp("seqimap[RKCL]: found ','. finish without val"); + m_evt_handler->set_val_scalar_plain_empty(); + _end_map_flow(); + goto seqimap_finish; + } + else + { + _c4err("parse error"); // LCOV_EXCL_LINE + } + } + + seqimap_again: + _c4dbgt("seqimap: go again", 0); + if(_finished_line()) + { + if(C4_LIKELY(!_finished_file())) + { + _line_ended(); + _scan_line(); + _c4dbgnextline(); + } + else + { + _c4err("parse error"); + } + } + goto seqimap_start; + + seqimap_finish: + _c4dbgp("seqimap: finish"); +} + + +//----------------------------------------------------------------------------- + +template +void ParseEngine::_handle_seq_flow() +{ +seqflow_start: + _c4dbgpf("handle_seq_flow: node_id={} level={} indentation={}", m_evt_handler->m_curr->node_id, m_evt_handler->m_curr->level, m_evt_handler->m_curr->indref); + + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RKEY), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_all(RSEQ), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_all(RFLOW), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RVAL|RNXT), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_all(RVAL) != has_all(RNXT), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->indref != npos, m_evt_handler->m_curr->pos); + + if(m_evt_handler->m_curr->at_line_beginning()) + { + _handle_flow_line_beginning(); + } + + _handle_flow_skip_whitespace(); + if(!m_evt_handler->m_curr->line_contents.rem.len) + goto seqflow_again; + + if(has_any(RVAL)) + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RNXT), m_evt_handler->m_curr->pos); + const char first = m_evt_handler->m_curr->line_contents.rem.str[0]; + ScannedScalar sc; + if(first == '\'') + { + _c4dbgp("seqflow[RVAL]: scanning single-quoted scalar"); + sc = _scan_scalar_squot(); + csubstr maybe_filtered = _maybe_filter_val_scalar_squot(sc); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->set_val_scalar_squoted(maybe_filtered); + addrem_flags(RNXT, RVAL); + _mark_seqflow_val_end(); + } + else if(first == '"') + { + _c4dbgp("seqflow[RVAL]: scanning double-quoted scalar"); + sc = _scan_scalar_dquot(); + csubstr maybe_filtered = _maybe_filter_val_scalar_dquot(sc); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->set_val_scalar_dquoted(maybe_filtered); + addrem_flags(RNXT, RVAL); + _mark_seqflow_val_end(); + } + // block scalars (ie | and >) cannot appear in flow containers + else if(_scan_scalar_plain_seq_flow(&sc)) + { + _c4dbgp("seqflow[RVAL]: it's a scalar."); + csubstr maybe_filtered = _maybe_filter_val_scalar_plain(sc, m_evt_handler->m_curr->indref); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->set_val_scalar_plain(maybe_filtered); + addrem_flags(RNXT, RVAL); + _mark_seqflow_val_end(); + } + else if(first == '[') + { + _c4dbgp("seqflow[RVAL]: start child seqflow"); + addrem_flags(RNXT, RVAL); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->begin_seq_val_flow(); + _set_indentation(m_evt_handler->m_parent->indref); + addrem_flags(RVAL, RNXT); + _line_progressed(1); + } + else if(first == '{') + { + _c4dbgp("seqflow[RVAL]: start child mapflow"); + addrem_flags(RNXT, RVAL); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->begin_map_val_flow(); + _set_indentation(m_evt_handler->m_parent->indref); + addrem_flags(RMAP|RKEY, RSEQ|RVAL|RNXT); + _line_progressed(1); + goto seqflow_finish; + } + else if(first == ']') // this happens on cases such as [] or [.., ] + { + _c4dbgp("seqflow[RVAL]: end!"); + if(m_pending_anchors.num_entries | m_pending_tags.num_entries) + { + _c4dbgp("seqflow[RVAL]: add pending annotations"); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->set_val_scalar_plain_empty(); + } + _line_progressed(1); + _end_seq_flow(); + goto seqflow_finish; + } + else if(first == '*') + { + csubstr ref = _scan_ref_seq(); + _c4dbgpf("seqflow[RVAL]: ref! {}", _prs(ref)); + _handle_valref(ref); + addrem_flags(RNXT, RVAL); + } + else if(first == '&') + { + csubstr anchor = _scan_anchor(); + _c4dbgpf("seqflow[RVAL]: anchor! {}", _prs(anchor)); + _add_annotation(&m_pending_anchors, anchor); + } + else if(first == '!') + { + csubstr tag = _scan_tag(); + _c4dbgpf("seqflow[RVAL]: tag! {}", _prs(tag)); + _add_annotation(&m_pending_tags, tag); + } + else if(first == ':') + { + _c4dbgpf("seqflow[RVAL]: actually seqimap at node[{}], with empty key", m_evt_handler->m_curr->node_id); + addrem_flags(RNXT, RVAL); + m_evt_handler->begin_map_val_flow(); + _set_indentation(m_evt_handler->m_parent->indref); + _handle_annotations_before_blck_key_scalar(); + m_evt_handler->set_key_scalar_plain_empty(); + addrem_flags(RSEQIMAP|RVAL, RSEQ|RNXT); + _line_progressed(1); + goto seqflow_finish; + } + else if(first == '?') + { + _c4dbgp("seqflow[RVAL]: start child mapflow, explicit key"); + addrem_flags(RNXT, RVAL); + m_evt_handler->begin_map_val_flow(); + _set_indentation(m_evt_handler->m_parent->indref); + addrem_flags(RSEQIMAP|QMRK, RSEQ|RNXT); + _line_progressed(1); + _maybe_skip_whitespace_tokens(); + goto seqflow_finish; + } + else if(first == ',') + { + if(m_pending_anchors.num_entries || m_pending_tags.num_entries) + { + _c4dbgp("seqflow[RVAL]: add pending annotations"); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->set_val_scalar_plain_empty(); + addrem_flags(RNXT, RVAL); + _mark_seqflow_val_end(); + } + else + { + _c4err("parse error"); + } + } + else + { + _c4err("parse error"); + } + } + else // RNXT + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RNXT), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RVAL), m_evt_handler->m_curr->pos); + const char first = m_evt_handler->m_curr->line_contents.rem.str[0]; + if(first == ',') + { + _c4dbgp("seqflow[RNXT]: expect next val"); + addrem_flags(RVAL, RNXT); + m_evt_handler->add_sibling(); + _line_progressed(1); + if(m_evt_handler->m_curr->line_contents.rem.begins_with('#')) + { + _c4err("parse error: invalid comment after comma"); + } + _mark_seqflow_val_end(); + } + else if(first == ']') + { + _c4dbgp("seqflow[RNXT]: end!"); + _line_progressed(1); + _end_seq_flow(); + goto seqflow_finish; + } + else if(first == ':') + { + _c4dbgpf("seqflow[RNXT]: line@valend={} line@now={}", m_prev_val_end, m_evt_handler->m_curr->pos.line); + if(m_prev_val_end != NONE && m_evt_handler->m_curr->pos.line == m_prev_val_end) + { + _c4dbgpf("seqflow[RNXT]: actually seqimap at node[{}]", m_evt_handler->m_curr->node_id); + m_evt_handler->actually_val_is_first_key_of_new_map_flow(); + _set_indentation(m_evt_handler->m_parent->indref); + _line_progressed(1); + addrem_flags(RSEQIMAP|RVAL, RNXT); + goto seqflow_finish; + } + else + { + _c4err("parse error"); + } + } + else + { + _c4err("parse error"); + } + } + + seqflow_again: + _c4dbgt("seqflow: go again", 0); + if(_finished_line()) + { + if(C4_LIKELY(!_finished_file())) + { + _line_ended(); + _scan_line(); + _c4dbgnextline(); + } + else + { + _c4err("missing terminating ]"); + } + } + goto seqflow_start; + + seqflow_finish: + _c4dbgp("seqflow: finish"); +} + + +//----------------------------------------------------------------------------- + +template +void ParseEngine::_handle_map_flow() +{ +mapflow_start: + _c4dbgpf("handle_map_flow: node_id={} level={} indentation={}", m_evt_handler->m_curr->node_id, m_evt_handler->m_curr->level, m_evt_handler->m_curr->indref); + + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_all(RMAP), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_all(RFLOW), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RKEY|RKCL|RVAL|RNXT|QMRK), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, 1 == (has_any(RKEY) + has_any(RKCL) + has_any(RVAL) + has_any(RNXT) + has_any(QMRK)), m_evt_handler->m_curr->pos); + + if(m_evt_handler->m_curr->at_line_beginning()) + { + _handle_flow_line_beginning(); + } + + _handle_flow_skip_whitespace(); + if(!m_evt_handler->m_curr->line_contents.rem.len) + goto mapflow_again; + + if(has_any(RKEY)) + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RKCL), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RVAL), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RNXT), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(QMRK), m_evt_handler->m_curr->pos); + const char first = m_evt_handler->m_curr->line_contents.rem.str[0]; + _c4dbgpf("mapflow[RKEY]: '{}'", first); + ScannedScalar sc; + if(first == '\'') + { + _c4dbgp("mapflow[RKEY]: scanning single-quoted scalar"); + sc = _scan_scalar_squot(); + csubstr maybe_filtered = _maybe_filter_key_scalar_squot(sc); + _handle_annotations_before_blck_key_scalar(); + m_evt_handler->set_key_scalar_squoted(maybe_filtered); + addrem_flags(RKCL, RKEY|QMRK); + } + else if(first == '"') + { + _c4dbgp("mapflow[RKEY]: scanning double-quoted scalar"); + sc = _scan_scalar_dquot(); + csubstr maybe_filtered = _maybe_filter_key_scalar_dquot(sc); + _handle_annotations_before_blck_key_scalar(); + m_evt_handler->set_key_scalar_dquoted(maybe_filtered); + addrem_flags(RKCL, RKEY|QMRK); + } + // block scalars (ie | and >) cannot appear in flow containers + else if(_scan_scalar_plain_map_flow(&sc)) + { + _c4dbgp("mapflow[RKEY]: plain scalar"); + csubstr maybe_filtered = _maybe_filter_key_scalar_plain(sc, m_evt_handler->m_curr->indref); + _handle_annotations_before_blck_key_scalar(); + m_evt_handler->set_key_scalar_plain(maybe_filtered); + addrem_flags(RKCL, RKEY|QMRK); + } + else if(first == '?') + { + _c4dbgp("mapflow[RKEY]: explicit key"); + _handle_annotations_before_blck_key_scalar(); + addrem_flags(QMRK, RKEY); + _line_progressed(1); + _maybe_skip_whitespace_tokens(); + } + else if(first == ':') + { + _c4dbgp("mapflow[RKEY]: setting empty key"); + _handle_annotations_before_blck_key_scalar(); + m_evt_handler->set_key_scalar_plain_empty(); + addrem_flags(RVAL, RKEY|QMRK); + _line_progressed(1); + _maybe_skip_whitespace_tokens(); + } + else if(first == ',') + { + _c4dbgp("mapflow[RKEY]: comma!"); + if(!_handle_annotations_before_unexpected_flow_token_rkey()) + _c4err("unexpected comma"); + addrem_flags(RNXT, RKEY|QMRK); + // keep going in this function + } + else if(first == '}') // this happens on a trailing comma like ", }" + { + _c4dbgp("mapflow[RKEY]: end!"); + (void)_handle_annotations_before_unexpected_flow_token_rkey(); + _line_progressed(1); + _end_map_flow(); + goto mapflow_finish; + } + else if(first == '&') + { + csubstr anchor = _scan_anchor(); + _c4dbgpf("mapflow[RKEY]: key anchor! {}", _prs(anchor)); + _add_annotation(&m_pending_anchors, anchor); + } + else if(first == '!') + { + csubstr tag = _scan_tag(); + _c4dbgpf("mapflow[RKEY]: tag! {}", _prs(tag)); + _add_annotation(&m_pending_tags, tag); + } + else if(first == '*') + { + csubstr ref = _scan_ref_map(); + _c4dbgpf("mapflow[RKEY]: key ref! {}", _prs(ref)); + _handle_keyref(ref); + addrem_flags(RKCL, RKEY); + } + else if(first == '[') + { + // RYML's tree cannot store container keys, but that's + // handled inside the tree event handler. Other handler + // types may be able to handle it. + _c4dbgp("mapflow[RKEY]: start child seqflow (!)"); + _handle_annotations_before_blck_key_scalar(); + addrem_flags(RKCL, RKEY); + m_evt_handler->begin_seq_key_flow(); + addrem_flags(RSEQ|RVAL, RMAP|RKCL); + _set_indentation(m_evt_handler->m_parent->indref); + _line_progressed(1); + goto mapflow_finish; + } + else if(first == '{') + { + // RYML's tree cannot store container keys, but that's + // handled inside the tree event handler. Other handler + // types may be able to handle it. + _c4dbgp("mapflow[RKEY]: start child mapflow (!)"); + _handle_annotations_before_blck_key_scalar(); + addrem_flags(RKCL, RKEY); + m_evt_handler->begin_map_key_flow(); + addrem_flags(RKEY, RVAL|RKCL); + _set_indentation(m_evt_handler->m_parent->indref); + _line_progressed(1); + // keep going in this function + } + else + { + _c4err("parse error"); // LCOV_EXCL_LINE + } + } + else if(has_any(RKCL)) // read the key colon + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RKEY), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RVAL), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RNXT), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(QMRK), m_evt_handler->m_curr->pos); + const char first = m_evt_handler->m_curr->line_contents.rem.str[0]; + _c4dbgpf("mapflow[RKCL]: '{}'", first); + if(first == ':') + { + _c4dbgp("mapflow[RKCL]: found the colon"); + addrem_flags(RVAL, RKCL); + _line_progressed(1); + } + else if(first == '}') + { + _c4dbgp("mapflow[RKCL]: end with missing val!"); + addrem_flags(RVAL, RKCL); + m_evt_handler->set_val_scalar_plain_empty(); + _line_progressed(1); + _end_map_flow(); + goto mapflow_finish; + } + else if(first == ',') + { + _c4dbgp("mapflow[RKCL]: got comma. val is missing"); + m_evt_handler->set_val_scalar_plain_empty(); + m_evt_handler->add_sibling(); + addrem_flags(RKEY, RKCL); + _line_progressed(1); + if(m_evt_handler->m_curr->line_contents.rem.begins_with('#')) + { + _c4err("parse error: invalid comment after comma"); + } + } + else + { + _c4err("parse error"); + } + } + else if(has_any(RVAL)) + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RKEY), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RKCL), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RNXT), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(QMRK), m_evt_handler->m_curr->pos); + const char first = m_evt_handler->m_curr->line_contents.rem.str[0]; + _c4dbgpf("mapflow[RVAL]: '{}'", first); + ScannedScalar sc; + if(first == '\'') + { + _c4dbgp("mapflow[RVAL]: scanning single-quoted scalar"); + sc = _scan_scalar_squot(); + csubstr maybe_filtered = _maybe_filter_val_scalar_squot(sc); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->set_val_scalar_squoted(maybe_filtered); + addrem_flags(RNXT, RVAL); + } + else if(first == '"') + { + _c4dbgp("mapflow[RVAL]: scanning double-quoted scalar"); + sc = _scan_scalar_dquot(); + csubstr maybe_filtered = _maybe_filter_val_scalar_dquot(sc); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->set_val_scalar_dquoted(maybe_filtered); + addrem_flags(RNXT, RVAL); + } + // block scalars (ie | and >) cannot appear in flow containers + else if(_scan_scalar_plain_map_flow(&sc)) + { + _c4dbgp("mapflow[RVAL]: plain scalar."); + csubstr maybe_filtered = _maybe_filter_val_scalar_plain(sc, m_evt_handler->m_curr->indref); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->set_val_scalar_plain(maybe_filtered); + addrem_flags(RNXT, RVAL); + } + else if(first == '[') + { + _c4dbgp("mapflow[RVAL]: start val seqflow"); + addrem_flags(RNXT, RVAL); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->begin_seq_val_flow(); + _set_indentation(m_evt_handler->m_parent->indref); + addrem_flags(RSEQ|RVAL, RMAP|RNXT); + _line_progressed(1); + goto mapflow_finish; + } + else if(first == '{') + { + _c4dbgp("mapflow[RVAL]: start val mapflow"); + addrem_flags(RNXT, RVAL); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->begin_map_val_flow(); + _set_indentation(m_evt_handler->m_parent->indref); + addrem_flags(RKEY, RNXT); + _line_progressed(1); + // keep going in this function + } + else if(first == '}') + { + _c4dbgp("mapflow[RVAL]: end!"); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->set_val_scalar_plain_empty(); + _line_progressed(1); + _end_map_flow(); + goto mapflow_finish; + } + else if(first == ',') + { + _c4dbgp("mapflow[RVAL]: empty val!"); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->set_val_scalar_plain_empty(); + addrem_flags(RNXT, RVAL); + // keep going in this function + } + else if(first == '*') + { + csubstr ref = _scan_ref_map(); + _c4dbgpf("mapflow[RVAL]: key ref! {}", _prs(ref)); + _handle_valref(ref); + addrem_flags(RNXT, RVAL); + } + else if(first == '&') + { + csubstr anchor = _scan_anchor(); + _c4dbgpf("mapflow[RVAL]: key anchor! {}", _prs(anchor)); + _add_annotation(&m_pending_anchors, anchor); + } + else if(first == '!') + { + csubstr tag = _scan_tag(); + _c4dbgpf("mapflow[RVAL]: tag! {}", _prs(tag)); + _add_annotation(&m_pending_tags, tag); + } + else + { + _c4err("parse error"); + } + } + else if(has_any(RNXT)) + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RKEY), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RKCL), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RVAL), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(QMRK), m_evt_handler->m_curr->pos); + _c4dbgpf("mapflow[RNXT]: '{}'", m_evt_handler->m_curr->line_contents.rem.str[0]); + if(m_evt_handler->m_curr->line_contents.rem.begins_with(',')) + { + _c4dbgp("mapflow[RNXT]: expect next keyval"); + m_evt_handler->add_sibling(); + addrem_flags(RKEY, RNXT); + _line_progressed(1); + if(m_evt_handler->m_curr->line_contents.rem.begins_with('#')) + { + _c4err("parse error: invalid comment after comma"); + } + } + else if(m_evt_handler->m_curr->line_contents.rem.begins_with('}')) + { + _c4dbgp("mapflow[RNXT]: end!"); + _line_progressed(1); + _end_map_flow(); + goto mapflow_finish; + } + else + { + _c4err("parse error"); + } + } + else if(has_any(QMRK)) + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RKEY), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RKCL), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RVAL), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RNXT), m_evt_handler->m_curr->pos); + const char first = m_evt_handler->m_curr->line_contents.rem.str[0]; + _c4dbgpf("mapflow[QMRK]: '{}'", first); + ScannedScalar sc; + if(first == '\'') + { + _c4dbgp("mapflow[QMRK]: scanning single-quoted scalar"); + sc = _scan_scalar_squot(); + csubstr maybe_filtered = _maybe_filter_key_scalar_squot(sc); + _handle_annotations_before_blck_key_scalar(); + m_evt_handler->set_key_scalar_squoted(maybe_filtered); + addrem_flags(RKCL, QMRK); + } + else if(first == '"') + { + _c4dbgp("mapflow[QMRK]: scanning double-quoted scalar"); + sc = _scan_scalar_dquot(); + csubstr maybe_filtered = _maybe_filter_key_scalar_dquot(sc); + _handle_annotations_before_blck_key_scalar(); + m_evt_handler->set_key_scalar_dquoted(maybe_filtered); + addrem_flags(RKCL, QMRK); + } + // block scalars (ie | and >) cannot appear in flow containers + else if(_scan_scalar_plain_map_flow(&sc)) + { + _c4dbgp("mapflow[QMRK]: plain scalar"); + csubstr maybe_filtered = _maybe_filter_key_scalar_plain(sc, m_evt_handler->m_curr->indref); + _handle_annotations_before_blck_key_scalar(); + m_evt_handler->set_key_scalar_plain(maybe_filtered); + addrem_flags(RKCL, QMRK); + } + else if(first == ':') + { + _c4dbgp("mapflow[QMRK]: setting empty key"); + _handle_annotations_before_blck_key_scalar(); + m_evt_handler->set_key_scalar_plain_empty(); + addrem_flags(RVAL, QMRK); + _line_progressed(1); + _maybe_skip_whitespace_tokens(); + } + else if(first == '}') // this happens on a trailing comma like ", }" + { + _c4dbgp("mapflow[QMRK]: end!"); + _handle_annotations_before_blck_key_scalar(); + m_evt_handler->set_key_scalar_plain_empty(); + m_evt_handler->set_val_scalar_plain_empty(); + _end_map_flow(); + _line_progressed(1); + goto mapflow_finish; + } + else if(first == ',') + { + _c4dbgp("mapflow[QMRK]: empty key+val!"); + _handle_annotations_before_blck_key_scalar(); + m_evt_handler->set_key_scalar_plain_empty(); + m_evt_handler->set_val_scalar_plain_empty(); + addrem_flags(RNXT, QMRK); + } + else if(first == '&') + { + csubstr anchor = _scan_anchor(); + _c4dbgpf("mapflow[QMRK]: key anchor! {}", _prs(anchor)); + _add_annotation(&m_pending_anchors, anchor); + } + else if(first == '*') + { + csubstr ref = _scan_ref_map(); + _c4dbgpf("mapflow[QMRK]: key ref! {}", _prs(ref)); + _handle_keyref(ref); + addrem_flags(RKCL, QMRK); + } + else if(first == '[') + { + // RYML's tree cannot store container keys, but that's + // handled inside the tree sink. Other sink types may be + // able to handle it. + _c4dbgp("mapflow[QMRK]: start child seqflow (!)"); + addrem_flags(RKCL, QMRK); + _handle_annotations_before_blck_key_scalar(); + m_evt_handler->begin_seq_key_flow(); + addrem_flags(RSEQ|RVAL, RMAP|RKCL); + _set_indentation(m_evt_handler->m_parent->indref); + _line_progressed(1); + goto mapflow_finish; + } + else if(first == '{') + { + // RYML's tree cannot store container keys, but that's + // handled inside the tree sink. Other sink types may be + // able to handle it. + _c4dbgp("mapflow[QMRK]: start child mapflow (!)"); + addrem_flags(RKCL, QMRK); + _handle_annotations_before_blck_key_scalar(); + m_evt_handler->begin_map_key_flow(); + _set_indentation(m_evt_handler->m_parent->indref); + addrem_flags(RKEY, RKCL); + _line_progressed(1); + // keep going in this function + } + else if(first == '!') + { + csubstr tag = _scan_tag(); + _c4dbgpf("mapflow[QMRK]: tag! {}", _prs(tag)); + _add_annotation(&m_pending_tags, tag); + } + else + { + _c4err("parse error"); // LCOV_EXCL_LINE + } + } + + mapflow_again: + _c4dbgt("mapflow: go again", 0); + if(_finished_line()) + { + if(C4_LIKELY(!_finished_file())) + { + _line_ended(); + _scan_line(); + _c4dbgnextline(); + } + else + { + _c4err("missing terminating }"); + } + } + goto mapflow_start; + + mapflow_finish: + _c4dbgp("mapflow: finish"); +} + + +//----------------------------------------------------------------------------- + +template +void ParseEngine::_handle_seq_block() +{ +seqblck_start: + _c4dbgpf("handle_seq_block: seq_id={} node_id={} level={} indent={}", m_evt_handler->m_parent->node_id, m_evt_handler->m_curr->node_id, m_evt_handler->m_curr->level, m_evt_handler->m_curr->indref); + + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_all(RSEQ), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_all(RBLCK), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RVAL|RNXT), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, 1 == (has_any(RVAL) + has_any(RNXT)), m_evt_handler->m_curr->pos); + + _maybe_skip_comment_strict(); + if(!m_evt_handler->m_curr->line_contents.rem.len) + goto seqblck_again; + + if(has_any(RVAL)) + { + _c4dbgpf("seqblck[RVAL]: col={}", m_evt_handler->m_curr->pos.col); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RNXT), m_evt_handler->m_curr->pos); + if(m_evt_handler->m_curr->at_line_beginning()) + { + _c4dbgpf("seqblck[RVAL]: indref={} indentation={}", m_evt_handler->m_curr->indref+1, m_evt_handler->m_curr->line_contents.indentation); + if(m_evt_handler->m_curr->indentation_ge_extra()) + { + _c4dbgpf("seqblck[RVAL]: skip {} from indentation", m_evt_handler->m_curr->line_contents.indentation); + _line_progressed(m_evt_handler->m_curr->line_contents.indentation); + if(!m_evt_handler->m_curr->line_contents.rem.len) + goto seqblck_again; + } + else if(m_evt_handler->m_curr->indentation_lt_extra()) + { + _c4dbgp("seqblck[RVAL]: smaller indentation than RVAL!"); + if(m_evt_handler->m_curr->indentation_eq()) + { + _c4dbgp("seqblck[RVAL]: smaller indentation than RVAL!"); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->set_val_scalar_plain_empty(); + addrem_flags(RNXT, RVAL); + goto seqblck_again; + } + else + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->indentation_lt(), m_evt_handler->m_curr->pos); + _c4dbgp("seqblck[RVAL]: smaller indentation!"); + _handle_indentation_pop_from_block_seq(); + goto seqblck_finish; + } + } + else if(m_evt_handler->m_curr->line_contents.indentation == npos) + { + _c4dbgp("seqblck[RVAL]: empty line!"); + _line_progressed(m_evt_handler->m_curr->line_contents.rem.len); + goto seqblck_again; + } + } + _RYML_ASSERT_PARSE_(callbacks(), m_evt_handler->m_curr->line_contents.rem.len, m_evt_handler->m_curr->pos); + const size_t startmark = _handle_block_skip_leading_whitespace(); + _c4dbgpf("seqblck[RVAL]: startmark={}", startmark); + if(startmark == npos) + { + _c4dbgp("seqblck[RVAL]: whitespace only"); + goto seqblck_again; + } + const size_t tabmark = _handle_block_get_whitespace_mark(); + const char first = m_evt_handler->m_curr->line_contents.rem.str[0]; + _c4dbgpf("seqblck[RVAL]: first='{}' currcol={}", first, m_evt_handler->m_curr->pos.col - 1); + const size_t startline = m_evt_handler->m_curr->pos.line; + const size_t startindent = m_evt_handler->m_curr->line_contents.current_col() - m_bom_len; + ScannedScalar sc; + if(first == '\'') + { + _c4dbgp("seqblck[RVAL]: single-quoted scalar"); + sc = _scan_scalar_squot(); + if(!_maybe_scan_following_colon()) + { + _c4dbgp("seqblck[RVAL]: set as val"); + _handle_annotations_before_blck_val_scalar(); + csubstr maybe_filtered = _maybe_filter_val_scalar_squot(sc); // VAL! + m_evt_handler->set_val_scalar_squoted(maybe_filtered); + addrem_flags(RNXT, RVAL); + } + else + { + _c4dbgp("seqblck[RVAL]: start mapblck, set scalar as key"); + _handle_block_check_leading_tabs(startmark); + addrem_flags(RNXT, RVAL); + _handle_annotations_before_start_mapblck(startline); + _handle_colon(); + m_evt_handler->begin_map_val_block(); + _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); + csubstr maybe_filtered = _maybe_filter_key_scalar_squot(sc); // KEY! + m_evt_handler->set_key_scalar_squoted(maybe_filtered); + addrem_flags(RMAP|RVAL, RSEQ|RNXT); + _maybe_skip_whitespace_tokens(); + goto seqblck_finish; + } + } + else if(first == '"') + { + _c4dbgp("seqblck[RVAL]: double-quoted scalar"); + sc = _scan_scalar_dquot(); + if(!_maybe_scan_following_colon()) + { + _c4dbgp("seqblck[RVAL]: set as val"); + _handle_annotations_before_blck_val_scalar(); + csubstr maybe_filtered = _maybe_filter_val_scalar_dquot(sc); // VAL! + m_evt_handler->set_val_scalar_dquoted(maybe_filtered); + addrem_flags(RNXT, RVAL); + } + else + { + _c4dbgp("seqblck[RVAL]: start mapblck, set scalar as key"); + addrem_flags(RNXT, RVAL); + _handle_block_check_leading_tabs(startmark); + _handle_annotations_before_start_mapblck(startline); + _handle_colon(); + m_evt_handler->begin_map_val_block(); + _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); + csubstr maybe_filtered = _maybe_filter_key_scalar_dquot(sc); // KEY! + m_evt_handler->set_key_scalar_dquoted(maybe_filtered); + addrem_flags(RMAP|RVAL, RSEQ|RNXT); + _maybe_skip_whitespace_tokens(); + goto seqblck_finish; + } + } + // block scalars can only appear as keys when in QMRK scope + // (ie, after ? tokens), so no need to scan following colon in + // here. + else if(first == '|') + { + _c4dbgp("seqblck[RVAL]: block-literal scalar"); + ScannedBlock sb; + _scan_block(&sb, m_evt_handler->m_curr->indref + 1); + _handle_annotations_before_blck_val_scalar(); + csubstr maybe_filtered = _maybe_filter_val_scalar_literal(sb); + m_evt_handler->set_val_scalar_literal(maybe_filtered); + addrem_flags(RNXT, RVAL); + } + else if(first == '>') + { + _c4dbgp("seqblck[RVAL]: block-folded scalar"); + ScannedBlock sb; + _scan_block(&sb, m_evt_handler->m_curr->indref + 1); + _handle_annotations_before_blck_val_scalar(); + csubstr maybe_filtered = _maybe_filter_val_scalar_folded(sb); + m_evt_handler->set_val_scalar_folded(maybe_filtered); + addrem_flags(RNXT, RVAL); + } + else if(_scan_scalar_plain_seq_blck(&sc)) + { + _c4dbgp("seqblck[RVAL]: plain scalar."); + if(!_maybe_scan_following_colon()) + { + _c4dbgp("seqblck[RVAL]: set as val"); + _handle_annotations_before_blck_val_scalar(); + csubstr maybe_filtered = _maybe_filter_val_scalar_plain(sc, m_evt_handler->m_curr->indref); // VAL! + m_evt_handler->set_val_scalar_plain(maybe_filtered); + addrem_flags(RNXT, RVAL); + } + else + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->indref != npos, m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, startindent > m_evt_handler->m_curr->indref, m_evt_handler->m_curr->pos); + _c4dbgp("seqblck[RVAL]: start mapblck, set scalar as key"); + _handle_block_check_leading_tabs(startmark, tabmark); + addrem_flags(RNXT, RVAL); + _handle_annotations_before_start_mapblck(startline); + _handle_colon(); + m_evt_handler->begin_map_val_block(); + _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); + csubstr maybe_filtered = _maybe_filter_key_scalar_plain(sc, m_evt_handler->m_curr->indref); // KEY! + m_evt_handler->set_key_scalar_plain(maybe_filtered); + addrem_flags(RMAP|RVAL, RSEQ|RNXT); + _maybe_skip_whitespace_tokens(); + goto seqblck_finish; + } + } + else if(first == '[') + { + _c4dbgp("seqblck[RVAL]: start child seqflow"); + addrem_flags(RNXT, RVAL); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->begin_seq_val_flow(); + addrem_flags(RFLOW|RVAL, RBLCK|RNXT); + _line_progressed(1); + _set_indentation(m_evt_handler->m_parent->indref + 1u); + goto seqblck_finish; + } + else if(first == '{') + { + _c4dbgp("seqblck[RVAL]: start child mapflow"); + addrem_flags(RNXT, RVAL); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->begin_map_val_flow(); + addrem_flags(RMAP|RKEY|RFLOW, RBLCK|RSEQ|RVAL|RNXT); + _line_progressed(1); + _set_indentation(m_evt_handler->m_parent->indref + 1u); + goto seqblck_finish; + } + else if(first == '-') + { + _c4dbgp("seqblck[RVAL]: dash"); + _handle_block_check_leading_tabs(startmark); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->indref != npos, m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, startindent > m_evt_handler->m_curr->indref, m_evt_handler->m_curr->pos); + _c4dbgp("seqblck[RVAL]: start child seqblck"); + _RYML_ASSERT_PARSE_(this->callbacks(), startindent > m_evt_handler->m_curr->indref, m_evt_handler->m_curr->pos); + addrem_flags(RNXT, RVAL); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->begin_seq_val_block(); + addrem_flags(RVAL, RNXT); + _set_indentation(startindent); + // keep going on inside this function + _line_progressed(1); + } + else if(first == ':') + { + _c4dbgp("seqblck[RVAL]: start child mapblck with empty key"); + addrem_flags(RNXT, RVAL); + _handle_annotations_before_start_mapblck(startline); + _handle_colon(); + m_evt_handler->begin_map_val_block(); + _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); + m_evt_handler->set_key_scalar_plain_empty(); + addrem_flags(RMAP|RVAL, RSEQ|RNXT); + _line_progressed(1); + _maybe_skip_whitespace_tokens(); + goto seqblck_finish; + } + else if(first == '&') + { + const csubstr anchor = _scan_anchor(); + _c4dbgpf("seqblck[RVAL]: anchor! {}", _prs(anchor)); + // we need to buffer the anchors, as there may be two + // consecutive anchors in here + _add_annotation(&m_pending_anchors, anchor, startindent, startline); + } + else if(first == '*') + { + csubstr ref = _scan_ref_seq(); + _c4dbgpf("seqblck[RVAL]: ref! {}", _prs(ref)); + if(!_maybe_scan_following_colon()) + { + _c4dbgp("seqblck[RVAL]: set ref as val!"); + _handle_valref(ref); + addrem_flags(RNXT, RVAL); + } + else + { + _c4dbgp("seqblck[RVAL]: ref is key of map"); + addrem_flags(RNXT, RVAL); + _handle_annotations_before_start_mapblck(startline); + m_evt_handler->begin_map_val_block(); + _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); + _handle_keyref(ref); + addrem_flags(RMAP|RVAL, RSEQ|RNXT); + _set_indentation(startindent); + _maybe_skip_whitespace_tokens(); + goto seqblck_finish; + } + } + else if(first == '!') + { + csubstr tag = _scan_tag(); + _c4dbgpf("seqblck[RVAL]: val tag! {}", _prs(tag)); + // we need to buffer the tags, as there may be two + // consecutive tags in here + _add_annotation(&m_pending_tags, tag, startindent, startline); + } + else if(first == '?') + { + _c4dbgp("seqblck[RVAL]: start child mapblck, explicit key"); + addrem_flags(RNXT, RVAL); + m_evt_handler->begin_map_val_block(); + addrem_flags(RMAP|QMRK, RSEQ|RNXT); + _set_indentation(startindent); + _line_progressed(1); + _maybe_skipchars(' '); + if(_is_blck_seq_token_maybe(m_evt_handler->m_curr->line_contents.rem)) + { + _c4dbgp("seqblck[RVAL]: seqblck starts after ?"); + addrem_flags(RKCL, QMRK); + m_evt_handler->begin_seq_key_block(); + addrem_flags(RSEQ|RVAL, RMAP|RKCL); + _save_indentation(); + _line_progressed(1); + _maybe_skipchars(' '); + } + goto seqblck_finish; + } + else + { + _c4err("parse error"); + } + } + else // RNXT + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RNXT), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RVAL), m_evt_handler->m_curr->pos); + // + // handle indentation + // + _c4dbgpf("seqblck[RNXT]: indref={} indentation={}", m_evt_handler->m_curr->indref, m_evt_handler->m_curr->line_contents.indentation); + if(C4_LIKELY(m_evt_handler->m_curr->at_line_beginning())) + { + _c4dbgp("seqblck[RNXT]: at line begin"); + if(m_evt_handler->m_curr->indentation_ge()) + { + _c4dbgpf("seqblck[RNXT]: skip {} from indref", m_evt_handler->m_curr->indref); + _line_progressed(m_evt_handler->m_curr->indref); + if(!m_evt_handler->m_curr->line_contents.rem.len) + goto seqblck_again; + } + else if(m_evt_handler->m_curr->indentation_lt()) + { + _c4dbgp("seqblck[RNXT]: smaller indentation!"); + _handle_indentation_pop_from_block_seq(); + if(has_all(RSEQ|RBLCK)) + { + _c4dbgp("seqblck[RNXT]: still seqblck!"); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RNXT), m_evt_handler->m_curr->pos); + _line_progressed(m_evt_handler->m_curr->line_contents.indentation); + if(!m_evt_handler->m_curr->line_contents.rem.len) + goto seqblck_again; // LCOV_EXCL_LINE + } + else + { + _c4dbgp("seqblck[RNXT]: no longer seqblck!"); + goto seqblck_finish; + } + } + else if(m_evt_handler->m_curr->line_contents.indentation == npos) + { + _c4dbgpf("seqblck[RNXT]: blank line, len={}", m_evt_handler->m_curr->line_contents.rem); + _line_progressed(m_evt_handler->m_curr->line_contents.rem.len); + if(!m_evt_handler->m_curr->line_contents.rem.len) + goto seqblck_again; // LCOV_EXCL_LINE + } + } + else + { + _c4dbgp("seqblck[RNXT]: NOT at line begin"); + if(!m_evt_handler->m_curr->line_contents.rem.begins_with_any(" \t")) + { + _c4err("parse error"); + } + else + { + _skipchars(" \t"); + if(!m_evt_handler->m_curr->line_contents.rem.len) + { + _c4dbgp("seqblck[RNXT]: again"); + goto seqblck_again; // LCOV_EXCL_LINE + } + } + } + // + // now handle the tokens + // + _c4assert(m_evt_handler->m_curr->line_contents.rem.len > 0); + const char first = m_evt_handler->m_curr->line_contents.rem.str[0]; + _c4dbgpf("seqblck[RNXT]: '{}' node_id={}", _c4prc(first), m_evt_handler->m_curr->node_id); + if(first == '-') + { + if(m_evt_handler->m_curr->indref > 0 + || m_evt_handler->m_curr->line_contents.indentation > 0 + || !_is_doc_begin_token(m_evt_handler->m_curr->line_contents.rem)) + { + if(C4_LIKELY(_is_blck_seq_token_maybe(m_evt_handler->m_curr->line_contents.rem))) + { + _c4dbgp("seqblck[RNXT]: expect next val"); + addrem_flags(RVAL, RNXT); + m_evt_handler->add_sibling(); + _line_progressed(1); + } + else + { + _c4err("parse error"); + } + } + else + { + _c4dbgp("seqblck[RNXT]: start doc"); + _start_doc_suddenly(); + _line_progressed(3); + _maybe_skip_whitespace_tokens(); + goto seqblck_finish; + } + } + else if(first == ':') + { + // This happens for example in `- [a: b]: c` (after + // terminating the seq, ie, after `]`). All other cases + // (ie colon after scalars) are caught elsewhere (ie, in + // RVAL state). + if(C4_LIKELY(m_evt_handler->m_parent && (m_evt_handler->m_parent->flags & RMAP))) + { + _c4dbgp("seqblck[RNXT]: actually this seq was '?' key of parent map"); + m_evt_handler->end_seq_block(); + goto seqblck_finish; + } + else + { + _c4err("parse error"); + } + } + else if(first == '.') + { + _c4dbgp("seqblck[RNXT]: maybe doc?"); + if(_is_doc_end_token(m_evt_handler->m_curr->line_contents.rem)) + { + _c4dbgp("seqblck[RNXT]: end doc"); + _end_doc_suddenly(); + _line_progressed(3); + _maybe_skip_whitespace_tokens(); + _check_doc_end_tokens(); + goto seqblck_finish; + } + else + { + _c4err("parse error"); + } + } + else + { + // may be an indentless sequence nested in a map... + #ifdef RYML_DBG + _print_state_stack(); + #endif + if(m_evt_handler->m_parent + && has_all(RMAP|RBLCK, m_evt_handler->m_parent) + && m_evt_handler->m_curr->indref == m_evt_handler->m_parent->indref) + { + _c4dbgpf("seqblck[RNXT]: end indentless seq, go to parent={}. node={}", m_evt_handler->m_parent->node_id, m_evt_handler->m_curr->node_id); + _RYML_ASSERT_PARSE_(this->callbacks(), m_evt_handler->m_curr != m_evt_handler->m_parent, m_evt_handler->m_curr->pos); + _handle_indentation_pop(m_evt_handler->m_parent); + _RYML_ASSERT_PARSE_(this->callbacks(), has_all(RMAP|RBLCK), m_evt_handler->m_curr->pos); + m_evt_handler->add_sibling(); + addrem_flags(RKEY, RNXT); + goto seqblck_finish; + } + else if(first == '\t') + { + size_t pos = m_evt_handler->m_curr->line_contents.rem.first_not_of('\t'); + if(pos == npos) + { + _line_progressed(m_evt_handler->m_curr->line_contents.rem.len); + goto seqblck_again; + } + } + _c4err("parse error"); + } + } + + seqblck_again: + _c4dbgt("seqblck: go again", 0); + if(_finished_line()) + { + m_bom_len = 0; + _line_ended(); + _scan_line(); + if(_finished_file()) + { + _c4dbgp("seqblck: finish!"); + _end_seq_blck(); + goto seqblck_finish; + } + _c4dbgnextline(); + } + goto seqblck_start; + + seqblck_finish: + _c4dbgp("seqblck: finish"); +} + + +//----------------------------------------------------------------------------- + +template +void ParseEngine::_handle_map_block() +{ +mapblck_start: + _c4dbgpf("handle_map_block: map_id={} node_id={} level={} indref={}", m_evt_handler->m_parent->node_id, m_evt_handler->m_curr->node_id, m_evt_handler->m_curr->level, m_evt_handler->m_curr->indref); + + // states: RKEY -> RVAL -> RNXT + // states: QMRK -> RKCL -> RVAL -> RNXT + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_all(RMAP), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_all(RBLCK), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RKEY|RKCL|RVAL|RNXT|QMRK), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, 1 == (has_any(RKEY) + has_any(RKCL) + has_any(RVAL) + has_any(RNXT) + has_any(QMRK)), m_evt_handler->m_curr->pos); + + _maybe_skip_comment(); + if(!m_evt_handler->m_curr->line_contents.rem.len) + goto mapblck_again; + + if(has_any(RKEY)) + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RKCL), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(QMRK), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RVAL), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RNXT), m_evt_handler->m_curr->pos); + // + // handle indentation + // + if(m_evt_handler->m_curr->at_line_beginning()) + { + if(m_evt_handler->m_curr->indentation_eq()) + { + _c4dbgpf("mapblck[RKEY]: skip {} from indref", m_evt_handler->m_curr->indref); + _line_progressed(m_evt_handler->m_curr->indref); + if(!m_evt_handler->m_curr->line_contents.rem.len) + goto mapblck_again; + } + else if(m_evt_handler->m_curr->indentation_lt()) + { + _c4dbgp("mapblck[RKEY]: smaller indentation!"); + _handle_indentation_pop_from_block_map(); + _line_progressed(m_evt_handler->m_curr->line_contents.indentation); + if(has_all(RMAP|RBLCK)) + { + _c4dbgp("mapblck[RKEY]: still mapblck!"); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_any(RKEY), m_evt_handler->m_curr->pos); + if(!m_evt_handler->m_curr->line_contents.rem.len) + goto mapblck_again; + } + else + { + _c4dbgp("mapblck[RKEY]: no longer mapblck!"); + goto mapblck_finish; + } + } + else + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->indentation_gt(), m_evt_handler->m_curr->pos); + _c4err("invalid indentation"); + } + } + // + // now handle the tokens + // + const char first = m_evt_handler->m_curr->line_contents.rem.str[0]; + const size_t startline = m_evt_handler->m_curr->pos.line; + const size_t startindent = m_evt_handler->m_curr->line_contents.current_col(); + _c4dbgpf("mapblck[RKEY]: '{}'", _c4prc(first)); + ScannedScalar sc; + if(first == '\'') + { + _c4dbgp("mapblck[RKEY]: scanning single-quoted scalar"); + sc = _scan_scalar_squot(); + csubstr maybe_filtered = _maybe_filter_val_scalar_squot(sc); + _handle_annotations_before_blck_key_scalar(); + m_evt_handler->set_key_scalar_squoted(maybe_filtered); + addrem_flags(RVAL, RKEY); + if(!_maybe_scan_following_colon()) + _c4err("could not find ':' colon after key"); + _handle_colon(); + _maybe_skip_whitespace_tokens(); + } + else if(first == '"') + { + _c4dbgp("mapblck[RKEY]: scanning double-quoted scalar"); + sc = _scan_scalar_dquot(); + csubstr maybe_filtered = _maybe_filter_val_scalar_dquot(sc); + _handle_annotations_before_blck_key_scalar(); + m_evt_handler->set_key_scalar_dquoted(maybe_filtered); + addrem_flags(RVAL, RKEY); + if(!_maybe_scan_following_colon()) + _c4err("could not find ':' colon after key"); + _handle_colon(); + _maybe_skip_whitespace_tokens(); + } + // block scalars (| and >) can not be used as keys unless they + // appear in an explicit QMRK scope (ie, after the ? token), + else if(C4_UNLIKELY(first == '|')) + { + _c4err("block map: literal keys must be enclosed in '?'"); + } + else if(C4_UNLIKELY(first == '>')) + { + _c4err("block map: folded keys must be enclosed in '?'"); + } + else if(_scan_scalar_plain_map_blck(&sc)) + { + _c4dbgp("mapblck[RKEY]: plain scalar"); + csubstr maybe_filtered = _maybe_filter_val_scalar_plain(sc, m_evt_handler->m_curr->indref); + _handle_annotations_before_blck_key_scalar(); + m_evt_handler->set_key_scalar_plain(maybe_filtered); + addrem_flags(RVAL, RKEY); + if(!_maybe_scan_following_colon()) + _c4err("could not find ':' colon after key"); + _handle_colon(); + _maybe_skip_whitespace_tokens(); + } + else if(first == '?') + { + _c4dbgp("mapblck[RKEY]: key token!"); + addrem_flags(QMRK, RKEY); + _line_progressed(1); + _maybe_skipchars(' '); + if(_is_blck_seq_token_maybe(m_evt_handler->m_curr->line_contents.rem)) + { + _c4dbgp("mapblck[RKEY]: seqblck starts after ?"); + addrem_flags(RKCL, QMRK); + m_evt_handler->begin_seq_key_block(); + addrem_flags(RSEQ|RVAL, RMAP|RKCL); + _save_indentation(); + _line_progressed(1); + _maybe_skipchars(' '); + goto mapblck_finish; + } + goto mapblck_again; + } + else if(first == ':') + { + _c4dbgp("mapblck[RKEY]: setting empty key"); + _handle_annotations_before_blck_key_scalar(); + m_evt_handler->set_key_scalar_plain_empty(); + addrem_flags(RVAL, RKEY); + _line_progressed(1); + _handle_colon(); + _maybe_skip_whitespace_tokens(); + } + else if(first == '*') + { + csubstr ref = _scan_ref_map(); + _c4dbgpf("mapblck[RKEY]: key ref! {}", _prs(ref)); + _handle_keyref(ref); + addrem_flags(RVAL, RKEY); + if(!_maybe_scan_following_colon()) + _c4err("could not find ':' colon after key"); + _handle_colon(); + _maybe_skip_whitespace_tokens(); + } + else if(first == '&') + { + csubstr anchor = _scan_anchor(); + _c4dbgpf("mapblck[RKEY]: key anchor! {}", _prs(anchor)); + _add_annotation(&m_pending_anchors, anchor, startindent, startline); + } + else if(first == '!') + { + csubstr tag = _scan_tag(); + _c4dbgpf("mapblck[RKEY]: key tag! {}", _prs(tag)); + _add_annotation(&m_pending_tags, tag, startindent, startline); + } + else if(first == '[') + { + // RYML's tree cannot store container keys, but that's + // handled inside the tree handler. Other handlers may be + // able to handle it. + _c4dbgp("mapblck[RKEY]: start child seqflow (!)"); + _handle_annotations_before_blck_key_scalar(); + m_evt_handler->begin_seq_key_flow(); + addrem_flags(RSEQ|RFLOW|RVAL, RKEY|RMAP|RBLCK); + _line_progressed(1); + _set_indentation(startindent); + goto mapblck_finish; + } + else if(first == '{') + { + // RYML's tree cannot store container keys, but that's + // handled inside the tree handler. Other handlers may be + // able to handle it. + _c4dbgp("mapblck[RKEY]: start child mapflow (!)"); + _handle_annotations_before_blck_key_scalar(); + m_evt_handler->begin_map_key_flow(); + addrem_flags(RFLOW|RKEY, RBLCK); + _line_progressed(1); + _set_indentation(startindent); + goto mapblck_finish; + } + else if(first == '-') + { + _c4dbgp("mapblck[RKEY]: maybe doc?"); + if(m_evt_handler->m_curr->line_contents.indentation == 0 && _is_doc_begin_token(m_evt_handler->m_curr->line_contents.rem)) + { + _c4dbgp("mapblck[RKEY]: end+start doc"); + _start_doc_suddenly(); + _line_progressed(3); + _maybe_skip_whitespace_tokens(); + goto mapblck_finish; + } + else + { + _c4err("parse error"); + } + } + else if(first == '.') + { + _c4dbgp("mapblck[RKEY]: maybe end doc?"); + if(m_evt_handler->m_curr->line_contents.indentation == 0 && _is_doc_end_token(m_evt_handler->m_curr->line_contents.rem)) + { + _c4dbgp("mapblck[RKEY]: end doc"); + _end_doc_suddenly(); + _line_progressed(3); + _maybe_skip_whitespace_tokens(); + _check_doc_end_tokens(); + goto mapblck_finish; + } + else + { + _c4err("parse error"); // LCOV_EXCL_LINE + } + } + else + { + _c4err("parse error"); + } + } + else if(has_any(RVAL)) + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RKEY), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RKCL), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RNXT), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(QMRK), m_evt_handler->m_curr->pos); + // + // handle indentation + // + if(m_evt_handler->m_curr->at_line_beginning()) + { + _c4dbgpf("mapblck[RVAL]: indref={} indentation={}", m_evt_handler->m_curr->indref+1, m_evt_handler->m_curr->line_contents.indentation); + m_evt_handler->m_curr->more_indented = false; + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->indref != npos, m_evt_handler->m_curr->pos); + if(m_evt_handler->m_curr->indentation_eq_extra()) + { + _c4dbgp("mapblck[RVAL]: skip indentation!"); + _line_progressed(m_evt_handler->m_curr->indref + 1); + if(!m_evt_handler->m_curr->line_contents.rem.len) + goto mapblck_again; + } + else if(m_evt_handler->m_curr->indentation_gt_extra()) + { + _c4dbgp("mapblck[RVAL]: more indented!"); + m_evt_handler->m_curr->more_indented = true; + _line_progressed(m_evt_handler->m_curr->line_contents.indentation); + if(!m_evt_handler->m_curr->line_contents.rem.len) + goto mapblck_again; // LCOV_EXCL_LINE + } + else if(m_evt_handler->m_curr->indentation_lt_extra()) + { + if(m_evt_handler->m_curr->indentation_eq()) + { + _c4dbgp("mapblck[RVAL]: smaller indentation than RVAL!"); + // watchout for indentless seqs + if(!_is_blck_seq_token_maybe(m_evt_handler->m_curr->line_contents.rem.sub(m_evt_handler->m_curr->line_contents.indentation))) + { + _c4dbgp("mapblck[RVAL]: smaller indentation than RVAL!"); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->set_val_scalar_plain_empty(); + addrem_flags(RNXT, RVAL); + goto mapblck_again; + } + } + else + { + _c4dbgp("mapblck[RVAL]: smaller indentation than RKEY!"); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->indentation_lt(), m_evt_handler->m_curr->pos); + _handle_indentation_pop_from_block_map(); + if(has_all(RMAP|RBLCK)) + { + _c4dbgp("mapblck[RVAL]: still mapblck!"); + _line_progressed(m_evt_handler->m_curr->line_contents.indentation); + if(has_any(RNXT)) + { + _c4dbgp("mapblck[RVAL]: speculatively expect next keyval"); + m_evt_handler->add_sibling(); + addrem_flags(RKEY, RNXT); + } + goto mapblck_again; + } + else + { + _c4dbgp("mapblck[RVAL]: no longer mapblck!"); + goto mapblck_finish; + } + } + } + } + const size_t startcol = _handle_block_skip_leading_whitespace(); + if(startcol == npos) + { + _c4dbgp("mapblck[RVAL]: whitespace only"); + goto mapblck_again; // LCOV_EXCL_LINE + } + const size_t tabmark = _handle_block_get_whitespace_mark(); + // + // now handle the tokens + // + _c4assert(m_evt_handler->m_curr->line_contents.rem.len); + const char first = m_evt_handler->m_curr->line_contents.rem.str[0]; + const size_t startline = m_evt_handler->m_curr->pos.line; + const size_t startindent = m_evt_handler->m_curr->line_contents.current_col(); + _c4dbgpf("mapblck[RVAL]: '{}'", _c4prc(first)); + ScannedScalar sc; + if(first == '\'') + { + _c4dbgp("mapblck[RVAL]: scanning single-quoted scalar"); + sc = _scan_scalar_squot(); + if(!_maybe_scan_following_colon()) + { + _c4dbgp("mapblck[RVAL]: set as val"); + _handle_annotations_before_blck_val_scalar(); + csubstr maybe_filtered = _maybe_filter_val_scalar_squot(sc); // VAL! + m_evt_handler->set_val_scalar_squoted(maybe_filtered); + addrem_flags(RNXT, RVAL); + } + else + { + _c4assert(m_evt_handler->m_curr->indref != npos); + _c4assert(startindent > m_evt_handler->m_curr->indref); + _c4dbgp("mapblck[RVAL]: start new block map, set scalar as key"); + _handle_block_check_leading_tabs(startcol); + _handle_annotations_before_start_mapblck(startline); + addrem_flags(RNXT, RVAL); + _handle_colon(); + m_evt_handler->begin_map_val_block(); + _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); + csubstr maybe_filtered = _maybe_filter_key_scalar_squot(sc); // KEY! + m_evt_handler->set_key_scalar_squoted(maybe_filtered); + _maybe_skip_whitespace_tokens(); + // keep the child state on RVAL + addrem_flags(RVAL, RNXT); + } + } + else if(first == '"') + { + _c4dbgp("mapblck[RVAL]: scanning double-quoted scalar"); + sc = _scan_scalar_dquot(); + if(!_maybe_scan_following_colon()) + { + _c4dbgp("mapblck[RVAL]: set as val"); + _handle_annotations_before_blck_val_scalar(); + csubstr maybe_filtered = _maybe_filter_val_scalar_dquot(sc); // VAL! + m_evt_handler->set_val_scalar_dquoted(maybe_filtered); + addrem_flags(RNXT, RVAL); + } + else + { + _c4assert(m_evt_handler->m_curr->indref != npos); + _c4assert(startindent > m_evt_handler->m_curr->indref); + _c4dbgp("mapblck[RVAL]: start new block map, set scalar as key"); + _handle_block_check_leading_tabs(startcol); + _handle_annotations_before_start_mapblck(startline); + addrem_flags(RNXT, RVAL); + _handle_colon(); + m_evt_handler->begin_map_val_block(); + _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); + csubstr maybe_filtered = _maybe_filter_key_scalar_dquot(sc); // KEY! + m_evt_handler->set_key_scalar_dquoted(maybe_filtered); + _maybe_skip_whitespace_tokens(); + // keep the child state on RVAL + addrem_flags(RVAL, RNXT); + } + } + // block scalars can only appear as keys when in QMRK scope + // (ie, after ? tokens), so no need to scan following colon + else if(first == '|') + { + _c4dbgp("mapblck[RVAL]: scanning block-literal scalar"); + ScannedBlock sb; + _scan_block(&sb, m_evt_handler->m_curr->indref + 1); + _handle_annotations_before_blck_val_scalar(); + csubstr maybe_filtered = _maybe_filter_val_scalar_literal(sb); + m_evt_handler->set_val_scalar_literal(maybe_filtered); + addrem_flags(RNXT, RVAL); + } + else if(first == '>') + { + _c4dbgp("mapblck[RVAL]: scanning block-folded scalar"); + ScannedBlock sb; + _scan_block(&sb, m_evt_handler->m_curr->indref + 1); + _handle_annotations_before_blck_val_scalar(); + csubstr maybe_filtered = _maybe_filter_val_scalar_folded(sb); + m_evt_handler->set_val_scalar_folded(maybe_filtered); + addrem_flags(RNXT, RVAL); + } + else if(_scan_scalar_plain_map_blck(&sc)) + { + _c4dbgp("mapblck[RVAL]: plain scalar."); + if(!_maybe_scan_following_colon()) + { + _c4dbgp("mapblck[RVAL]: set as val"); + _handle_annotations_before_blck_val_scalar(); + csubstr maybe_filtered = _maybe_filter_val_scalar_plain(sc, m_evt_handler->m_curr->indref); // VAL! + m_evt_handler->set_val_scalar_plain(maybe_filtered); + addrem_flags(RNXT, RVAL); + } + else + { + _c4assert(m_evt_handler->m_curr->indref != npos); + _c4assert(startindent > m_evt_handler->m_curr->indref); + _c4dbgpf("mapblck[RVAL]: start new block map, set scalar as key {}", m_evt_handler->m_curr->indref); + _handle_block_check_leading_tabs(startcol, tabmark); + addrem_flags(RNXT, RVAL); + _handle_annotations_before_start_mapblck(startline); + _handle_colon(); + m_evt_handler->begin_map_val_block(); + _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); + csubstr maybe_filtered = _maybe_filter_key_scalar_plain(sc, m_evt_handler->m_curr->indref); // KEY! + m_evt_handler->set_key_scalar_plain(maybe_filtered); + _maybe_skip_whitespace_tokens(); + // keep the child state on RVAL + addrem_flags(RVAL, RNXT); + } + } + else if(first == '-' && _is_blck_seq_token_maybe(m_evt_handler->m_curr->line_contents.rem)) + { + if(C4_UNLIKELY(!m_evt_handler->m_curr->at_first_token())) + _c4err("parse error"); + _c4dbgp("mapblck[RVAL]: start val seqblck"); + _handle_block_check_leading_tabs(startcol); + addrem_flags(RNXT, RVAL); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->begin_seq_val_block(); + addrem_flags(RSEQ|RVAL, RMAP|RNXT); + _set_indentation(startindent); + _line_progressed(1); + _maybe_skip_whitespace_tokens(); + goto mapblck_finish; + } + else if(first == '[') + { + _c4dbgp("mapblck[RVAL]: start val seqflow"); + addrem_flags(RNXT, RVAL); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->begin_seq_val_flow(); + addrem_flags(RSEQ|RFLOW|RVAL, RMAP|RBLCK|RNXT); + _set_indentation(m_evt_handler->m_parent->indref + 1u); + _line_progressed(1); + goto mapblck_finish; + } + else if(first == '{') + { + _c4dbgp("mapblck[RVAL]: start val mapflow"); + addrem_flags(RNXT, RVAL); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->begin_map_val_flow(); + addrem_flags(RKEY|RFLOW, RBLCK|RVAL|RNXT); + m_evt_handler->m_curr->scalar_col = m_evt_handler->m_curr->line_contents.indentation; + _set_indentation(m_evt_handler->m_parent->indref + 1u); + _line_progressed(1); + goto mapblck_finish; + } + else if(first == '*') + { + csubstr ref = _scan_ref_map(); + _c4dbgpf("mapblck[RVAL]: ref! {}", _prs(ref)); + if(_maybe_scan_following_colon()) + { + _c4dbgp("mapblck[RVAL]: start child map, block"); + addrem_flags(RNXT, RVAL); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->begin_map_val_block(); + _handle_keyref(ref); + _set_indentation(startindent); + // keep going in RVAL + addrem_flags(RVAL, RNXT); + } + else + { + _c4dbgp("mapblck[RVAL]: was val ref"); + _handle_valref(ref); + addrem_flags(RNXT, RVAL); + } + _maybe_skip_whitespace_tokens(); + } + else if(first == '&') + { + csubstr anchor = _scan_anchor(); + _c4dbgpf("mapblck[RVAL]: anchor! {}", _prs(anchor)); + // we need to buffer the anchors, as there may be two + // consecutive anchors in here + _add_annotation(&m_pending_anchors, anchor, startindent, startline); + } + else if(first == '!') + { + csubstr tag = _scan_tag(); + _c4dbgpf("mapblck[RVAL]: tag! {}", _prs(tag)); + // we need to buffer the tags, as there may be two + // consecutive tags in here + _add_annotation(&m_pending_tags, tag, startindent, startline); + } + else if(first == '?') + { + if(C4_UNLIKELY(!m_evt_handler->m_curr->at_first_token())) + _c4err("parse error"); + _c4dbgp("mapblck[RVAL]: start val mapblck"); + addrem_flags(RNXT, RVAL); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->begin_map_val_block(); + addrem_flags(QMRK, RNXT); + _set_indentation(startindent); + _line_progressed(1); + _maybe_skipchars(' '); + if(_is_blck_seq_token_maybe(m_evt_handler->m_curr->line_contents.rem)) + { + _c4dbgp("mapblck[RVAL]: seqblck starts after ?"); + addrem_flags(RKCL, QMRK); + m_evt_handler->begin_seq_key_block(); + addrem_flags(RSEQ|RVAL, RMAP|RKCL); + _save_indentation(); + _line_progressed(1); + _maybe_skipchars(' '); + goto mapblck_finish; + } + goto mapblck_again; + } + else if(first == ':') + { + _c4dbgp("mapblck[RVAL]: start val mapblck"); + addrem_flags(RNXT, RVAL); + _handle_annotations_before_start_mapblck(startline); + _handle_colon(); + m_evt_handler->begin_map_val_block(); + _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); + m_evt_handler->set_key_scalar_plain_empty(); + // keep the child state on RVAL + addrem_flags(RVAL, RNXT); + _line_progressed(1); + _maybe_skip_whitespace_tokens(); + goto mapblck_again; + } + else + { + _c4err("parse error"); // LCOV_EXCL_LINE + } + } + else if(has_any(RNXT)) + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RKEY), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RKCL), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RVAL), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(QMRK), m_evt_handler->m_curr->pos); + // + // handle indentation + // + if(m_evt_handler->m_curr->at_line_beginning()) + { + _c4dbgpf("mapblck[RNXT]: indref={} indentation={}", m_evt_handler->m_curr->indref, m_evt_handler->m_curr->line_contents.indentation); + if(m_evt_handler->m_curr->indentation_eq()) + { + _c4dbgpf("mapblck[RNXT]: skip {} from indref", m_evt_handler->m_curr->indref); + _line_progressed(m_evt_handler->m_curr->indref); + _c4dbgp("mapblck[RNXT]: speculatively expect next keyval"); + m_evt_handler->add_sibling(); + addrem_flags(RKEY, RNXT); + goto mapblck_again; + } + else if(m_evt_handler->m_curr->indentation_lt()) + { + _c4dbgp("mapblck[RNXT]: smaller indentation!"); + _handle_indentation_pop_from_block_map(); + if(has_all(RMAP|RBLCK)) + { + _line_progressed(m_evt_handler->m_curr->line_contents.indentation); + if(!has_any(RKCL)) + { + _c4dbgp("mapblck[RNXT]: speculatively expect next keyval"); + m_evt_handler->add_sibling(); + addrem_flags(RKEY, RNXT); + } + goto mapblck_again; + } + else + { + goto mapblck_finish; + } + } + } + else + { + _c4dbgp("mapblck[RNXT]: NOT at line begin"); + if(!m_evt_handler->m_curr->line_contents.rem.begins_with_any(" \t")) + { + _c4err("parse error"); + } + else + { + _skipchars(" \t"); + if(!m_evt_handler->m_curr->line_contents.rem.len) + { + _c4dbgp("seqblck[RNXT]: again"); + goto mapblck_again; // LCOV_EXCL_LINE + } + } + } + // + // handle tokens + // + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->line_contents.rem.len > 0, m_evt_handler->m_curr->pos); + const char first = m_evt_handler->m_curr->line_contents.rem.str[0]; + _c4dbgpf("mapblck[RNXT]: '{}'", _c4prc(first)); + if(first == ' ') + { + _c4dbgp("mapblck[RNXT]: skip spaces"); + _maybe_skip_whitespace_tokens(); + } + else + { + _c4err("parse error"); + } + } + else if(has_any(QMRK)) + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RKEY), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RKCL), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RVAL), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RNXT), m_evt_handler->m_curr->pos); + if(_handle_map_block_qmrk()) + goto mapblck_again; + else + goto mapblck_finish; + } + else if(has_any(RKCL)) // read the key colon (after QMRK) + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RKEY), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RVAL), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RNXT), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(QMRK), m_evt_handler->m_curr->pos); + if(_handle_map_block_rkcl()) + goto mapblck_again; + else + goto mapblck_finish; + } + + mapblck_again: + _c4dbgt("mapblck: again", 0); + if(_finished_line()) + { + _line_ended(); + _scan_line(); + if(_finished_file()) + { + _c4dbgp("mapblck: file finished!"); + _end_map_blck(); + goto mapblck_finish; + } + _c4dbgnextline(); + } + goto mapblck_start; + + mapblck_finish: + _c4dbgp("mapblck: finish"); +} + + +//----------------------------------------------------------------------------- + +// return true if we should remain in map_block +template +bool ParseEngine::_handle_map_block_qmrk() +{ + // + // handle indentation + // + if(m_evt_handler->m_curr->at_line_beginning()) + { + _c4dbgpf("mapblck[QMRK]: at line beginning. ind={} indref={}", m_evt_handler->m_curr->line_contents.indentation, m_evt_handler->m_curr->indref); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->line_contents.indentation != npos, m_evt_handler->m_curr->pos); + if(m_evt_handler->m_curr->indentation_eq_extra()) + { + _c4dbgpf("mapblck[QMRK]: skip {} from indref", m_evt_handler->m_curr->indref + 1); + _line_progressed(m_evt_handler->m_curr->indref + 1); + if(!m_evt_handler->m_curr->line_contents.rem.len) + return true; // go again + } + // indentation can be larger in QMRK state + else if(m_evt_handler->m_curr->indentation_gt_extra()) + { + _c4dbgp("mapblck[QMRK]: larger indentation !"); + _line_progressed(m_evt_handler->m_curr->line_contents.indentation); + if(!m_evt_handler->m_curr->line_contents.rem.len) + return true; // go again + } + else + { + _c4dbgp("mapblck[QMRK]: smaller indentation!"); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->indentation_lt_extra(), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->line_contents.rem.len > 0, m_evt_handler->m_curr->pos); + if(m_evt_handler->m_curr->indentation_eq() + // defend against docs or indentless seqs + && m_evt_handler->m_curr->line_contents.rem.str[0] != '-') + { + _c4dbgp("mapblck[QMRK]: QMRK finished!"); + _handle_annotations_before_blck_key_scalar(); + m_evt_handler->set_key_scalar_plain_empty(); + addrem_flags(RKCL, QMRK); + return true; // go again + } + else if(m_evt_handler->m_curr->indentation_lt()) + { + _c4dbgp("mapblck[QMRK]: indentation pop!"); + _handle_indentation_pop_from_block_map(); + _line_progressed(m_evt_handler->m_curr->line_contents.indentation); + if(has_all(RMAP|RBLCK)) + { + _c4dbgp("mapblck[QMRK]: still mapblck!"); + return true; // go again + } + else + { + _c4dbgp("mapblck[QMRK]: no longer mapblck!"); + return false; // finish mapblck + } + } + } + } + // + // now handle the tokens + // + _c4assert(m_evt_handler->m_curr->line_contents.rem.len); + const char first = m_evt_handler->m_curr->line_contents.rem.str[0]; + const size_t startline = m_evt_handler->m_curr->pos.line; + const size_t startindent = m_evt_handler->m_curr->line_contents.current_col(); + _c4dbgpf("mapblck[QMRK]: '{}'", first); + ScannedScalar sc; + if(first == '\'') + { + _c4dbgp("mapblck[QMRK]: scanning single-quoted scalar"); + sc = _scan_scalar_squot(); + csubstr maybe_filtered = _maybe_filter_key_scalar_squot(sc); // KEY! + addrem_flags(RKCL, QMRK); + if(!_maybe_scan_following_colon()) + { + _c4dbgp("mapblck[QMRK]: set as key"); + _handle_annotations_before_blck_key_scalar(); + m_evt_handler->set_key_scalar_squoted(maybe_filtered); + } + else + { + _c4dbgp("mapblck[QMRK]: start new block map as key (!), set scalar as key"); + _handle_annotations_before_start_mapblck_as_key(); + m_evt_handler->begin_map_key_block(); + _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); + m_evt_handler->set_key_scalar_squoted(maybe_filtered); + _maybe_skip_whitespace_tokens(); + _set_indentation(startindent); + // keep the child state on RVAL + addrem_flags(RVAL, RKCL); + } + } + else if(first == '"') + { + _c4dbgp("mapblck[QMRK]: scanning double-quoted scalar"); + sc = _scan_scalar_dquot(); + csubstr maybe_filtered = _maybe_filter_key_scalar_dquot(sc); // KEY! + addrem_flags(RKCL, QMRK); + if(!_maybe_scan_following_colon()) + { + _c4dbgp("mapblck[QMRK]: set as key"); + _handle_annotations_before_blck_key_scalar(); + m_evt_handler->set_key_scalar_dquoted(maybe_filtered); + } + else + { + _c4dbgp("mapblck[QMRK]: start new block map as key (!), set scalar as key"); + _handle_annotations_before_start_mapblck_as_key(); + m_evt_handler->begin_map_key_block(); + _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); + m_evt_handler->set_key_scalar_dquoted(maybe_filtered); + _maybe_skip_whitespace_tokens(); + _set_indentation(startindent); + // keep the child state on RVAL + addrem_flags(RVAL, RKCL); + } + } + else if(first == '|') + { + _c4dbgp("mapblck[QMRK]: scanning block-literal scalar"); + ScannedBlock sb; + _scan_block(&sb, m_evt_handler->m_curr->indref + 1); + csubstr maybe_filtered = _maybe_filter_key_scalar_literal(sb); // KEY! + _handle_annotations_before_blck_key_scalar(); + m_evt_handler->set_key_scalar_literal(maybe_filtered); + addrem_flags(RKCL, QMRK); + } + else if(first == '>') + { + _c4dbgp("mapblck[QMRK]: scanning block-literal scalar"); + ScannedBlock sb; + _scan_block(&sb, m_evt_handler->m_curr->indref + 1); + csubstr maybe_filtered = _maybe_filter_key_scalar_folded(sb); // KEY! + _handle_annotations_before_blck_key_scalar(); + m_evt_handler->set_key_scalar_folded(maybe_filtered); + addrem_flags(RKCL, QMRK); + } + else if(_scan_scalar_plain_map_blck(&sc)) + { + _c4dbgp("mapblck[QMRK]: plain scalar"); + csubstr maybe_filtered = _maybe_filter_key_scalar_plain(sc, m_evt_handler->m_curr->indref); // KEY! + addrem_flags(RKCL, QMRK); + if(!_maybe_scan_following_colon()) + { + _c4dbgp("mapblck[QMRK]: set as key"); + _handle_annotations_before_blck_key_scalar(); + m_evt_handler->set_key_scalar_plain(maybe_filtered); + } + else + { + _c4dbgp("mapblck[QMRK]: start new block map as key (!), set scalar as key"); + _handle_annotations_before_start_mapblck_as_key(); + m_evt_handler->begin_map_key_block(); + _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); + m_evt_handler->set_key_scalar_plain(maybe_filtered); + _maybe_skip_whitespace_tokens(); + _set_indentation(startindent); + // keep the child state on RVAL + addrem_flags(RVAL, RKCL); + } + } + else if(first == ':') + { + _c4dbgp("mapblck[QMRK]: start new block map as key (!), empty key"); + addrem_flags(RKCL, QMRK); + _handle_annotations_before_start_mapblck_as_key(); + m_evt_handler->begin_map_key_block(); + _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); + m_evt_handler->set_key_scalar_plain_empty(); + _line_progressed(1); + _maybe_skip_whitespace_tokens(); + _set_indentation(startindent); + // keep the child state on RVAL + addrem_flags(RVAL, RKCL); + } + else if(first == '*') + { + csubstr ref = _scan_ref_map(); + _c4dbgpf("mapblck[QMRK]: key ref! {}", _prs(ref)); + addrem_flags(RKCL, QMRK); + if(!_maybe_scan_following_colon()) + { + _c4dbgp("mapblck[QMRK]: set ref as key"); + _handle_keyref(ref); + } + else + { + _c4dbgp("mapblck[QMRK]: start new block map as key (!), set ref as key"); + _handle_annotations_before_start_mapblck_as_key(); + m_evt_handler->begin_map_key_block(); + _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); + _handle_keyref(ref); + _set_indentation(startindent); + // keep the child state on RVAL + addrem_flags(RVAL, RKCL|QMRK); + } + _maybe_skip_whitespace_tokens(); + } + else if(first == '&') + { + csubstr anchor = _scan_anchor(); + _c4dbgpf("mapblck[QMRK]: key anchor! {}", _prs(anchor)); + _add_annotation(&m_pending_anchors, anchor, startindent, startline); + } + else if(first == '!') + { + csubstr tag = _scan_tag(); + _c4dbgpf("mapblck[QMRK]: key tag! {}", _prs(tag)); + _add_annotation(&m_pending_tags, tag, startindent, startline); + } + else if(first == '-') + { + _c4dbgp("mapblck[QMRK]: maybe seq or doc?"); + if(_is_blck_seq_token_maybe(m_evt_handler->m_curr->line_contents.rem)) + { + _c4dbgp("mapblck[QMRK]: start child seqblck (!)"); + addrem_flags(RKCL, QMRK); + _handle_annotations_before_blck_key_scalar(); + m_evt_handler->begin_seq_key_block(); + addrem_flags(RVAL|RSEQ, RMAP|RKCL); + _set_indentation(startindent); + _line_progressed(1); + } + else + { + _c4dbgp("mapblck[QMRK]: end+start doc"); + _c4assert(_is_doc_begin_token(m_evt_handler->m_curr->line_contents.rem)); + _start_doc_suddenly(); + _line_progressed(3); + } + _maybe_skip_whitespace_tokens(); + return false; // finish mapblck + } + else if(first == '[') + { + _c4dbgp("mapblck[QMRK]: start child seqflow (!)"); + addrem_flags(RKCL, QMRK); + _handle_annotations_before_blck_key_scalar(); + m_evt_handler->begin_seq_key_flow(); + addrem_flags(RVAL|RSEQ|RFLOW, RMAP|RKCL|RBLCK); + _set_indentation(m_evt_handler->m_parent->indref + 1); + _line_progressed(1); + return false; // finish mapblck + } + else if(first == '{') + { + _c4dbgp("mapblck[QMRK]: start child mapflow (!)"); + addrem_flags(RKCL, QMRK); + _handle_annotations_before_blck_key_scalar(); + m_evt_handler->begin_map_key_flow(); + addrem_flags(RKEY|RFLOW, RVAL|RKCL|RBLCK); + _set_indentation(m_evt_handler->m_parent->indref + 1); + _line_progressed(1); + return false; // finish mapblck + } + else if(first == '?') + { + _c4dbgpf("mapblck[QMRK]: another QMRK '?'. ind={} indref={}", startindent, m_evt_handler->m_curr->indref); + _RYML_ASSERT_PARSE_(callbacks(), startindent > m_evt_handler->m_curr->indref, m_evt_handler->m_curr->pos); + _c4dbgp("mapblck[QMRK]: ? indent gt - start child mapblck (!)"); + addrem_flags(RKCL, QMRK); + _handle_annotations_before_blck_key_scalar(); + m_evt_handler->begin_map_key_block(); + addrem_flags(QMRK, RKCL); + _set_indentation(startindent); + // indentation_lt() should be handled elsewhere + _line_progressed(1); + _maybe_skipchars(' '); + if(_is_blck_seq_token_maybe(m_evt_handler->m_curr->line_contents.rem)) + { + _c4dbgp("mapblck[RVAL]: seqblck starts after ?"); + addrem_flags(RKCL, QMRK); + m_evt_handler->begin_seq_key_block(); + addrem_flags(RSEQ|RVAL, RMAP|RKCL); + _save_indentation(); + _line_progressed(1); + _maybe_skipchars(' '); + return false; + } + } + else + { + _c4err("parse error"); + } + return true; // continue in mapblck +} + + +//----------------------------------------------------------------------------- + +// return true if we should remain in map_block +template +bool ParseEngine::_handle_map_block_rkcl() +{ + // + // handle indentation + // + if(m_evt_handler->m_curr->at_line_beginning()) + { + if(m_evt_handler->m_curr->indentation_eq()) + { + _c4dbgpf("mapblck[RKCL]: skip {} from indref", m_evt_handler->m_curr->indref); + _line_progressed(m_evt_handler->m_curr->indref); + if(!m_evt_handler->m_curr->line_contents.rem.len) + return true; // continue in mapblck + } + else if(C4_UNLIKELY(m_evt_handler->m_curr->indentation_lt())) + { + _c4err("invalid indentation"); + } + } + const char first = m_evt_handler->m_curr->line_contents.rem.str[0]; + _c4dbgpf("mapblck[RKCL]: '{}'", first); + if(first == ':') + { + _c4dbgp("mapblck[RKCL]: found the colon"); + _line_progressed(1); + _maybe_skipchars(' '); + #if defined(__GNUC__) && ( \ + ((__GNUC__ >= 12) && ((C4_WORDSIZE == 4) || defined(C4_CPU_S390_X) || defined(C4_CPU_PPC64))) \ + || \ + (__GNUC__ == 16 && defined(C4_CPU_X86_64))) + C4_DONT_OPTIMIZE(m_evt_handler->m_curr->line_contents.rem); + #endif + // sequence is valid after the RKCL ':' + if(!_is_blck_seq_token_maybe(m_evt_handler->m_curr->line_contents.rem)) + { + addrem_flags(RVAL, RKCL); + return true; // continue in mapblck + } + else + { + _c4dbgp("mapblck[RKCL]: start val seqblck"); + addrem_flags(RNXT, RKCL); + m_evt_handler->begin_seq_val_block(); + addrem_flags(RSEQ|RVAL, RMAP|RNXT); + _save_indentation(); + _line_progressed(1); + _maybe_skipchars(' '); + return false; // finish mapblck + } + } + else if(first == '?') + { + _c4dbgp("mapblck[RKCL]: got '?'. val was empty"); + m_evt_handler->set_val_scalar_plain_empty(); + m_evt_handler->add_sibling(); + addrem_flags(QMRK, RKCL); + _line_progressed(1); + _maybe_skipchars(' '); + if(_is_blck_seq_token_maybe(m_evt_handler->m_curr->line_contents.rem)) + { + _c4dbgp("mapblck[RKCL]: seqblck starts after ?"); + addrem_flags(RKCL, QMRK); + m_evt_handler->begin_seq_key_block(); + addrem_flags(RSEQ|RVAL, RMAP|QMRK); + _save_indentation(); + _line_progressed(1); + _maybe_skipchars(' '); + return false; + } + } + else if(first == '-') + { + if(m_evt_handler->m_curr->indref == 0 || m_evt_handler->m_curr->line_contents.indentation == 0 || _is_doc_begin_token(m_evt_handler->m_curr->line_contents.rem)) + { + _c4dbgp("mapblck[RKCL]: end+start doc"); + _RYML_CHECK_PARSE_(m_evt_handler->m_stack.m_callbacks, _is_doc_begin_token(m_evt_handler->m_curr->line_contents.rem), m_evt_handler->m_curr->pos); + _start_doc_suddenly(); + _line_progressed(3); + _maybe_skip_whitespace_tokens(); + return false; // finish mapblck + } + else + { + _c4err("parse error"); // LCOV_EXCL_LINE + } + } + else if(first == '.') + { + _c4dbgp("mapblck[RKCL]: maybe end doc?"); + csubstr rs = m_evt_handler->m_curr->line_contents.rem.sub(1); + if(rs == ".." || rs.begins_with(".. ")) + { + _c4dbgp("mapblck[RKCL]: end+start doc"); + _end_doc_suddenly(); + _line_progressed(3); + _maybe_skip_whitespace_tokens(); + _check_doc_end_tokens(); + return false; // finish mapblck + } + else + { + _c4err("parse error"); // LCOV_EXCL_LINE + } + } + else/* if(m_was_inside_qmrk) */ + { + _c4dbgp("mapblck[RKCL]: missing :"); + if(C4_UNLIKELY(!m_evt_handler->m_curr->indentation_eq())) + _c4err("parse error"); // LCOV_EXCL_LINE + m_evt_handler->set_val_scalar_plain_empty(); + m_evt_handler->add_sibling(); + addrem_flags(RKEY, RKCL); + } + return true; +} + + +//----------------------------------------------------------------------------- + +template +void ParseEngine::_handle_unk_json() +{ + _c4dbgpf("handle_unk_json indref={} target={}", m_evt_handler->m_curr->indref, m_evt_handler->m_curr->node_id); + + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RNXT|RSEQ|RMAP), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_all(RTOP), m_evt_handler->m_curr->pos); + + _maybe_skip_comment(); + csubstr rem = m_evt_handler->m_curr->line_contents.rem; + if(!rem.len) + return; + + size_t pos = rem.first_not_of(" \t"); + if(pos) + { + pos = pos != npos ? pos : rem.len; + _c4dbgpf("skipping indentation of {}", pos); + _line_progressed(pos); + rem = m_evt_handler->m_curr->line_contents.rem; + if(!rem.len) + return; + _c4dbgpf("rem is now {}", _prs(rem)); + } + + if(rem.begins_with('[')) + { + _c4dbgp("it's a seq"); + _check_trailing_doc_token(); + _maybe_begin_doc(); + m_evt_handler->begin_seq_val_flow(); + addrem_flags(RSEQ|RFLOW|RVAL, RUNK|RTOP|RDOC); + _set_indentation(m_evt_handler->m_curr->line_contents.current_col(rem)); + m_doc_empty = false; + _line_progressed(1); + } + else if(rem.begins_with('{')) + { + _c4dbgp("it's a map"); + _check_trailing_doc_token(); + _maybe_begin_doc(); + m_evt_handler->begin_map_val_flow(); + addrem_flags(RMAP|RFLOW|RKEY, RVAL|RTOP|RUNK|RDOC); + m_doc_empty = false; + _set_indentation(m_evt_handler->m_curr->line_contents.current_col(rem)); + _line_progressed(1); + } + else if(_handle_bom()) + { + _c4dbgp("byte order mark"); + } + else + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, ! has_any(SSCL), m_evt_handler->m_curr->pos); + _maybe_skip_whitespace_tokens(); + csubstr s = m_evt_handler->m_curr->line_contents.rem; + if(!s.len) + return; + const size_t startindent = m_evt_handler->m_curr->line_contents.indentation; // save + const char first = s.str[0]; + ScannedScalar sc; + if(first == '"') + { + _c4dbgp("runk_json: scanning double-quoted scalar"); + _check_trailing_doc_token(); + _maybe_begin_doc(); + add_flags(RDOC); + m_doc_empty = false; + sc = _scan_scalar_dquot(); + csubstr maybe_filtered = _maybe_filter_val_scalar_dquot(sc); + if(!_maybe_scan_following_colon()) + { + _c4dbgp("runk_json: set as val"); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->set_val_scalar_dquoted(maybe_filtered); + } + else + { + _c4err("parse error"); + } + } + else if(_scan_scalar_plain_unk(&sc)) + { + _c4dbgp("runk_json: got a plain scalar"); + _check_trailing_doc_token(); + _maybe_begin_doc(); + add_flags(RDOC); + m_doc_empty = false; + if(!_maybe_scan_following_colon()) + { + _c4dbgp("runk_json: set as val"); + _handle_annotations_before_blck_val_scalar(); + csubstr maybe_filtered = _maybe_filter_val_scalar_plain(sc, startindent); + m_evt_handler->set_val_scalar_plain(maybe_filtered); + } + else + { + _c4err("parse error"); // LCOV_EXCL_LINE + } + } + else + { + _c4err("parse error"); // LCOV_EXCL_LINE + } + } +} + + +//----------------------------------------------------------------------------- + +template +void ParseEngine::_handle_unk() +{ + _c4dbgpf("handle_unk indref={} target={}", m_evt_handler->m_curr->indref, m_evt_handler->m_curr->node_id); + + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RNXT|RSEQ|RMAP), m_evt_handler->m_curr->pos); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_all(RTOP), m_evt_handler->m_curr->pos); + + _maybe_skipchars(' '); + _maybe_skip_comment(); + + if(!m_evt_handler->m_curr->line_contents.rem.len) + return; + + _c4dbgpf("runk: rem is now {}", _prs(m_evt_handler->m_curr->line_contents.rem)); + + if(m_evt_handler->m_curr->line_contents.indentation == 0u && (m_evt_handler->m_curr->at_line_beginning() || (m_bom_len && (m_evt_handler->m_curr->pos.line == m_bom_line)))) + { + _c4dbgpf("runk: rtop: zero indent + at line begin. offset={}", m_evt_handler->m_curr->pos.offset); + _c4dbgp("runk: check BOM"); + if(_handle_bom()) + { + m_bom_line = m_evt_handler->m_curr->pos.line; + _c4dbgpf("runk: byte order mark! line={} offset={}", m_bom_line, m_evt_handler->m_curr->pos.offset); + return; + } + const char first = m_evt_handler->m_curr->line_contents.rem.str[0]; + _c4dbgpf("runk: rtop: first={}", _c4prc(first)); + if(first == '-') + { + _c4dbgp("runk: rtop: suspecting doc"); + if(_is_doc_begin_token(m_evt_handler->m_curr->line_contents.rem)) + { + _c4dbgp("runk: rtop: begin doc"); + _maybe_end_doc(); + _begin2_doc_expl(); + _set_indentation(0); + addrem_flags(RDOC|RUNK, NDOC); + _line_progressed(3u); + _maybe_skip_whitespace_tokens(); + return; + } + } + else if(first == '.') + { + _c4dbgp("runk: rtop: suspecting doc end"); + if(_is_doc_end_token(m_evt_handler->m_curr->line_contents.rem)) + { + _c4dbgp("runk: rtop: end doc"); + if(has_any(RDOC)) + { + _end2_doc_expl(); + } + else + { + _c4dbgp("runk: rtop: ignore end doc"); + } + addrem_flags(NDOC|RUNK, RDOC); + _line_progressed(3u); + _maybe_skip_whitespace_tokens(); + _check_doc_end_tokens(); + return; + } + } + else if(first == '%') + { + _c4dbgpf("directive: {}", m_evt_handler->m_curr->line_contents.rem); + if(C4_UNLIKELY(has_any(RDOC) || (!m_doc_empty && has_none(NDOC)))) + _c4err("need document footer before directives"); + _handle_directive(m_evt_handler->m_curr->line_contents.rem); + return; + } + } + + /* no else-if! */ + + size_t startindent = m_evt_handler->m_curr->line_contents.indentation; + size_t remindent = m_evt_handler->m_curr->line_contents.current_col(m_evt_handler->m_curr->line_contents.rem); + if(m_bom_len) + { + _c4dbgpf("runk: prev BOMlen={}", m_bom_len); + if(m_evt_handler->m_curr->pos.line == m_bom_line) + { + _c4dbgpf("runk: BOM remindent={} offset={}", remindent, m_evt_handler->m_curr->pos.offset); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, remindent >= m_bom_len, m_evt_handler->m_curr->pos); + remindent -= m_bom_len; + } + else + { + m_bom_len = 0; + } + } + + size_t startcol = _handle_block_skip_leading_whitespace(); + const char first = m_evt_handler->m_curr->line_contents.rem.str[0]; + + if(first == '[') + { + _c4dbgp("runk: flow seq?"); + _handle_unk_begin_doc(); + if(C4_LIKELY( ! _annotations_require_key_container())) + { + _c4dbgp("runk: it's a seq, flow"); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->begin_seq_val_flow(); + addrem_flags(RSEQ|RFLOW|RVAL, RUNK|RTOP|RDOC); + _set_indentation(0); + } + else + { + _c4dbgp("runk: start new block map, set flow seq as key (!)"); + _handle_annotations_before_start_mapblck(m_evt_handler->m_curr->pos.line); + m_evt_handler->begin_map_val_block(); + addrem_flags(RMAP|RBLCK|RKEY, RUNK|RTOP|RDOC); + _handle_annotations_and_indentation_after_start_mapblck(remindent, m_evt_handler->m_curr->pos.line); + m_evt_handler->begin_seq_key_flow(); + addrem_flags(RSEQ|RFLOW|RVAL, RMAP|RBLCK|RKEY); + _set_indentation(0); + } + _line_progressed(1); + } + else if(first == '{') + { + _c4dbgp("runk: flow map?"); + _handle_unk_begin_doc(); + if(C4_LIKELY( ! _annotations_require_key_container())) + { + _c4dbgp("runk: it's a map, flow"); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->begin_map_val_flow(); + addrem_flags(RMAP|RFLOW|RKEY, RVAL|RTOP|RUNK|RDOC); + _set_indentation(0); + } + else + { + _c4dbgp("runk: start new block map, set flow map as key (!)"); + _handle_annotations_before_start_mapblck(m_evt_handler->m_curr->pos.line); + m_evt_handler->begin_map_val_block(); + addrem_flags(RMAP|RBLCK|RKEY, RUNK|RTOP|RDOC); + _handle_annotations_and_indentation_after_start_mapblck(remindent, m_evt_handler->m_curr->pos.line); + m_evt_handler->begin_map_key_flow(); + addrem_flags(RMAP|RFLOW, RBLCK); + _set_indentation(0); + } + _line_progressed(1); + } + else if(first == '-' && _is_blck_token(m_evt_handler->m_curr->line_contents.rem)) + { + _c4dbgp("runk: it's a seq, block"); + if(C4_UNLIKELY(!m_evt_handler->m_curr->at_first_token())) + startindent = _handle_unk_check_left_tokens(startindent, m_evt_handler->m_curr->pos.col, /*skip_annotations*/false); + _handle_unk_begin_doc(); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->begin_seq_val_block(); + addrem_flags(RSEQ|RBLCK|RVAL, RNXT|RTOP|RUNK|RDOC); + _set_indentation(startindent); + _line_progressed(1); + _maybe_skipchars(' '); + } + else if(first == '?' && _is_blck_token(m_evt_handler->m_curr->line_contents.rem)) + { + _c4dbgp("runk: it's a map + this key is complex"); + if(C4_UNLIKELY(!m_evt_handler->m_curr->at_first_token())) + startindent = _handle_unk_check_left_tokens(startindent, m_evt_handler->m_curr->pos.col, /*skip_annotations*/false); + _handle_block_check_leading_tabs(startcol); + _handle_unk_begin_doc(); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->begin_map_val_block(); + addrem_flags(RMAP|RBLCK|QMRK, RKEY|RVAL|RTOP|RUNK|RDOC); + _set_indentation(startindent); + _line_progressed(1); + _maybe_skipchars(' '); + if(_is_blck_seq_token_maybe(m_evt_handler->m_curr->line_contents.rem)) + { + _c4dbgp("runk: seqblck key starts after ?"); + addrem_flags(RKCL, QMRK); + m_evt_handler->begin_seq_key_block(); + addrem_flags(RSEQ|RVAL, RMAP|RKCL); + _save_indentation(); + _line_progressed(1); + _maybe_skipchars(' '); + } + } + else if(first == ':' && _is_blck_token(m_evt_handler->m_curr->line_contents.rem)) + { + if(m_doc_empty || (m_pending_anchors.num_entries | m_pending_tags.num_entries)) + { + _c4dbgp("runk: it's a map with an empty key"); + if(C4_UNLIKELY(!m_evt_handler->m_curr->at_first_token())) + startindent = _handle_unk_check_left_tokens(startindent, m_evt_handler->m_curr->pos.col); + _handle_block_check_leading_tabs(startcol); + const size_t startline = m_evt_handler->m_curr->pos.line; // save + _handle_unk_begin_doc(); + _handle_annotations_before_start_mapblck(startline); + _handle_colon(); + m_evt_handler->begin_map_val_block(); + _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); + m_evt_handler->set_key_scalar_plain_empty(); + _set_indentation(startindent); + } + else + { + _c4err("block colon cannot occur on a new line unless ? is used"); + } + addrem_flags(RMAP|RBLCK|RVAL, RTOP|RUNK|RDOC); + _line_progressed(1); + _maybe_skip_whitespace_tokens(); + } + else if(first == '&') + { + csubstr anchor = _scan_anchor(); + _c4dbgpf("anchor! {}", _prs(anchor)); + const size_t line = m_evt_handler->m_curr->pos.line; + _handle_unk_begin_doc(); + _add_annotation(&m_pending_anchors, anchor, remindent, line); + _set_indentation(0); + } + else if(first == '*') + { + csubstr ref = _scan_ref_map(); + _c4dbgpf("runk: ref! {}", _prs(ref)); + _handle_unk_begin_doc(); + if(!_maybe_scan_following_colon()) + { + _c4dbgp("runk: set val ref"); + _handle_valref(ref); + } + else + { + _c4dbgp("runk: start new block map, set ref as key"); + _handle_block_check_leading_tabs(startcol); + const size_t startline = m_evt_handler->m_curr->pos.line; // save + _handle_annotations_before_start_mapblck(startline); + m_evt_handler->begin_map_val_block(); + _handle_keyref(ref); + _maybe_skip_whitespace_tokens(); + _set_indentation(0); + addrem_flags(RMAP|RBLCK|RVAL, RTOP|RUNK|RDOC); + } + } + else if(first == '!') + { + csubstr tag_orig; + csubstr tag = _scan_tag(&tag_orig); + _c4dbgpf("runk: val tag! {}", _prs(tag)); + // we need to buffer the tags, as there may be two + // consecutive tags in here + const size_t indentation = m_evt_handler->m_curr->line_contents.current_col(m_evt_handler->m_curr->line_contents.rem); + const size_t line = m_evt_handler->m_curr->pos.line; + _add_annotation(&m_pending_tags, tag, indentation, line, tag_orig); + } + else + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, ! has_any(SSCL), m_evt_handler->m_curr->pos); + const size_t startscalar = _handle_block_get_whitespace_mark(); + const size_t startline = m_evt_handler->m_curr->pos.line; // save + auto beginmap = [&](size_t startindent_){ + if(C4_UNLIKELY(m_evt_handler->m_curr->pos.line > startline)) + _c4err("multiline scalars cannot be used as implicit keys"); + _handle_block_check_leading_tabs(startcol, startscalar); + _handle_annotations_before_start_mapblck(startline); + _handle_colon(); + m_evt_handler->begin_map_val_block(); + _handle_annotations_and_indentation_after_start_mapblck(startindent_, startline); + }; + auto after_beginmap = [&](size_t startindent_){ + _maybe_skip_whitespace_tokens(); + _set_indentation(startindent_); + addrem_flags(RMAP|RBLCK|RVAL, RTOP|RUNK|RDOC); + }; + if(first == '|') + { + _c4dbgp("runk: block-literal scalar"); + _handle_unk_begin_doc(); + ScannedBlock sb; + _scan_block(&sb, startindent); + _handle_annotations_before_blck_val_scalar(); + csubstr maybe_filtered = _maybe_filter_val_scalar_literal(sb); + m_evt_handler->set_val_scalar_literal(maybe_filtered); + } + else if(first == '>') + { + _c4dbgp("runk: block-folded scalar"); + _handle_unk_begin_doc(); + ScannedBlock sb; + _scan_block(&sb, startindent); + _handle_annotations_before_blck_val_scalar(); + csubstr maybe_filtered = _maybe_filter_val_scalar_folded(sb); + m_evt_handler->set_val_scalar_folded(maybe_filtered); + } + else if(first == '\'') + { + _c4dbgp("runk: single-quoted scalar"); + _handle_unk_begin_doc(); + bool firsttoken = m_evt_handler->m_curr->at_first_token(); + size_t col = m_evt_handler->m_curr->pos.col; + ScannedScalar sc = _scan_scalar_squot(); + if(!_maybe_scan_following_colon()) + { + _c4dbgp("runk: set as val"); + _handle_annotations_before_blck_val_scalar(); + csubstr maybe_filtered = _maybe_filter_val_scalar_squot(sc); + m_evt_handler->set_val_scalar_squoted(maybe_filtered); + } + else + { + _c4dbgp("runk: start new block map, set single-quoted scalar as key"); + if(!firsttoken) + startindent = _handle_unk_check_left_tokens(startindent, col); + beginmap(startindent); + csubstr maybe_filtered = _maybe_filter_val_scalar_squot(sc); + m_evt_handler->set_key_scalar_squoted(maybe_filtered); + after_beginmap(startindent); + } + } + else if(first == '"') + { + _c4dbgp("runk: double-quoted scalar"); + _handle_unk_begin_doc(); + bool firsttoken = m_evt_handler->m_curr->at_first_token(); + size_t col = m_evt_handler->m_curr->pos.col; + ScannedScalar sc = _scan_scalar_dquot(); + if(!_maybe_scan_following_colon()) + { + _c4dbgp("runk: set as val"); + _handle_annotations_before_blck_val_scalar(); + csubstr maybe_filtered = _maybe_filter_val_scalar_dquot(sc); + m_evt_handler->set_val_scalar_dquoted(maybe_filtered); + } + else + { + _c4dbgp("runk: start new block map, set double-quoted scalar as key"); + if(!firsttoken) + startindent = _handle_unk_check_left_tokens(startindent, col); + beginmap(startindent); + csubstr maybe_filtered = _maybe_filter_val_scalar_dquot(sc); + m_evt_handler->set_key_scalar_dquoted(maybe_filtered); + after_beginmap(startindent); + } + } + else + { + bool firsttoken = m_evt_handler->m_curr->at_first_token(); + size_t col = m_evt_handler->m_curr->pos.col; + ScannedScalar sc; + if(_scan_scalar_plain_unk(&sc)) + { + _c4dbgp("runk: plain scalar"); + _handle_unk_begin_doc(); + if(!_maybe_scan_following_colon()) + { + _c4dbgp("runk: set as val"); + _handle_annotations_before_blck_val_scalar(); + csubstr maybe_filtered = _maybe_filter_val_scalar_plain(sc, startindent); + m_evt_handler->set_val_scalar_plain(maybe_filtered); + } + else + { + _c4dbgp("runk: start new block map, set plain scalar as key"); + if(!firsttoken) + startindent = _handle_unk_check_left_tokens(startindent, col); + beginmap(startindent); + csubstr maybe_filtered = _maybe_filter_val_scalar_plain(sc, startindent); + m_evt_handler->set_key_scalar_plain(maybe_filtered); + after_beginmap(startindent); + } + } + else + { + _c4err("parse error"); // LCOV_EXCL_LINE + } + } + } +} + +template +void ParseEngine::_handle_unk_begin_doc() +{ + _c4dbgp("runk: begin doc"); + _check_trailing_doc_token(); + _maybe_begin_doc(); + add_flags(RDOC); + m_doc_empty = false; +} + +template +size_t ParseEngine::_handle_unk_check_left_tokens(size_t realindent, size_t col, bool skip_annotations) +{ + _c4assert(col >= 1); + col -= 1; + _c4assert(col >= m_bom_len); + csubstr s = m_evt_handler->m_curr->line_contents.full.range(m_bom_len, col); + size_t pos = 0; + _c4dbgpf("runk: check left tokens: s={}", _prs(s, /*escape*/true)); + if(skip_annotations) + { + _handle_unk_get_first_non_pending_token_pos(s, &realindent, &pos); + _c4dbgpf("runk: skip annotations: realindent={} pos={}", realindent, pos); + } + size_t firstns = s.first_not_of(' ', pos); + if(firstns == npos) + firstns = s.len; + _c4dbgpf("runk: check left tokens:\n" + " tokens={} skipped={}\n" + " bomlen={} first={} col={}\n" + " (bomlen+first)={} vs {}=col\n" + " startindent={} lineindent={}" + , _prs(s, /*escape*/true), _prs(s.sub(firstns), /*escape*/true) + , m_bom_len, firstns, col + , m_bom_len+firstns, col, + realindent, m_evt_handler->m_curr->line_contents.indentation); + if(m_bom_len + firstns != col) + _c4err("parse error"); + if(!skip_annotations) + realindent = firstns; + _c4dbgpf("runk: pos={} firstns={} -> realindent={}", pos, firstns, realindent); + return realindent; +} + + +/** skip annotations which are pending on the same line */ +template +void ParseEngine::_handle_unk_get_first_non_pending_token_pos(csubstr s, size_t *indent, size_t *first_non_token_pos) +{ + csubstr first, second; + uint32_t total = _get_annotations_same_line(s, &first, &second); + _c4dbgpf("runk: before skip: {}", _prs(s, true)); + size_t pos = s.first_not_of(" \t"); + if(pos == npos) + pos = s.len; + if(!total) + { + *indent = *first_non_token_pos = pos; + return; + } + _c4assert(!s.sub(pos).begins_with_any(" \t")); + _c4dbgpf("runk: after skip leading {} whitespace: {}", pos, _prs(s.sub(pos), true)); + _c4dbgpf("runk: first annotation: {}", first); + _c4assert(first.len); + _c4assert(first.is_sub(s)); + _c4assert(first.is_sub(s.sub(pos))); + _c4assert(s.sub(pos).begins_with(first)); + *indent = pos; + pos += first.len; + _c4dbgpf("runk: after skip first annotation: pos={} {}", pos, _prs(s.sub(pos), true)); + if(total > 1) + { + _c4dbgpf("runk: second annotation: {}", second); + _c4assert(total == 2); + _c4assert(second.len); + _c4assert(second.is_sub(s)); + _c4assert(second.is_sub(s.sub(pos))); + csubstr spos = s.sub(pos); + size_t more = spos.first_not_of(" \t"); + _c4assert(more != npos); // because the annotations are on the same line + _c4dbgpf("runk: next nonspace: {}", pos + more); + pos += more; + _c4dbgpf("runk: after skip annotation whitespace: pos={} {}", pos, _prs(s.sub(pos), true)); + _c4assert(s.sub(pos).begins_with(second)); + pos += second.len; + _c4dbgpf("runk: after skip annotation 2: pos={} {}", pos, _prs(s.sub(pos), true)); + } + *first_non_token_pos = pos; +} + + +template +uint32_t ParseEngine::_get_annotations_same_line(csubstr token_soup, csubstr *first_, csubstr *second_) const +{ + _c4assert(!m_evt_handler->m_curr->at_first_token()); + (void)token_soup; + using EntryPtr = typename Annotation::Entry const* C4_RESTRICT; + EntryPtr first = nullptr; + EntryPtr second = nullptr; + uint32_t total = (uint32_t)(m_pending_anchors.num_entries + m_pending_tags.num_entries); + if(total) + { + _c4dbgpf("there are {} pending annotations: {} anchors + {} tags", total, m_pending_anchors.num_entries, m_pending_tags.num_entries); + auto valid_if_same_line = [this](EntryPtr entry){ + _c4dbgpf("pending: {} indent={} line={} vs currline={}", _maybe_null_str(entry->str), entry->indentation, entry->line, m_evt_handler->m_curr->pos.line); + return (entry->line == m_evt_handler->m_curr->pos.line) ? entry : nullptr; + }; + // now select annotations only on the same line + total = 0; + for(size_t i = 0; i < m_pending_anchors.num_entries; ++i) + total += !!valid_if_same_line(&m_pending_anchors.annotations[i]); + for(size_t i = 0; i < m_pending_tags.num_entries; ++i) + total += !!valid_if_same_line(&m_pending_tags.annotations[i]); + _c4dbgpf("{} annotations on same line", total); + _c4assert(total > 0); // because this function is only called + // while not at the first token. That + // means we must have same-line + // annotations. + auto get_first_on_same_line = [this](EntryPtr not_this_one){ + for(size_t i = 0; i < m_pending_anchors.num_entries; ++i) + if(&m_pending_anchors.annotations[i] != not_this_one + && m_pending_anchors.annotations[i].line == m_evt_handler->m_curr->pos.line) + return &m_pending_anchors.annotations[i]; + for(size_t i = 0; i < m_pending_tags.num_entries; ++i) + if(&m_pending_tags.annotations[i] != not_this_one + && m_pending_tags.annotations[i].line == m_evt_handler->m_curr->pos.line) + return &m_pending_tags.annotations[i]; + C4_UNREACHABLE(); + return (EntryPtr)nullptr; // LCOV_EXCL_LINE + }; + _c4assert(total >= 1); + // assign to first + first = get_first_on_same_line(nullptr); + _c4assert(first); + _c4dbgpf("first annotation: {} indent={} line={}", _maybe_null_str(first->str), first->indentation, first->line); + if(total > 1) + { + _c4assert(total == 2); + // assign to second + second = get_first_on_same_line(first); + _c4assert(second); + _c4dbgpf("second annotation: {} indent={} line={}", _maybe_null_str(second->str), second->indentation, second->line); + } + auto extract_string = [&](EntryPtr e){ + // tags can be null when the arena ran out of space + if(!e->str.str || e->str.begins_with_any("!<")) + { + csubstr tag = e->orig; + _c4assert(tag.str); + _c4assert(tag.len); + _c4assert(tag.is_sub(token_soup)); + _c4dbgpf("tag: {} -> {}", _maybe_null_str(e->str), tag); + return tag; + } + csubstr anchor = e->str; + _c4assert(anchor.len); + _c4assert(anchor.str); + _c4assert(anchor.is_sub(token_soup)); + _c4assert(!anchor.begins_with('&')); + _c4assert(anchor.str - token_soup.str > 0); + // add back the anchor's & + --anchor.str; + ++anchor.len; + _c4assert(anchor.begins_with('&')); + _c4dbgpf("anchor: {} -> {}", e->str, anchor); + return anchor; + }; + *first_ = first ? extract_string(first) : nullptr; + *second_ = second ? extract_string(second) : nullptr; + if(total > 1 && (first_->str > second_->str)) + { + csubstr tmp = *first_; + *first_ = *second_; + *second_ = tmp; + _c4dbgpf("swap first and second: {} -> {}", *first_, *second_); + } + } + return total; +} + + +//----------------------------------------------------------------------------- + +template +C4_COLD void ParseEngine::_handle_usty() +{ + _c4dbgpf("handle_usty target={}", m_evt_handler->m_curr->indref, m_evt_handler->m_curr->node_id); + + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_none(RBLCK|RFLOW), m_evt_handler->m_curr->pos); + + #ifdef RYML_NO_COVERAGE__TO_BE_DELETED + if(has_any(RNXT)) + { + _c4dbgp("usty[RNXT]: finishing!"); + _end_stream(); + } + #endif + + _maybe_skip_comment(); + csubstr rem = m_evt_handler->m_curr->line_contents.rem; + if(!rem.len) + return; + + size_t pos = rem.first_not_of(" \t"); + if(pos) + { + pos = pos != npos ? pos : rem.len; + _c4dbgpf("skipping indentation of {}", pos); + _line_progressed(pos); + rem = m_evt_handler->m_curr->line_contents.rem; + if(!rem.len) + return; + _c4dbgpf("rem is now {}", _prs(rem)); + } + + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, rem.len > 0, m_evt_handler->m_curr->pos); + size_t startindent = m_evt_handler->m_curr->line_contents.indentation; // save + char first = rem.str[0]; + if(has_any(RSEQ)) // destination is a sequence + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, ! has_any(RMAP), m_evt_handler->m_curr->pos); + _c4dbgpf("usty[RSEQ]: first='{}'", _c4prc(first)); + if(first == '[') + { + _c4dbgp("usty[RSEQ]: it's a flow seq. merging it"); + add_flags(RNXT); + m_evt_handler->_push(); + addrem_flags(RFLOW|RVAL, RNXT|USTY); + _set_indentation(startindent); + _line_progressed(1); + _maybe_skip_whitespace_tokens(); + } + else if(first == '-' && _is_blck_token(rem)) + { + _c4dbgp("usty[RSEQ]: it's a block seq. merging it"); + add_flags(RNXT); + m_evt_handler->_push(); + addrem_flags(RBLCK|RVAL, RNXT|USTY); + _set_indentation(startindent); + _line_progressed(1); + _maybe_skip_whitespace_tokens(); + } + else + { + _c4err("can only parse a seq into an existing seq"); + } + } + else if(has_any(RMAP)) // destination is a map + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, ! has_any(RSEQ), m_evt_handler->m_curr->pos); + _c4dbgpf("usty[RMAP]: first='{}'", _c4prc(first)); + if(first == '{') + { + _c4dbgp("usty[RMAP]: it's a flow map. merging it"); + add_flags(RNXT); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->_push(); + addrem_flags(RMAP|RFLOW|RKEY, RNXT|USTY); + _set_indentation(startindent); + _line_progressed(1); + _maybe_skip_whitespace_tokens(); + } + else if(first == '?' && _is_blck_token(rem)) + { + _c4dbgp("usty[RMAP]: it's a block map + this key is complex"); + add_flags(RNXT); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->_push(); + addrem_flags(RMAP|RBLCK|QMRK, RNXT|USTY); + _save_indentation(); + _line_progressed(1); + _maybe_skip_whitespace_tokens(); + } + else if(first == ':' && _is_blck_token(rem)) + { + _c4dbgp("usty[RMAP]: it's a map with an empty key"); + add_flags(RNXT); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->_push(); + m_evt_handler->set_key_scalar_plain_empty(); + addrem_flags(RMAP|RBLCK|RVAL, RNXT|USTY); + _save_indentation(); + _line_progressed(1); + _maybe_skip_whitespace_tokens(); + } + else if(rem.begins_with('&')) + { + csubstr anchor = _scan_anchor(); + _c4dbgpf("usty[RMAP]: anchor! {}", _prs(anchor)); + const size_t indentation = m_evt_handler->m_curr->line_contents.current_col(rem); + const size_t line = m_evt_handler->m_curr->pos.line; + _add_annotation(&m_pending_anchors, anchor, indentation, line); + _set_indentation(m_evt_handler->m_curr->line_contents.current_col(rem)); + } + else if(first == '*') + { + csubstr ref = _scan_ref_map(); + _c4dbgpf("usty[RMAP]: ref! {}", _prs(ref)); + if(!_maybe_scan_following_colon()) + { + _c4err("cannot read a VAL to a map"); + } + else + { + _c4dbgp("usty[RMAP]: start new block map, set ref as key"); + const size_t startline = m_evt_handler->m_curr->pos.line; // save + add_flags(RNXT); + _handle_annotations_before_start_mapblck(startline); + m_evt_handler->_push(); + _handle_keyref(ref); + _maybe_skip_whitespace_tokens(); + _set_indentation(startindent); + addrem_flags(RMAP|RBLCK|RVAL, RNXT|USTY); + } + } + else if(first == '!') + { + csubstr tag = _scan_tag(); + _c4dbgpf("usty[RMAP]: val tag! {}", _prs(tag)); + // we need to buffer the tags, as there may be two + // consecutive tags in here + const size_t indentation = m_evt_handler->m_curr->line_contents.current_col(rem); + const size_t line = m_evt_handler->m_curr->pos.line; + _add_annotation(&m_pending_tags, tag, indentation, line); + } + else if(first == '[' || (first == '-' && _is_blck_token(rem))) + { + _c4err("cannot parse a seq into an existing map"); + } + else + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, ! has_any(SSCL), m_evt_handler->m_curr->pos); + startindent = m_evt_handler->m_curr->line_contents.indentation; // save + const size_t startline = m_evt_handler->m_curr->pos.line; // save + ScannedScalar sc; + _c4dbgpf("usty[RMAP]: maybe scalar. first='{}'", _c4prc(first)); + if(first == '\'') + { + _c4dbgp("usty[RMAP]: scanning single-quoted scalar"); + sc = _scan_scalar_squot(); + if(!_maybe_scan_following_colon()) + { + _c4err("cannot read a VAL to a map"); + } + else + { + _c4dbgp("usty[RMAP]: start new block map, set scalar as key"); + add_flags(RNXT); + _handle_annotations_before_start_mapblck(startline); + m_evt_handler->_push(); + _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); + csubstr maybe_filtered = _maybe_filter_key_scalar_squot(sc); + m_evt_handler->set_key_scalar_squoted(maybe_filtered); + _set_indentation(startindent); + addrem_flags(RMAP|RBLCK|RVAL, RNXT|USTY); + _maybe_skip_whitespace_tokens(); + } + } + else if(first == '"') + { + _c4dbgp("usty[RMAP]: scanning double-quoted scalar"); + sc = _scan_scalar_dquot(); + if(!_maybe_scan_following_colon()) + { + _c4err("cannot read a VAL to a map"); + } + else + { + _c4dbgp("usty[RMAP]: start new block map, set double-quoted scalar as key"); + add_flags(RNXT); + _handle_annotations_before_start_mapblck(startline); + m_evt_handler->_push(); + _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); + csubstr maybe_filtered = _maybe_filter_key_scalar_dquot(sc); + m_evt_handler->set_key_scalar_dquoted(maybe_filtered); + _set_indentation(startindent); + addrem_flags(RMAP|RBLCK|RVAL, RNXT|USTY); + _maybe_skip_whitespace_tokens(); + } + } + else if(first == '|') + { + _c4err("block literal keys must be enclosed in '?'"); + } + else if(first == '>') + { + _c4err("block literal keys must be enclosed in '?'"); + } + else if(_scan_scalar_plain_unk(&sc)) + { + _c4dbgp("usty[RMAP]: got a plain scalar"); + if(!_maybe_scan_following_colon()) + { + _c4err("cannot read a VAL to a map"); + } + else + { + _c4dbgp("usty[RMAP]: start new block map, set scalar as key"); + add_flags(RNXT); + _handle_annotations_before_start_mapblck(startline); + m_evt_handler->_push(); + _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); + csubstr maybe_filtered = _maybe_filter_key_scalar_plain(sc, startindent); + m_evt_handler->set_key_scalar_plain(maybe_filtered); + _set_indentation(startindent); + addrem_flags(RMAP|RBLCK|RVAL, RNXT|USTY); + _maybe_skip_whitespace_tokens(); + } + } + else + { + _c4err("parse error"); // LCOV_EXCL_LINE + } + } + } + else // destination is unknown + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, ! has_any(RSEQ), m_evt_handler->m_curr->pos); + _c4dbgpf("usty[UNK]: first='{}'", _c4prc(first)); + if(first == '[') + { + _c4dbgp("usty[UNK]: it's a flow seq"); + add_flags(RNXT); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->begin_seq_val_flow(); + addrem_flags(RSEQ|RFLOW|RVAL, RNXT|USTY); + _set_indentation(startindent); + _line_progressed(1); + _maybe_skip_whitespace_tokens(); + } + else if(first == '-' && _is_blck_token(rem)) + { + _c4dbgp("usty[UNK]: it's a block seq"); + add_flags(RNXT); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->begin_seq_val_block(); + addrem_flags(RSEQ|RBLCK|RVAL, RNXT|USTY); + _set_indentation(startindent); + _line_progressed(1); + _maybe_skip_whitespace_tokens(); + } + else if(first == '{') + { + _c4dbgp("usty[UNK]: it's a flow map"); + add_flags(RNXT); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->begin_map_val_flow(); + addrem_flags(RMAP|RFLOW|RKEY, RNXT|USTY); + _set_indentation(startindent); + _line_progressed(1); + _maybe_skip_whitespace_tokens(); + } + else if(first == '?' && _is_blck_token(rem)) + { + _c4dbgp("usty[UNK]: it's a map + this key is complex"); + add_flags(RNXT); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->begin_map_val_block(); + addrem_flags(RMAP|RBLCK|QMRK, RNXT|USTY); + _save_indentation(); + _line_progressed(1); + _maybe_skip_whitespace_tokens(); + } + else if(first == ':' && _is_blck_token(rem)) + { + _c4dbgp("usty[UNK]: it's a map with an empty key"); + add_flags(RNXT); + _handle_annotations_before_blck_val_scalar(); + m_evt_handler->begin_map_val_block(); + m_evt_handler->set_key_scalar_plain_empty(); + addrem_flags(RMAP|RBLCK|RVAL, RNXT|USTY); + _save_indentation(); + _line_progressed(1); + _maybe_skip_whitespace_tokens(); + } + else if(first == '&') + { + csubstr anchor = _scan_anchor(); + _c4dbgpf("usty[UNK]: anchor! {}", _prs(anchor)); + const size_t indentation = m_evt_handler->m_curr->line_contents.current_col(rem); + const size_t line = m_evt_handler->m_curr->pos.line; + _add_annotation(&m_pending_anchors, anchor, indentation, line); + _set_indentation(m_evt_handler->m_curr->line_contents.current_col(rem)); + } + else if(first == '*') + { + csubstr ref = _scan_ref_map(); + _c4dbgpf("usty[UNK]: ref! {}", _prs(ref)); + if(!_maybe_scan_following_colon()) + { + _c4dbgp("usty[UNK]: set val ref"); + _handle_valref(ref); + } + else + { + _c4dbgp("usty[UNK]: start new block map, set ref as key"); + const size_t startline = m_evt_handler->m_curr->pos.line; // save + add_flags(RNXT); + _handle_annotations_before_start_mapblck(startline); + m_evt_handler->begin_map_val_block(); + _handle_keyref(ref); + _maybe_skip_whitespace_tokens(); + _set_indentation(startindent); + addrem_flags(RMAP|RBLCK|RVAL, RNXT|USTY); + } + } + else if(first == '!') + { + csubstr tag = _scan_tag(); + _c4dbgpf("usty[UNK]: val tag! {}", _prs(tag)); + // we need to buffer the tags, as there may be two + // consecutive tags in here + const size_t indentation = m_evt_handler->m_curr->line_contents.current_col(rem); + const size_t line = m_evt_handler->m_curr->pos.line; + _add_annotation(&m_pending_tags, tag, indentation, line); + } + else + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, ! has_any(SSCL), m_evt_handler->m_curr->pos); + startindent = m_evt_handler->m_curr->line_contents.indentation; // save + const size_t startline = m_evt_handler->m_curr->pos.line; // save + first = rem.str[0]; + ScannedScalar sc; + _c4dbgpf("usty[UNK]: maybe scalar. first='{}'", _c4prc(first)); + if(first == '\'') + { + _c4dbgp("usty[UNK]: scanning single-quoted scalar"); + sc = _scan_scalar_squot(); + if(!_maybe_scan_following_colon()) + { + _c4dbgp("usty[UNK]: set as val"); + _handle_annotations_before_blck_val_scalar(); + csubstr maybe_filtered = _maybe_filter_val_scalar_squot(sc); + m_evt_handler->set_val_scalar_squoted(maybe_filtered); + _end_stream(); + } + else + { + _c4dbgp("usty[UNK]: start new block map, set scalar as key"); + add_flags(RNXT); + _handle_annotations_before_start_mapblck(startline); + m_evt_handler->begin_map_val_block(); + _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); + csubstr maybe_filtered = _maybe_filter_key_scalar_squot(sc); + m_evt_handler->set_key_scalar_squoted(maybe_filtered); + _set_indentation(startindent); + addrem_flags(RMAP|RBLCK|RVAL, RNXT|USTY); + _maybe_skip_whitespace_tokens(); + } + } + else if(first == '"') + { + _c4dbgp("usty[UNK]: scanning double-quoted scalar"); + sc = _scan_scalar_dquot(); + if(!_maybe_scan_following_colon()) + { + _c4dbgp("usty[UNK]: set as val"); + _handle_annotations_before_blck_val_scalar(); + csubstr maybe_filtered = _maybe_filter_val_scalar_dquot(sc); + m_evt_handler->set_val_scalar_dquoted(maybe_filtered); + _end_stream(); + } + else + { + _c4dbgp("usty[UNK]: start new block map, set double-quoted scalar as key"); + add_flags(RNXT); + _handle_annotations_before_start_mapblck(startline); + m_evt_handler->begin_map_val_block(); + _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); + csubstr maybe_filtered = _maybe_filter_key_scalar_dquot(sc); + m_evt_handler->set_key_scalar_dquoted(maybe_filtered); + _set_indentation(startindent); + addrem_flags(RMAP|RBLCK|RVAL, RNXT|USTY); + _maybe_skip_whitespace_tokens(); + } + } + else if(first == '|') + { + _c4dbgp("usty[UNK]: scanning block-literal scalar"); + ScannedBlock sb; + _scan_block(&sb, startindent); + _c4dbgp("usty[UNK]: set as val"); + _handle_annotations_before_blck_val_scalar(); + csubstr maybe_filtered = _maybe_filter_val_scalar_literal(sb); + m_evt_handler->set_val_scalar_literal(maybe_filtered); + _end_stream(); + } + else if(first == '>') + { + _c4dbgp("usty[UNK]: scanning block-folded scalar"); + ScannedBlock sb; + _scan_block(&sb, startindent); + _c4dbgp("usty[UNK]: set as val"); + _handle_annotations_before_blck_val_scalar(); + csubstr maybe_filtered = _maybe_filter_val_scalar_folded(sb); + m_evt_handler->set_val_scalar_folded(maybe_filtered); + _end_stream(); + } + else if(_scan_scalar_plain_unk(&sc)) + { + _c4dbgp("usty[UNK]: got a plain scalar"); + if(!_maybe_scan_following_colon()) + { + _c4dbgp("usty[UNK]: set as val"); + _handle_annotations_before_blck_val_scalar(); + csubstr maybe_filtered = _maybe_filter_val_scalar_plain(sc, startindent); + m_evt_handler->set_val_scalar_plain(maybe_filtered); + _end_stream(); + } + else + { + _c4dbgp("usty[UNK]: start new block map, set scalar as key"); + add_flags(RNXT); + _handle_annotations_before_start_mapblck(startline); + m_evt_handler->begin_map_val_block(); + _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); + csubstr maybe_filtered = _maybe_filter_key_scalar_plain(sc, startindent); + m_evt_handler->set_key_scalar_plain(maybe_filtered); + _set_indentation(startindent); + addrem_flags(RMAP|RBLCK|RVAL, RNXT|USTY); + _maybe_skip_whitespace_tokens(); + } + } + else + { + _c4err("parse error"); // LCOV_EXCL_LINE + } + } + } +} + + +//----------------------------------------------------------------------------- + +template +void ParseEngine::parse_json_in_place_ev(csubstr filename, substr src) +{ + _RYML_ASSERT_BASIC_(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_stack.size() >= 1); + _RYML_SAVE_TEST_JSON(filename, src); + m_evt_handler->start_parse(filename.str, src); + m_evt_handler->begin_stream(); + _reset(); + while( ! _finished_file()) + { + _scan_line(); + while( ! _finished_line()) + { + _c4dbgnextline(); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, ! m_evt_handler->m_curr->line_contents.rem.empty(), m_evt_handler->m_curr->pos); + if(has_any(RSEQ)) + { + _handle_seq_json(); + } + else if(has_any(RMAP)) + { + _handle_map_json(); + } + else if(has_any(RUNK)) + { + _handle_unk_json(); + } + else + { + _c4err("internal error"); // LCOV_EXCL_LINE + } + } + if(_finished_file()) + break; // it may have finished because of multiline blocks + _line_ended(); + } + _end_stream(); + m_evt_handler->finish_parse(); +} + + +//----------------------------------------------------------------------------- + +template +void ParseEngine::parse_in_place_ev(csubstr filename, substr src) +{ + _RYML_ASSERT_BASIC_(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_stack.size() >= 1); + _RYML_SAVE_TEST_YAML(filename, src); + m_evt_handler->start_parse(filename.str, src); + m_evt_handler->begin_stream(); + _reset(); + while( ! _finished_file()) + { + _scan_line(); + while( ! _finished_line()) + { + _c4dbgnextline(); + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, ! m_evt_handler->m_curr->line_contents.rem.empty(), m_evt_handler->m_curr->pos); + if(has_any(RFLOW)) + { + if(has_none(RSEQIMAP)) + { + if(has_any(RSEQ)) + { + _handle_seq_flow(); + } + else + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_all(RMAP), m_evt_handler->m_curr->pos); + _handle_map_flow(); + } + } + else + { + _handle_seq_imap(); + } + } + else if(has_any(RBLCK)) + { + if(has_any(RSEQ)) + { + _handle_seq_block(); + } + else + { + _RYML_ASSERT_PARSE_(m_evt_handler->m_stack.m_callbacks, has_all(RMAP), m_evt_handler->m_curr->pos); + _handle_map_block(); + } + } + else if(has_any(RUNK)) + { + _handle_unk(); + } + else if(has_any(USTY)) + { + _handle_usty(); + } + else + { + _c4err("internal error"); // LCOV_EXCL_LINE + } + } + if(_finished_file()) + break; // it may have finished because of multiline blocks + _line_ended(); + } + _end_stream(); + m_evt_handler->finish_parse(); +} +/** @endcond */ + +} // namespace yml +} // namespace c4 + +// NOLINTEND(hicpp-signed-bitwise,cppcoreguidelines-avoid-goto,hicpp-avoid-goto,hicpp-multiway-paths-covered,modernize-avoid-c-style-cast) + +#undef _c4dbgnextline +#undef _c4assert +#undef _c4err + +C4_SUPPRESS_WARNING_MSVC_POP +C4_SUPPRESS_WARNING_GCC_CLANG_POP + +#endif // _C4_YML_PARSE_ENGINE_DEF_HPP_ + + +// (end src/c4/yml/parse_engine.def.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/tree.cpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifdef RYML_SINGLE_HDR_DEFINE_NOW +// amalgamate: removed include of +// c4/yml/tree.hpp +//#include "c4/yml/tree.hpp" +#if !defined(C4_YML_TREE_HPP_) && !defined(_C4_YML_TREE_HPP_) +#error "amalgamate: file c4/yml/tree.hpp must have been included at this point" +#endif /* C4_YML_TREE_HPP_ */ + +// amalgamate: removed include of +// c4/yml/detail/dbgprint.hpp +//#include "c4/yml/detail/dbgprint.hpp" +#if !defined(C4_YML_DETAIL_DBGPRINT_HPP_) && !defined(_C4_YML_DETAIL_DBGPRINT_HPP_) +#error "amalgamate: file c4/yml/detail/dbgprint.hpp must have been included at this point" +#endif /* C4_YML_DETAIL_DBGPRINT_HPP_ */ + +// amalgamate: removed include of +// c4/yml/node.hpp +//#include "c4/yml/node.hpp" +#if !defined(C4_YML_NODE_HPP_) && !defined(_C4_YML_NODE_HPP_) +#error "amalgamate: file c4/yml/node.hpp must have been included at this point" +#endif /* C4_YML_NODE_HPP_ */ + +// amalgamate: removed include of +// c4/yml/reference_resolver.hpp +//#include "c4/yml/reference_resolver.hpp" +#if !defined(C4_YML_REFERENCE_RESOLVER_HPP_) && !defined(_C4_YML_REFERENCE_RESOLVER_HPP_) +#error "amalgamate: file c4/yml/reference_resolver.hpp must have been included at this point" +#endif /* C4_YML_REFERENCE_RESOLVER_HPP_ */ + + + +C4_SUPPRESS_WARNING_MSVC_WITH_PUSH(4296/*expression is always 'boolean_value'*/) +C4_SUPPRESS_WARNING_MSVC(4702/*unreachable code*/) +C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wold-style-cast") +C4_SUPPRESS_WARNING_GCC("-Wtype-limits") +C4_SUPPRESS_WARNING_GCC("-Wuseless-cast") +// NOLINTBEGIN(modernize-avoid-c-style-cast) + + +namespace c4 { +namespace yml { + + +csubstr serialize_to_arena(Tree * C4_RESTRICT tree, csubstr a) +{ + if(a.len > 0) + { + substr rem(tree->m_arena.sub(tree->m_arena_pos)); + size_t num = to_chars(rem, a); + if(num > rem.len) + { + rem = tree->_grow_arena(num); + num = to_chars(rem, a); + _RYML_ASSERT_VISIT_(tree->m_callbacks, num <= rem.len, tree, NONE); + } + return tree->_request_span(num); + } + else + { + if(a.str == nullptr) + { + return csubstr{}; + } + else if(tree->m_arena.str == nullptr) + { + // Arena is empty and we want to store a non-null + // zero-length string. + // Even though the string has zero length, we need + // some "memory" to store a non-nullptr string + tree->_grow_arena(1); + } + return tree->_request_span(0); + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +NodeRef Tree::rootref() +{ + return NodeRef(this, root_id()); +} +ConstNodeRef Tree::rootref() const +{ + return ConstNodeRef(this, root_id()); +} + +ConstNodeRef Tree::crootref() const +{ + return ConstNodeRef(this, root_id()); +} + +NodeRef Tree::ref(id_type id) +{ + _RYML_ASSERT_VISIT_(m_callbacks, id != NONE && id >= 0 && id < m_cap, this, id); + return NodeRef(this, id); +} +ConstNodeRef Tree::ref(id_type id) const +{ + _RYML_ASSERT_VISIT_(m_callbacks, id != NONE && id >= 0 && id < m_cap, this, id); + return ConstNodeRef(this, id); +} +ConstNodeRef Tree::cref(id_type id) const +{ + _RYML_ASSERT_VISIT_(m_callbacks, id != NONE && id >= 0 && id < m_cap, this, id); + return ConstNodeRef(this, id); +} + +NodeRef Tree::operator[] (csubstr key) +{ + return rootref()[key]; +} +ConstNodeRef Tree::operator[] (csubstr key) const +{ + return rootref()[key]; +} + +NodeRef Tree::operator[] (id_type i) +{ + return rootref()[i]; +} +ConstNodeRef Tree::operator[] (id_type i) const +{ + return rootref()[i]; +} + +NodeRef Tree::docref(id_type i) +{ + return ref(doc(i)); +} +ConstNodeRef Tree::docref(id_type i) const +{ + return cref(doc(i)); +} +ConstNodeRef Tree::cdocref(id_type i) const +{ + return cref(doc(i)); +} + + +//----------------------------------------------------------------------------- +Tree::Tree(Callbacks const& cb) + : Tree(RYML_DEFAULT_TREE_CAPACITY, RYML_DEFAULT_TREE_ARENA_CAPACITY, cb) +{ +} + +Tree::Tree(id_type node_capacity, size_t arena_capacity, Callbacks const& cb) + : m_buf(nullptr) + , m_cap(0) + , m_size(0) + , m_free_head(NONE) + , m_free_tail(NONE) + , m_arena() + , m_arena_pos(0) + , m_callbacks(cb) + , m_tag_directives() +{ + if(node_capacity) + reserve(node_capacity); + if(arena_capacity) + reserve_arena(arena_capacity); +} + +Tree::~Tree() +{ + _free(); +} + + +Tree::Tree(Tree const& that) : m_callbacks(that.m_callbacks) +{ + _clear(); + _copy(that); +} + +Tree::Tree(Tree && that) noexcept : m_callbacks(that.m_callbacks) +{ + _clear(); + _move(that); +} + +Tree& Tree::operator= (Tree const& that) +{ + if(&that != this) + { + _free(); + m_callbacks = that.m_callbacks; + _copy(that); + } + return *this; +} + +Tree& Tree::operator= (Tree && that) noexcept +{ + if(&that != this) + { + _free(); + m_callbacks = that.m_callbacks; + _move(that); + } + return *this; +} + +void Tree::_free() +{ + if(m_buf) + { + _RYML_ASSERT_VISIT_(m_callbacks, m_cap > 0, this, NONE); + _RYML_CB_FREE(m_callbacks, m_buf, NodeData, (size_t)m_cap); + } + if(m_arena.str) + { + _RYML_ASSERT_VISIT_(m_callbacks, m_arena.len > 0, this, NONE); + _RYML_CB_FREE(m_callbacks, m_arena.str, char, m_arena.len); + } + _clear(); +} + + +C4_SUPPRESS_WARNING_GCC_PUSH +#if defined(__GNUC__) && __GNUC__>= 8 + C4_SUPPRESS_WARNING_GCC_WITH_PUSH("-Wclass-memaccess") // error: ‘void* memset(void*, int, size_t)’ clearing an object of type ‘class c4::yml::Tree’ with no trivial copy-assignment; use assignment or value-initialization instead +#endif + +void Tree::_clear() +{ + m_buf = nullptr; + m_cap = 0; + m_size = 0; + m_free_head = 0; + m_free_tail = 0; + m_arena = {}; + m_arena_pos = 0; + m_tag_directives.clear(); +} + +void Tree::_copy(Tree const& that) +{ + _RYML_ASSERT_VISIT_(m_callbacks, m_buf == nullptr, this, NONE); + _RYML_ASSERT_VISIT_(m_callbacks, m_arena.str == nullptr, this, NONE); + _RYML_ASSERT_VISIT_(m_callbacks, m_arena.len == 0, this, NONE); + if(that.m_cap) + { + m_buf = _RYML_CB_ALLOC_HINT(m_callbacks, NodeData, (size_t)that.m_cap, that.m_buf); + memcpy(m_buf, that.m_buf, (size_t)that.m_cap * sizeof(NodeData)); + } + m_cap = that.m_cap; + m_size = that.m_size; + m_free_head = that.m_free_head; + m_free_tail = that.m_free_tail; + m_arena_pos = that.m_arena_pos; + m_arena = that.m_arena; + m_tag_directives = that.m_tag_directives; + if(that.m_arena.str) + { + _RYML_ASSERT_VISIT_(m_callbacks, that.m_arena.len > 0, this, NONE); + substr arena; + arena.str = _RYML_CB_ALLOC_HINT(m_callbacks, char, that.m_arena.len, that.m_arena.str); + arena.len = that.m_arena.len; + _relocate(arena); // does a memcpy of the arena and updates nodes using the old arena + m_arena = arena; + } +} + +void Tree::_move(Tree & that) noexcept +{ + _RYML_ASSERT_VISIT_(m_callbacks, m_buf == nullptr, this, NONE); + _RYML_ASSERT_VISIT_(m_callbacks, m_arena.str == nullptr, this, NONE); + _RYML_ASSERT_VISIT_(m_callbacks, m_arena.len == 0, this, NONE); + m_buf = that.m_buf; + m_cap = that.m_cap; + m_size = that.m_size; + m_free_head = that.m_free_head; + m_free_tail = that.m_free_tail; + m_arena = that.m_arena; + m_arena_pos = that.m_arena_pos; + m_tag_directives = that.m_tag_directives; + that._clear(); +} + +void Tree::_relocate(substr next_arena) +{ + _RYML_ASSERT_VISIT_(m_callbacks, next_arena.not_empty(), this, NONE); + _RYML_ASSERT_VISIT_(m_callbacks, next_arena.len >= m_arena.len, this, NONE); + if(m_arena_pos) + { + memcpy(next_arena.str, m_arena.str, m_arena_pos); + } + for(NodeData *C4_RESTRICT n = m_buf, *e = m_buf + m_cap; n != e; ++n) + { + if(in_arena(n->m_key.scalar)) + n->m_key.scalar = _relocated(n->m_key.scalar, next_arena); + if(in_arena(n->m_key.tag)) + n->m_key.tag = _relocated(n->m_key.tag, next_arena); + if(in_arena(n->m_key.anchor)) + n->m_key.anchor = _relocated(n->m_key.anchor, next_arena); + if(in_arena(n->m_val.scalar)) + n->m_val.scalar = _relocated(n->m_val.scalar, next_arena); + if(in_arena(n->m_val.tag)) + n->m_val.tag = _relocated(n->m_val.tag, next_arena); + if(in_arena(n->m_val.anchor)) + n->m_val.anchor = _relocated(n->m_val.anchor, next_arena); + } + for(TagDirective &C4_RESTRICT td : m_tag_directives) + { + if(in_arena(td.prefix)) + td.prefix = _relocated(td.prefix, next_arena); + if(in_arena(td.handle)) + td.handle = _relocated(td.handle, next_arena); + } +} + + +//----------------------------------------------------------------------------- +void Tree::reserve(id_type cap) +{ + if(cap > m_cap) + { + NodeData *buf = _RYML_CB_ALLOC_HINT(m_callbacks, NodeData, (size_t)cap, m_buf); + if(m_buf) + { + memcpy(buf, m_buf, (size_t)m_cap * sizeof(NodeData)); + _RYML_CB_FREE(m_callbacks, m_buf, NodeData, (size_t)m_cap); + } + id_type first = m_cap, del = cap - m_cap; + m_cap = cap; + m_buf = buf; + _clear_range(first, del); + if(m_free_head != NONE) + { + _RYML_ASSERT_VISIT_(m_callbacks, m_buf != nullptr, this, NONE); + _RYML_ASSERT_VISIT_(m_callbacks, m_free_tail != NONE, this, NONE); + m_buf[m_free_tail].m_next_sibling = first; + m_buf[first].m_prev_sibling = m_free_tail; + m_free_tail = cap-1; + } + else + { + _RYML_ASSERT_VISIT_(m_callbacks, m_free_tail == NONE, this, NONE); + m_free_head = first; + m_free_tail = cap-1; + } + _RYML_ASSERT_VISIT_(m_callbacks, m_free_head == NONE || (m_free_head >= 0 && m_free_head < cap), this, NONE); + _RYML_ASSERT_VISIT_(m_callbacks, m_free_tail == NONE || (m_free_tail >= 0 && m_free_tail < cap), this, NONE); + + if( ! m_size) + _claim_root(); + } +} + + +//----------------------------------------------------------------------------- +void Tree::clear() +{ + _clear_range(0, m_cap); + m_size = 0; + if(m_buf) + { + _RYML_ASSERT_VISIT_(m_callbacks, m_cap >= 0, this, NONE); + m_free_head = 0; + m_free_tail = m_cap-1; + _claim_root(); + } + else + { + m_free_head = NONE; + m_free_tail = NONE; + } + m_tag_directives.clear(); +} + +void Tree::_claim_root() +{ + id_type r = _claim(); + _RYML_ASSERT_VISIT_(m_callbacks, r == 0, this, r); + _set_hierarchy(r, NONE, NONE); +} + + +//----------------------------------------------------------------------------- +void Tree::_clear_range(id_type first, id_type num) +{ + if(num == 0) + return; // prevent overflow when subtracting + _RYML_ASSERT_VISIT_(m_callbacks, first >= 0 && first + num <= m_cap, this, first); + memset(m_buf + first, 0, (size_t)num * sizeof(NodeData)); // TODO we should not need this + for(id_type i = first, e = first + num; i < e; ++i) + { + _clear(i); + NodeData *n = m_buf + i; + n->m_prev_sibling = i - 1; + n->m_next_sibling = i + 1; + } + m_buf[first + num - 1].m_next_sibling = NONE; +} + +C4_SUPPRESS_WARNING_GCC_POP + + +//----------------------------------------------------------------------------- +void Tree::_release(id_type i) +{ + _RYML_ASSERT_VISIT_(m_callbacks, i >= 0 && i < m_cap, this, i); + + _rem_hierarchy(i); + _free_list_add(i); + _clear(i); + + --m_size; +} + +//----------------------------------------------------------------------------- +// add to the front of the free list +void Tree::_free_list_add(id_type i) +{ + _RYML_ASSERT_VISIT_(m_callbacks, i >= 0 && i < m_cap, this, i); + NodeData &C4_RESTRICT w = m_buf[i]; + + w.m_parent = NONE; + w.m_next_sibling = m_free_head; + w.m_prev_sibling = NONE; + if(m_free_head != NONE) + m_buf[m_free_head].m_prev_sibling = i; + m_free_head = i; + if(m_free_tail == NONE) + m_free_tail = m_free_head; +} + +void Tree::_free_list_rem(id_type i) +{ + if(m_free_head == i) + m_free_head = _p(i)->m_next_sibling; + _rem_hierarchy(i); +} + +//----------------------------------------------------------------------------- +id_type Tree::_claim() +{ + if(m_free_head == NONE || m_buf == nullptr) + { + id_type sz = 2 * m_cap; + sz = sz ? sz : 16; + reserve(sz); + _RYML_ASSERT_VISIT_(m_callbacks, m_free_head != NONE, this, NONE); + } + + _RYML_ASSERT_VISIT_(m_callbacks, m_size < m_cap, this, NONE); + _RYML_ASSERT_VISIT_(m_callbacks, m_free_head >= 0 && m_free_head < m_cap, this, NONE); + + id_type ichild = m_free_head; + NodeData *child = m_buf + ichild; + + ++m_size; + m_free_head = child->m_next_sibling; + if(m_free_head == NONE) + { + m_free_tail = NONE; + _RYML_ASSERT_VISIT_(m_callbacks, m_size == m_cap, this, NONE); + } + + _clear(ichild); + + return ichild; +} + +//----------------------------------------------------------------------------- + +C4_SUPPRESS_WARNING_GCC_PUSH +C4_SUPPRESS_WARNING_CLANG_PUSH +C4_SUPPRESS_WARNING_CLANG("-Wnull-dereference") +#if defined(__GNUC__) +#if (__GNUC__ >= 6) +C4_SUPPRESS_WARNING_GCC("-Wnull-dereference") +#endif +#if (__GNUC__ > 9) +C4_SUPPRESS_WARNING_GCC("-Wanalyzer-fd-leak") +#endif +#endif + +void Tree::_set_hierarchy(id_type ichild, id_type iparent, id_type iprev_sibling) +{ + _RYML_ASSERT_VISIT_(m_callbacks, ichild >= 0 && ichild < m_cap, this, ichild); + _RYML_ASSERT_VISIT_(m_callbacks, iparent == NONE || (iparent >= 0 && iparent < m_cap), this, iparent); + _RYML_ASSERT_VISIT_(m_callbacks, iprev_sibling == NONE || (iprev_sibling >= 0 && iprev_sibling < m_cap), this, iprev_sibling); + + NodeData *C4_RESTRICT child = _p(ichild); + + child->m_parent = iparent; + child->m_prev_sibling = NONE; + child->m_next_sibling = NONE; + + if(iparent == NONE) + { + _RYML_ASSERT_VISIT_(m_callbacks, ichild == 0, this, ichild); + _RYML_ASSERT_VISIT_(m_callbacks, iprev_sibling == NONE, this, iprev_sibling); + } + + if(iparent == NONE) + return; + + id_type inext_sibling = iprev_sibling != NONE ? next_sibling(iprev_sibling) : first_child(iparent); + NodeData *C4_RESTRICT parent = get(iparent); + NodeData *C4_RESTRICT psib = get(iprev_sibling); + NodeData *C4_RESTRICT nsib = get(inext_sibling); + + if(psib) + { + _RYML_ASSERT_VISIT_(m_callbacks, next_sibling(iprev_sibling) == id(nsib), this, iprev_sibling); + child->m_prev_sibling = id(psib); + psib->m_next_sibling = id(child); + _RYML_ASSERT_VISIT_(m_callbacks, psib->m_prev_sibling != psib->m_next_sibling || psib->m_prev_sibling == NONE, this, iprev_sibling); + } + + if(nsib) + { + _RYML_ASSERT_VISIT_(m_callbacks, prev_sibling(inext_sibling) == id(psib), this, inext_sibling); + child->m_next_sibling = id(nsib); + nsib->m_prev_sibling = id(child); + _RYML_ASSERT_VISIT_(m_callbacks, nsib->m_prev_sibling != nsib->m_next_sibling || nsib->m_prev_sibling == NONE, this, inext_sibling); + } + + if(parent->m_first_child == NONE) + { + _RYML_ASSERT_VISIT_(m_callbacks, parent->m_last_child == NONE, this, parent->m_last_child); + parent->m_first_child = id(child); + parent->m_last_child = id(child); + } + else + { + if(child->m_next_sibling == parent->m_first_child) + parent->m_first_child = id(child); + + if(child->m_prev_sibling == parent->m_last_child) + parent->m_last_child = id(child); + } +} + +C4_SUPPRESS_WARNING_GCC_POP +C4_SUPPRESS_WARNING_CLANG_POP + + +//----------------------------------------------------------------------------- +void Tree::_rem_hierarchy(id_type i) +{ + _RYML_ASSERT_VISIT_(m_callbacks, i >= 0 && i < m_cap, this, i); + + NodeData &C4_RESTRICT w = m_buf[i]; + + // remove from the parent + if(w.m_parent != NONE) + { + NodeData &C4_RESTRICT p = m_buf[w.m_parent]; + if(p.m_first_child == i) + { + p.m_first_child = w.m_next_sibling; + } + if(p.m_last_child == i) + { + p.m_last_child = w.m_prev_sibling; + } + } + + // remove from the used list + if(w.m_prev_sibling != NONE) + { + NodeData *C4_RESTRICT prev = get(w.m_prev_sibling); + prev->m_next_sibling = w.m_next_sibling; + } + if(w.m_next_sibling != NONE) + { + NodeData *C4_RESTRICT next = get(w.m_next_sibling); + next->m_prev_sibling = w.m_prev_sibling; + } +} + +//----------------------------------------------------------------------------- +/** @cond dev */ +id_type Tree::_do_reorder(id_type *node, id_type count) +{ + // swap this node if it's not in place + if(*node != count) + { + _swap(*node, count); + *node = count; + } + ++count; // bump the count from this node + + // now descend in the hierarchy + for(id_type i = first_child(*node); i != NONE; i = next_sibling(i)) + { + // this child may have been relocated to a different index, + // so get an updated version + count = _do_reorder(&i, count); + } + return count; +} +/** @endcond */ + +void Tree::reorder() +{ + id_type r = root_id(); + _do_reorder(&r, 0); +} + + +//----------------------------------------------------------------------------- +/** @cond dev */ +void Tree::_swap(id_type n_, id_type m_) +{ + _RYML_ASSERT_VISIT_(m_callbacks, (parent(n_) != NONE) || type(n_) == NOTYPE, this, n_); + _RYML_ASSERT_VISIT_(m_callbacks, (parent(m_) != NONE) || type(m_) == NOTYPE, this, m_); + NodeType tn = type(n_); + NodeType tm = type(m_); + if(tn != NOTYPE && tm != NOTYPE) + { + _swap_props(n_, m_); + _swap_hierarchy(n_, m_); + } + else if(tn == NOTYPE && tm != NOTYPE) + { + _copy_props(n_, m_); + _free_list_rem(n_); + _copy_hierarchy(n_, m_); + _clear(m_); + _free_list_add(m_); + } + else if(tn != NOTYPE && tm == NOTYPE) + { + _copy_props(m_, n_); + _free_list_rem(m_); + _copy_hierarchy(m_, n_); + _clear(n_); + _free_list_add(n_); + } + else + { + C4_NEVER_REACH(); + } +} + +//----------------------------------------------------------------------------- +void Tree::_swap_hierarchy(id_type ia, id_type ib) +{ + if(ia == ib) return; + + for(id_type i = first_child(ia); i != NONE; i = next_sibling(i)) + { + if(i == ib || i == ia) + continue; + _p(i)->m_parent = ib; + } + + for(id_type i = first_child(ib); i != NONE; i = next_sibling(i)) + { + if(i == ib || i == ia) + continue; + _p(i)->m_parent = ia; + } + + auto & C4_RESTRICT a = *_p(ia); + auto & C4_RESTRICT b = *_p(ib); + auto & C4_RESTRICT pa = *_p(a.m_parent); + auto & C4_RESTRICT pb = *_p(b.m_parent); + + if(&pa == &pb) + { + if((pa.m_first_child == ib && pa.m_last_child == ia) + || + (pa.m_first_child == ia && pa.m_last_child == ib)) + { + std::swap(pa.m_first_child, pa.m_last_child); + } + else + { + bool changed = false; + if(pa.m_first_child == ia) + { + pa.m_first_child = ib; + changed = true; + } + if(pa.m_last_child == ia) + { + pa.m_last_child = ib; + changed = true; + } + if(pb.m_first_child == ib && !changed) + { + pb.m_first_child = ia; + } + if(pb.m_last_child == ib && !changed) + { + pb.m_last_child = ia; + } + } + } + else + { + if(pa.m_first_child == ia) + pa.m_first_child = ib; + if(pa.m_last_child == ia) + pa.m_last_child = ib; + if(pb.m_first_child == ib) + pb.m_first_child = ia; + if(pb.m_last_child == ib) + pb.m_last_child = ia; + } + std::swap(a.m_first_child , b.m_first_child); + std::swap(a.m_last_child , b.m_last_child); + + if(a.m_prev_sibling != ib && b.m_prev_sibling != ia && + a.m_next_sibling != ib && b.m_next_sibling != ia) + { + if(a.m_prev_sibling != NONE && a.m_prev_sibling != ib) + _p(a.m_prev_sibling)->m_next_sibling = ib; + if(a.m_next_sibling != NONE && a.m_next_sibling != ib) + _p(a.m_next_sibling)->m_prev_sibling = ib; + if(b.m_prev_sibling != NONE && b.m_prev_sibling != ia) + _p(b.m_prev_sibling)->m_next_sibling = ia; + if(b.m_next_sibling != NONE && b.m_next_sibling != ia) + _p(b.m_next_sibling)->m_prev_sibling = ia; + std::swap(a.m_prev_sibling, b.m_prev_sibling); + std::swap(a.m_next_sibling, b.m_next_sibling); + } + else + { + if(a.m_next_sibling == ib) // n will go after m + { + _RYML_ASSERT_VISIT_(m_callbacks, b.m_prev_sibling == ia, this, ia); + if(a.m_prev_sibling != NONE) + { + _RYML_ASSERT_VISIT_(m_callbacks, a.m_prev_sibling != ib, this, ib); + _p(a.m_prev_sibling)->m_next_sibling = ib; + } + if(b.m_next_sibling != NONE) + { + _RYML_ASSERT_VISIT_(m_callbacks, b.m_next_sibling != ia, this, ia); + _p(b.m_next_sibling)->m_prev_sibling = ia; + } + id_type ns = b.m_next_sibling; + b.m_prev_sibling = a.m_prev_sibling; + b.m_next_sibling = ia; + a.m_prev_sibling = ib; + a.m_next_sibling = ns; + } + else if(a.m_prev_sibling == ib) // m will go after n + { + _RYML_ASSERT_VISIT_(m_callbacks, b.m_next_sibling == ia, this, ia); + if(b.m_prev_sibling != NONE) + { + _RYML_ASSERT_VISIT_(m_callbacks, b.m_prev_sibling != ia, this, ia); + _p(b.m_prev_sibling)->m_next_sibling = ia; + } + if(a.m_next_sibling != NONE) + { + _RYML_ASSERT_VISIT_(m_callbacks, a.m_next_sibling != ib, this, ib); + _p(a.m_next_sibling)->m_prev_sibling = ib; + } + id_type ns = b.m_prev_sibling; + a.m_prev_sibling = b.m_prev_sibling; + a.m_next_sibling = ib; + b.m_prev_sibling = ia; + b.m_next_sibling = ns; + } + else + { + C4_NEVER_REACH(); + } + } + _RYML_ASSERT_VISIT_(m_callbacks, a.m_next_sibling != ia, this, ia); + _RYML_ASSERT_VISIT_(m_callbacks, a.m_prev_sibling != ia, this, ia); + _RYML_ASSERT_VISIT_(m_callbacks, b.m_next_sibling != ib, this, ib); + _RYML_ASSERT_VISIT_(m_callbacks, b.m_prev_sibling != ib, this, ib); + + if(a.m_parent != ib && b.m_parent != ia) + { + std::swap(a.m_parent, b.m_parent); + } + else + { + if(a.m_parent == ib && b.m_parent != ia) + { + a.m_parent = b.m_parent; + b.m_parent = ia; + } + else if(a.m_parent != ib && b.m_parent == ia) + { + b.m_parent = a.m_parent; + a.m_parent = ib; + } + else + { + C4_NEVER_REACH(); + } + } +} + +//----------------------------------------------------------------------------- +void Tree::_copy_hierarchy(id_type dst_, id_type src_) +{ + auto const& C4_RESTRICT src = *_p(src_); + auto & C4_RESTRICT dst = *_p(dst_); + auto & C4_RESTRICT prt = *_p(src.m_parent); + for(id_type i = src.m_first_child; i != NONE; i = next_sibling(i)) + { + _p(i)->m_parent = dst_; + } + if(src.m_prev_sibling != NONE) + { + _p(src.m_prev_sibling)->m_next_sibling = dst_; + } + if(src.m_next_sibling != NONE) + { + _p(src.m_next_sibling)->m_prev_sibling = dst_; + } + if(prt.m_first_child == src_) + { + prt.m_first_child = dst_; + } + if(prt.m_last_child == src_) + { + prt.m_last_child = dst_; + } + dst.m_parent = src.m_parent; + dst.m_first_child = src.m_first_child; + dst.m_last_child = src.m_last_child; + dst.m_prev_sibling = src.m_prev_sibling; + dst.m_next_sibling = src.m_next_sibling; +} + +//----------------------------------------------------------------------------- +void Tree::_swap_props(id_type n_, id_type m_) +{ + NodeData &C4_RESTRICT n = *_p(n_); + NodeData &C4_RESTRICT m = *_p(m_); + std::swap(n.m_type, m.m_type); + std::swap(n.m_key, m.m_key); + std::swap(n.m_val, m.m_val); +} +/** @endcond */ + +//----------------------------------------------------------------------------- +void Tree::move(id_type node, id_type after) +{ + _RYML_ASSERT_VISIT_(m_callbacks, node != NONE, this, node); + _RYML_ASSERT_VISIT_(m_callbacks, node != after, this, node); + _RYML_ASSERT_VISIT_(m_callbacks, ! is_root(node), this, node); + _RYML_ASSERT_VISIT_(m_callbacks, (after == NONE) || (has_sibling(node, after) && has_sibling(after, node)), this, node); + + _rem_hierarchy(node); + _set_hierarchy(node, parent(node), after); +} + +//----------------------------------------------------------------------------- + +void Tree::move(id_type node, id_type new_parent, id_type after) +{ + _RYML_ASSERT_VISIT_(m_callbacks, node != NONE, this, node); + _RYML_ASSERT_VISIT_(m_callbacks, node != after, this, node); + _RYML_ASSERT_VISIT_(m_callbacks, new_parent != NONE, this, new_parent); + _RYML_ASSERT_VISIT_(m_callbacks, new_parent != node, this, new_parent); + _RYML_ASSERT_VISIT_(m_callbacks, new_parent != after, this, new_parent); + _RYML_ASSERT_VISIT_(m_callbacks, ! is_root(node), this, node); + + _rem_hierarchy(node); + _set_hierarchy(node, new_parent, after); +} + +id_type Tree::move(Tree *src, id_type node, id_type new_parent, id_type after) +{ + _RYML_ASSERT_VISIT_(m_callbacks, src != nullptr, this, new_parent); + _RYML_ASSERT_VISIT_(m_callbacks, node != NONE, this, new_parent); + _RYML_ASSERT_VISIT_(m_callbacks, new_parent != NONE, this, new_parent); + _RYML_ASSERT_VISIT_(m_callbacks, new_parent != after, this, new_parent); + + id_type dup = duplicate(src, node, new_parent, after); + src->remove(node); + return dup; +} + +void Tree::set_root_as_stream() +{ + id_type root = root_id(); + if(is_stream(root)) + return; + _c4dbgpf("set_root_as_stream. rootty={}", type(root).type); + bool empty_root = ((type(root) & (SEQ|MAP|VAL)) == 0); + for(TagDirective &C4_RESTRICT td : m_tag_directives) + { + if(td.doc_id >= m_cap || _p(td.doc_id)->m_parent == NONE) + { + _c4dbgpf("tagd[{}]: id={}->NONE", &td-m_tag_directives.m_directives, td.doc_id); + td.doc_id = NONE; + } + } + // don't use _add_flags() because it's checked and will fail + id_type next_doc; + if(!has_children(root)) + { + if(is_container(root)) + { + next_doc = append_child(root); + _copy_props_wo_key(next_doc, root); + _p(next_doc)->m_type.add(DOC); + } + else + { + _p(root)->m_type.add(SEQ); + next_doc = append_child(root); + _copy_props_wo_key(next_doc, root); + _p(next_doc)->m_type.add(DOC); + _p(next_doc)->m_type.rem(SEQ); + } + } + else + { + _RYML_ASSERT_VISIT_(m_callbacks, !has_key(root), this, root); + next_doc = append_child(root); + _copy_props_wo_key(next_doc, root); + _add_flags(next_doc, DOC); + for(id_type prev = NONE, ch = first_child(root), next = next_sibling(ch); ch != NONE; ) + { + if(ch == next_doc) + break; + move(ch, next_doc, prev); + prev = ch; + ch = next; + next = next_sibling(next); + } + } + _p(root)->m_type = STREAM; + for(TagDirective &C4_RESTRICT td : m_tag_directives) + { + id_type id = (td.doc_id != NONE) ? next_doc : (empty_root ? first_child(root) : m_free_head); + _c4dbgpf("tagd[{}]: id={}->{}", &td-m_tag_directives.m_directives, td.doc_id, id); + td.doc_id = id; + } +} + + +//----------------------------------------------------------------------------- +void Tree::remove_children(id_type node) +{ + _RYML_ASSERT_VISIT_(m_callbacks, get(node) != nullptr, this, node); + C4_SUPPRESS_WARNING_GCC_PUSH + #if defined(__GNUC__) && __GNUC__ >= 6 + C4_SUPPRESS_WARNING_GCC("-Wnull-dereference") + #endif + id_type ich = get(node)->m_first_child; + while(ich != NONE) + { + remove_children(ich); + _RYML_ASSERT_VISIT_(m_callbacks, get(ich) != nullptr, this, node); + id_type next = get(ich)->m_next_sibling; + _release(ich); + if(ich == get(node)->m_last_child) + break; + ich = next; + } + C4_SUPPRESS_WARNING_GCC_POP +} + +bool Tree::change_type(id_type node, NodeType type) +{ + _RYML_ASSERT_VISIT_(m_callbacks, type.is_val() || type.is_map() || type.is_seq(), this, node); + _RYML_ASSERT_VISIT_(m_callbacks, type.is_val() + type.is_map() + type.is_seq() == 1, this, node); + _RYML_ASSERT_VISIT_(m_callbacks, type.has_key() == has_key(node) || (has_key(node) && !type.has_key()), this, node); + NodeData *d = _p(node); + if(type.is_map() && is_map(node)) + return false; + else if(type.is_seq() && is_seq(node)) + return false; + else if(type.is_val() && is_val(node)) + return false; + d->m_type = (d->m_type & (~(MAP|SEQ|VAL|CONTAINER_STYLE|KEY_STYLE|VAL_STYLE))) | type; + remove_children(node); + return true; +} + + +//----------------------------------------------------------------------------- +id_type Tree::duplicate(id_type node, id_type parent, id_type after) +{ + return duplicate(this, node, parent, after); +} + +id_type Tree::duplicate(Tree const* src, id_type node, id_type parent, id_type after) +{ + _RYML_ASSERT_VISIT_(m_callbacks, src != nullptr, src, node); + _RYML_ASSERT_VISIT_(m_callbacks, node != NONE, src, node); + _RYML_ASSERT_VISIT_(m_callbacks, parent != NONE, this, parent); + _RYML_ASSERT_VISIT_(m_callbacks, ! src->is_root(node), src, node); + + id_type copy = _claim(); + + _copy_props(copy, src, node); + _set_hierarchy(copy, parent, after); + duplicate_children(src, node, copy, NONE); + + return copy; +} + +//----------------------------------------------------------------------------- +id_type Tree::duplicate_children(id_type node, id_type parent, id_type after) +{ + return duplicate_children(this, node, parent, after); +} + +id_type Tree::duplicate_children(Tree const* src, id_type node, id_type parent, id_type after) +{ + _RYML_ASSERT_VISIT_(m_callbacks, src != nullptr, src, node); + _RYML_ASSERT_VISIT_(m_callbacks, node != NONE, src, node); + _RYML_ASSERT_VISIT_(m_callbacks, parent != NONE, this, parent); + _RYML_ASSERT_VISIT_(m_callbacks, after == NONE || has_child(parent, after), this, parent); + + id_type prev = after; + for(id_type i = src->first_child(node); i != NONE; i = src->next_sibling(i)) + { + prev = duplicate(src, i, parent, prev); + } + + return prev; +} + +//----------------------------------------------------------------------------- +void Tree::duplicate_contents(id_type node, id_type where) +{ + duplicate_contents(this, node, where); +} + +void Tree::duplicate_contents(Tree const *src, id_type node, id_type where) +{ + _RYML_ASSERT_VISIT_(m_callbacks, src != nullptr, src, node); + _RYML_ASSERT_VISIT_(m_callbacks, node != NONE, src, node); + _RYML_ASSERT_VISIT_(m_callbacks, where != NONE, this, where); + _copy_props_wo_key(where, src, node); + duplicate_children(src, node, where, last_child(where)); +} + +//----------------------------------------------------------------------------- +id_type Tree::duplicate_children_no_rep(id_type node, id_type parent, id_type after) +{ + return duplicate_children_no_rep(this, node, parent, after); +} + +id_type Tree::duplicate_children_no_rep(Tree const *src, id_type node, id_type parent, id_type after) +{ + _RYML_ASSERT_VISIT_(m_callbacks, node != NONE, src, node); + _RYML_ASSERT_VISIT_(m_callbacks, parent != NONE, this, parent); + _RYML_ASSERT_VISIT_(m_callbacks, after == NONE || has_child(parent, after), this, parent); + + // don't loop using pointers as there may be a relocation + + // find the position where "after" is + id_type after_pos = NONE; + if(after != NONE) + { + for(id_type i = first_child(parent), icount = 0; i != NONE; ++icount, i = next_sibling(i)) + { + if(i == after) + { + after_pos = icount; + break; + } + } + _RYML_ASSERT_VISIT_(m_callbacks, after_pos != NONE, this, node); + } + + // for each child to be duplicated... + id_type prev = after; + for(id_type i = src->first_child(node); i != NONE; i = src->next_sibling(i)) + { + _c4dbgpf("duplicate_no_rep: {} -> {}/{}", i, parent, prev); + _RYML_CHECK_VISIT_(m_callbacks, this != src || (parent != i && !is_ancestor(parent, i)), this, parent); + if(is_seq(parent)) + { + _c4dbgpf("duplicate_no_rep: {} is seq", parent); + prev = duplicate(src, i, parent, prev); + } + else + { + _c4dbgpf("duplicate_no_rep: {} is map", parent); + _RYML_ASSERT_VISIT_(m_callbacks, is_map(parent), this, parent); + // does the parent already have a node with key equal to that of the current duplicate? + id_type dstnode_dup = NONE, dstnode_dup_pos = NONE; + { + csubstr srckey = src->key(i); + for(id_type j = first_child(parent), jcount = 0; j != NONE; ++jcount, j = next_sibling(j)) + { + if(key(j) == srckey) + { + _c4dbgpf("duplicate_no_rep: found matching key '{}' src={}/{} dst={}/{}", srckey, node, i, parent, j); + dstnode_dup = j; + dstnode_dup_pos = jcount; + break; + } + } + } + _c4dbgpf("duplicate_no_rep: dstnode_dup={} dstnode_dup_pos={} after_pos={}", dstnode_dup, dstnode_dup_pos, after_pos); + if(dstnode_dup == NONE) // there is no repetition; just duplicate + { + _c4dbgpf("duplicate_no_rep: no repetition, just duplicate i={} parent={} prev={}", i, parent, prev); + prev = duplicate(src, i, parent, prev); + } + else // yes, there is a repetition + { + if(after_pos != NONE && dstnode_dup_pos <= after_pos) + { + // the dst duplicate is located before the node which will be inserted, + // and will be overridden by the duplicate. So replace it. + _c4dbgpf("duplicate_no_dstnode_dup: replace {}/{} with {}/{}", parent, dstnode_dup, node, i); + if(prev == dstnode_dup) + prev = prev_sibling(dstnode_dup); + remove(dstnode_dup); + prev = duplicate(src, i, parent, prev); + } + else if(prev == NONE) + { + _c4dbgpf("duplicate_no_dstnode_dup: {}=prev <- {}", prev, dstnode_dup); + // first iteration with prev = after = NONE and dstnode_dupetition + prev = dstnode_dup; + } + else if(dstnode_dup != prev) + { + // dstnode_dup is located after the node which will be inserted + // and overrides it. So move the dstnode_dup into this node's place. + _c4dbgpf("duplicate_no_dstnode_dup: move({}, {})", dstnode_dup, prev); + move(dstnode_dup, prev); + prev = dstnode_dup; + } + } // there's a dstnode_dupetition + } + } + + return prev; +} + + +//----------------------------------------------------------------------------- + +void Tree::merge_with(Tree const *src, id_type src_node, id_type dst_node) +{ + _RYML_ASSERT_VISIT_(m_callbacks, src != nullptr, src, src_node); + if(src_node == NONE) + src_node = src->root_id(); + if(dst_node == NONE) + dst_node = root_id(); + _RYML_ASSERT_VISIT_(m_callbacks, src->has_val(src_node) || src->is_seq(src_node) || src->is_map(src_node), src, src_node); + if(src->has_val(src_node)) + { + type_bits mask_src = ~STYLE; // keep the existing style if it is already a val + if( ! has_val(dst_node)) + { + if(has_children(dst_node)) + remove_children(dst_node); + mask_src |= VAL_STYLE; // copy the src style + } + if(src->is_keyval(src_node)) + { + _copy_props(dst_node, src, src_node, mask_src); + } + else + { + _RYML_ASSERT_VISIT_(m_callbacks, src->is_val(src_node), src, src_node); + _copy_props_wo_key(dst_node, src, src_node, mask_src); + } + } + else if(src->is_seq(src_node)) + { + if( ! is_seq(dst_node)) + { + if(has_children(dst_node)) + remove_children(dst_node); + _clear_type(dst_node); + if(src->has_key(src_node)) + to_seq(dst_node, src->key(src_node)); + else + to_seq(dst_node); + _p(dst_node)->m_type = src->_p(src_node)->m_type; + } + for(id_type sch = src->first_child(src_node); sch != NONE; sch = src->next_sibling(sch)) + { + id_type dch = append_child(dst_node); + _copy_props_wo_key(dch, src, sch); + merge_with(src, sch, dch); + } + } + else + { + _RYML_ASSERT_VISIT_(m_callbacks, src->is_map(src_node), src, src_node); + if( ! is_map(dst_node)) + { + if(has_children(dst_node)) + remove_children(dst_node); + _clear_type(dst_node); + if(src->has_key(src_node)) + to_map(dst_node, src->key(src_node)); + else + to_map(dst_node); + _p(dst_node)->m_type = src->_p(src_node)->m_type; + } + for(id_type sch = src->first_child(src_node); sch != NONE; sch = src->next_sibling(sch)) + { + id_type dch = find_child(dst_node, src->key(sch)); + if(dch == NONE) + { + dch = append_child(dst_node); + _copy_props(dch, src, sch); + } + merge_with(src, sch, dch); + } + } +} + + +//----------------------------------------------------------------------------- + +void Tree::resolve(bool clear_anchors) +{ + if(m_size == 0) + return; + ReferenceResolver rr; + resolve(&rr, clear_anchors); +} + +void Tree::resolve(ReferenceResolver *C4_RESTRICT rr, bool clear_anchors) +{ + if(m_size == 0) + return; + rr->resolve(this, clear_anchors); +} + + +//----------------------------------------------------------------------------- + +id_type Tree::num_children(id_type node) const +{ + id_type count = 0; + for(id_type i = first_child(node); i != NONE; i = next_sibling(i)) + ++count; + return count; +} + +id_type Tree::child(id_type node, id_type pos) const +{ + _RYML_ASSERT_VISIT_(m_callbacks, node != NONE, this, node); + id_type count = 0; + for(id_type i = first_child(node); i != NONE; i = next_sibling(i)) + { + if(count++ == pos) + return i; + } + return NONE; +} + +id_type Tree::child_pos(id_type node, id_type ch) const +{ + _RYML_ASSERT_VISIT_(m_callbacks, node != NONE, this, node); + id_type count = 0; + for(id_type i = first_child(node); i != NONE; i = next_sibling(i)) + { + if(i == ch) + return count; + ++count; + } + return NONE; +} + +#if defined(__clang__) +# pragma clang diagnostic push +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# if __GNUC__ >= 6 +# pragma GCC diagnostic ignored "-Wnull-dereference" +# endif +# if __GNUC__ > 9 +# pragma GCC diagnostic ignored "-Wanalyzer-null-dereference" +# endif +#endif + +id_type Tree::find_child(id_type node, csubstr const& name) const +{ + _RYML_ASSERT_VISIT_(m_callbacks, node != NONE, this, node); + _RYML_ASSERT_VISIT_(m_callbacks, is_map(node), this, node); + if(get(node)->m_first_child == NONE) + { + _RYML_ASSERT_VISIT_(m_callbacks, _p(node)->m_last_child == NONE, this, node); + return NONE; + } + else + { + _RYML_ASSERT_VISIT_(m_callbacks, _p(node)->m_last_child != NONE, this, node); + } + for(id_type i = first_child(node); i != NONE; i = next_sibling(i)) + { + if(_p(i)->m_key.scalar == name) + { + return i; + } + } + return NONE; +} + +#if defined(__clang__) +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#endif + +namespace { +id_type depth_desc_(Tree const& C4_RESTRICT t, id_type id, id_type currdepth=0, id_type maxdepth=0) +{ + maxdepth = currdepth > maxdepth ? currdepth : maxdepth; + for(id_type child = t.first_child(id); child != NONE; child = t.next_sibling(child)) + { + const id_type d = depth_desc_(t, child, currdepth+1, maxdepth); + maxdepth = d > maxdepth ? d : maxdepth; + } + return maxdepth; +} +} + +id_type Tree::depth_desc(id_type node) const +{ + _RYML_ASSERT_VISIT_(m_callbacks, node != NONE, this, node); + return depth_desc_(*this, node); +} + +id_type Tree::depth_asc(id_type node) const +{ + _RYML_ASSERT_VISIT_(m_callbacks, node != NONE, this, node); + id_type depth = 0; + while(!is_root(node)) + { + ++depth; + node = parent(node); + } + return depth; +} + +bool Tree::is_ancestor(id_type node, id_type ancestor) const +{ + _RYML_ASSERT_VISIT_(m_callbacks, node != NONE, this, node); + id_type p = parent(node); + while(p != NONE) + { + if(p == ancestor) + return true; + p = parent(p); + } + return false; +} + + +//----------------------------------------------------------------------------- + +void Tree::to_val(id_type node, csubstr val, type_bits more_flags) +{ + _RYML_ASSERT_VISIT_(m_callbacks, ! has_children(node), this, node); + _RYML_ASSERT_VISIT_(m_callbacks, parent(node) == NONE || ! parent_is_map(node), this, node); + _set_flags(node, VAL|more_flags); + _p(node)->m_key.clear(); + _p(node)->m_val = val; +} + +void Tree::to_keyval(id_type node, csubstr key, csubstr val, type_bits more_flags) +{ + _RYML_ASSERT_VISIT_(m_callbacks, ! has_children(node), this, node); + _RYML_ASSERT_VISIT_(m_callbacks, parent(node) == NONE || parent_is_map(node), this, node); + _set_flags(node, KEYVAL|more_flags); + _p(node)->m_key = key; + _p(node)->m_val = val; +} + +void Tree::to_map(id_type node, type_bits more_flags) +{ + _RYML_ASSERT_VISIT_(m_callbacks, ! has_children(node), this, node); + _RYML_ASSERT_VISIT_(m_callbacks, parent(node) == NONE || ! parent_is_map(node), this, node); // parent must not have children with keys + _set_flags(node, MAP|more_flags); + _p(node)->m_key.clear(); + _p(node)->m_val.clear(); +} + +void Tree::to_map(id_type node, csubstr key, type_bits more_flags) +{ + _RYML_ASSERT_VISIT_(m_callbacks, ! has_children(node), this, node); + _RYML_ASSERT_VISIT_(m_callbacks, parent(node) == NONE || parent_is_map(node), this, node); + _set_flags(node, KEY|MAP|more_flags); + _p(node)->m_key = key; + _p(node)->m_val.clear(); +} + +void Tree::to_seq(id_type node, type_bits more_flags) +{ + _RYML_ASSERT_VISIT_(m_callbacks, ! has_children(node), this, node); + _RYML_ASSERT_VISIT_(m_callbacks, parent(node) == NONE || parent_is_seq(node), this, node); + _set_flags(node, SEQ|more_flags); + _p(node)->m_key.clear(); + _p(node)->m_val.clear(); +} + +void Tree::to_seq(id_type node, csubstr key, type_bits more_flags) +{ + _RYML_ASSERT_VISIT_(m_callbacks, ! has_children(node), this, node); + _RYML_ASSERT_VISIT_(m_callbacks, parent(node) == NONE || parent_is_map(node), this, node); + _set_flags(node, KEY|SEQ|more_flags); + _p(node)->m_key = key; + _p(node)->m_val.clear(); +} + +void Tree::to_doc(id_type node, type_bits more_flags) +{ + _RYML_ASSERT_VISIT_(m_callbacks, ! has_children(node), this, node); + _set_flags(node, DOC|more_flags); + _p(node)->m_key.clear(); + _p(node)->m_val.clear(); +} + +void Tree::to_stream(id_type node, type_bits more_flags) +{ + _RYML_ASSERT_VISIT_(m_callbacks, ! has_children(node), this, node); + _set_flags(node, STREAM|more_flags); + _p(node)->m_key.clear(); + _p(node)->m_val.clear(); +} + + +//----------------------------------------------------------------------------- + +void Tree::clear_style(id_type node, bool recurse) +{ + NodeData *C4_RESTRICT d = _p(node); + d->m_type.clear_style(); + if(!recurse) + return; + for(id_type child = d->m_first_child; child != NONE; child = next_sibling(child)) + clear_style(child, recurse); +} + +void Tree::set_style_conditionally(id_type node, + NodeType type_mask, + NodeType rem_style_flags, + NodeType add_style_flags, + bool recurse) +{ + NodeData *C4_RESTRICT d = _p(node); + if((d->m_type & type_mask) == type_mask) + { + d->m_type &= ~(NodeType)rem_style_flags; + d->m_type |= (NodeType)add_style_flags; + } + if(!recurse) + return; + for(id_type child = d->m_first_child; child != NONE; child = next_sibling(child)) + set_style_conditionally(child, type_mask, rem_style_flags, add_style_flags, recurse); +} + + +//----------------------------------------------------------------------------- +id_type Tree::num_tag_directives() const +{ + return m_tag_directives.size(); +} + +void Tree::clear_tag_directives() +{ + m_tag_directives.clear(); +} + +void Tree::add_tag_directive(csubstr handle, csubstr prefix, id_type id) +{ + _RYML_CHECK_BASIC_(m_callbacks, + !handle.empty() + && + !prefix.empty() + && + is_valid_tag_handle(handle) + && + m_tag_directives.add(handle, prefix, id)); +} + +size_t Tree::resolve_tag(substr output, csubstr tag, id_type node_id) const +{ + size_t reqsz = 0; + m_tag_directives.resolve(output, &reqsz, tag, node_id, Location{}, callbacks()); + return reqsz; +} + +namespace { +// return the extra size needed for the arena to accomodate the resolved tag +size_t _transform_tag(Tree *t, id_type node_id, id_type doc_id, TagCache &cache, csubstr tag, csubstr *resolved) +{ + _c4dbgpf("tag: doc={} node={} resolving tag ~~~{}~~~", doc_id, node_id, tag); + (void)node_id; + size_t reqsize = 0; + if(tag.begins_with('<')) + { + *resolved = tag; + } + else + { + _RYML_ASSERT_VISIT_(t->callbacks(), !tag.begins_with("!<"), t, node_id); // this should have been handled elsewhere + TagCache::LookupResult ret = cache.find(tag, doc_id); + if(ret) + { + _c4dbgpf("tag: doc={} node={} resolving tag: found in cache[{}]: {}", doc_id, node_id, ret.pos, _prs(ret.resolved)); + *resolved = ret.resolved; + } + else + { + _c4dbgpf("tag: doc={} node={} tag not in cache ~~~{}~~~", doc_id, node_id, tag); + substr buf = t->m_arena.sub(t->m_arena_pos); + reqsize = t->resolve_tag(buf, tag, doc_id); + if(!reqsize) + { + *resolved = tag; + } + else if(reqsize <= buf.len) + { + t->m_arena_pos += reqsize; + *resolved = buf.first(reqsize); + cache.add(tag, *resolved, doc_id, ret.pos); + reqsize = 0; + } + else + { + _c4dbgpf("tag: doc={} node={} extra size needed: {}", doc_id, node_id, reqsize); + } + _c4dbgpf("tag: doc={} node={} resolved tag: ~~~{}~~~", doc_id, node_id, *resolved); + } + } + return reqsize; +} +size_t _resolve_tags(Tree *t, id_type node, id_type doc_id, TagCache &cache, bool all=true) +{ + NodeData *C4_RESTRICT d = t->_p(node); + size_t extra_size = 0; + if((d->m_type & KEYTAG) && (all || is_custom_tag(d->m_key.tag))) + extra_size += _transform_tag(t, node, doc_id, cache, d->m_key.tag, &d->m_key.tag); + if((d->m_type & VALTAG) && (all || is_custom_tag(d->m_val.tag))) + extra_size += _transform_tag(t, node, doc_id, cache, d->m_val.tag, &d->m_val.tag); + for(id_type child = t->first_child(node); child != NONE; child = t->next_sibling(child)) + extra_size += _resolve_tags(t, child, doc_id, cache); + return extra_size; +} +size_t _resolve_tags(Tree *t, TagCache &cache, bool all) +{ + id_type r = t->root_id(); + size_t extra_size = 0; + if(!t->is_stream(r)) + extra_size += _resolve_tags(t, r, r, cache, all); + else + for(id_type doc_id = t->first_child(r); doc_id != NONE; doc_id = t->next_sibling(doc_id)) + extra_size += _resolve_tags(t, doc_id, doc_id, cache, all); + return extra_size; +} +void _normalize_tags(Tree *t, id_type node) +{ + NodeData *C4_RESTRICT d = t->_p(node); + if(d->m_type & KEYTAG) + d->m_key.tag = normalize_tag(d->m_key.tag); + if(d->m_type & VALTAG) + d->m_val.tag = normalize_tag(d->m_val.tag); + for(id_type child = t->first_child(node); child != NONE; child = t->next_sibling(child)) + _normalize_tags(t, child); +} +void _normalize_tags_long(Tree *t, id_type node) +{ + NodeData *C4_RESTRICT d = t->_p(node); + if(d->m_type & KEYTAG) + d->m_key.tag = normalize_tag_long(d->m_key.tag); + if(d->m_type & VALTAG) + d->m_val.tag = normalize_tag_long(d->m_val.tag); + for(id_type child = t->first_child(node); child != NONE; child = t->next_sibling(child)) + _normalize_tags_long(t, child); +} +} // namespace + +void Tree::resolve_tags(TagCache &cache, bool all) +{ + if(empty()) + return; + // try to resolve. While doing so, get the extra size needed for + // the arena, if the arena is currently too small. + size_t extra_size = _resolve_tags(this, cache, all); + // if the arena requires extra size, grow it and then resolve the + // missing entries + if(extra_size) + { + _c4dbgpf("tag: extrasize={} -- retry! {}->{}", extra_size, m_arena.len, m_arena.len + extra_size); + _grow_arena(extra_size); + extra_size = _resolve_tags(this, cache, all); + _RYML_ASSERT_BASIC_(callbacks(), extra_size == 0); + } +} + +void Tree::normalize_tags() +{ + if(empty()) + return; + _normalize_tags(this, root_id()); +} + +void Tree::normalize_tags_long() +{ + if(empty()) + return; + _normalize_tags_long(this, root_id()); +} + + +//----------------------------------------------------------------------------- + +csubstr Tree::lookup_result::resolved() const +{ + csubstr p = path.first(path_pos); + if(p.ends_with('.')) + p = p.first(p.len-1); + return p; +} + +csubstr Tree::lookup_result::unresolved() const +{ + return path.sub(path_pos); +} + +void Tree::_advance(lookup_result *r, size_t more) +{ + r->path_pos += more; + if(r->path.sub(r->path_pos).begins_with('.')) + ++r->path_pos; +} + +Tree::lookup_result Tree::lookup_path(csubstr path, id_type start) const +{ + if(start == NONE) + start = root_id(); + lookup_result r(path, start); + if(path.empty()) + return r; + _lookup_path(&r); + if(r.target == NONE && r.closest == start) + r.closest = NONE; + return r; +} + +id_type Tree::lookup_path_or_modify(csubstr default_value, csubstr path, id_type start) +{ + id_type target = _lookup_path_or_create(path, start); + if(parent_is_map(target)) + to_keyval(target, key(target), default_value); + else + to_val(target, default_value); + return target; +} + +id_type Tree::lookup_path_or_modify(Tree const *src, id_type src_node, csubstr path, id_type start) +{ + id_type target = _lookup_path_or_create(path, start); + merge_with(src, src_node, target); + return target; +} + +id_type Tree::_lookup_path_or_create(csubstr path, id_type start) +{ + if(start == NONE) + start = root_id(); + lookup_result r(path, start); + _lookup_path(&r); + if(r.target != NONE) + { + C4_ASSERT(r.unresolved().empty()); + return r.target; + } + _lookup_path_modify(&r); + return r.target; +} + +void Tree::_lookup_path(lookup_result *r) const +{ + C4_ASSERT( ! r->unresolved().empty()); + _lookup_path_token parent{"", type(r->closest)}; + id_type node; + do + { + node = _next_node(r, &parent); + if(node != NONE) + r->closest = node; + if(r->unresolved().empty()) + { + r->target = node; + return; + } + } while(node != NONE); +} + +void Tree::_lookup_path_modify(lookup_result *r) +{ + C4_ASSERT( ! r->unresolved().empty()); + _lookup_path_token parent{"", type(r->closest)}; + id_type node; + do + { + node = _next_node_modify(r, &parent); + if(node != NONE) + r->closest = node; + if(r->unresolved().empty()) + { + r->target = node; + return; + } + } while(node != NONE); +} + +id_type Tree::_next_node(lookup_result * r, _lookup_path_token *parent) const +{ + _lookup_path_token token = _next_token(r, *parent); + if( ! token) + return NONE; + + id_type node = NONE; + csubstr prev = token.value; + if(token.type == MAP || token.type == SEQ) + { + _RYML_ASSERT_VISIT_(m_callbacks, !token.value.begins_with('['), this, r->closest); + //_RYML_ASSERT_VISIT_(m_callbacks, is_container(r->closest) || r->closest == NONE); + _RYML_ASSERT_VISIT_(m_callbacks, is_map(r->closest), this, r->closest); + node = find_child(r->closest, token.value); + } + else if(token.type == KEYVAL) + { + _RYML_ASSERT_VISIT_(m_callbacks, r->unresolved().empty(), this, r->closest); + if(is_map(r->closest)) + node = find_child(r->closest, token.value); + } + else if(token.type == KEY) + { + _RYML_ASSERT_VISIT_(m_callbacks, token.value.begins_with('[') && token.value.ends_with(']'), this, r->closest); + token.value = token.value.offs(1, 1).trim(' '); + id_type idx = 0; + _RYML_CHECK_BASIC_(m_callbacks, from_chars(token.value, &idx)); + node = child(r->closest, idx); + } + else + { + C4_NEVER_REACH(); + } + + if(node != NONE) + { + *parent = token; + } + else + { + csubstr p = r->path.sub(r->path_pos > 0 ? r->path_pos - 1 : r->path_pos); + r->path_pos -= prev.len; + if(p.begins_with('.')) + r->path_pos -= 1u; + } + + return node; +} + +id_type Tree::_next_node_modify(lookup_result * r, _lookup_path_token *parent) +{ + _lookup_path_token token = _next_token(r, *parent); + if( ! token) + return NONE; + + id_type node = NONE; + if(token.type == MAP || token.type == SEQ) + { + _RYML_ASSERT_VISIT_(m_callbacks, !token.value.begins_with('['), this, r->closest); + //_RYML_ASSERT_VISIT_(m_callbacks, is_container(r->closest) || r->closest == NONE); + if( ! is_container(r->closest)) + { + if(has_key(r->closest)) + to_map(r->closest, key(r->closest)); + else + to_map(r->closest); + } + else + { + if(is_map(r->closest)) + { + node = find_child(r->closest, token.value); + } + else + { + id_type pos = NONE; + _RYML_CHECK_BASIC_(m_callbacks, c4::atox(token.value, &pos)); + _RYML_ASSERT_VISIT_(m_callbacks, pos != NONE, this, r->closest); + node = child(r->closest, pos); + } + } + if(node == NONE) + { + _RYML_ASSERT_VISIT_(m_callbacks, is_map(r->closest), this, r->closest); + node = append_child(r->closest); + NodeData *n = _p(node); + n->m_key.scalar = token.value; + n->m_type.add(KEY); + } + } + else if(token.type == KEYVAL) + { + _RYML_ASSERT_VISIT_(m_callbacks, r->unresolved().empty(), this, r->closest); + if(is_map(r->closest)) + { + node = find_child(r->closest, token.value); + if(node == NONE) + node = append_child(r->closest); + } + else + { + _RYML_ASSERT_VISIT_(m_callbacks, !is_seq(r->closest), this, r->closest); + _add_flags(r->closest, MAP); + node = append_child(r->closest); + } + NodeData *n = _p(node); + n->m_key.scalar = token.value; + n->m_val.scalar = ""; + n->m_type.add(KEYVAL); + } + else if(token.type == KEY) + { + _RYML_ASSERT_VISIT_(m_callbacks, token.value.begins_with('[') && token.value.ends_with(']'), this, r->closest); + token.value = token.value.offs(1, 1).trim(' '); + id_type idx; + if( ! from_chars(token.value, &idx)) + return NONE; + if( ! is_container(r->closest)) + { + if(has_key(r->closest)) + { + csubstr k = key(r->closest); + _clear_type(r->closest); + to_seq(r->closest, k); + } + else + { + _clear_type(r->closest); + to_seq(r->closest); + } + } + _RYML_ASSERT_VISIT_(m_callbacks, is_container(r->closest), this, r->closest); + node = child(r->closest, idx); + if(node == NONE) + { + _RYML_ASSERT_VISIT_(m_callbacks, num_children(r->closest) <= idx, this, r->closest); + for(id_type i = num_children(r->closest); i <= idx; ++i) + { + node = append_child(r->closest); + if(i < idx) + { + if(is_map(r->closest)) + to_keyval(node, /*"~"*/{}, /*"~"*/{}); + else if(is_seq(r->closest)) + to_val(node, /*"~"*/{}); + } + } + } + } + else + { + C4_NEVER_REACH(); + } + + _RYML_ASSERT_VISIT_(m_callbacks, node != NONE, this, r->closest); + *parent = token; + return node; +} + +/* types of tokens: + * - seeing "map." ---> "map"/MAP + * - finishing "scalar" ---> "scalar"/KEYVAL + * - seeing "seq[n]" ---> "seq"/SEQ (--> "[n]"/KEY) + * - seeing "[n]" ---> "[n]"/KEY + */ +Tree::_lookup_path_token Tree::_next_token(lookup_result *r, _lookup_path_token const& parent) const +{ + csubstr unres = r->unresolved(); + if(unres.empty()) + return {}; + + // is it an indexation like [0], [1], etc? + if(unres.begins_with('[')) + { + size_t pos = unres.find(']'); + if(pos == csubstr::npos) + return {}; + csubstr idx = unres.first(pos + 1); + _advance(r, pos + 1); + return {idx, KEY}; + } + + // no. so it must be a name + size_t pos = unres.first_of(".["); + if(pos == csubstr::npos) + { + _advance(r, unres.len); + NodeType t; + if(( ! parent) || parent.type.is_seq()) + return {unres, VAL}; + return {unres, KEYVAL}; + } + + // it's either a map or a seq + _RYML_ASSERT_VISIT_(m_callbacks, unres[pos] == '.' || unres[pos] == '[', this, r->closest); + if(unres[pos] == '.') + { + _RYML_ASSERT_VISIT_(m_callbacks, pos != 0, this, r->closest); + _advance(r, pos + 1); + return {unres.first(pos), MAP}; + } + + _RYML_ASSERT_VISIT_(m_callbacks, unres[pos] == '[', this, r->closest); + _advance(r, pos); + return {unres.first(pos), SEQ}; +} + + +} // namespace yml +} // namespace c4 + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +// amalgamate: removed include of +// c4/yml/event_handler_tree.hpp +//#include "c4/yml/event_handler_tree.hpp" +#if !defined(C4_YML_EVENT_HANDLER_TREE_HPP_) && !defined(_C4_YML_EVENT_HANDLER_TREE_HPP_) +#error "amalgamate: file c4/yml/event_handler_tree.hpp must have been included at this point" +#endif /* C4_YML_EVENT_HANDLER_TREE_HPP_ */ + +// amalgamate: removed include of +// c4/yml/parse_engine.def.hpp +//#include "c4/yml/parse_engine.def.hpp" +#if !defined(C4_YML_PARSE_ENGINE_DEF_HPP_) && !defined(_C4_YML_PARSE_ENGINE_DEF_HPP_) +#error "amalgamate: file c4/yml/parse_engine.def.hpp must have been included at this point" +#endif /* C4_YML_PARSE_ENGINE_DEF_HPP_ */ + +// amalgamate: removed include of +// c4/yml/parse.hpp +//#include "c4/yml/parse.hpp" +#if !defined(C4_YML_PARSE_HPP_) && !defined(_C4_YML_PARSE_HPP_) +#error "amalgamate: file c4/yml/parse.hpp must have been included at this point" +#endif /* C4_YML_PARSE_HPP_ */ + + +namespace c4 { +namespace yml { + +Location Tree::location(Parser const& parser, id_type node) const +{ + // try hard to avoid getting the location from a null string. + Location loc; + if(_location_from_node(parser, node, &loc, 0)) + return loc; + return parser.val_location(parser.source().str); +} + +bool Tree::_location_from_node(Parser const& parser, id_type node, Location *C4_RESTRICT loc, id_type level) const +{ + if(has_key(node)) + { + csubstr k = key(node); + if(C4_LIKELY(k.str != nullptr)) + { + _RYML_ASSERT_BASIC_(m_callbacks, k.is_sub(parser.source())); + _RYML_ASSERT_BASIC_(m_callbacks, parser.source().is_super(k)); + *loc = parser.val_location(k.str); + return true; + } + } + + if(has_val(node)) + { + csubstr v = val(node); + if(C4_LIKELY(v.str != nullptr)) + { + _RYML_ASSERT_BASIC_(m_callbacks, v.is_sub(parser.source())); + _RYML_ASSERT_BASIC_(m_callbacks, parser.source().is_super(v)); + *loc = parser.val_location(v.str); + return true; + } + } + + if(is_container(node)) + { + if(_location_from_cont(parser, node, loc)) + return true; + } + + if(type(node) != NOTYPE && level == 0) + { + // try the prev sibling + { + const id_type prev = prev_sibling(node); + if(prev != NONE) + { + if(_location_from_node(parser, prev, loc, level+1)) + return true; + } + } + // try the next sibling + { + const id_type next = next_sibling(node); + if(next != NONE) + { + if(_location_from_node(parser, next, loc, level+1)) + return true; + } + } + // try the parent + { + const id_type parent = this->parent(node); + if(parent != NONE) + { + if(_location_from_node(parser, parent, loc, level+1)) + return true; + } + } + } + return false; +} + +bool Tree::_location_from_cont(Parser const& parser, id_type node, Location *C4_RESTRICT loc) const +{ + _RYML_ASSERT_BASIC_(m_callbacks, is_container(node)); + if(!is_stream(node)) + { + const char *node_start = _p(node)->m_val.scalar.str; // this was stored in the container + if(has_children(node)) + { + id_type child = first_child(node); + if(has_key(child)) + { + // when a map starts, the container was set after the key + csubstr k = key(child); + if(k.str && node_start > k.str) + node_start = k.str; + } + } + *loc = parser.val_location(node_start); + return true; + } + else // it's a stream + { + *loc = parser.val_location(parser.source().str); // just return the front of the buffer + } + return true; +} + +} // namespace yml +} // namespace c4 + + +// NOLINTEND(modernize-avoid-c-style-cast) +C4_SUPPRESS_WARNING_GCC_CLANG_POP +C4_SUPPRESS_WARNING_MSVC_POP + +#endif /* RYML_SINGLE_HDR_DEFINE_NOW */ + + +// (end src/c4/yml/tree.cpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/reference_resolver.cpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifdef RYML_SINGLE_HDR_DEFINE_NOW +// amalgamate: removed include of +// c4/yml/reference_resolver.hpp +//#include "c4/yml/reference_resolver.hpp" +#if !defined(C4_YML_REFERENCE_RESOLVER_HPP_) && !defined(_C4_YML_REFERENCE_RESOLVER_HPP_) +#error "amalgamate: file c4/yml/reference_resolver.hpp must have been included at this point" +#endif /* C4_YML_REFERENCE_RESOLVER_HPP_ */ + +// amalgamate: removed include of +// c4/yml/common.hpp +//#include "c4/yml/common.hpp" +#if !defined(C4_YML_COMMON_HPP_) && !defined(_C4_YML_COMMON_HPP_) +#error "amalgamate: file c4/yml/common.hpp must have been included at this point" +#endif /* C4_YML_COMMON_HPP_ */ + +// amalgamate: removed include of +// c4/yml/detail/dbgprint.hpp +//#include "c4/yml/detail/dbgprint.hpp" +#if !defined(C4_YML_DETAIL_DBGPRINT_HPP_) && !defined(_C4_YML_DETAIL_DBGPRINT_HPP_) +#error "amalgamate: file c4/yml/detail/dbgprint.hpp must have been included at this point" +#endif /* C4_YML_DETAIL_DBGPRINT_HPP_ */ + +#ifdef RYML_DBG +// amalgamate: removed include of +// c4/yml/detail/print.hpp +//#include "c4/yml/detail/print.hpp" +#if !defined(C4_YML_DETAIL_PRINT_HPP_) && !defined(_C4_YML_DETAIL_PRINT_HPP_) +#error "amalgamate: file c4/yml/detail/print.hpp must have been included at this point" +#endif /* C4_YML_DETAIL_PRINT_HPP_ */ + +#else +#define _c4dbg_tree(...) +#define _c4dbg_node(...) +#endif + +namespace c4 { +namespace yml { + +/** @cond dev */ + +id_type ReferenceResolver::count_anchors_and_refs_(id_type n) +{ + id_type c = 0; + c += m_tree->has_key_anchor(n); + c += m_tree->has_val_anchor(n); + c += m_tree->is_key_ref(n); + c += m_tree->is_val_ref(n); + c += m_tree->has_key(n) && m_tree->key(n) == "<<"; + for(id_type ch = m_tree->first_child(n); ch != NONE; ch = m_tree->next_sibling(ch)) + c += count_anchors_and_refs_(ch); + return c; +} + +void ReferenceResolver::gather_anchors_and_refs__(id_type n) +{ + // insert key refs BEFORE inserting val refs + if(m_tree->has_key(n)) + { + if(!m_tree->is_key_quoted(n) && m_tree->key(n) == "<<") + { + _c4dbgpf("node[{}]: key is <<", n); + if(m_tree->has_val(n)) + { + if(m_tree->is_val_ref(n)) + { + _c4dbgpf("node[{}]: instance[{}]: val ref, inheriting! '{}'", n, m_refs.size(), m_tree->val_ref(n)); + m_refs.push({VALREF, n, NONE, NONE, NONE, NONE}); + //m_refs.push({KEYREF, n, NONE, NONE, NONE, NONE}); + } + else + { + _c4dbgpf("node[{}]: not ref!", n); + } + } + else if(m_tree->is_seq(n)) + { + // for merging multiple inheritance targets + // <<: [ *CENTER, *BIG ] + _c4dbgpf("node[{}]: is seq!", n); + for(id_type ich = m_tree->first_child(n); ich != NONE; ich = m_tree->next_sibling(ich)) + { + _c4dbgpf("node[{}]: instance [{}]: val ref, inheriting multiple: {} '{}'", n, m_refs.size(), ich, m_tree->val_ref(ich)); + if(C4_UNLIKELY(m_tree->is_container(ich))) + _RYML_ERR_VISIT_(m_tree->m_callbacks, m_tree, n, "child={}: refs for << cannot be containers.", ich); + m_refs.push({VALREF, ich, NONE, NONE, n, m_tree->next_sibling(n)}); + } + return; // don't descend into the seq + } + else + { + _RYML_ERR_VISIT_(m_tree->m_callbacks, m_tree, n, "refs for << must be either val or seq"); + } + } + else if(m_tree->is_key_ref(n)) + { + _c4dbgpf("node[{}]: instance[{}]: key ref: '{}', key='{}'", n, m_refs.size(), m_tree->key_ref(n), m_tree->has_key(n) ? m_tree->key(n) : csubstr{"-"}); + _RYML_ASSERT_VISIT_(m_tree->m_callbacks, m_tree->key(n) != "<<", m_tree, n); + _RYML_CHECK_VISIT_(m_tree->m_callbacks, (!m_tree->has_key(n)) || m_tree->key(n).ends_with(m_tree->key_ref(n)), m_tree, n); + m_refs.push({KEYREF, n, NONE, NONE, NONE, NONE}); + } + } + // val ref + if(m_tree->is_val_ref(n) && (!m_tree->has_key(n) || m_tree->key(n) != "<<")) + { + _c4dbgpf("node[{}]: instance[{}]: val ref: '{}'", n, m_refs.size(), m_tree->val_ref(n)); + _RYML_CHECK_VISIT((!m_tree->has_val(n)) || m_tree->val(n).ends_with(m_tree->val_ref(n)), m_tree, n); + m_refs.push({VALREF, n, NONE, NONE, NONE, NONE}); + } + // anchors + if(m_tree->has_key_anchor(n)) + { + _c4dbgpf("node[{}]: instance[{}]: key anchor: '{}'", n, m_refs.size(), m_tree->key_anchor(n)); + _RYML_CHECK_VISIT(m_tree->has_key(n), m_tree, n); + m_refs.push({KEYANCH, n, NONE, NONE, NONE, NONE}); + } + if(m_tree->has_val_anchor(n)) + { + _c4dbgpf("node[{}]: instance[{}]: val anchor: '{}'", n, m_refs.size(), m_tree->val_anchor(n)); + _RYML_CHECK_VISIT(m_tree->has_val(n) || m_tree->is_container(n), m_tree, n); + m_refs.push({VALANCH, n, NONE, NONE, NONE, NONE}); + } + // recurse + for(id_type ch = m_tree->first_child(n); ch != NONE; ch = m_tree->next_sibling(ch)) + gather_anchors_and_refs__(ch); +} + +void ReferenceResolver::gather_anchors_and_refs_() +{ + _c4dbgp("gathering anchors and refs..."); + + // minimize (re-)allocations by counting first + id_type num_anchors_and_refs = count_anchors_and_refs_(m_tree->root_id()); + if(!num_anchors_and_refs) + return; + m_refs.reserve(num_anchors_and_refs); + m_refs.clear(); + + // now descend through the hierarchy + gather_anchors_and_refs__(m_tree->root_id()); + + _c4dbgpf("found {} anchors/refs", m_refs.size()); + + // finally connect the reference list + id_type prev_anchor = NONE; + id_type count = 0; + for(auto &rd : m_refs) + { + rd.prev_anchor = prev_anchor; + if(rd.type.has_anchor()) + prev_anchor = count; + ++count; + } + _c4dbgp("gathering anchors and refs: finished"); +} + +id_type ReferenceResolver::lookup_(RefData const* C4_RESTRICT ra) +{ + #ifdef RYML_DBG + id_type instance = static_cast(ra-m_refs.m_stack); + id_type node = ra->node; + #endif + _RYML_ASSERT_VISIT_(m_tree->m_callbacks, ra->type.is_key_ref() || ra->type.is_val_ref(), m_tree, ra->node); + _RYML_ASSERT_VISIT_(m_tree->m_callbacks, ra->type.is_key_ref() != ra->type.is_val_ref(), m_tree, ra->node); + csubstr refname; + _c4dbgpf("instance[{}:node{}]: lookup from node={}...", instance, node, ra->node); + if(ra->type.is_val_ref()) + { + refname = m_tree->val_ref(ra->node); + _c4dbgpf("instance[{}:node{}]: valref: '{}'", instance, node, refname); + } + else + { + _RYML_ASSERT_VISIT_(m_tree->m_callbacks, ra->type.is_key_ref(), m_tree, ra->node); + refname = m_tree->key_ref(ra->node); + _c4dbgpf("instance[{}:node{}]: keyref: '{}'", instance, node, refname); + } + while(ra->prev_anchor != NONE) + { + ra = &m_refs[ra->prev_anchor]; + _c4dbgpf("instance[{}:node{}]: lookup '{}' at [{}:node{}]: keyref='{}' valref='{}'", instance, node, refname, ra-m_refs.m_stack, ra->node, + (m_tree->has_key_anchor(ra->node) ? m_tree->key_anchor(ra->node) : csubstr("~")), + (m_tree->has_val_anchor(ra->node) ? m_tree->val_anchor(ra->node) : csubstr("~"))); + if(m_tree->has_anchor(ra->node, refname)) + { + _c4dbgpf("instance[{}:node{}]: got it at [{}:node{}]!", instance, node, ra-m_refs.m_stack, ra->node); + return ra->node; + } + } + _RYML_ERR_VISIT_(m_tree->m_callbacks, m_tree, ra->node, "anchor not found: '{}'", refname); + C4_UNREACHABLE_AFTER_ERR(); +} + +void ReferenceResolver::reset_(Tree *t_) +{ + if(t_->callbacks() != m_refs.m_callbacks) + { + m_refs.m_callbacks = t_->callbacks(); + } + m_tree = t_; + m_refs.clear(); +} + +void ReferenceResolver::resolve_() +{ + /* from the specs: "an alias node refers to the most recent + * node in the serialization having the specified anchor". So + * we need to start looking upward from ref nodes. + * + * @see http://yaml.org/spec/1.2/spec.html#id2765878 */ + _c4dbgp("matching anchors/refs..."); + for(id_type i = 0, e = m_refs.size(); i < e; ++i) + { + RefData &C4_RESTRICT refdata = m_refs.top(i); + if( ! refdata.type.is_ref()) + continue; + refdata.target = lookup_(&refdata); + } + _c4dbgp("matching anchors/refs: finished"); + + // insert the resolved references + _c4dbgp("modifying tree..."); + id_type prev_parent_ref = NONE; + id_type prev_parent_ref_after = NONE; + for(id_type i = 0, e = m_refs.size(); i < e; ++i) + { + RefData const& C4_RESTRICT refdata = m_refs[i]; + _c4dbgpf("instance[{}:node{}]: {}/{}...", i, refdata.node, i+1, e); + if( ! refdata.type.is_ref()) + continue; + _c4dbgpf("instance[{}:node{}]: is reference!", i, refdata.node); + if(refdata.parent_ref != NONE) + { + _c4dbgpf("instance[{}:node{}] has parent: {}", i, refdata.node, refdata.parent_ref); + _RYML_ASSERT_VISIT_(m_tree->m_callbacks, m_tree->is_seq(refdata.parent_ref), m_tree, refdata.node); + const id_type p = m_tree->parent(refdata.parent_ref); + const id_type after = (prev_parent_ref != refdata.parent_ref) ? + refdata.parent_ref//prev_sibling(rd.parent_ref_sibling) + : + prev_parent_ref_after; + prev_parent_ref = refdata.parent_ref; + prev_parent_ref_after = m_tree->duplicate_children_no_rep(refdata.target, p, after); + m_tree->remove(refdata.node); + } + else + { + _c4dbgpf("instance[{}:node{}] has no parent", i, refdata.node, refdata.parent_ref); + if(m_tree->has_key(refdata.node) && m_tree->key(refdata.node) == "<<") + { + _c4dbgpf("instance[{}:node{}] is inheriting", i, refdata.node); + _RYML_ASSERT_VISIT_(m_tree->m_callbacks, m_tree->is_keyval(refdata.node), m_tree, refdata.node); + const id_type p = m_tree->parent(refdata.node); + const id_type after = m_tree->prev_sibling(refdata.node); + _c4dbgpf("instance[{}:node{}] p={} after={}", i, refdata.node, p, after); + m_tree->duplicate_children_no_rep(refdata.target, p, after); + m_tree->remove(refdata.node); + } + else if(refdata.type.is_key_ref()) + { + _c4dbgpf("instance[{}:node{}] is key ref", i, refdata.node); + _RYML_ASSERT_VISIT_(m_tree->m_callbacks, m_tree->is_key_ref(refdata.node), m_tree, refdata.node); + _RYML_ASSERT_VISIT_(m_tree->m_callbacks, m_tree->has_key_anchor(refdata.target) || m_tree->has_val_anchor(refdata.target), m_tree, refdata.node); + if(m_tree->has_val_anchor(refdata.target) && m_tree->val_anchor(refdata.target) == m_tree->key_ref(refdata.node)) + { + _c4dbgpf("instance[{}:node{}] target.anchor==val.anchor=={}", i, refdata.node, m_tree->val_anchor(refdata.target)); + _RYML_CHECK_VISIT_(m_tree->m_callbacks, !m_tree->is_container(refdata.target), m_tree, refdata.target); + _RYML_CHECK_VISIT_(m_tree->m_callbacks, m_tree->has_val(refdata.target), m_tree, refdata.target); + const type_bits existing_style_flags = VAL_STYLE & m_tree->_p(refdata.target)->m_type.type; + static_assert((VAL_STYLE >> 1u) == (KEY_STYLE), "bad flags"); + m_tree->_p(refdata.node)->m_key.scalar = m_tree->val(refdata.target); + m_tree->_add_flags(refdata.node, KEY | (existing_style_flags >> 1u)); + } + else + { + _c4dbgpf("instance[{}:node{}] don't inherit container flags", i, refdata.node); + _RYML_CHECK_BASIC_(m_tree->m_callbacks, m_tree->key_anchor(refdata.target) == m_tree->key_ref(refdata.node)); + m_tree->_p(refdata.node)->m_key.scalar = m_tree->key(refdata.target); + // keys cannot be containers, so don't inherit container flags + const type_bits existing_style_flags = KEY_STYLE & m_tree->_p(refdata.target)->m_type.type; + m_tree->_add_flags(refdata.node, KEY | existing_style_flags); + } + } + else // val ref + { + _c4dbgpf("instance[{}:node{}] is val ref", i, refdata.node); + _RYML_ASSERT_VISIT_(m_tree->m_callbacks, refdata.type.is_val_ref(), m_tree, refdata.node); + if(m_tree->has_key_anchor(refdata.target) && m_tree->key_anchor(refdata.target) == m_tree->val_ref(refdata.node)) + { + _c4dbgpf("instance[{}:node{}] target.anchor==key.anchor=={}", i, refdata.node, m_tree->key_anchor(refdata.target)); + _RYML_CHECK_BASIC_(m_tree->m_callbacks, !m_tree->is_container(refdata.target)); + _RYML_CHECK_BASIC_(m_tree->m_callbacks, m_tree->has_val(refdata.target)); + // keys cannot be containers, so don't inherit container flags + const type_bits existing_style_flags = (KEY_STYLE) & m_tree->_p(refdata.target)->m_type.type; + static_assert((KEY_STYLE << 1u) == (VAL_STYLE), "bad flags"); + m_tree->_p(refdata.node)->m_val.scalar = m_tree->key(refdata.target); + m_tree->_add_flags(refdata.node, VAL | (existing_style_flags << 1u)); + } + else + { + _c4dbgpf("instance[{}:node{}] duplicate contents", i, refdata.node); + m_tree->duplicate_contents(refdata.target, refdata.node); + } + } + } + _c4dbg_tree("after insertion", *m_tree); + } +} + +void ReferenceResolver::resolve(Tree *t_, bool clear_anchors) +{ + _c4dbgp("resolving references..."); + + reset_(t_); + + _c4dbg_tree("unresolved tree", *m_tree); + + gather_anchors_and_refs_(); + if(m_refs.empty()) + return; + resolve_(); + _c4dbg_tree("resolved tree", *m_tree); + + // clear anchors and refs + if(clear_anchors) + { + _c4dbgp("clearing anchors/refs"); + auto clear_ = [this]{ + for(auto const& C4_RESTRICT ar : m_refs) + { + m_tree->rem_anchor_ref(ar.node); + if(ar.parent_ref != NONE) + if(m_tree->type(ar.parent_ref) != NOTYPE) + m_tree->remove(ar.parent_ref); + } + }; + clear_(); + // some of the elements injected during the resolution may + // have nested anchors; these anchors will have been newly + // injected during the resolution; collect again, and clear + // again, to ensure those are also cleared: + gather_anchors_and_refs_(); + clear_(); + _c4dbgp("clearing anchors/refs: finished"); + } + + _c4dbg_tree("final resolved tree", *m_tree); + + m_tree = nullptr; + _c4dbgp("resolving references: finished"); +} + +/** @endcond */ + +} // namespace ryml +} // namespace c4 + +#endif /* RYML_SINGLE_HDR_DEFINE_NOW */ + + +// (end src/c4/yml/reference_resolver.cpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/parse.cpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifdef RYML_SINGLE_HDR_DEFINE_NOW +// amalgamate: removed include of +// c4/yml/parse.hpp +//#include "c4/yml/parse.hpp" +#if !defined(C4_YML_PARSE_HPP_) && !defined(_C4_YML_PARSE_HPP_) +#error "amalgamate: file c4/yml/parse.hpp must have been included at this point" +#endif /* C4_YML_PARSE_HPP_ */ + + +#ifndef _C4_YML_NODE_HPP_ +// amalgamate: removed include of +// c4/yml/node.hpp +//#include "c4/yml/node.hpp" +#if !defined(C4_YML_NODE_HPP_) && !defined(_C4_YML_NODE_HPP_) +#error "amalgamate: file c4/yml/node.hpp must have been included at this point" +#endif /* C4_YML_NODE_HPP_ */ + +#endif +#ifndef _C4_YML_PARSE_ENGINE_HPP_ +// amalgamate: removed include of +// c4/yml/parse_engine.hpp +//#include "c4/yml/parse_engine.hpp" +#if !defined(C4_YML_PARSE_ENGINE_HPP_) && !defined(_C4_YML_PARSE_ENGINE_HPP_) +#error "amalgamate: file c4/yml/parse_engine.hpp must have been included at this point" +#endif /* C4_YML_PARSE_ENGINE_HPP_ */ + +#endif +#ifndef _C4_YML_PARSE_ENGINE_DEF_HPP_ +// amalgamate: removed include of +// c4/yml/parse_engine.def.hpp +//#include "c4/yml/parse_engine.def.hpp" +#if !defined(C4_YML_PARSE_ENGINE_DEF_HPP_) && !defined(_C4_YML_PARSE_ENGINE_DEF_HPP_) +#error "amalgamate: file c4/yml/parse_engine.def.hpp must have been included at this point" +#endif /* C4_YML_PARSE_ENGINE_DEF_HPP_ */ + +#endif +#ifndef _C4_YML_EVENT_HANDLER_TREE_HPP_ +// amalgamate: removed include of +// c4/yml/event_handler_tree.hpp +//#include "c4/yml/event_handler_tree.hpp" +#if !defined(C4_YML_EVENT_HANDLER_TREE_HPP_) && !defined(_C4_YML_EVENT_HANDLER_TREE_HPP_) +#error "amalgamate: file c4/yml/event_handler_tree.hpp must have been included at this point" +#endif /* C4_YML_EVENT_HANDLER_TREE_HPP_ */ + +#endif + + +//----------------------------------------------------------------------------- + +namespace c4 { +namespace yml { + +// instantiate the parser class +template class RYML_EXPORT ParseEngine; + + +namespace { +void _reset_tree_handler(Parser *parser, Tree *t, id_type node_id) +{ + _RYML_ASSERT_BASIC(parser); + _RYML_ASSERT_BASIC(t); + if(!parser->m_evt_handler) + _RYML_ERR_BASIC_(t->m_callbacks, "event handler is not set"); + parser->m_evt_handler->reset(t, node_id); + _RYML_ASSERT_BASIC(parser->m_evt_handler->m_tree == t); +} +} // namespace + +void parse_in_place(Parser *parser, csubstr filename, substr yaml, Tree *t, id_type node_id) +{ + _reset_tree_handler(parser, t, node_id); + parser->parse_in_place_ev(filename, yaml); +} + +void parse_json_in_place(Parser *parser, csubstr filename, substr json, Tree *t, id_type node_id) +{ + _reset_tree_handler(parser, t, node_id); + parser->parse_json_in_place_ev(filename, json); +} + + +// this is vertically aligned to highlight the parameter differences. +void parse_in_place(Parser *parser, substr yaml, Tree *t, id_type node_id) { parse_in_place(parser, {}, yaml, t, node_id); } +void parse_in_place(Parser *parser, csubstr filename, substr yaml, Tree *t ) { _RYML_CHECK_BASIC(t); if(t->empty()) { t->reserve(); } parse_in_place(parser, filename, yaml, t, t->root_id()); } +void parse_in_place(Parser *parser, substr yaml, Tree *t ) { _RYML_CHECK_BASIC(t); if(t->empty()) { t->reserve(); } parse_in_place(parser, {} , yaml, t, t->root_id()); } +void parse_in_place(Parser *parser, csubstr filename, substr yaml, NodeRef node ) { _RYML_CHECK_BASIC(!node.invalid()); parse_in_place(parser, filename, yaml, node.tree(), node.id()); } +void parse_in_place(Parser *parser, substr yaml, NodeRef node ) { _RYML_CHECK_BASIC(!node.invalid()); parse_in_place(parser, {} , yaml, node.tree(), node.id()); } +Tree parse_in_place(Parser *parser, csubstr filename, substr yaml ) { _RYML_CHECK_BASIC(parser); _RYML_CHECK_BASIC(parser->m_evt_handler); Tree tree(parser->callbacks()); parse_in_place(parser, filename, yaml, &tree, tree.root_id()); return tree; } +Tree parse_in_place(Parser *parser, substr yaml ) { _RYML_CHECK_BASIC(parser); _RYML_CHECK_BASIC(parser->m_evt_handler); Tree tree(parser->callbacks()); parse_in_place(parser, {} , yaml, &tree, tree.root_id()); return tree; } + +// this is vertically aligned to highlight the parameter differences. +void parse_in_place(csubstr filename, substr yaml, Tree *t, id_type node_id, ParserOptions const& opts) { _RYML_CHECK_BASIC(t); Parser::handler_type event_handler(t->callbacks()); Parser parser(&event_handler, opts); parse_in_place(&parser, filename, yaml, t, node_id); } +void parse_in_place( substr yaml, Tree *t, id_type node_id, ParserOptions const& opts) { _RYML_CHECK_BASIC(t); Parser::handler_type event_handler(t->callbacks()); Parser parser(&event_handler, opts); parse_in_place(&parser, {} , yaml, t, node_id); } +void parse_in_place(csubstr filename, substr yaml, Tree *t , ParserOptions const& opts) { _RYML_CHECK_BASIC(t); Parser::handler_type event_handler(t->callbacks()); Parser parser(&event_handler, opts); if(t->empty()) { t->reserve(); } parse_in_place(&parser, filename, yaml, t, t->root_id()); } +void parse_in_place( substr yaml, Tree *t , ParserOptions const& opts) { _RYML_CHECK_BASIC(t); Parser::handler_type event_handler(t->callbacks()); Parser parser(&event_handler, opts); if(t->empty()) { t->reserve(); } parse_in_place(&parser, {} , yaml, t, t->root_id()); } +void parse_in_place(csubstr filename, substr yaml, NodeRef node , ParserOptions const& opts) { _RYML_CHECK_BASIC(!node.invalid()); Parser::handler_type event_handler(node.tree()->callbacks()); Parser parser(&event_handler, opts); parse_in_place(&parser, filename, yaml, node.tree(), node.id()); } +void parse_in_place( substr yaml, NodeRef node , ParserOptions const& opts) { _RYML_CHECK_BASIC(!node.invalid()); Parser::handler_type event_handler(node.tree()->callbacks()); Parser parser(&event_handler, opts); parse_in_place(&parser, {} , yaml, node.tree(), node.id()); } +Tree parse_in_place(csubstr filename, substr yaml , ParserOptions const& opts) { Parser::handler_type event_handler; Parser parser(&event_handler, opts); Tree tree(parser.callbacks()); parse_in_place(&parser, filename, yaml, &tree, tree.root_id()); return tree; } +Tree parse_in_place( substr yaml , ParserOptions const& opts) { Parser::handler_type event_handler; Parser parser(&event_handler, opts); Tree tree(parser.callbacks()); parse_in_place(&parser, {} , yaml, &tree, tree.root_id()); return tree; } + + +// this is vertically aligned to highlight the parameter differences. +void parse_json_in_place(Parser *parser, substr json, Tree *t, id_type node_id) { parse_json_in_place(parser, {}, json, t, node_id); } +void parse_json_in_place(Parser *parser, csubstr filename, substr json, Tree *t ) { _RYML_CHECK_BASIC(t); if(t->empty()) { t->reserve(); } parse_json_in_place(parser, filename, json, t, t->root_id()); } +void parse_json_in_place(Parser *parser, substr json, Tree *t ) { _RYML_CHECK_BASIC(t); if(t->empty()) { t->reserve(); } parse_json_in_place(parser, {} , json, t, t->root_id()); } +void parse_json_in_place(Parser *parser, csubstr filename, substr json, NodeRef node ) { _RYML_CHECK_BASIC(!node.invalid()); parse_json_in_place(parser, filename, json, node.tree(), node.id()); } +void parse_json_in_place(Parser *parser, substr json, NodeRef node ) { _RYML_CHECK_BASIC(!node.invalid()); parse_json_in_place(parser, {} , json, node.tree(), node.id()); } +Tree parse_json_in_place(Parser *parser, csubstr filename, substr json ) { _RYML_CHECK_BASIC(parser); _RYML_CHECK_BASIC(parser->m_evt_handler); Tree tree(parser->callbacks()); parse_json_in_place(parser, filename, json, &tree, tree.root_id()); return tree; } +Tree parse_json_in_place(Parser *parser, substr json ) { _RYML_CHECK_BASIC(parser); _RYML_CHECK_BASIC(parser->m_evt_handler); Tree tree(parser->callbacks()); parse_json_in_place(parser, {} , json, &tree, tree.root_id()); return tree; } + +// this is vertically aligned to highlight the parameter differences. +void parse_json_in_place(csubstr filename, substr json, Tree *t, id_type node_id, ParserOptions const& opts) { _RYML_CHECK_BASIC(t); Parser::handler_type event_handler(t->callbacks()); Parser parser(&event_handler, opts); parse_json_in_place(&parser, filename, json, t, node_id); } +void parse_json_in_place( substr json, Tree *t, id_type node_id, ParserOptions const& opts) { _RYML_CHECK_BASIC(t); Parser::handler_type event_handler(t->callbacks()); Parser parser(&event_handler, opts); parse_json_in_place(&parser, {} , json, t, node_id); } +void parse_json_in_place(csubstr filename, substr json, Tree *t , ParserOptions const& opts) { _RYML_CHECK_BASIC(t); Parser::handler_type event_handler(t->callbacks()); Parser parser(&event_handler, opts); if(t->empty()) { t->reserve(); } parse_json_in_place(&parser, filename, json, t, t->root_id()); } +void parse_json_in_place( substr json, Tree *t , ParserOptions const& opts) { _RYML_CHECK_BASIC(t); Parser::handler_type event_handler(t->callbacks()); Parser parser(&event_handler, opts); if(t->empty()) { t->reserve(); } parse_json_in_place(&parser, {} , json, t, t->root_id()); } +void parse_json_in_place(csubstr filename, substr json, NodeRef node , ParserOptions const& opts) { _RYML_CHECK_BASIC(!node.invalid()); Parser::handler_type event_handler(node.tree()->callbacks()); Parser parser(&event_handler, opts); parse_json_in_place(&parser, filename, json, node.tree(), node.id()); } +void parse_json_in_place( substr json, NodeRef node , ParserOptions const& opts) { _RYML_CHECK_BASIC(!node.invalid()); Parser::handler_type event_handler(node.tree()->callbacks()); Parser parser(&event_handler, opts); parse_json_in_place(&parser, {} , json, node.tree(), node.id()); } +Tree parse_json_in_place(csubstr filename, substr json , ParserOptions const& opts) { Parser::handler_type event_handler; Parser parser(&event_handler, opts); Tree tree(parser.callbacks()); parse_json_in_place(&parser, filename, json, &tree, tree.root_id()); return tree; } +Tree parse_json_in_place( substr json , ParserOptions const& opts) { Parser::handler_type event_handler; Parser parser(&event_handler, opts); Tree tree(parser.callbacks()); parse_json_in_place(&parser, {} , json, &tree, tree.root_id()); return tree; } + + +// this is vertically aligned to highlight the parameter differences. +void parse_in_arena(Parser *parser, csubstr filename, csubstr yaml, Tree *t, id_type node_id) { _RYML_CHECK_BASIC(t); substr src = t->copy_to_arena(yaml); parse_in_place(parser, filename, src, t, node_id); } +void parse_in_arena(Parser *parser, csubstr yaml, Tree *t, id_type node_id) { _RYML_CHECK_BASIC(t); substr src = t->copy_to_arena(yaml); parse_in_place(parser, {} , src, t, node_id); } +void parse_in_arena(Parser *parser, csubstr filename, csubstr yaml, Tree *t ) { _RYML_CHECK_BASIC(t); substr src = t->copy_to_arena(yaml); if(t->empty()) { t->reserve(); } parse_in_place(parser, filename, src, t, t->root_id()); } +void parse_in_arena(Parser *parser, csubstr yaml, Tree *t ) { _RYML_CHECK_BASIC(t); substr src = t->copy_to_arena(yaml); if(t->empty()) { t->reserve(); } parse_in_place(parser, {} , src, t, t->root_id()); } +void parse_in_arena(Parser *parser, csubstr filename, csubstr yaml, NodeRef node ) { _RYML_CHECK_BASIC(!node.invalid()); substr src = node.tree()->copy_to_arena(yaml); parse_in_place(parser, filename, src, node.tree(), node.id()); } +void parse_in_arena(Parser *parser, csubstr yaml, NodeRef node ) { _RYML_CHECK_BASIC(!node.invalid()); substr src = node.tree()->copy_to_arena(yaml); parse_in_place(parser, {} , src, node.tree(), node.id()); } +Tree parse_in_arena(Parser *parser, csubstr filename, csubstr yaml ) { _RYML_CHECK_BASIC(parser); _RYML_CHECK_BASIC(parser->m_evt_handler); Tree tree(parser->callbacks()); substr src = tree.copy_to_arena(yaml); parse_in_place(parser, filename, src, &tree, tree.root_id()); return tree; } +Tree parse_in_arena(Parser *parser, csubstr yaml ) { _RYML_CHECK_BASIC(parser); _RYML_CHECK_BASIC(parser->m_evt_handler); Tree tree(parser->callbacks()); substr src = tree.copy_to_arena(yaml); parse_in_place(parser, {} , src, &tree, tree.root_id()); return tree; } + +// this is vertically aligned to highlight the parameter differences. +void parse_in_arena(csubstr filename, csubstr yaml, Tree *t, id_type node_id, ParserOptions const& opts) { _RYML_CHECK_BASIC(t); Parser::handler_type event_handler(t->callbacks()); Parser parser(&event_handler, opts); substr src = t->copy_to_arena(yaml); parse_in_place(&parser, filename, src, t, node_id); } +void parse_in_arena( csubstr yaml, Tree *t, id_type node_id, ParserOptions const& opts) { _RYML_CHECK_BASIC(t); Parser::handler_type event_handler(t->callbacks()); Parser parser(&event_handler, opts); substr src = t->copy_to_arena(yaml); parse_in_place(&parser, {} , src, t, node_id); } +void parse_in_arena(csubstr filename, csubstr yaml, Tree *t , ParserOptions const& opts) { _RYML_CHECK_BASIC(t); Parser::handler_type event_handler(t->callbacks()); Parser parser(&event_handler, opts); substr src = t->copy_to_arena(yaml); if(t->empty()) { t->reserve(); } parse_in_place(&parser, filename, src, t, t->root_id()); } +void parse_in_arena( csubstr yaml, Tree *t , ParserOptions const& opts) { _RYML_CHECK_BASIC(t); Parser::handler_type event_handler(t->callbacks()); Parser parser(&event_handler, opts); substr src = t->copy_to_arena(yaml); if(t->empty()) { t->reserve(); } parse_in_place(&parser, {} , src, t, t->root_id()); } +void parse_in_arena(csubstr filename, csubstr yaml, NodeRef node , ParserOptions const& opts) { _RYML_CHECK_BASIC(!node.invalid()); Parser::handler_type event_handler(node.tree()->callbacks()); Parser parser(&event_handler, opts); substr src = node.tree()->copy_to_arena(yaml); parse_in_place(&parser, filename, src, node.tree(), node.id()); } +void parse_in_arena( csubstr yaml, NodeRef node , ParserOptions const& opts) { _RYML_CHECK_BASIC(!node.invalid()); Parser::handler_type event_handler(node.tree()->callbacks()); Parser parser(&event_handler, opts); substr src = node.tree()->copy_to_arena(yaml); parse_in_place(&parser, {} , src, node.tree(), node.id()); } +Tree parse_in_arena(csubstr filename, csubstr yaml , ParserOptions const& opts) { Parser::handler_type event_handler; Parser parser(&event_handler, opts); Tree tree(parser.callbacks()); substr src = tree.copy_to_arena(yaml); parse_in_place(&parser, filename, src, &tree, tree.root_id()); return tree; } +Tree parse_in_arena( csubstr yaml , ParserOptions const& opts) { Parser::handler_type event_handler; Parser parser(&event_handler, opts); Tree tree(parser.callbacks()); substr src = tree.copy_to_arena(yaml); parse_in_place(&parser, {} , src, &tree, tree.root_id()); return tree; } + + +// this is vertically aligned to highlight the parameter differences. +void parse_json_in_arena(Parser *parser, csubstr filename, csubstr json, Tree *t, id_type node_id) { _RYML_CHECK_BASIC(t); substr src = t->copy_to_arena(json); parse_json_in_place(parser, filename, src, t, node_id); } +void parse_json_in_arena(Parser *parser, csubstr json, Tree *t, id_type node_id) { _RYML_CHECK_BASIC(t); substr src = t->copy_to_arena(json); parse_json_in_place(parser, {} , src, t, node_id); } +void parse_json_in_arena(Parser *parser, csubstr filename, csubstr json, Tree *t ) { _RYML_CHECK_BASIC(t); substr src = t->copy_to_arena(json); if(t->empty()) { t->reserve(); } parse_json_in_place(parser, filename, src, t, t->root_id()); } +void parse_json_in_arena(Parser *parser, csubstr json, Tree *t ) { _RYML_CHECK_BASIC(t); substr src = t->copy_to_arena(json); if(t->empty()) { t->reserve(); } parse_json_in_place(parser, {} , src, t, t->root_id()); } +void parse_json_in_arena(Parser *parser, csubstr filename, csubstr json, NodeRef node ) { _RYML_CHECK_BASIC(!node.invalid()); substr src = node.tree()->copy_to_arena(json); parse_json_in_place(parser, filename, src, node.tree(), node.id()); } +void parse_json_in_arena(Parser *parser, csubstr json, NodeRef node ) { _RYML_CHECK_BASIC(!node.invalid()); substr src = node.tree()->copy_to_arena(json); parse_json_in_place(parser, {} , src, node.tree(), node.id()); } +Tree parse_json_in_arena(Parser *parser, csubstr filename, csubstr json ) { _RYML_CHECK_BASIC(parser); _RYML_CHECK_BASIC(parser->m_evt_handler); Tree tree(parser->callbacks()); substr src = tree.copy_to_arena(json); parse_json_in_place(parser, filename, src, &tree, tree.root_id()); return tree; } +Tree parse_json_in_arena(Parser *parser, csubstr json ) { _RYML_CHECK_BASIC(parser); _RYML_CHECK_BASIC(parser->m_evt_handler); Tree tree(parser->callbacks()); substr src = tree.copy_to_arena(json); parse_json_in_place(parser, {} , src, &tree, tree.root_id()); return tree; } + +// this is vertically aligned to highlight the parameter differences. +void parse_json_in_arena(csubstr filename, csubstr json, Tree *t, id_type node_id, ParserOptions const& opts) { _RYML_CHECK_BASIC(t); Parser::handler_type event_handler(t->callbacks()); Parser parser(&event_handler, opts); substr src = t->copy_to_arena(json); parse_json_in_place(&parser, filename, src, t, node_id); } +void parse_json_in_arena( csubstr json, Tree *t, id_type node_id, ParserOptions const& opts) { _RYML_CHECK_BASIC(t); Parser::handler_type event_handler(t->callbacks()); Parser parser(&event_handler, opts); substr src = t->copy_to_arena(json); parse_json_in_place(&parser, {} , src, t, node_id); } +void parse_json_in_arena(csubstr filename, csubstr json, Tree *t , ParserOptions const& opts) { _RYML_CHECK_BASIC(t); Parser::handler_type event_handler(t->callbacks()); Parser parser(&event_handler, opts); substr src = t->copy_to_arena(json); if(t->empty()) { t->reserve(); } parse_json_in_place(&parser, filename, src, t, t->root_id()); } +void parse_json_in_arena( csubstr json, Tree *t , ParserOptions const& opts) { _RYML_CHECK_BASIC(t); Parser::handler_type event_handler(t->callbacks()); Parser parser(&event_handler, opts); substr src = t->copy_to_arena(json); if(t->empty()) { t->reserve(); } parse_json_in_place(&parser, {} , src, t, t->root_id()); } +void parse_json_in_arena(csubstr filename, csubstr json, NodeRef node , ParserOptions const& opts) { _RYML_CHECK_BASIC(!node.invalid()); Parser::handler_type event_handler(node.tree()->callbacks()); Parser parser(&event_handler, opts); substr src = node.tree()->copy_to_arena(json); parse_json_in_place(&parser, filename, src, node.tree(), node.id()); } +void parse_json_in_arena( csubstr json, NodeRef node , ParserOptions const& opts) { _RYML_CHECK_BASIC(!node.invalid()); Parser::handler_type event_handler(node.tree()->callbacks()); Parser parser(&event_handler, opts); substr src = node.tree()->copy_to_arena(json); parse_json_in_place(&parser, {} , src, node.tree(), node.id()); } +Tree parse_json_in_arena(csubstr filename, csubstr json , ParserOptions const& opts) { Parser::handler_type event_handler; Parser parser(&event_handler, opts); Tree tree(parser.callbacks()); substr src = tree.copy_to_arena(json); parse_json_in_place(&parser, filename, src, &tree, tree.root_id()); return tree; } +Tree parse_json_in_arena( csubstr json , ParserOptions const& opts) { Parser::handler_type event_handler; Parser parser(&event_handler, opts); Tree tree(parser.callbacks()); substr src = tree.copy_to_arena(json); parse_json_in_place(&parser, {} , src, &tree, tree.root_id()); return tree; } + + +//----------------------------------------------------------------------------- + +RYML_EXPORT id_type estimate_tree_capacity(csubstr src) +{ + id_type num_nodes = 1; // root + for(size_t i = 0; i < src.len; ++i) + { + const char c = src.str[i]; + num_nodes += (c == '\n') || (c == ',') || (c == '[') || (c == '{'); + } + return num_nodes; +} + +} // namespace yml +} // namespace c4 + +#endif /* RYML_SINGLE_HDR_DEFINE_NOW */ + + +// (end src/c4/yml/parse.cpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/preprocess.cpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifdef RYML_SINGLE_HDR_DEFINE_NOW +// amalgamate: removed include of +// c4/yml/preprocess.hpp +//#include "c4/yml/preprocess.hpp" +#if !defined(C4_YML_PREPROCESS_HPP_) && !defined(_C4_YML_PREPROCESS_HPP_) +#error "amalgamate: file c4/yml/preprocess.hpp must have been included at this point" +#endif /* C4_YML_PREPROCESS_HPP_ */ + +// amalgamate: removed include of +// c4/yml/error.hpp +//#include "c4/yml/error.hpp" +#if !defined(C4_YML_ERROR_HPP_) && !defined(_C4_YML_ERROR_HPP_) +#error "amalgamate: file c4/yml/error.hpp must have been included at this point" +#endif /* C4_YML_ERROR_HPP_ */ + +// amalgamate: removed include of +// c4/yml/detail/dbgprint.hpp +//#include "c4/yml/detail/dbgprint.hpp" +#if !defined(C4_YML_DETAIL_DBGPRINT_HPP_) && !defined(_C4_YML_DETAIL_DBGPRINT_HPP_) +#error "amalgamate: file c4/yml/detail/dbgprint.hpp must have been included at this point" +#endif /* C4_YML_DETAIL_DBGPRINT_HPP_ */ + + +/** @file preprocess.hpp Functions for preprocessing YAML prior to parsing. */ + +namespace c4 { +namespace yml { + +C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wold-style-cast") +// NOLINTBEGIN(modernize-avoid-c-style-cast) + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +namespace { +C4_ALWAYS_INLINE bool _is_idchar(char c) +{ + return (c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || (c >= '0' && c <= '9') + || (c == '_' || c == '-' || c == '~' || c == '$'); +} + +enum _ppstate : int { kReadPending = 0, kKeyPending = 1, kValPending = 2 }; // NOLINT +C4_ALWAYS_INLINE _ppstate _next(_ppstate s) +{ + int n = (int)s + 1; + return (_ppstate)(n <= (int)kValPending ? n : 0); +} +} // empty namespace + + +//----------------------------------------------------------------------------- + +size_t preprocess_rxmap(csubstr s, substr buf) +{ + detail::_SubstrWriter writer(buf); + _ppstate state = kReadPending; + size_t last = 0; + + if(s.begins_with('{')) + { + _RYML_CHECK_BASIC(s.ends_with('}')); + s = s.offs(1, 1); + } + + writer.append('{'); + + for(size_t i = 0; i < s.len; ++i) + { + const char curr = s[i]; + const char next = i+1 < s.len ? s[i+1] : '\0'; + + if(curr == '\'' || curr == '"') + { + csubstr ss = s.sub(i).pair_range_esc(curr, '\\'); + i += static_cast(ss.end() - (s.str + i)); + state = _next(state); + } + else if(state == kReadPending && _is_idchar(curr)) + { + state = _next(state); + } + + switch(state) + { + case kKeyPending: + { + if(curr == ':' && next == ' ') + { + state = _next(state); + } + else if(curr == ',' && next == ' ') + { + writer.append(s.range(last, i)); + writer.append(": 1, "); + last = i + 2; + } + break; + } + case kValPending: + { + if(curr == '[' || curr == '{' || curr == '(') + { + csubstr ss = s.sub(i).pair_range_nested(curr, '\\'); + i += static_cast(ss.end() - (s.str + i)); + state = _next(state); + } + else if(curr == ',' && next == ' ') + { + state = _next(state); + } + break; + } + default: + // nothing to do + break; + } + } + + writer.append(s.sub(last)); + if(state == kKeyPending) + writer.append(": 1"); + writer.append('}'); + + return writer.pos; +} + +// NOLINTEND(modernize-avoid-c-style-cast) +C4_SUPPRESS_WARNING_GCC_CLANG_POP + +} // namespace yml +} // namespace c4 + +#endif /* RYML_SINGLE_HDR_DEFINE_NOW */ + + +// (end src/c4/yml/preprocess.cpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/detail/checks.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef C4_YML_DETAIL_CHECKS_HPP_ +#define C4_YML_DETAIL_CHECKS_HPP_ + +// amalgamate: removed include of +// c4/yml/tree.hpp +//#include "c4/yml/tree.hpp" +#if !defined(C4_YML_TREE_HPP_) && !defined(_C4_YML_TREE_HPP_) +#error "amalgamate: file c4/yml/tree.hpp must have been included at this point" +#endif /* C4_YML_TREE_HPP_ */ + + +#ifdef __clang__ +# pragma clang diagnostic push +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wtype-limits" // error: comparison of unsigned expression >= 0 is always true +#elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable: 4296/*expression is always 'boolean_value'*/) +#endif + +namespace c4 { +namespace yml { + + +void check_invariants(Tree const& t, id_type node=NONE); +void check_free_list(Tree const& t); +void check_arena(Tree const& t); + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +inline void check_invariants(Tree const& t, id_type node) +{ + if(node == NONE) + { + if(t.empty()) return; + node = t.root_id(); + } + + NodeData const& n = *t._p(node); +#if defined(RYML_DBG) && 0 + if(n.m_first_child != NONE || n.m_last_child != NONE) + { + printf("check(%zu): fc=%zu lc=%zu\n", node, n.m_first_child, n.m_last_child); + } + else + { + printf("check(%zu)\n", node); + } +#endif + + C4_CHECK(n.m_parent != node); + if(n.m_parent == NONE) + { + C4_CHECK(t.is_root(node)); + } + else //if(n.m_parent != NONE) + { + C4_CHECK(t.has_child(n.m_parent, node)); + + auto const& p = *t._p(n.m_parent); + if(n.m_prev_sibling == NONE) + { + C4_CHECK(p.m_first_child == node); + C4_CHECK(t.first_sibling(node) == node); + } + else + { + C4_CHECK(p.m_first_child != node); + C4_CHECK(t.first_sibling(node) != node); + } + + if(n.m_next_sibling == NONE) + { + C4_CHECK(p.m_last_child == node); + C4_CHECK(t.last_sibling(node) == node); + } + else + { + C4_CHECK(p.m_last_child != node); + C4_CHECK(t.last_sibling(node) != node); + } + } + + C4_CHECK(n.m_first_child != node); + C4_CHECK(n.m_last_child != node); + if(n.m_first_child != NONE || n.m_last_child != NONE) + { + C4_CHECK(n.m_first_child != NONE); + C4_CHECK(n.m_last_child != NONE); + } + + C4_CHECK(n.m_prev_sibling != node); + C4_CHECK(n.m_next_sibling != node); + if(n.m_prev_sibling != NONE) + { + C4_CHECK(t._p(n.m_prev_sibling)->m_next_sibling == node); + C4_CHECK(t._p(n.m_prev_sibling)->m_prev_sibling != node); + } + if(n.m_next_sibling != NONE) + { + C4_CHECK(t._p(n.m_next_sibling)->m_prev_sibling == node); + C4_CHECK(t._p(n.m_next_sibling)->m_next_sibling != node); + } + + id_type count = 0; + for(id_type i = n.m_first_child; i != NONE; i = t.next_sibling(i)) + { +#if defined(RYML_DBG) && 0 + printf("check(%zu): descend to child[%zu]=%zu\n", node, count, i); +#endif + auto const& ch = *t._p(i); + C4_CHECK(ch.m_parent == node); + C4_CHECK(ch.m_next_sibling != i); + ++count; + } + C4_CHECK(count == t.num_children(node)); + + if(n.m_prev_sibling == NONE && n.m_next_sibling == NONE) + { + if(n.m_parent != NONE) + { + C4_CHECK(t.num_children(n.m_parent) == 1); + C4_CHECK(t.num_siblings(node) == 1); + } + } + + if(node == t.root_id()) + { + C4_CHECK(t.size() == t.m_size); + C4_CHECK(t.capacity() == t.m_cap); + C4_CHECK(t.m_cap == t.m_size + t.slack()); + check_free_list(t); + check_arena(t); + } + + for(id_type i = t.first_child(node); i != NONE; i = t.next_sibling(i)) + { + check_invariants(t, i); + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +inline void check_free_list(Tree const& t) +{ + if(t.m_free_head == NONE) + { + C4_CHECK(t.m_free_tail == t.m_free_head); + return; + } + + C4_CHECK(t.m_free_head >= 0 && t.m_free_head < t.m_cap); + C4_CHECK(t.m_free_tail >= 0 && t.m_free_tail < t.m_cap); + + auto const& head = *t._p(t.m_free_head); + //auto const& tail = *t._p(t.m_free_tail); + + //C4_CHECK(head.m_prev_sibling == NONE); + //C4_CHECK(tail.m_next_sibling == NONE); + + id_type count = 0; + for(id_type i = t.m_free_head, prev = NONE; i != NONE; i = t._p(i)->m_next_sibling) + { + auto const& elm = *t._p(i); + if(&elm != &head) + { + C4_CHECK(elm.m_prev_sibling == prev); + } + prev = i; + ++count; + } + C4_CHECK(count == t.slack()); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +inline void check_arena(Tree const& t) +{ + C4_CHECK(t.m_arena.len == 0 || (t.m_arena_pos >= 0 && t.m_arena_pos <= t.m_arena.len)); + C4_CHECK(t.arena_size() == t.m_arena_pos); + C4_CHECK(t.arena_slack() + t.m_arena_pos == t.m_arena.len); +} + + +} /* namespace yml */ +} /* namespace c4 */ + +#ifdef __clang__ +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#elif defined(_MSC_VER) +# pragma warning(pop) +#endif + +#endif /* C4_YML_DETAIL_CHECKS_HPP_ */ + + +// (end src/c4/yml/detail/checks.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/detail/print.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef C4_YML_DETAIL_PRINT_HPP_ +#define C4_YML_DETAIL_PRINT_HPP_ + +// amalgamate: removed include of +// c4/yml/tree.hpp +//#include "c4/yml/tree.hpp" +#if !defined(C4_YML_TREE_HPP_) && !defined(_C4_YML_TREE_HPP_) +#error "amalgamate: file c4/yml/tree.hpp must have been included at this point" +#endif /* C4_YML_TREE_HPP_ */ + +// amalgamate: removed include of +// c4/yml/node.hpp +//#include "c4/yml/node.hpp" +#if !defined(C4_YML_NODE_HPP_) && !defined(_C4_YML_NODE_HPP_) +#error "amalgamate: file c4/yml/node.hpp must have been included at this point" +#endif /* C4_YML_NODE_HPP_ */ + + +#ifdef RYML_DBG +#define _c4dbg_tree(...) print_tree(__VA_ARGS__) +#define _c4dbg_node(...) print_tree(__VA_ARGS__) +#else +#define _c4dbg_tree(...) +#define _c4dbg_node(...) +#endif + +// NOLINTBEGIN(modernize-avoid-c-style-cast) + +namespace c4 { +namespace yml { + +C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wold-style-cast") +C4_SUPPRESS_WARNING_GCC("-Wuseless-cast") +C4_SUPPRESS_WARNING_GCC("-Wattributes") + +inline const char* _container_style_code(Tree const& p, id_type node) +{ + if(p.is_container(node)) + { + if(p._p(node)->m_type & (FLOW_SL|FLOW_MLX)) + { + return "[FLOW]"; + } + if(p._p(node)->m_type & (BLOCK)) + { + return "[BLCK]"; + } + } + return ""; +} +inline char _scalar_code(NodeType masked) +{ + if(masked & (KEY_LITERAL|VAL_LITERAL)) + return '|'; + if(masked & (KEY_FOLDED|VAL_FOLDED)) + return '>'; + if(masked & (KEY_SQUO|VAL_SQUO)) + return '\''; + if(masked & (KEY_DQUO|VAL_DQUO)) + return '"'; + if(masked & (KEY_PLAIN|VAL_PLAIN)) + return '~'; + return '@'; +} +inline char _scalar_code_key(NodeType t) +{ + return _scalar_code(t & KEY_STYLE); +} +inline char _scalar_code_val(NodeType t) +{ + return _scalar_code(t & VAL_STYLE); +} +inline char _scalar_code_key(Tree const& p, id_type node) +{ + return _scalar_code_key(p._p(node)->m_type); +} +inline char _scalar_code_val(Tree const& p, id_type node) +{ + return _scalar_code_key(p._p(node)->m_type); +} +inline C4_NO_INLINE id_type print_node(Tree const& p, id_type node, int level, id_type count, bool print_children, bool print_address=false) +{ + NodeType type = p.type(node); + if(type.is_doc()) + { + TagDirectiveRange tagds = p.m_tag_directives.lookup_range(node); + for(TagDirective const& td : tagds) + { + printf("%%TAG[%zd] %.*s %.*s [doc=%zu]\n", + &td - p.m_tag_directives.m_directives, + (int)td.handle.len, td.handle.str, + (int)td.prefix.len, td.prefix.str, + td.doc_id); + } + } + printf("[%zu]%*s[%zu]", (size_t)count, (2*level), "", (size_t)node); + if(print_address) printf(" %p", (void const*)p.get(node)); + if(p.is_root(node)) printf(" [ROOT]"); + char typebuf[128]; + csubstr typestr = type.type_str_sub(typebuf); + _RYML_CHECK_BASIC(typestr.str); + printf(" %.*s", (int)typestr.len, typestr.str); + if(p.has_key(node)) + { + if(p.has_key_anchor(node)) + { + csubstr ka = p.key_anchor(node); + printf(" &%.*s", (int)ka.len, ka.str); + } + if(p.has_key_tag(node)) + { + csubstr kt = p.key_tag(node); + if(kt.begins_with('<')) + printf(" %.*s", (int)kt.len, kt.str); + else + printf(" <%.*s>", (int)kt.len, kt.str); + } + const char code = _scalar_code_key(p, node); + csubstr k = p.key(node); + printf(" %c%.*s%c :", code, (int)k.len, k.str, code); + } + if(p.has_val_anchor(node)) + { + csubstr a = p.val_anchor(node); + printf(" &%.*s", (int)a.len, a.str); + } + if(p.has_val_tag(node)) + { + csubstr vt = p.val_tag(node); + if(vt.begins_with('<')) + printf(" %.*s", (int)vt.len, vt.str); + else + printf(" <%.*s>", (int)vt.len, vt.str); + } + if(p.has_val(node)) + { + const char code = _scalar_code_val(p, node); + csubstr v = p.val(node); + printf(" %c%.*s%c", code, (int)v.len, v.str, code); + } + printf(" (%zu sibs)", (size_t)p.num_siblings(node)); + + ++count; + + if(!p.is_container(node)) + { + printf("\n"); + } + else + { + printf(" (%zu children)\n", (size_t)p.num_children(node)); + if(print_children) + { + for(id_type i = p.first_child(node); i != NONE; i = p.next_sibling(i)) + { + count = print_node(p, i, level+1, count, print_children); + } + } + } + + return count; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +inline void print_node(ConstNodeRef const& p, int level=0, bool print_address=false) // LCOV_EXCL_LINE +{ + print_node(*p.tree(), p.id(), level, 0, true, print_address); // LCOV_EXCL_LINE +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +inline id_type print_tree(const char *message, Tree const& p, id_type node=NONE, bool print_address=false) +{ + printf("--------------------------------------\n"); + if(message != nullptr) + printf("%s:\n", message); + id_type ret = 0; + if(!p.empty()) + { + if(node == NONE) + node = p.root_id(); + ret = print_node(p, node, 0, 0, true, print_address); + } + printf("#nodes=%zu vs #printed=%zu\n", (size_t)p.size(), (size_t)ret); + printf("--------------------------------------\n"); + return ret; +} + +inline id_type print_tree(Tree const& p, id_type node=NONE, bool print_address=false) +{ + return print_tree(nullptr, p, node, print_address); +} + +inline void print_tree(ConstNodeRef const& p, int level, bool print_address=false) +{ + print_node(p, level, print_address); + for(ConstNodeRef ch : p.children()) + { + print_tree(ch, level+1, print_address); + } +} + +C4_SUPPRESS_WARNING_GCC_CLANG_POP + +} /* namespace yml */ +} /* namespace c4 */ + +// NOLINTEND(modernize-avoid-c-style-cast) + +#endif /* C4_YML_DETAIL_PRINT_HPP_ */ + + +// (end src/c4/yml/detail/print.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/c4/yml/yml.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _C4_YML_YML_HPP_ +#define _C4_YML_YML_HPP_ + +// amalgamate: removed include of +// c4/yml/version.hpp +//#include "c4/yml/version.hpp" +#if !defined(C4_YML_VERSION_HPP_) && !defined(_C4_YML_VERSION_HPP_) +#error "amalgamate: file c4/yml/version.hpp must have been included at this point" +#endif /* C4_YML_VERSION_HPP_ */ + +// amalgamate: removed include of +// c4/yml/tree.hpp +//#include "c4/yml/tree.hpp" +#if !defined(C4_YML_TREE_HPP_) && !defined(_C4_YML_TREE_HPP_) +#error "amalgamate: file c4/yml/tree.hpp must have been included at this point" +#endif /* C4_YML_TREE_HPP_ */ + +// amalgamate: removed include of +// c4/yml/node.hpp +//#include "c4/yml/node.hpp" +#if !defined(C4_YML_NODE_HPP_) && !defined(_C4_YML_NODE_HPP_) +#error "amalgamate: file c4/yml/node.hpp must have been included at this point" +#endif /* C4_YML_NODE_HPP_ */ + +// amalgamate: removed include of +// c4/yml/emit.hpp +//#include "c4/yml/emit.hpp" +#if !defined(C4_YML_EMIT_HPP_) && !defined(_C4_YML_EMIT_HPP_) +#error "amalgamate: file c4/yml/emit.hpp must have been included at this point" +#endif /* C4_YML_EMIT_HPP_ */ + +// amalgamate: removed include of +// c4/yml/event_handler_tree.hpp +//#include "c4/yml/event_handler_tree.hpp" +#if !defined(C4_YML_EVENT_HANDLER_TREE_HPP_) && !defined(_C4_YML_EVENT_HANDLER_TREE_HPP_) +#error "amalgamate: file c4/yml/event_handler_tree.hpp must have been included at this point" +#endif /* C4_YML_EVENT_HANDLER_TREE_HPP_ */ + +// amalgamate: removed include of +// c4/yml/parse_engine.hpp +//#include "c4/yml/parse_engine.hpp" +#if !defined(C4_YML_PARSE_ENGINE_HPP_) && !defined(_C4_YML_PARSE_ENGINE_HPP_) +#error "amalgamate: file c4/yml/parse_engine.hpp must have been included at this point" +#endif /* C4_YML_PARSE_ENGINE_HPP_ */ + +// amalgamate: removed include of +// c4/yml/filter_processor.hpp +//#include "c4/yml/filter_processor.hpp" +#if !defined(C4_YML_FILTER_PROCESSOR_HPP_) && !defined(_C4_YML_FILTER_PROCESSOR_HPP_) +#error "amalgamate: file c4/yml/filter_processor.hpp must have been included at this point" +#endif /* C4_YML_FILTER_PROCESSOR_HPP_ */ + +// amalgamate: removed include of +// c4/yml/parse.hpp +//#include "c4/yml/parse.hpp" +#if !defined(C4_YML_PARSE_HPP_) && !defined(_C4_YML_PARSE_HPP_) +#error "amalgamate: file c4/yml/parse.hpp must have been included at this point" +#endif /* C4_YML_PARSE_HPP_ */ + +// amalgamate: removed include of +// c4/yml/preprocess.hpp +//#include "c4/yml/preprocess.hpp" +#if !defined(C4_YML_PREPROCESS_HPP_) && !defined(_C4_YML_PREPROCESS_HPP_) +#error "amalgamate: file c4/yml/preprocess.hpp must have been included at this point" +#endif /* C4_YML_PREPROCESS_HPP_ */ + +// amalgamate: removed include of +// c4/yml/reference_resolver.hpp +//#include "c4/yml/reference_resolver.hpp" +#if !defined(C4_YML_REFERENCE_RESOLVER_HPP_) && !defined(_C4_YML_REFERENCE_RESOLVER_HPP_) +#error "amalgamate: file c4/yml/reference_resolver.hpp must have been included at this point" +#endif /* C4_YML_REFERENCE_RESOLVER_HPP_ */ + +// amalgamate: removed include of +// c4/yml/tag.hpp +//#include "c4/yml/tag.hpp" +#if !defined(C4_YML_TAG_HPP_) && !defined(_C4_YML_TAG_HPP_) +#error "amalgamate: file c4/yml/tag.hpp must have been included at this point" +#endif /* C4_YML_TAG_HPP_ */ + + +#endif // _C4_YML_YML_HPP_ + + +// (end src/c4/yml/yml.hpp) + + + +//******************************************************************************** +//-------------------------------------------------------------------------------- +// src/ryml.hpp +//-------------------------------------------------------------------------------- +//******************************************************************************** + +#ifndef _RYML_HPP_ +#define _RYML_HPP_ + +// amalgamate: removed include of +// c4/yml/yml.hpp +//#include "c4/yml/yml.hpp" +#if !defined(C4_YML_YML_HPP_) && !defined(_C4_YML_YML_HPP_) +#error "amalgamate: file c4/yml/yml.hpp must have been included at this point" +#endif /* C4_YML_YML_HPP_ */ + + +namespace ryml { +using namespace c4::yml; +using namespace c4; +} + +#endif /* _RYML_HPP_ */ + + +// (end src/ryml.hpp) + +#endif /* _RYML_SINGLE_HEADER_AMALGAMATED_HPP_ */ + From fa490dc9bce2d0d4a252b96e41d4072cba8be190 Mon Sep 17 00:00:00 2001 From: Caitlin Ross Date: Thu, 2 Jul 2026 20:05:46 -0500 Subject: [PATCH 3/5] third-party: vendor googletest Used only for unit testing framework that upcoming c++ improvements will use. --- CMakeLists.txt | 12 +++---- thirdparty/CMakeLists.txt | 4 +++ thirdparty/googletest/CMakeLists.txt | 46 ++++++++++++++++++++++++++ thirdparty/googletest/README.md | 11 +++++++ thirdparty/googletest/update.sh | 49 ++++++++++++++++++++++++++++ 5 files changed, 116 insertions(+), 6 deletions(-) create mode 100644 thirdparty/googletest/CMakeLists.txt create mode 100644 thirdparty/googletest/README.md create mode 100755 thirdparty/googletest/update.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index b24f1051..d39e47c1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -262,6 +262,12 @@ else() endif() +# Ctest creates BUILD_TESTING option and sets true by default +# We disable it by default +option(BUILD_TESTING "Build the testing tree (unit + model tests)" OFF) +include(CTest) +mark_as_advanced(CLEAR BUILD_TESTING) + # Vendored third-party dependencies (built before src, which links them). add_subdirectory(thirdparty) @@ -289,13 +295,7 @@ if(CODES_BUILD_DOXYGEN) add_subdirectory(doc) endif() -# Tests are gated solely on BUILD_TESTING (the canonical CTest knob). A -# $ genex can't gate this since add_subdirectory() runs at -# configure time, and the old `STREQUAL "RELEASE"` compare never matched the -# conventional `Release` spelling anyway, so tests already built in every -# standard config. if(BUILD_TESTING) - include(CTest) set(CODES_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") set(CODES_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}") add_subdirectory(tests) diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index f0368929..5c1f6d72 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -23,3 +23,7 @@ function(message_start_thirdparty) endfunction() add_subdirectory(rapidyaml) + +if(BUILD_TESTING) + add_subdirectory(googletest) +endif() diff --git a/thirdparty/googletest/CMakeLists.txt b/thirdparty/googletest/CMakeLists.txt new file mode 100644 index 00000000..4ed67065 --- /dev/null +++ b/thirdparty/googletest/CMakeLists.txt @@ -0,0 +1,46 @@ +# GoogleTest — the unit-test framework. +# +# Test-only: built only when BUILD_TESTING is on. Wired from the thirdparty +# dispatcher (thirdparty/CMakeLists.txt), which gates its add_subdirectory of this +# wrapper on BUILD_TESTING — an option declared in the top-level CMakeLists before +# the dispatcher runs. It is never installed or exported. +# +# Vendored as a reduced copy of the upstream repo under googletest/ (see +# README.kitware.md), integrated the standard way — add_subdirectory of upstream's +# own CMake, the same approach ADIOS2 uses — rather than hand-compiling gtest-all.cc. +message_start_thirdparty() + +# gtest only: gmock isn't needed (death tests live in gtest core). With BUILD_GMOCK +# OFF, upstream's root CMake recurses into googletest/ alone. +set(BUILD_GMOCK OFF CACHE BOOL "" FORCE) +# Keep the framework out of the CODES install/export. +set(INSTALL_GTEST OFF CACHE BOOL "" FORCE) +# Windows-only knob (link the CRT dynamically); harmless elsewhere. Matches +# upstream's recommendation for projects embedding googletest. +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + +# The project builds its own sources with -Wundef (see the top-level CMakeLists), a +# directory property inherited here. Upstream googletest is not -Wundef-clean, so +# disable it for gtest's own compiles — appended after the inherited -Wundef, so it +# wins — keeping third-party warnings out of the build log. +add_compile_options(-Wno-undef) + +# EXCLUDE_FROM_ALL: gtest compiles only when a test target actually links it, never +# as part of `all`. Upstream exposes the gtest / gtest_main targets and the +# GTest::gtest / GTest::gtest_main aliases that unit-test targets link. +add_subdirectory(googletest EXCLUDE_FROM_ALL) + +# Upstream googletest doesn't mark its headers SYSTEM, so a unit-test TU that +# #includes would inherit the project's own warning flags (the +# -Wundef above, say) through the framework's headers. Re-expose the include dirs +# as SYSTEM on the gtest targets so every consumer gets them via -isystem and stays +# warning-clean. +foreach(_gtest_tgt gtest gtest_main) + if(TARGET ${_gtest_tgt}) + get_target_property(_gtest_inc ${_gtest_tgt} INTERFACE_INCLUDE_DIRECTORIES) + if(_gtest_inc) + set_target_properties(${_gtest_tgt} PROPERTIES + INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${_gtest_inc}") + endif() + endif() +endforeach() diff --git a/thirdparty/googletest/README.md b/thirdparty/googletest/README.md new file mode 100644 index 00000000..a7a76f2e --- /dev/null +++ b/thirdparty/googletest/README.md @@ -0,0 +1,11 @@ +# GoogleTest + +A reduced copy of [GoogleTest][gtest] vendored into CODES as the unit-test +framework. + +See [../UPDATING.md](../UPDATING.md) for the vendoring layout and how to +re-import. In short: do not edit the imported `googletest/` subtree directly; +the pinned version is the `tag` in `update.sh`, and re-importing is `./update.sh` +after bumping it. + +[gtest]: https://github.com/google/googletest diff --git a/thirdparty/googletest/update.sh b/thirdparty/googletest/update.sh new file mode 100755 index 00000000..f84d5ffa --- /dev/null +++ b/thirdparty/googletest/update.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash + +#============================================================================= +# Re-import the vendored GoogleTest source from upstream. +# +# Usage (run from anywhere in the checkout): +# +# ./thirdparty/googletest/update.sh +# +# Follows the Kitware third-party update convention: it sets the variables below +# and defines extract_source, then sources the shared update-common.sh, which +# clones upstream at $tag and merges the extracted tree onto the $subtree/ subtree +# as a single import commit. +# +# We vendor a *reduced* copy — the upstream repo root plus the googletest/ library +# subdirectory — and drop googlemock/, the library's own test/ and samples/, docs, +# CI, and bazel files. gmock is disabled at build time (BUILD_GMOCK=OFF in the +# wrapper CMakeLists), so the googletest/ subdirectory alone is sufficient. +# +# To move to a new release: bump $tag here, then re-run. +#============================================================================= + +set -e +shopt -s dotglob + +readonly name="googletest" +readonly ownership="Googletest Upstream " +readonly subtree="thirdparty/googletest/googletest" +readonly repo="https://github.com/google/googletest.git" +readonly tag="v1.17.0" +readonly shortlog="false" +# The imported tree is a reduced subset of upstream's tree, so tree-object matching +# against a full-upstream import can't find the previous import; fall back to +# log-based matching on the import commit message. +readonly exact_tree_match="false" + +extract_source () { + # Keep the repo root (for its CMakeLists + LICENSE + README + CONTRIBUTORS) and + # the googletest/ library subdirectory (its CMakeLists, cmake/, include/, src/). + # Everything else — googlemock, the library test/ and samples, docs, CI, bazel — + # is intentionally excluded. git archive honors export-ignore gitattributes. + local paths="CMakeLists.txt LICENSE README.md CONTRIBUTORS \ + googletest/CMakeLists.txt googletest/README.md \ + googletest/cmake googletest/include googletest/src" + git archive --worktree-attributes --prefix="$name-reduced/" HEAD -- $paths | \ + tar -C "$extractdir" -x +} + +. "${BASH_SOURCE%/*}/../update-common.sh" From 60e14c9ff427bb20ea0f8c7cc9e5e0f502e5c2fd Mon Sep 17 00:00:00 2001 From: Googletest Upstream Date: Wed, 30 Apr 2025 12:54:29 -0400 Subject: [PATCH 4/5] googletest 2025-04-30 (52eb8108) Code extracted from: https://github.com/google/googletest.git at commit 52eb8108c5bdec04579160ae17225d66034bd723 (v1.17.0). --- CMakeLists.txt | 36 + CONTRIBUTORS | 66 + LICENSE | 28 + README.md | 133 + googletest/CMakeLists.txt | 330 + googletest/README.md | 231 + googletest/cmake/Config.cmake.in | 13 + googletest/cmake/gtest.pc.in | 9 + googletest/cmake/gtest_main.pc.in | 10 + googletest/cmake/internal_utils.cmake | 334 + googletest/cmake/libgtest.la.in | 21 + .../include/gtest/gtest-assertion-result.h | 244 + googletest/include/gtest/gtest-death-test.h | 345 + googletest/include/gtest/gtest-matchers.h | 923 +++ googletest/include/gtest/gtest-message.h | 251 + googletest/include/gtest/gtest-param-test.h | 602 ++ googletest/include/gtest/gtest-printers.h | 1236 +++ googletest/include/gtest/gtest-spi.h | 250 + googletest/include/gtest/gtest-test-part.h | 192 + googletest/include/gtest/gtest-typed-test.h | 331 + googletest/include/gtest/gtest.h | 2338 ++++++ googletest/include/gtest/gtest_pred_impl.h | 279 + googletest/include/gtest/gtest_prod.h | 60 + .../include/gtest/internal/custom/README.md | 44 + .../gtest/internal/custom/gtest-port.h | 37 + .../gtest/internal/custom/gtest-printers.h | 42 + .../include/gtest/internal/custom/gtest.h | 37 + .../internal/gtest-death-test-internal.h | 306 + .../include/gtest/internal/gtest-filepath.h | 233 + .../include/gtest/internal/gtest-internal.h | 1517 ++++ .../include/gtest/internal/gtest-param-util.h | 1064 +++ .../include/gtest/internal/gtest-port-arch.h | 124 + .../include/gtest/internal/gtest-port.h | 2486 ++++++ .../include/gtest/internal/gtest-string.h | 178 + .../include/gtest/internal/gtest-type-util.h | 220 + googletest/src/gtest-all.cc | 49 + googletest/src/gtest-assertion-result.cc | 77 + googletest/src/gtest-death-test.cc | 1587 ++++ googletest/src/gtest-filepath.cc | 414 + googletest/src/gtest-internal-inl.h | 1234 +++ googletest/src/gtest-matchers.cc | 98 + googletest/src/gtest-port.cc | 1434 ++++ googletest/src/gtest-printers.cc | 555 ++ googletest/src/gtest-test-part.cc | 106 + googletest/src/gtest-typed-test.cc | 108 + googletest/src/gtest.cc | 7083 +++++++++++++++++ googletest/src/gtest_main.cc | 66 + 47 files changed, 27361 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 CONTRIBUTORS create mode 100644 LICENSE create mode 100644 README.md create mode 100644 googletest/CMakeLists.txt create mode 100644 googletest/README.md create mode 100644 googletest/cmake/Config.cmake.in create mode 100644 googletest/cmake/gtest.pc.in create mode 100644 googletest/cmake/gtest_main.pc.in create mode 100644 googletest/cmake/internal_utils.cmake create mode 100644 googletest/cmake/libgtest.la.in create mode 100644 googletest/include/gtest/gtest-assertion-result.h create mode 100644 googletest/include/gtest/gtest-death-test.h create mode 100644 googletest/include/gtest/gtest-matchers.h create mode 100644 googletest/include/gtest/gtest-message.h create mode 100644 googletest/include/gtest/gtest-param-test.h create mode 100644 googletest/include/gtest/gtest-printers.h create mode 100644 googletest/include/gtest/gtest-spi.h create mode 100644 googletest/include/gtest/gtest-test-part.h create mode 100644 googletest/include/gtest/gtest-typed-test.h create mode 100644 googletest/include/gtest/gtest.h create mode 100644 googletest/include/gtest/gtest_pred_impl.h create mode 100644 googletest/include/gtest/gtest_prod.h create mode 100644 googletest/include/gtest/internal/custom/README.md create mode 100644 googletest/include/gtest/internal/custom/gtest-port.h create mode 100644 googletest/include/gtest/internal/custom/gtest-printers.h create mode 100644 googletest/include/gtest/internal/custom/gtest.h create mode 100644 googletest/include/gtest/internal/gtest-death-test-internal.h create mode 100644 googletest/include/gtest/internal/gtest-filepath.h create mode 100644 googletest/include/gtest/internal/gtest-internal.h create mode 100644 googletest/include/gtest/internal/gtest-param-util.h create mode 100644 googletest/include/gtest/internal/gtest-port-arch.h create mode 100644 googletest/include/gtest/internal/gtest-port.h create mode 100644 googletest/include/gtest/internal/gtest-string.h create mode 100644 googletest/include/gtest/internal/gtest-type-util.h create mode 100644 googletest/src/gtest-all.cc create mode 100644 googletest/src/gtest-assertion-result.cc create mode 100644 googletest/src/gtest-death-test.cc create mode 100644 googletest/src/gtest-filepath.cc create mode 100644 googletest/src/gtest-internal-inl.h create mode 100644 googletest/src/gtest-matchers.cc create mode 100644 googletest/src/gtest-port.cc create mode 100644 googletest/src/gtest-printers.cc create mode 100644 googletest/src/gtest-test-part.cc create mode 100644 googletest/src/gtest-typed-test.cc create mode 100644 googletest/src/gtest.cc create mode 100644 googletest/src/gtest_main.cc diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..0567ae7d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,36 @@ +# Note: CMake support is community-based. The maintainers do not use CMake +# internally. + +cmake_minimum_required(VERSION 3.16) + +project(googletest-distribution) +set(GOOGLETEST_VERSION 1.17.0) + +if(NOT CYGWIN AND NOT MSYS AND NOT ${CMAKE_SYSTEM_NAME} STREQUAL QNX) + set(CMAKE_CXX_EXTENSIONS OFF) +endif() + +enable_testing() + +include(CMakeDependentOption) +include(GNUInstallDirs) + +# Note that googlemock target already builds googletest. +option(BUILD_GMOCK "Builds the googlemock subproject" ON) +option(INSTALL_GTEST "Enable installation of googletest. (Projects embedding googletest may want to turn this OFF.)" ON) +option(GTEST_HAS_ABSL "Use Abseil and RE2. Requires Abseil and RE2 to be separately added to the build." OFF) + +if(GTEST_HAS_ABSL) + if(NOT TARGET absl::base) + find_package(absl REQUIRED) + endif() + if(NOT TARGET re2::re2) + find_package(re2 REQUIRED) + endif() +endif() + +if(BUILD_GMOCK) + add_subdirectory( googlemock ) +else() + add_subdirectory( googletest ) +endif() diff --git a/CONTRIBUTORS b/CONTRIBUTORS new file mode 100644 index 00000000..ccea41ea --- /dev/null +++ b/CONTRIBUTORS @@ -0,0 +1,66 @@ +# This file contains a list of people who've made non-trivial +# contribution to the Google C++ Testing Framework project. People +# who commit code to the project are encouraged to add their names +# here. Please keep the list sorted by first names. + +Ajay Joshi +Balázs Dán +Benoit Sigoure +Bharat Mediratta +Bogdan Piloca +Chandler Carruth +Chris Prince +Chris Taylor +Dan Egnor +Dave MacLachlan +David Anderson +Dean Sturtevant +Eric Roman +Gene Volovich +Hady Zalek +Hal Burch +Jeffrey Yasskin +Jim Keller +Joe Walnes +Jon Wray +Jói Sigurðsson +Keir Mierle +Keith Ray +Kenton Varda +Kostya Serebryany +Krystian Kuzniarek +Lev Makhlis +Manuel Klimek +Mario Tanev +Mark Paskin +Markus Heule +Martijn Vels +Matthew Simmons +Mika Raento +Mike Bland +Miklós Fazekas +Neal Norwitz +Nermin Ozkiranartli +Owen Carlsen +Paneendra Ba +Pasi Valminen +Patrick Hanna +Patrick Riley +Paul Menage +Peter Kaminski +Piotr Kaminski +Preston Jackson +Rainer Klaffenboeck +Russ Cox +Russ Rufer +Sean Mcafee +Sigurður Ásgeirsson +Soyeon Kim +Sverre Sundsdal +Szymon Sobik +Takeshi Yoshino +Tracy Bialik +Vadim Berman +Vlad Losev +Wolfgang Klier +Zhanyong Wan diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..1941a11f --- /dev/null +++ b/LICENSE @@ -0,0 +1,28 @@ +Copyright 2008, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..598cf312 --- /dev/null +++ b/README.md @@ -0,0 +1,133 @@ +# GoogleTest + +### Announcements + +#### Documentation Updates + +Our documentation is now live on GitHub Pages at +https://google.github.io/googletest/. We recommend browsing the documentation on +GitHub Pages rather than directly in the repository. + +#### Release 1.17.0 + +[Release 1.17.0](https://github.com/google/googletest/releases/tag/v1.17.0) is +now available. + +The 1.17.x branch [requires at least C++17]((https://opensource.google/documentation/policies/cplusplus-support#c_language_standard). + +#### Continuous Integration + +We use Google's internal systems for continuous integration. + +#### Coming Soon + +* We are planning to take a dependency on + [Abseil](https://github.com/abseil/abseil-cpp). + +## Welcome to **GoogleTest**, Google's C++ test framework! + +This repository is a merger of the formerly separate GoogleTest and GoogleMock +projects. These were so closely related that it makes sense to maintain and +release them together. + +### Getting Started + +See the [GoogleTest User's Guide](https://google.github.io/googletest/) for +documentation. We recommend starting with the +[GoogleTest Primer](https://google.github.io/googletest/primer.html). + +More information about building GoogleTest can be found at +[googletest/README.md](googletest/README.md). + +## Features + +* xUnit test framework: \ + Googletest is based on the [xUnit](https://en.wikipedia.org/wiki/XUnit) + testing framework, a popular architecture for unit testing +* Test discovery: \ + Googletest automatically discovers and runs your tests, eliminating the need + to manually register your tests +* Rich set of assertions: \ + Googletest provides a variety of assertions, such as equality, inequality, + exceptions, and more, making it easy to test your code +* User-defined assertions: \ + You can define your own assertions with Googletest, making it simple to + write tests that are specific to your code +* Death tests: \ + Googletest supports death tests, which verify that your code exits in a + certain way, making it useful for testing error-handling code +* Fatal and non-fatal failures: \ + You can specify whether a test failure should be treated as fatal or + non-fatal with Googletest, allowing tests to continue running even if a + failure occurs +* Value-parameterized tests: \ + Googletest supports value-parameterized tests, which run multiple times with + different input values, making it useful for testing functions that take + different inputs +* Type-parameterized tests: \ + Googletest also supports type-parameterized tests, which run with different + data types, making it useful for testing functions that work with different + data types +* Various options for running tests: \ + Googletest provides many options for running tests including running + individual tests, running tests in a specific order and running tests in + parallel + +## Supported Platforms + +GoogleTest follows Google's +[Foundational C++ Support Policy](https://opensource.google/documentation/policies/cplusplus-support). +See +[this table](https://github.com/google/oss-policies-info/blob/main/foundational-cxx-support-matrix.md) +for a list of currently supported versions of compilers, platforms, and build +tools. + +## Who Is Using GoogleTest? + +In addition to many internal projects at Google, GoogleTest is also used by the +following notable projects: + +* The [Chromium projects](https://www.chromium.org/) (behind the Chrome + browser and Chrome OS). +* The [LLVM](https://llvm.org/) compiler. +* [Protocol Buffers](https://github.com/google/protobuf), Google's data + interchange format. +* The [OpenCV](https://opencv.org/) computer vision library. + +## Related Open Source Projects + +[GTest Runner](https://github.com/nholthaus/gtest-runner) is a Qt5 based +automated test-runner and Graphical User Interface with powerful features for +Windows and Linux platforms. + +[GoogleTest UI](https://github.com/ospector/gtest-gbar) is a test runner that +runs your test binary, allows you to track its progress via a progress bar, and +displays a list of test failures. Clicking on one shows failure text. GoogleTest +UI is written in C#. + +[GTest TAP Listener](https://github.com/kinow/gtest-tap-listener) is an event +listener for GoogleTest that implements the +[TAP protocol](https://en.wikipedia.org/wiki/Test_Anything_Protocol) for test +result output. If your test runner understands TAP, you may find it useful. + +[gtest-parallel](https://github.com/google/gtest-parallel) is a test runner that +runs tests from your binary in parallel to provide significant speed-up. + +[GoogleTest Adapter](https://marketplace.visualstudio.com/items?itemName=DavidSchuldenfrei.gtest-adapter) +is a VS Code extension allowing to view GoogleTest in a tree view and run/debug +your tests. + +[C++ TestMate](https://github.com/matepek/vscode-catch2-test-adapter) is a VS +Code extension allowing to view GoogleTest in a tree view and run/debug your +tests. + +[Cornichon](https://pypi.org/project/cornichon/) is a small Gherkin DSL parser +that generates stub code for GoogleTest. + +## Contributing Changes + +Please read +[`CONTRIBUTING.md`](https://github.com/google/googletest/blob/main/CONTRIBUTING.md) +for details on how to contribute to this project. + +Happy testing! diff --git a/googletest/CMakeLists.txt b/googletest/CMakeLists.txt new file mode 100644 index 00000000..dce6a7c9 --- /dev/null +++ b/googletest/CMakeLists.txt @@ -0,0 +1,330 @@ +######################################################################## +# Note: CMake support is community-based. The maintainers do not use CMake +# internally. +# +# CMake build script for Google Test. +# +# To run the tests for Google Test itself on Linux, use 'make test' or +# ctest. You can select which tests to run using 'ctest -R regex'. +# For more options, run 'ctest --help'. + +# When other libraries are using a shared version of runtime libraries, +# Google Test also has to use one. +option( + gtest_force_shared_crt + "Use shared (DLL) run-time lib even when Google Test is built as static lib." + OFF) + +option(gtest_build_tests "Build all of gtest's own tests." OFF) + +option(gtest_build_samples "Build gtest's sample programs." OFF) + +option(gtest_disable_pthreads "Disable uses of pthreads in gtest." OFF) + +option( + gtest_hide_internal_symbols + "Build gtest with internal symbols hidden in shared libraries." + OFF) + +# Defines pre_project_set_up_hermetic_build() and set_up_hermetic_build(). +include(cmake/hermetic_build.cmake OPTIONAL) + +if (COMMAND pre_project_set_up_hermetic_build) + pre_project_set_up_hermetic_build() +endif() + +######################################################################## +# +# Project-wide settings. + +# Name of the project. +# +# CMake files in this project can refer to the root source directory +# as ${gtest_SOURCE_DIR} and to the root binary directory as +# ${gtest_BINARY_DIR}. +# Language "C" is required for find_package(Threads). + +# Project version. + +cmake_minimum_required(VERSION 3.13) +project(gtest VERSION ${GOOGLETEST_VERSION} LANGUAGES CXX C) + +if (COMMAND set_up_hermetic_build) + set_up_hermetic_build() +endif() + +# These commands only run if this is the main project. +if(CMAKE_PROJECT_NAME STREQUAL "gtest" OR CMAKE_PROJECT_NAME STREQUAL "googletest-distribution") + + # BUILD_SHARED_LIBS is a standard CMake variable, but we declare it here to + # make it prominent in the GUI. + option(BUILD_SHARED_LIBS "Build shared libraries (DLLs)." OFF) + +else() + + mark_as_advanced( + gtest_force_shared_crt + gtest_build_tests + gtest_build_samples + gtest_disable_pthreads + gtest_hide_internal_symbols) + +endif() + + +if (gtest_hide_internal_symbols) + set(CMAKE_CXX_VISIBILITY_PRESET hidden) + set(CMAKE_VISIBILITY_INLINES_HIDDEN 1) +endif() + +# Define helper functions and macros used by Google Test. +include(cmake/internal_utils.cmake) + +config_compiler_and_linker() # Defined in internal_utils.cmake. + +# Needed to set the namespace for both the export targets and the +# alias libraries. +set(cmake_package_name GTest CACHE INTERNAL "") + +# Create the CMake package file descriptors. +if (INSTALL_GTEST) + include(CMakePackageConfigHelpers) + set(targets_export_name ${cmake_package_name}Targets CACHE INTERNAL "") + set(generated_dir "${CMAKE_CURRENT_BINARY_DIR}/generated" CACHE INTERNAL "") + set(cmake_files_install_dir "${CMAKE_INSTALL_LIBDIR}/cmake/${cmake_package_name}") + set(version_file "${generated_dir}/${cmake_package_name}ConfigVersion.cmake") + write_basic_package_version_file(${version_file} VERSION ${GOOGLETEST_VERSION} COMPATIBILITY AnyNewerVersion) + install(EXPORT ${targets_export_name} + COMPONENT "${PROJECT_NAME}" + NAMESPACE ${cmake_package_name}:: + DESTINATION ${cmake_files_install_dir}) + set(config_file "${generated_dir}/${cmake_package_name}Config.cmake") + configure_package_config_file("${gtest_SOURCE_DIR}/cmake/Config.cmake.in" + "${config_file}" INSTALL_DESTINATION ${cmake_files_install_dir}) + install(FILES ${version_file} ${config_file} + COMPONENT "${PROJECT_NAME}" + DESTINATION ${cmake_files_install_dir}) +endif() + +# Where Google Test's .h files can be found. +set(gtest_build_include_dirs + "${gtest_SOURCE_DIR}/include" + "${gtest_SOURCE_DIR}") +include_directories(${gtest_build_include_dirs}) + +######################################################################## +# +# Defines the gtest & gtest_main libraries. User tests should link +# with one of them. + +# Google Test libraries. We build them using more strict warnings than what +# are used for other targets, to ensure that gtest can be compiled by a user +# aggressive about warnings. +cxx_library(gtest "${cxx_strict}" src/gtest-all.cc) +set_target_properties(gtest PROPERTIES VERSION ${GOOGLETEST_VERSION}) +if(GTEST_HAS_ABSL) + target_compile_definitions(gtest PUBLIC GTEST_HAS_ABSL=1) + target_link_libraries(gtest PUBLIC + absl::failure_signal_handler + absl::stacktrace + absl::symbolize + absl::flags_parse + absl::flags_reflection + absl::flags_usage + absl::strings + absl::any + absl::optional + absl::variant + re2::re2 + ) +endif() +cxx_library(gtest_main "${cxx_strict}" src/gtest_main.cc) +set_target_properties(gtest_main PROPERTIES VERSION ${GOOGLETEST_VERSION}) +string(REPLACE ";" "$" dirs "${gtest_build_include_dirs}") +target_include_directories(gtest SYSTEM INTERFACE + "$" + "$/${CMAKE_INSTALL_INCLUDEDIR}>") +target_include_directories(gtest_main SYSTEM INTERFACE + "$" + "$/${CMAKE_INSTALL_INCLUDEDIR}>") +if(CMAKE_SYSTEM_NAME MATCHES "QNX" AND CMAKE_SYSTEM_VERSION VERSION_GREATER_EQUAL 7.1) + target_link_libraries(gtest PUBLIC regex) +endif() +target_link_libraries(gtest_main PUBLIC gtest) + +######################################################################## +# +# Install rules. +install_project(gtest gtest_main) + +######################################################################## +# +# Samples on how to link user tests with gtest or gtest_main. +# +# They are not built by default. To build them, set the +# gtest_build_samples option to ON. You can do it by running ccmake +# or specifying the -Dgtest_build_samples=ON flag when running cmake. + +if (gtest_build_samples) + cxx_executable(sample1_unittest samples gtest_main samples/sample1.cc) + cxx_executable(sample2_unittest samples gtest_main samples/sample2.cc) + cxx_executable(sample3_unittest samples gtest_main) + cxx_executable(sample4_unittest samples gtest_main samples/sample4.cc) + cxx_executable(sample5_unittest samples gtest_main samples/sample1.cc) + cxx_executable(sample6_unittest samples gtest_main) + cxx_executable(sample7_unittest samples gtest_main) + cxx_executable(sample8_unittest samples gtest_main) + cxx_executable(sample9_unittest samples gtest) + cxx_executable(sample10_unittest samples gtest) +endif() + +######################################################################## +# +# Google Test's own tests. +# +# You can skip this section if you aren't interested in testing +# Google Test itself. +# +# The tests are not built by default. To build them, set the +# gtest_build_tests option to ON. You can do it by running ccmake +# or specifying the -Dgtest_build_tests=ON flag when running cmake. + +if (gtest_build_tests) + # This must be set in the root directory for the tests to be run by + # 'make test' or ctest. + enable_testing() + + ############################################################ + # C++ tests built with standard compiler flags. + + cxx_test(googletest-death-test-test gtest_main) + cxx_test(gtest_environment_test gtest) + cxx_test(googletest-filepath-test gtest_main) + cxx_test(googletest-listener-test gtest_main) + cxx_test(gtest_main_unittest gtest_main) + cxx_test(googletest-message-test gtest_main) + cxx_test(gtest_no_test_unittest gtest) + cxx_test(googletest-options-test gtest_main) + cxx_test(googletest-param-test-test gtest + test/googletest-param-test2-test.cc) + cxx_test(googletest-port-test gtest_main) + cxx_test(gtest_pred_impl_unittest gtest_main) + cxx_test(gtest_premature_exit_test gtest + test/gtest_premature_exit_test.cc) + cxx_test(googletest-printers-test gtest_main) + cxx_test(gtest_prod_test gtest_main + test/production.cc) + cxx_test(gtest_repeat_test gtest) + cxx_test(gtest_sole_header_test gtest_main) + cxx_test(gtest_stress_test gtest) + cxx_test(googletest-test-part-test gtest_main) + cxx_test(gtest_throw_on_failure_ex_test gtest) + cxx_test(gtest-typed-test_test gtest_main + test/gtest-typed-test2_test.cc) + cxx_test(gtest_unittest gtest_main) + cxx_test(gtest-unittest-api_test gtest) + cxx_test(gtest_skip_in_environment_setup_test gtest_main) + cxx_test(gtest_skip_test gtest_main) + + ############################################################ + # C++ tests built with non-standard compiler flags. + + # MSVC 7.1 does not support STL with exceptions disabled. + if (NOT MSVC OR MSVC_VERSION GREATER 1310) + cxx_library(gtest_no_exception "${cxx_no_exception}" + src/gtest-all.cc) + cxx_library(gtest_main_no_exception "${cxx_no_exception}" + src/gtest-all.cc src/gtest_main.cc) + endif() + cxx_library(gtest_main_no_rtti "${cxx_no_rtti}" + src/gtest-all.cc src/gtest_main.cc) + + cxx_test_with_flags(gtest-death-test_ex_nocatch_test + "${cxx_exception} -DGTEST_ENABLE_CATCH_EXCEPTIONS_=0" + gtest test/googletest-death-test_ex_test.cc) + cxx_test_with_flags(gtest-death-test_ex_catch_test + "${cxx_exception} -DGTEST_ENABLE_CATCH_EXCEPTIONS_=1" + gtest test/googletest-death-test_ex_test.cc) + + cxx_test_with_flags(gtest_no_rtti_unittest "${cxx_no_rtti}" + gtest_main_no_rtti test/gtest_unittest.cc) + + cxx_shared_library(gtest_dll "${cxx_default}" + src/gtest-all.cc src/gtest_main.cc) + + cxx_executable_with_flags(gtest_dll_test_ "${cxx_default}" + gtest_dll test/gtest_all_test.cc) + set_target_properties(gtest_dll_test_ + PROPERTIES + COMPILE_DEFINITIONS "GTEST_LINKED_AS_SHARED_LIBRARY=1") + + ############################################################ + # Python tests. + + cxx_executable(googletest-break-on-failure-unittest_ test gtest) + py_test(googletest-break-on-failure-unittest) + + py_test(gtest_skip_check_output_test) + py_test(gtest_skip_environment_check_output_test) + + # Visual Studio .NET 2003 does not support STL with exceptions disabled. + if (NOT MSVC OR MSVC_VERSION GREATER 1310) # 1310 is Visual Studio .NET 2003 + cxx_executable_with_flags( + googletest-catch-exceptions-no-ex-test_ + "${cxx_no_exception}" + gtest_main_no_exception + test/googletest-catch-exceptions-test_.cc) + endif() + + cxx_executable_with_flags( + googletest-catch-exceptions-ex-test_ + "${cxx_exception}" + gtest_main + test/googletest-catch-exceptions-test_.cc) + py_test(googletest-catch-exceptions-test) + + cxx_executable(googletest-color-test_ test gtest) + py_test(googletest-color-test) + + cxx_executable(googletest-env-var-test_ test gtest) + py_test(googletest-env-var-test) + + cxx_executable(googletest-filter-unittest_ test gtest) + py_test(googletest-filter-unittest) + + cxx_executable(gtest_help_test_ test gtest_main) + py_test(gtest_help_test) + + cxx_executable(googletest-list-tests-unittest_ test gtest) + py_test(googletest-list-tests-unittest) + + cxx_executable(googletest-output-test_ test gtest) + py_test(googletest-output-test --no_stacktrace_support) + + cxx_executable(googletest-shuffle-test_ test gtest) + py_test(googletest-shuffle-test) + + # MSVC 7.1 does not support STL with exceptions disabled. + if (NOT MSVC OR MSVC_VERSION GREATER 1310) + cxx_executable(googletest-throw-on-failure-test_ test gtest_no_exception) + set_target_properties(googletest-throw-on-failure-test_ + PROPERTIES + COMPILE_FLAGS "${cxx_no_exception}") + py_test(googletest-throw-on-failure-test) + endif() + + cxx_executable(googletest-uninitialized-test_ test gtest) + py_test(googletest-uninitialized-test) + + cxx_executable(gtest_list_output_unittest_ test gtest) + py_test(gtest_list_output_unittest) + + cxx_executable(gtest_xml_outfile1_test_ test gtest_main) + cxx_executable(gtest_xml_outfile2_test_ test gtest_main) + py_test(gtest_xml_outfiles_test) + py_test(googletest-json-outfiles-test) + + cxx_executable(gtest_xml_output_unittest_ test gtest) + py_test(gtest_xml_output_unittest --no_stacktrace_support) + py_test(googletest-json-output-unittest --no_stacktrace_support) +endif() diff --git a/googletest/README.md b/googletest/README.md new file mode 100644 index 00000000..a760759e --- /dev/null +++ b/googletest/README.md @@ -0,0 +1,231 @@ +### Generic Build Instructions + +#### Setup + +To build GoogleTest and your tests that use it, you need to tell your build +system where to find its headers and source files. The exact way to do it +depends on which build system you use, and is usually straightforward. + +### Build with CMake + +GoogleTest comes with a CMake build script +([CMakeLists.txt](https://github.com/google/googletest/blob/main/CMakeLists.txt)) +that can be used on a wide range of platforms ("C" stands for cross-platform.). +If you don't have CMake installed already, you can download it for free from +. + +CMake works by generating native makefiles or build projects that can be used in +the compiler environment of your choice. You can either build GoogleTest as a +standalone project or it can be incorporated into an existing CMake build for +another project. + +#### Standalone CMake Project + +When building GoogleTest as a standalone project, the typical workflow starts +with + +``` +git clone https://github.com/google/googletest.git -b v1.17.0 +cd googletest # Main directory of the cloned repository. +mkdir build # Create a directory to hold the build output. +cd build +cmake .. # Generate native build scripts for GoogleTest. +``` + +The above command also includes GoogleMock by default. And so, if you want to +build only GoogleTest, you should replace the last command with + +``` +cmake .. -DBUILD_GMOCK=OFF +``` + +If you are on a \*nix system, you should now see a Makefile in the current +directory. Just type `make` to build GoogleTest. And then you can simply install +GoogleTest if you are a system administrator. + +``` +make +sudo make install # Install in /usr/local/ by default +``` + +If you use Windows and have Visual Studio installed, a `gtest.sln` file and +several `.vcproj` files will be created. You can then build them using Visual +Studio. + +On Mac OS X with Xcode installed, a `.xcodeproj` file will be generated. + +#### Incorporating Into An Existing CMake Project + +If you want to use GoogleTest in a project which already uses CMake, the easiest +way is to get installed libraries and headers. + +* Import GoogleTest by using `find_package` (or `pkg_check_modules`). For + example, if `find_package(GTest CONFIG REQUIRED)` succeeds, you can use the + libraries as `GTest::gtest`, `GTest::gmock`. + +And a more robust and flexible approach is to build GoogleTest as part of that +project directly. This is done by making the GoogleTest source code available to +the main build and adding it using CMake's `add_subdirectory()` command. This +has the significant advantage that the same compiler and linker settings are +used between GoogleTest and the rest of your project, so issues associated with +using incompatible libraries (eg debug/release), etc. are avoided. This is +particularly useful on Windows. Making GoogleTest's source code available to the +main build can be done a few different ways: + +* Download the GoogleTest source code manually and place it at a known + location. This is the least flexible approach and can make it more difficult + to use with continuous integration systems, etc. +* Embed the GoogleTest source code as a direct copy in the main project's + source tree. This is often the simplest approach, but is also the hardest to + keep up to date. Some organizations may not permit this method. +* Add GoogleTest as a git submodule or equivalent. This may not always be + possible or appropriate. Git submodules, for example, have their own set of + advantages and drawbacks. +* Use CMake to download GoogleTest as part of the build's configure step. This + approach doesn't have the limitations of the other methods. + +The last of the above methods is implemented with a small piece of CMake code +that downloads and pulls the GoogleTest code into the main build. + +Just add to your `CMakeLists.txt`: + +```cmake +include(FetchContent) +FetchContent_Declare( + googletest + # Specify the commit you depend on and update it regularly. + URL https://github.com/google/googletest/archive/5376968f6948923e2411081fd9372e71a59d8e77.zip +) +# For Windows: Prevent overriding the parent project's compiler/linker settings +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(googletest) + +# Now simply link against gtest or gtest_main as needed. Eg +add_executable(example example.cpp) +target_link_libraries(example gtest_main) +add_test(NAME example_test COMMAND example) +``` + +Note that this approach requires CMake 3.14 or later due to its use of the +`FetchContent_MakeAvailable()` command. + +##### Visual Studio Dynamic vs Static Runtimes + +By default, new Visual Studio projects link the C runtimes dynamically but +GoogleTest links them statically. This will generate an error that looks +something like the following: gtest.lib(gtest-all.obj) : error LNK2038: mismatch +detected for 'RuntimeLibrary': value 'MTd_StaticDebug' doesn't match value +'MDd_DynamicDebug' in main.obj + +GoogleTest already has a CMake option for this: `gtest_force_shared_crt` + +Enabling this option will make gtest link the runtimes dynamically too, and +match the project in which it is included. + +#### C++ Standard Version + +An environment that supports C++17 is required in order to successfully build +GoogleTest. One way to ensure this is to specify the standard in the top-level +project, for example by using the `set(CMAKE_CXX_STANDARD 17)` command along +with `set(CMAKE_CXX_STANDARD_REQUIRED ON)`. If this is not feasible, for example +in a C project using GoogleTest for validation, then it can be specified by +adding it to the options for cmake via the`-DCMAKE_CXX_FLAGS` option. + +### Tweaking GoogleTest + +GoogleTest can be used in diverse environments. The default configuration may +not work (or may not work well) out of the box in some environments. However, +you can easily tweak GoogleTest by defining control macros on the compiler +command line. Generally, these macros are named like `GTEST_XYZ` and you define +them to either 1 or 0 to enable or disable a certain feature. + +We list the most frequently used macros below. For a complete list, see file +[include/gtest/internal/gtest-port.h](https://github.com/google/googletest/blob/main/googletest/include/gtest/internal/gtest-port.h). + +### Multi-threaded Tests + +GoogleTest is thread-safe where the pthread library is available. After +`#include `, you can check the +`GTEST_IS_THREADSAFE` macro to see whether this is the case (yes if the macro is +`#defined` to 1, no if it's undefined.). + +If GoogleTest doesn't correctly detect whether pthread is available in your +environment, you can force it with + +``` +-DGTEST_HAS_PTHREAD=1 +``` + +or + +``` +-DGTEST_HAS_PTHREAD=0 +``` + +When GoogleTest uses pthread, you may need to add flags to your compiler and/or +linker to select the pthread library, or you'll get link errors. If you use the +CMake script, this is taken care of for you. If you use your own build script, +you'll need to read your compiler and linker's manual to figure out what flags +to add. + +### As a Shared Library (DLL) + +GoogleTest is compact, so most users can build and link it as a static library +for the simplicity. You can choose to use GoogleTest as a shared library (known +as a DLL on Windows) if you prefer. + +To compile *gtest* as a shared library, add + +``` +-DGTEST_CREATE_SHARED_LIBRARY=1 +``` + +to the compiler flags. You'll also need to tell the linker to produce a shared +library instead - consult your linker's manual for how to do it. + +To compile your *tests* that use the gtest shared library, add + +``` +-DGTEST_LINKED_AS_SHARED_LIBRARY=1 +``` + +to the compiler flags. + +Note: while the above steps aren't technically necessary today when using some +compilers (e.g. GCC), they may become necessary in the future, if we decide to +improve the speed of loading the library (see + for details). Therefore you are +recommended to always add the above flags when using GoogleTest as a shared +library. Otherwise a future release of GoogleTest may break your build script. + +### Avoiding Macro Name Clashes + +In C++, macros don't obey namespaces. Therefore two libraries that both define a +macro of the same name will clash if you `#include` both definitions. In case a +GoogleTest macro clashes with another library, you can force GoogleTest to +rename its macro to avoid the conflict. + +Specifically, if both GoogleTest and some other code define macro FOO, you can +add + +``` +-DGTEST_DONT_DEFINE_FOO=1 +``` + +to the compiler flags to tell GoogleTest to change the macro's name from `FOO` +to `GTEST_FOO`. Currently `FOO` can be `ASSERT_EQ`, `ASSERT_FALSE`, `ASSERT_GE`, +`ASSERT_GT`, `ASSERT_LE`, `ASSERT_LT`, `ASSERT_NE`, `ASSERT_TRUE`, +`EXPECT_FALSE`, `EXPECT_TRUE`, `FAIL`, `SUCCEED`, `TEST`, or `TEST_F`. For +example, with `-DGTEST_DONT_DEFINE_TEST=1`, you'll need to write + +``` +GTEST_TEST(SomeTest, DoesThis) { ... } +``` + +instead of + +``` +TEST(SomeTest, DoesThis) { ... } +``` + +in order to define a test. diff --git a/googletest/cmake/Config.cmake.in b/googletest/cmake/Config.cmake.in new file mode 100644 index 00000000..3f706612 --- /dev/null +++ b/googletest/cmake/Config.cmake.in @@ -0,0 +1,13 @@ +@PACKAGE_INIT@ +include(CMakeFindDependencyMacro) +if (@GTEST_HAS_PTHREAD@) + set(THREADS_PREFER_PTHREAD_FLAG @THREADS_PREFER_PTHREAD_FLAG@) + find_dependency(Threads) +endif() +if (@GTEST_HAS_ABSL@) + find_dependency(absl) + find_dependency(re2) +endif() + +include("${CMAKE_CURRENT_LIST_DIR}/@targets_export_name@.cmake") +check_required_components("@project_name@") diff --git a/googletest/cmake/gtest.pc.in b/googletest/cmake/gtest.pc.in new file mode 100644 index 00000000..b4148fae --- /dev/null +++ b/googletest/cmake/gtest.pc.in @@ -0,0 +1,9 @@ +libdir=@CMAKE_INSTALL_FULL_LIBDIR@ +includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ + +Name: gtest +Description: GoogleTest (without main() function) +Version: @PROJECT_VERSION@ +URL: https://github.com/google/googletest +Libs: -L${libdir} -lgtest @CMAKE_THREAD_LIBS_INIT@ +Cflags: -I${includedir} @GTEST_HAS_PTHREAD_MACRO@ diff --git a/googletest/cmake/gtest_main.pc.in b/googletest/cmake/gtest_main.pc.in new file mode 100644 index 00000000..38c88c54 --- /dev/null +++ b/googletest/cmake/gtest_main.pc.in @@ -0,0 +1,10 @@ +libdir=@CMAKE_INSTALL_FULL_LIBDIR@ +includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ + +Name: gtest_main +Description: GoogleTest (with main() function) +Version: @PROJECT_VERSION@ +URL: https://github.com/google/googletest +Requires: gtest = @PROJECT_VERSION@ +Libs: -L${libdir} -lgtest_main @CMAKE_THREAD_LIBS_INIT@ +Cflags: -I${includedir} @GTEST_HAS_PTHREAD_MACRO@ diff --git a/googletest/cmake/internal_utils.cmake b/googletest/cmake/internal_utils.cmake new file mode 100644 index 00000000..7ca256a7 --- /dev/null +++ b/googletest/cmake/internal_utils.cmake @@ -0,0 +1,334 @@ +# Defines functions and macros useful for building Google Test and +# Google Mock. +# +# Note: +# +# - This file will be run twice when building Google Mock (once via +# Google Test's CMakeLists.txt, and once via Google Mock's). +# Therefore it shouldn't have any side effects other than defining +# the functions and macros. +# +# - The functions/macros defined in this file may depend on Google +# Test and Google Mock's option() definitions, and thus must be +# called *after* the options have been defined. + +# Tweaks CMake's default compiler/linker settings to suit Google Test's needs. +# +# This must be a macro(), as inside a function string() can only +# update variables in the function scope. +macro(fix_default_compiler_settings_) + if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC|Clang") + # For MSVC and Clang, CMake sets certain flags to defaults we want to + # override. + # This replacement code is taken from sample in the CMake Wiki at + # https://gitlab.kitware.com/cmake/community/wikis/FAQ#dynamic-replace. + foreach (flag_var + CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE + CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO + CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE + CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) + if (NOT BUILD_SHARED_LIBS AND NOT gtest_force_shared_crt) + # When Google Test is built as a shared library, it should also use + # shared runtime libraries. Otherwise, it may end up with multiple + # copies of runtime library data in different modules, resulting in + # hard-to-find crashes. When it is built as a static library, it is + # preferable to use CRT as static libraries, as we don't have to rely + # on CRT DLLs being available. CMake always defaults to using shared + # CRT libraries, so we override that default here. + string(REPLACE "/MD" "-MT" ${flag_var} "${${flag_var}}") + + # When using Ninja with Clang, static builds pass -D_DLL on Windows. + # This is incorrect and should not happen, so we fix that here. + string(REPLACE "-D_DLL" "" ${flag_var} "${${flag_var}}") + endif() + + # We prefer more strict warning checking for building Google Test. + # Replaces /W3 with /W4 in defaults. + string(REPLACE "/W3" "/W4" ${flag_var} "${${flag_var}}") + + # Prevent D9025 warning for targets that have exception handling + # turned off (/EHs-c- flag). Where required, exceptions are explicitly + # re-enabled using the cxx_exception_flags variable. + string(REPLACE "/EHsc" "" ${flag_var} "${${flag_var}}") + endforeach() + endif() +endmacro() + +# Defines the compiler/linker flags used to build Google Test and +# Google Mock. You can tweak these definitions to suit your need. A +# variable's value is empty before it's explicitly assigned to. +macro(config_compiler_and_linker) + # Note: pthreads on MinGW is not supported, even if available + # instead, we use windows threading primitives. + unset(GTEST_HAS_PTHREAD) + if (NOT gtest_disable_pthreads AND NOT MINGW) + # Defines CMAKE_USE_PTHREADS_INIT and CMAKE_THREAD_LIBS_INIT. + find_package(Threads) + if (CMAKE_USE_PTHREADS_INIT) + set(GTEST_HAS_PTHREAD ON) + endif() + endif() + + fix_default_compiler_settings_() + if (MSVC) + # Newlines inside flags variables break CMake's NMake generator. + # TODO(vladl@google.com): Add -RTCs and -RTCu to debug builds. + set(cxx_base_flags "-GS -W4 -WX -wd4251 -wd4275 -nologo -J") + set(cxx_base_flags "${cxx_base_flags} -D_UNICODE -DUNICODE -DWIN32 -D_WIN32") + set(cxx_base_flags "${cxx_base_flags} -DSTRICT -DWIN32_LEAN_AND_MEAN") + set(cxx_exception_flags "-EHsc -D_HAS_EXCEPTIONS=1") + set(cxx_no_exception_flags "-EHs-c- -D_HAS_EXCEPTIONS=0") + set(cxx_no_rtti_flags "-GR-") + # Suppress "unreachable code" warning, + # https://stackoverflow.com/questions/3232669 explains the issue. + set(cxx_base_flags "${cxx_base_flags} -wd4702") + # Ensure MSVC treats source files as UTF-8 encoded. + if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + set(cxx_base_flags "${cxx_base_flags} -utf-8") + endif() + if (CMAKE_CXX_COMPILER_ID STREQUAL "IntelLLVM") + set(cxx_base_flags "${cxx_base_flags} /fp:precise -Wno-inconsistent-missing-override -Wno-microsoft-exception-spec -Wno-unused-function -Wno-unused-but-set-variable") + endif() + elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR + CMAKE_CXX_COMPILER_ID STREQUAL "IntelLLVM") + set(cxx_base_flags "-Wall -Wshadow -Wconversion -Wundef") + set(cxx_exception_flags "-fexceptions") + set(cxx_no_exception_flags "-fno-exceptions") + set(cxx_strict_flags "-W -Wpointer-arith -Wreturn-type -Wcast-qual -Wwrite-strings -Wswitch -Wunused-parameter -Wcast-align -Winline -Wredundant-decls") + set(cxx_no_rtti_flags "-fno-rtti") + if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + set(cxx_strict_flags "${cxx_strict_flags} -Wchar-subscripts") + endif() + if (CMAKE_CXX_COMPILER_ID STREQUAL "IntelLLVM") + set(cxx_base_flags "${cxx_base_flags} -Wno-implicit-float-size-conversion -ffp-model=precise") + endif() + elseif (CMAKE_COMPILER_IS_GNUCXX) + set(cxx_base_flags "-Wall -Wshadow -Wundef") + if(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0.0) + set(cxx_base_flags "${cxx_base_flags} -Wno-error=dangling-else") + endif() + set(cxx_exception_flags "-fexceptions") + set(cxx_no_exception_flags "-fno-exceptions") + # Until version 4.3.2, GCC doesn't define a macro to indicate + # whether RTTI is enabled. Therefore we define GTEST_HAS_RTTI + # explicitly. + set(cxx_no_rtti_flags "-fno-rtti -DGTEST_HAS_RTTI=0") + set(cxx_strict_flags + "-Wextra -Wno-unused-parameter -Wno-missing-field-initializers") + elseif (CMAKE_CXX_COMPILER_ID STREQUAL "SunPro") + set(cxx_exception_flags "-features=except") + # Sun Pro doesn't provide macros to indicate whether exceptions and + # RTTI are enabled, so we define GTEST_HAS_* explicitly. + set(cxx_no_exception_flags "-features=no%except -DGTEST_HAS_EXCEPTIONS=0") + set(cxx_no_rtti_flags "-features=no%rtti -DGTEST_HAS_RTTI=0") + elseif (CMAKE_CXX_COMPILER_ID STREQUAL "VisualAge" OR + CMAKE_CXX_COMPILER_ID STREQUAL "XL") + # CMake 2.8 changes Visual Age's compiler ID to "XL". + set(cxx_exception_flags "-qeh") + set(cxx_no_exception_flags "-qnoeh") + # Until version 9.0, Visual Age doesn't define a macro to indicate + # whether RTTI is enabled. Therefore we define GTEST_HAS_RTTI + # explicitly. + set(cxx_no_rtti_flags "-qnortti -DGTEST_HAS_RTTI=0") + elseif (CMAKE_CXX_COMPILER_ID STREQUAL "HP") + set(cxx_base_flags "-AA -mt") + set(cxx_exception_flags "-DGTEST_HAS_EXCEPTIONS=1") + set(cxx_no_exception_flags "+noeh -DGTEST_HAS_EXCEPTIONS=0") + # RTTI can not be disabled in HP aCC compiler. + set(cxx_no_rtti_flags "") + endif() + + # The pthreads library is available and allowed? + if (DEFINED GTEST_HAS_PTHREAD) + set(GTEST_HAS_PTHREAD_MACRO "-DGTEST_HAS_PTHREAD=1") + else() + set(GTEST_HAS_PTHREAD_MACRO "-DGTEST_HAS_PTHREAD=0") + endif() + set(cxx_base_flags "${cxx_base_flags} ${GTEST_HAS_PTHREAD_MACRO}") + + # For building gtest's own tests and samples. + set(cxx_exception "${cxx_base_flags} ${cxx_exception_flags}") + set(cxx_no_exception + "${CMAKE_CXX_FLAGS} ${cxx_base_flags} ${cxx_no_exception_flags}") + set(cxx_default "${cxx_exception}") + set(cxx_no_rtti "${cxx_default} ${cxx_no_rtti_flags}") + + # For building the gtest libraries. + set(cxx_strict "${cxx_default} ${cxx_strict_flags}") +endmacro() + +# Defines the gtest & gtest_main libraries. User tests should link +# with one of them. +function(cxx_library_with_type name type cxx_flags) + # type can be either STATIC or SHARED to denote a static or shared library. + # ARGN refers to additional arguments after 'cxx_flags'. + add_library(${name} ${type} ${ARGN}) + add_library(${cmake_package_name}::${name} ALIAS ${name}) + set_target_properties(${name} + PROPERTIES + COMPILE_FLAGS "${cxx_flags}") + # Set the output directory for build artifacts. + set_target_properties(${name} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" + PDB_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" + COMPILE_PDB_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib") + # Make PDBs match library name. + get_target_property(pdb_debug_postfix ${name} DEBUG_POSTFIX) + set_target_properties(${name} + PROPERTIES + PDB_NAME "${name}" + PDB_NAME_DEBUG "${name}${pdb_debug_postfix}" + COMPILE_PDB_NAME "${name}" + COMPILE_PDB_NAME_DEBUG "${name}${pdb_debug_postfix}") + + if (BUILD_SHARED_LIBS OR type STREQUAL "SHARED") + set_target_properties(${name} + PROPERTIES + COMPILE_DEFINITIONS "GTEST_CREATE_SHARED_LIBRARY=1") + target_compile_definitions(${name} INTERFACE + $) + endif() + if (DEFINED GTEST_HAS_PTHREAD) + target_link_libraries(${name} PUBLIC Threads::Threads) + endif() + + target_compile_features(${name} PUBLIC cxx_std_17) +endfunction() + +######################################################################## +# +# Helper functions for creating build targets. + +function(cxx_shared_library name cxx_flags) + cxx_library_with_type(${name} SHARED "${cxx_flags}" ${ARGN}) +endfunction() + +function(cxx_library name cxx_flags) + cxx_library_with_type(${name} "" "${cxx_flags}" ${ARGN}) +endfunction() + +# cxx_executable_with_flags(name cxx_flags libs srcs...) +# +# Creates a named C++ executable that depends on the given libraries and +# is built from the given source files with the given compiler flags. +function(cxx_executable_with_flags name cxx_flags libs) + add_executable(${name} ${ARGN}) + if (MSVC) + # BigObj required for tests. + set(cxx_flags "${cxx_flags} -bigobj") + endif() + if (cxx_flags) + set_target_properties(${name} + PROPERTIES + COMPILE_FLAGS "${cxx_flags}") + endif() + if (BUILD_SHARED_LIBS) + set_target_properties(${name} + PROPERTIES + COMPILE_DEFINITIONS "GTEST_LINKED_AS_SHARED_LIBRARY=1") + endif() + # To support mixing linking in static and dynamic libraries, link each + # library in with an extra call to target_link_libraries. + foreach (lib "${libs}") + target_link_libraries(${name} ${lib}) + endforeach() +endfunction() + +# cxx_executable(name dir lib srcs...) +# +# Creates a named target that depends on the given libs and is built +# from the given source files. dir/name.cc is implicitly included in +# the source file list. +function(cxx_executable name dir libs) + cxx_executable_with_flags( + ${name} "${cxx_default}" "${libs}" "${dir}/${name}.cc" ${ARGN}) +endfunction() + +if(gtest_build_tests) + find_package(Python3) +endif() + +# cxx_test_with_flags(name cxx_flags libs srcs...) +# +# Creates a named C++ test that depends on the given libs and is built +# from the given source files with the given compiler flags. +function(cxx_test_with_flags name cxx_flags libs) + cxx_executable_with_flags(${name} "${cxx_flags}" "${libs}" ${ARGN}) + add_test(NAME ${name} COMMAND "$") +endfunction() + +# cxx_test(name libs srcs...) +# +# Creates a named test target that depends on the given libs and is +# built from the given source files. Unlike cxx_test_with_flags, +# test/name.cc is already implicitly included in the source file list. +function(cxx_test name libs) + cxx_test_with_flags("${name}" "${cxx_default}" "${libs}" + "test/${name}.cc" ${ARGN}) +endfunction() + +# py_test(name) +# +# Creates a Python test with the given name whose main module is in +# test/name.py. It does nothing if Python is not installed. +function(py_test name) + if (NOT Python3_Interpreter_FOUND) + return() + endif() + + get_cmake_property(is_multi "GENERATOR_IS_MULTI_CONFIG") + set(build_dir "${CMAKE_CURRENT_BINARY_DIR}") + if (is_multi) + set(build_dir "${CMAKE_CURRENT_BINARY_DIR}/$") + endif() + + add_test(NAME ${name} + COMMAND Python3::Interpreter ${CMAKE_CURRENT_SOURCE_DIR}/test/${name}.py + --build_dir=${build_dir} ${ARGN}) + + # Make the Python import path consistent between Bazel and CMake. + set_tests_properties(${name} PROPERTIES ENVIRONMENT PYTHONPATH=${CMAKE_SOURCE_DIR}) +endfunction() + +# install_project(targets...) +# +# Installs the specified targets and configures the associated pkgconfig files. +function(install_project) + if(INSTALL_GTEST) + install(DIRECTORY "${PROJECT_SOURCE_DIR}/include/" + COMPONENT "${PROJECT_NAME}" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") + # Install the project targets. + install(TARGETS ${ARGN} + EXPORT ${targets_export_name} + COMPONENT "${PROJECT_NAME}" + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}") + if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + # Install PDBs. + foreach(t ${ARGN}) + get_target_property(t_pdb_name ${t} COMPILE_PDB_NAME) + get_target_property(t_pdb_name_debug ${t} COMPILE_PDB_NAME_DEBUG) + get_target_property(t_pdb_output_directory ${t} PDB_OUTPUT_DIRECTORY) + install(FILES + "${t_pdb_output_directory}/\${CMAKE_INSTALL_CONFIG_NAME}/$<$:${t_pdb_name_debug}>$<$>:${t_pdb_name}>.pdb" + COMPONENT "${PROJECT_NAME}" + DESTINATION ${CMAKE_INSTALL_LIBDIR} + OPTIONAL) + endforeach() + endif() + # Configure and install pkgconfig files. + foreach(t ${ARGN}) + set(configured_pc "${generated_dir}/${t}.pc") + configure_file("${PROJECT_SOURCE_DIR}/cmake/${t}.pc.in" + "${configured_pc}" @ONLY) + install(FILES "${configured_pc}" + COMPONENT "${PROJECT_NAME}" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") + endforeach() + endif() +endfunction() diff --git a/googletest/cmake/libgtest.la.in b/googletest/cmake/libgtest.la.in new file mode 100644 index 00000000..840c8388 --- /dev/null +++ b/googletest/cmake/libgtest.la.in @@ -0,0 +1,21 @@ +# libgtest.la - a libtool library file +# Generated by libtool (GNU libtool) 2.4.6 + +# Please DO NOT delete this file! +# It is necessary for linking the library. + +# Names of this library. +library_names='libgtest.so' + +# Is this an already installed library? +installed=yes + +# Should we warn about portability when linking against -modules? +shouldnotlink=no + +# Files to dlopen/dlpreopen +dlopen='' +dlpreopen='' + +# Directory that this library needs to be installed in: +libdir='@CMAKE_INSTALL_FULL_LIBDIR@' diff --git a/googletest/include/gtest/gtest-assertion-result.h b/googletest/include/gtest/gtest-assertion-result.h new file mode 100644 index 00000000..954e7c40 --- /dev/null +++ b/googletest/include/gtest/gtest-assertion-result.h @@ -0,0 +1,244 @@ +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// The Google C++ Testing and Mocking Framework (Google Test) +// +// This file implements the AssertionResult type. + +// IWYU pragma: private, include "gtest/gtest.h" +// IWYU pragma: friend gtest/.* +// IWYU pragma: friend gmock/.* + +#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_ASSERTION_RESULT_H_ +#define GOOGLETEST_INCLUDE_GTEST_GTEST_ASSERTION_RESULT_H_ + +#include +#include +#include +#include + +#include "gtest/gtest-message.h" +#include "gtest/internal/gtest-port.h" + +GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ +/* class A needs to have dll-interface to be used by clients of class B */) + +namespace testing { + +// A class for indicating whether an assertion was successful. When +// the assertion wasn't successful, the AssertionResult object +// remembers a non-empty message that describes how it failed. +// +// To create an instance of this class, use one of the factory functions +// (AssertionSuccess() and AssertionFailure()). +// +// This class is useful for two purposes: +// 1. Defining predicate functions to be used with Boolean test assertions +// EXPECT_TRUE/EXPECT_FALSE and their ASSERT_ counterparts +// 2. Defining predicate-format functions to be +// used with predicate assertions (ASSERT_PRED_FORMAT*, etc). +// +// For example, if you define IsEven predicate: +// +// testing::AssertionResult IsEven(int n) { +// if ((n % 2) == 0) +// return testing::AssertionSuccess(); +// else +// return testing::AssertionFailure() << n << " is odd"; +// } +// +// Then the failed expectation EXPECT_TRUE(IsEven(Fib(5))) +// will print the message +// +// Value of: IsEven(Fib(5)) +// Actual: false (5 is odd) +// Expected: true +// +// instead of a more opaque +// +// Value of: IsEven(Fib(5)) +// Actual: false +// Expected: true +// +// in case IsEven is a simple Boolean predicate. +// +// If you expect your predicate to be reused and want to support informative +// messages in EXPECT_FALSE and ASSERT_FALSE (negative assertions show up +// about half as often as positive ones in our tests), supply messages for +// both success and failure cases: +// +// testing::AssertionResult IsEven(int n) { +// if ((n % 2) == 0) +// return testing::AssertionSuccess() << n << " is even"; +// else +// return testing::AssertionFailure() << n << " is odd"; +// } +// +// Then a statement EXPECT_FALSE(IsEven(Fib(6))) will print +// +// Value of: IsEven(Fib(6)) +// Actual: true (8 is even) +// Expected: false +// +// NB: Predicates that support negative Boolean assertions have reduced +// performance in positive ones so be careful not to use them in tests +// that have lots (tens of thousands) of positive Boolean assertions. +// +// To use this class with EXPECT_PRED_FORMAT assertions such as: +// +// // Verifies that Foo() returns an even number. +// EXPECT_PRED_FORMAT1(IsEven, Foo()); +// +// you need to define: +// +// testing::AssertionResult IsEven(const char* expr, int n) { +// if ((n % 2) == 0) +// return testing::AssertionSuccess(); +// else +// return testing::AssertionFailure() +// << "Expected: " << expr << " is even\n Actual: it's " << n; +// } +// +// If Foo() returns 5, you will see the following message: +// +// Expected: Foo() is even +// Actual: it's 5 +// + +// Returned AssertionResult objects may not be ignored. +// Note: Disabled for SWIG as it doesn't parse attributes correctly. +#if !defined(SWIG) +class [[nodiscard]] AssertionResult; +#endif // !SWIG + +class GTEST_API_ AssertionResult { + public: + // Copy constructor. + // Used in EXPECT_TRUE/FALSE(assertion_result). + AssertionResult(const AssertionResult& other); + +// C4800 is a level 3 warning in Visual Studio 2015 and earlier. +// This warning is not emitted in Visual Studio 2017. +// This warning is off by default starting in Visual Studio 2019 but can be +// enabled with command-line options. +#if defined(_MSC_VER) && (_MSC_VER < 1910 || _MSC_VER >= 1920) + GTEST_DISABLE_MSC_WARNINGS_PUSH_(4800 /* forcing value to bool */) +#endif + + // Used in the EXPECT_TRUE/FALSE(bool_expression). + // + // T must be contextually convertible to bool. + // + // The second parameter prevents this overload from being considered if + // the argument is implicitly convertible to AssertionResult. In that case + // we want AssertionResult's copy constructor to be used. + template + explicit AssertionResult( + const T& success, + typename std::enable_if< + !std::is_convertible::value>::type* + /*enabler*/ + = nullptr) + : success_(success) {} + +#if defined(_MSC_VER) && (_MSC_VER < 1910 || _MSC_VER >= 1920) + GTEST_DISABLE_MSC_WARNINGS_POP_() +#endif + + // Assignment operator. + AssertionResult& operator=(AssertionResult other) { + swap(other); + return *this; + } + + // Returns true if and only if the assertion succeeded. + operator bool() const { return success_; } // NOLINT + + // Returns the assertion's negation. Used with EXPECT/ASSERT_FALSE. + AssertionResult operator!() const; + + // Returns the text streamed into this AssertionResult. Test assertions + // use it when they fail (i.e., the predicate's outcome doesn't match the + // assertion's expectation). When nothing has been streamed into the + // object, returns an empty string. + const char* message() const { + return message_ != nullptr ? message_->c_str() : ""; + } + // Deprecated; please use message() instead. + const char* failure_message() const { return message(); } + + // Streams a custom failure message into this object. + template + AssertionResult& operator<<(const T& value) { + AppendMessage(Message() << value); + return *this; + } + + // Allows streaming basic output manipulators such as endl or flush into + // this object. + AssertionResult& operator<<( + ::std::ostream& (*basic_manipulator)(::std::ostream& stream)) { + AppendMessage(Message() << basic_manipulator); + return *this; + } + + private: + // Appends the contents of message to message_. + void AppendMessage(const Message& a_message) { + if (message_ == nullptr) message_ = ::std::make_unique<::std::string>(); + message_->append(a_message.GetString().c_str()); + } + + // Swap the contents of this AssertionResult with other. + void swap(AssertionResult& other); + + // Stores result of the assertion predicate. + bool success_; + // Stores the message describing the condition in case the expectation + // construct is not satisfied with the predicate's outcome. + // Referenced via a pointer to avoid taking too much stack frame space + // with test assertions. + std::unique_ptr< ::std::string> message_; +}; + +// Makes a successful assertion result. +GTEST_API_ AssertionResult AssertionSuccess(); + +// Makes a failed assertion result. +GTEST_API_ AssertionResult AssertionFailure(); + +// Makes a failed assertion result with the given failure message. +// Deprecated; use AssertionFailure() << msg. +GTEST_API_ AssertionResult AssertionFailure(const Message& msg); + +} // namespace testing + +GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 + +#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_ASSERTION_RESULT_H_ diff --git a/googletest/include/gtest/gtest-death-test.h b/googletest/include/gtest/gtest-death-test.h new file mode 100644 index 00000000..3c619097 --- /dev/null +++ b/googletest/include/gtest/gtest-death-test.h @@ -0,0 +1,345 @@ +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// The Google C++ Testing and Mocking Framework (Google Test) +// +// This header file defines the public API for death tests. It is +// #included by gtest.h so a user doesn't need to include this +// directly. + +// IWYU pragma: private, include "gtest/gtest.h" +// IWYU pragma: friend gtest/.* +// IWYU pragma: friend gmock/.* + +#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_DEATH_TEST_H_ +#define GOOGLETEST_INCLUDE_GTEST_GTEST_DEATH_TEST_H_ + +#include "gtest/internal/gtest-death-test-internal.h" + +// This flag controls the style of death tests. Valid values are "threadsafe", +// meaning that the death test child process will re-execute the test binary +// from the start, running only a single death test, or "fast", +// meaning that the child process will execute the test logic immediately +// after forking. +GTEST_DECLARE_string_(death_test_style); + +namespace testing { + +#ifdef GTEST_HAS_DEATH_TEST + +namespace internal { + +// Returns a Boolean value indicating whether the caller is currently +// executing in the context of the death test child process. Tools such as +// Valgrind heap checkers may need this to modify their behavior in death +// tests. IMPORTANT: This is an internal utility. Using it may break the +// implementation of death tests. User code MUST NOT use it. +GTEST_API_ bool InDeathTestChild(); + +} // namespace internal + +// The following macros are useful for writing death tests. + +// Here's what happens when an ASSERT_DEATH* or EXPECT_DEATH* is +// executed: +// +// 1. It generates a warning if there is more than one active +// thread. This is because it's safe to fork() or clone() only +// when there is a single thread. +// +// 2. The parent process clone()s a sub-process and runs the death +// test in it; the sub-process exits with code 0 at the end of the +// death test, if it hasn't exited already. +// +// 3. The parent process waits for the sub-process to terminate. +// +// 4. The parent process checks the exit code and error message of +// the sub-process. +// +// Examples: +// +// ASSERT_DEATH(server.SendMessage(56, "Hello"), "Invalid port number"); +// for (int i = 0; i < 5; i++) { +// EXPECT_DEATH(server.ProcessRequest(i), +// "Invalid request .* in ProcessRequest()") +// << "Failed to die on request " << i; +// } +// +// ASSERT_EXIT(server.ExitNow(), ::testing::ExitedWithCode(0), "Exiting"); +// +// bool KilledBySIGHUP(int exit_code) { +// return WIFSIGNALED(exit_code) && WTERMSIG(exit_code) == SIGHUP; +// } +// +// ASSERT_EXIT(client.HangUpServer(), KilledBySIGHUP, "Hanging up!"); +// +// The final parameter to each of these macros is a matcher applied to any data +// the sub-process wrote to stderr. For compatibility with existing tests, a +// bare string is interpreted as a regular expression matcher. +// +// On the regular expressions used in death tests: +// +// On POSIX-compliant systems (*nix), we use the library, +// which uses the POSIX extended regex syntax. +// +// On other platforms (e.g. Windows or Mac), we only support a simple regex +// syntax implemented as part of Google Test. This limited +// implementation should be enough most of the time when writing +// death tests; though it lacks many features you can find in PCRE +// or POSIX extended regex syntax. For example, we don't support +// union ("x|y"), grouping ("(xy)"), brackets ("[xy]"), and +// repetition count ("x{5,7}"), among others. +// +// Below is the syntax that we do support. We chose it to be a +// subset of both PCRE and POSIX extended regex, so it's easy to +// learn wherever you come from. In the following: 'A' denotes a +// literal character, period (.), or a single \\ escape sequence; +// 'x' and 'y' denote regular expressions; 'm' and 'n' are for +// natural numbers. +// +// c matches any literal character c +// \\d matches any decimal digit +// \\D matches any character that's not a decimal digit +// \\f matches \f +// \\n matches \n +// \\r matches \r +// \\s matches any ASCII whitespace, including \n +// \\S matches any character that's not a whitespace +// \\t matches \t +// \\v matches \v +// \\w matches any letter, _, or decimal digit +// \\W matches any character that \\w doesn't match +// \\c matches any literal character c, which must be a punctuation +// . matches any single character except \n +// A? matches 0 or 1 occurrences of A +// A* matches 0 or many occurrences of A +// A+ matches 1 or many occurrences of A +// ^ matches the beginning of a string (not that of each line) +// $ matches the end of a string (not that of each line) +// xy matches x followed by y +// +// If you accidentally use PCRE or POSIX extended regex features +// not implemented by us, you will get a run-time failure. In that +// case, please try to rewrite your regular expression within the +// above syntax. +// +// This implementation is *not* meant to be as highly tuned or robust +// as a compiled regex library, but should perform well enough for a +// death test, which already incurs significant overhead by launching +// a child process. +// +// Known caveats: +// +// A "threadsafe" style death test obtains the path to the test +// program from argv[0] and re-executes it in the sub-process. For +// simplicity, the current implementation doesn't search the PATH +// when launching the sub-process. This means that the user must +// invoke the test program via a path that contains at least one +// path separator (e.g. path/to/foo_test and +// /absolute/path/to/bar_test are fine, but foo_test is not). This +// is rarely a problem as people usually don't put the test binary +// directory in PATH. +// + +// Asserts that a given `statement` causes the program to exit, with an +// integer exit status that satisfies `predicate`, and emitting error output +// that matches `matcher`. +#define ASSERT_EXIT(statement, predicate, matcher) \ + GTEST_DEATH_TEST_(statement, predicate, matcher, GTEST_FATAL_FAILURE_) + +// Like `ASSERT_EXIT`, but continues on to successive tests in the +// test suite, if any: +#define EXPECT_EXIT(statement, predicate, matcher) \ + GTEST_DEATH_TEST_(statement, predicate, matcher, GTEST_NONFATAL_FAILURE_) + +// Asserts that a given `statement` causes the program to exit, either by +// explicitly exiting with a nonzero exit code or being killed by a +// signal, and emitting error output that matches `matcher`. +#define ASSERT_DEATH(statement, matcher) \ + ASSERT_EXIT(statement, ::testing::internal::ExitedUnsuccessfully, matcher) + +// Like `ASSERT_DEATH`, but continues on to successive tests in the +// test suite, if any: +#define EXPECT_DEATH(statement, matcher) \ + EXPECT_EXIT(statement, ::testing::internal::ExitedUnsuccessfully, matcher) + +// Two predicate classes that can be used in {ASSERT,EXPECT}_EXIT*: + +// Tests that an exit code describes a normal exit with a given exit code. +class GTEST_API_ ExitedWithCode { + public: + explicit ExitedWithCode(int exit_code); + ExitedWithCode(const ExitedWithCode&) = default; + void operator=(const ExitedWithCode& other) = delete; + bool operator()(int exit_status) const; + + private: + const int exit_code_; +}; + +#if !defined(GTEST_OS_WINDOWS) && !defined(GTEST_OS_FUCHSIA) +// Tests that an exit code describes an exit due to termination by a +// given signal. +class GTEST_API_ KilledBySignal { + public: + explicit KilledBySignal(int signum); + bool operator()(int exit_status) const; + + private: + const int signum_; +}; +#endif // !GTEST_OS_WINDOWS + +// EXPECT_DEBUG_DEATH asserts that the given statements die in debug mode. +// The death testing framework causes this to have interesting semantics, +// since the sideeffects of the call are only visible in opt mode, and not +// in debug mode. +// +// In practice, this can be used to test functions that utilize the +// LOG(DFATAL) macro using the following style: +// +// int DieInDebugOr12(int* sideeffect) { +// if (sideeffect) { +// *sideeffect = 12; +// } +// LOG(DFATAL) << "death"; +// return 12; +// } +// +// TEST(TestSuite, TestDieOr12WorksInDgbAndOpt) { +// int sideeffect = 0; +// // Only asserts in dbg. +// EXPECT_DEBUG_DEATH(DieInDebugOr12(&sideeffect), "death"); +// +// #ifdef NDEBUG +// // opt-mode has sideeffect visible. +// EXPECT_EQ(12, sideeffect); +// #else +// // dbg-mode no visible sideeffect. +// EXPECT_EQ(0, sideeffect); +// #endif +// } +// +// This will assert that DieInDebugReturn12InOpt() crashes in debug +// mode, usually due to a DCHECK or LOG(DFATAL), but returns the +// appropriate fallback value (12 in this case) in opt mode. If you +// need to test that a function has appropriate side-effects in opt +// mode, include assertions against the side-effects. A general +// pattern for this is: +// +// EXPECT_DEBUG_DEATH({ +// // Side-effects here will have an effect after this statement in +// // opt mode, but none in debug mode. +// EXPECT_EQ(12, DieInDebugOr12(&sideeffect)); +// }, "death"); +// +#ifdef NDEBUG + +#define EXPECT_DEBUG_DEATH(statement, regex) \ + GTEST_EXECUTE_STATEMENT_(statement, regex) + +#define ASSERT_DEBUG_DEATH(statement, regex) \ + GTEST_EXECUTE_STATEMENT_(statement, regex) + +#else + +#define EXPECT_DEBUG_DEATH(statement, regex) EXPECT_DEATH(statement, regex) + +#define ASSERT_DEBUG_DEATH(statement, regex) ASSERT_DEATH(statement, regex) + +#endif // NDEBUG for EXPECT_DEBUG_DEATH +#endif // GTEST_HAS_DEATH_TEST + +// This macro is used for implementing macros such as +// EXPECT_DEATH_IF_SUPPORTED and ASSERT_DEATH_IF_SUPPORTED on systems where +// death tests are not supported. Those macros must compile on such systems +// if and only if EXPECT_DEATH and ASSERT_DEATH compile with the same parameters +// on systems that support death tests. This allows one to write such a macro on +// a system that does not support death tests and be sure that it will compile +// on a death-test supporting system. It is exposed publicly so that systems +// that have death-tests with stricter requirements than GTEST_HAS_DEATH_TEST +// can write their own equivalent of EXPECT_DEATH_IF_SUPPORTED and +// ASSERT_DEATH_IF_SUPPORTED. +// +// Parameters: +// statement - A statement that a macro such as EXPECT_DEATH would test +// for program termination. This macro has to make sure this +// statement is compiled but not executed, to ensure that +// EXPECT_DEATH_IF_SUPPORTED compiles with a certain +// parameter if and only if EXPECT_DEATH compiles with it. +// regex_or_matcher - A regex that a macro such as EXPECT_DEATH would use +// to test the output of statement. This parameter has to be +// compiled but not evaluated by this macro, to ensure that +// this macro only accepts expressions that a macro such as +// EXPECT_DEATH would accept. +// terminator - Must be an empty statement for EXPECT_DEATH_IF_SUPPORTED +// and a return statement for ASSERT_DEATH_IF_SUPPORTED. +// This ensures that ASSERT_DEATH_IF_SUPPORTED will not +// compile inside functions where ASSERT_DEATH doesn't +// compile. +// +// The branch that has an always false condition is used to ensure that +// statement and regex are compiled (and thus syntactically correct) but +// never executed. The unreachable code macro protects the terminator +// statement from generating an 'unreachable code' warning in case +// statement unconditionally returns or throws. The Message constructor at +// the end allows the syntax of streaming additional messages into the +// macro, for compilational compatibility with EXPECT_DEATH/ASSERT_DEATH. +#define GTEST_UNSUPPORTED_DEATH_TEST(statement, regex_or_matcher, terminator) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::testing::internal::AlwaysTrue()) { \ + GTEST_LOG_(WARNING) << "Death tests are not supported on this platform.\n" \ + << "Statement '" #statement "' cannot be verified."; \ + } else if (::testing::internal::AlwaysFalse()) { \ + ::testing::internal::MakeDeathTestMatcher(regex_or_matcher); \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + terminator; \ + } else \ + ::testing::Message() + +// EXPECT_DEATH_IF_SUPPORTED(statement, regex) and +// ASSERT_DEATH_IF_SUPPORTED(statement, regex) expand to real death tests if +// death tests are supported; otherwise they just issue a warning. This is +// useful when you are combining death test assertions with normal test +// assertions in one test. +#ifdef GTEST_HAS_DEATH_TEST +#define EXPECT_DEATH_IF_SUPPORTED(statement, regex) \ + EXPECT_DEATH(statement, regex) +#define ASSERT_DEATH_IF_SUPPORTED(statement, regex) \ + ASSERT_DEATH(statement, regex) +#else +#define EXPECT_DEATH_IF_SUPPORTED(statement, regex) \ + GTEST_UNSUPPORTED_DEATH_TEST(statement, regex, ) +#define ASSERT_DEATH_IF_SUPPORTED(statement, regex) \ + GTEST_UNSUPPORTED_DEATH_TEST(statement, regex, return) +#endif + +} // namespace testing + +#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_DEATH_TEST_H_ diff --git a/googletest/include/gtest/gtest-matchers.h b/googletest/include/gtest/gtest-matchers.h new file mode 100644 index 00000000..78160f0e --- /dev/null +++ b/googletest/include/gtest/gtest-matchers.h @@ -0,0 +1,923 @@ +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// The Google C++ Testing and Mocking Framework (Google Test) +// +// This file implements just enough of the matcher interface to allow +// EXPECT_DEATH and friends to accept a matcher argument. + +// IWYU pragma: private, include "gtest/gtest.h" +// IWYU pragma: friend gtest/.* +// IWYU pragma: friend gmock/.* + +#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_MATCHERS_H_ +#define GOOGLETEST_INCLUDE_GTEST_GTEST_MATCHERS_H_ + +#include +#include +#include +#include +#include +#include + +#include "gtest/gtest-printers.h" +#include "gtest/internal/gtest-internal.h" +#include "gtest/internal/gtest-port.h" + +// MSVC warning C5046 is new as of VS2017 version 15.8. +#if defined(_MSC_VER) && _MSC_VER >= 1915 +#define GTEST_MAYBE_5046_ 5046 +#else +#define GTEST_MAYBE_5046_ +#endif + +GTEST_DISABLE_MSC_WARNINGS_PUSH_( + 4251 GTEST_MAYBE_5046_ /* class A needs to have dll-interface to be used by + clients of class B */ + /* Symbol involving type with internal linkage not defined */) + +namespace testing { + +// To implement a matcher Foo for type T, define: +// 1. a class FooMatcherMatcher that implements the matcher interface: +// using is_gtest_matcher = void; +// bool MatchAndExplain(const T&, std::ostream*) const; +// (MatchResultListener* can also be used instead of std::ostream*) +// void DescribeTo(std::ostream*) const; +// void DescribeNegationTo(std::ostream*) const; +// +// 2. a factory function that creates a Matcher object from a +// FooMatcherMatcher. + +class MatchResultListener { + public: + // Creates a listener object with the given underlying ostream. The + // listener does not own the ostream, and does not dereference it + // in the constructor or destructor. + explicit MatchResultListener(::std::ostream* os) : stream_(os) {} + virtual ~MatchResultListener() = 0; // Makes this class abstract. + + // Streams x to the underlying ostream; does nothing if the ostream + // is NULL. + template + MatchResultListener& operator<<(const T& x) { + if (stream_ != nullptr) *stream_ << x; + return *this; + } + + // Returns the underlying ostream. + ::std::ostream* stream() { return stream_; } + + // Returns true if and only if the listener is interested in an explanation + // of the match result. A matcher's MatchAndExplain() method can use + // this information to avoid generating the explanation when no one + // intends to hear it. + bool IsInterested() const { return stream_ != nullptr; } + + private: + ::std::ostream* const stream_; + + MatchResultListener(const MatchResultListener&) = delete; + MatchResultListener& operator=(const MatchResultListener&) = delete; +}; + +inline MatchResultListener::~MatchResultListener() = default; + +// An instance of a subclass of this knows how to describe itself as a +// matcher. +class GTEST_API_ MatcherDescriberInterface { + public: + virtual ~MatcherDescriberInterface() = default; + + // Describes this matcher to an ostream. The function should print + // a verb phrase that describes the property a value matching this + // matcher should have. The subject of the verb phrase is the value + // being matched. For example, the DescribeTo() method of the Gt(7) + // matcher prints "is greater than 7". + virtual void DescribeTo(::std::ostream* os) const = 0; + + // Describes the negation of this matcher to an ostream. For + // example, if the description of this matcher is "is greater than + // 7", the negated description could be "is not greater than 7". + // You are not required to override this when implementing + // MatcherInterface, but it is highly advised so that your matcher + // can produce good error messages. + virtual void DescribeNegationTo(::std::ostream* os) const { + *os << "not ("; + DescribeTo(os); + *os << ")"; + } +}; + +// The implementation of a matcher. +template +class MatcherInterface : public MatcherDescriberInterface { + public: + // Returns true if and only if the matcher matches x; also explains the + // match result to 'listener' if necessary (see the next paragraph), in + // the form of a non-restrictive relative clause ("which ...", + // "whose ...", etc) that describes x. For example, the + // MatchAndExplain() method of the Pointee(...) matcher should + // generate an explanation like "which points to ...". + // + // Implementations of MatchAndExplain() should add an explanation of + // the match result *if and only if* they can provide additional + // information that's not already present (or not obvious) in the + // print-out of x and the matcher's description. Whether the match + // succeeds is not a factor in deciding whether an explanation is + // needed, as sometimes the caller needs to print a failure message + // when the match succeeds (e.g. when the matcher is used inside + // Not()). + // + // For example, a "has at least 10 elements" matcher should explain + // what the actual element count is, regardless of the match result, + // as it is useful information to the reader; on the other hand, an + // "is empty" matcher probably only needs to explain what the actual + // size is when the match fails, as it's redundant to say that the + // size is 0 when the value is already known to be empty. + // + // You should override this method when defining a new matcher. + // + // It's the responsibility of the caller (Google Test) to guarantee + // that 'listener' is not NULL. This helps to simplify a matcher's + // implementation when it doesn't care about the performance, as it + // can talk to 'listener' without checking its validity first. + // However, in order to implement dummy listeners efficiently, + // listener->stream() may be NULL. + virtual bool MatchAndExplain(T x, MatchResultListener* listener) const = 0; + + // Inherits these methods from MatcherDescriberInterface: + // virtual void DescribeTo(::std::ostream* os) const = 0; + // virtual void DescribeNegationTo(::std::ostream* os) const; +}; + +namespace internal { + +// A match result listener that ignores the explanation. +class DummyMatchResultListener : public MatchResultListener { + public: + DummyMatchResultListener() : MatchResultListener(nullptr) {} + + private: + DummyMatchResultListener(const DummyMatchResultListener&) = delete; + DummyMatchResultListener& operator=(const DummyMatchResultListener&) = delete; +}; + +// A match result listener that forwards the explanation to a given +// ostream. The difference between this and MatchResultListener is +// that the former is concrete. +class StreamMatchResultListener : public MatchResultListener { + public: + explicit StreamMatchResultListener(::std::ostream* os) + : MatchResultListener(os) {} + + private: + StreamMatchResultListener(const StreamMatchResultListener&) = delete; + StreamMatchResultListener& operator=(const StreamMatchResultListener&) = + delete; +}; + +struct SharedPayloadBase { + std::atomic ref{1}; + void Ref() { ref.fetch_add(1, std::memory_order_relaxed); } + bool Unref() { return ref.fetch_sub(1, std::memory_order_acq_rel) == 1; } +}; + +template +struct SharedPayload : SharedPayloadBase { + explicit SharedPayload(const T& v) : value(v) {} + explicit SharedPayload(T&& v) : value(std::move(v)) {} + + static void Destroy(SharedPayloadBase* shared) { + delete static_cast(shared); + } + + T value; +}; + +// An internal class for implementing Matcher, which will derive +// from it. We put functionalities common to all Matcher +// specializations here to avoid code duplication. +template +class MatcherBase : private MatcherDescriberInterface { + public: + // Returns true if and only if the matcher matches x; also explains the + // match result to 'listener'. + bool MatchAndExplain(const T& x, MatchResultListener* listener) const { + GTEST_CHECK_(vtable_ != nullptr); + return vtable_->match_and_explain(*this, x, listener); + } + + // Returns true if and only if this matcher matches x. + bool Matches(const T& x) const { + DummyMatchResultListener dummy; + return MatchAndExplain(x, &dummy); + } + + // Describes this matcher to an ostream. + void DescribeTo(::std::ostream* os) const final { + GTEST_CHECK_(vtable_ != nullptr); + vtable_->describe(*this, os, false); + } + + // Describes the negation of this matcher to an ostream. + void DescribeNegationTo(::std::ostream* os) const final { + GTEST_CHECK_(vtable_ != nullptr); + vtable_->describe(*this, os, true); + } + + // Explains why x matches, or doesn't match, the matcher. + void ExplainMatchResultTo(const T& x, ::std::ostream* os) const { + StreamMatchResultListener listener(os); + MatchAndExplain(x, &listener); + } + + // Returns the describer for this matcher object; retains ownership + // of the describer, which is only guaranteed to be alive when + // this matcher object is alive. + const MatcherDescriberInterface* GetDescriber() const { + if (vtable_ == nullptr) return nullptr; + return vtable_->get_describer(*this); + } + + protected: + MatcherBase() : vtable_(nullptr), buffer_() {} + + // Constructs a matcher from its implementation. + template + explicit MatcherBase(const MatcherInterface* impl) + : vtable_(nullptr), buffer_() { + Init(impl); + } + + template ::type::is_gtest_matcher> + MatcherBase(M&& m) : vtable_(nullptr), buffer_() { // NOLINT + Init(std::forward(m)); + } + + MatcherBase(const MatcherBase& other) + : vtable_(other.vtable_), buffer_(other.buffer_) { + if (IsShared()) buffer_.shared->Ref(); + } + + MatcherBase& operator=(const MatcherBase& other) { + if (this == &other) return *this; + Destroy(); + vtable_ = other.vtable_; + buffer_ = other.buffer_; + if (IsShared()) buffer_.shared->Ref(); + return *this; + } + + MatcherBase(MatcherBase&& other) + : vtable_(other.vtable_), buffer_(other.buffer_) { + other.vtable_ = nullptr; + } + + MatcherBase& operator=(MatcherBase&& other) { + if (this == &other) return *this; + Destroy(); + vtable_ = other.vtable_; + buffer_ = other.buffer_; + other.vtable_ = nullptr; + return *this; + } + + ~MatcherBase() override { Destroy(); } + + private: + struct VTable { + bool (*match_and_explain)(const MatcherBase&, const T&, + MatchResultListener*); + void (*describe)(const MatcherBase&, std::ostream*, bool negation); + // Returns the captured object if it implements the interface, otherwise + // returns the MatcherBase itself. + const MatcherDescriberInterface* (*get_describer)(const MatcherBase&); + // Called on shared instances when the reference count reaches 0. + void (*shared_destroy)(SharedPayloadBase*); + }; + + bool IsShared() const { + return vtable_ != nullptr && vtable_->shared_destroy != nullptr; + } + + // If the implementation uses a listener, call that. + template + static auto MatchAndExplainImpl(const MatcherBase& m, const T& value, + MatchResultListener* listener) + -> decltype(P::Get(m).MatchAndExplain(value, listener->stream())) { + return P::Get(m).MatchAndExplain(value, listener->stream()); + } + + template + static auto MatchAndExplainImpl(const MatcherBase& m, const T& value, + MatchResultListener* listener) + -> decltype(P::Get(m).MatchAndExplain(value, listener)) { + return P::Get(m).MatchAndExplain(value, listener); + } + + template + static void DescribeImpl(const MatcherBase& m, std::ostream* os, + bool negation) { + if (negation) { + P::Get(m).DescribeNegationTo(os); + } else { + P::Get(m).DescribeTo(os); + } + } + + template + static const MatcherDescriberInterface* GetDescriberImpl( + const MatcherBase& m) { + // If the impl is a MatcherDescriberInterface, then return it. + // Otherwise use MatcherBase itself. + // This allows us to implement the GetDescriber() function without support + // from the impl, but some users really want to get their impl back when + // they call GetDescriber(). + // We use std::get on a tuple as a workaround of not having `if constexpr`. + return std::get<( + std::is_convertible::value + ? 1 + : 0)>(std::make_tuple(&m, &P::Get(m))); + } + + template + const VTable* GetVTable() { + static constexpr VTable kVTable = {&MatchAndExplainImpl

, + &DescribeImpl

, &GetDescriberImpl

, + P::shared_destroy}; + return &kVTable; + } + + union Buffer { + // Add some types to give Buffer some common alignment/size use cases. + void* ptr; + double d; + int64_t i; + // And add one for the out-of-line cases. + SharedPayloadBase* shared; + }; + + void Destroy() { + if (IsShared() && buffer_.shared->Unref()) { + vtable_->shared_destroy(buffer_.shared); + } + } + + template + static constexpr bool IsInlined() { + return sizeof(M) <= sizeof(Buffer) && alignof(M) <= alignof(Buffer) && + std::is_trivially_copy_constructible::value && + std::is_trivially_destructible::value; + } + + template ()> + struct ValuePolicy { + static const M& Get(const MatcherBase& m) { + // When inlined along with Init, need to be explicit to avoid violating + // strict aliasing rules. + const M* ptr = + static_cast(static_cast(&m.buffer_)); + return *ptr; + } + static void Init(MatcherBase& m, M impl) { + ::new (static_cast(&m.buffer_)) M(impl); + } + static constexpr auto shared_destroy = nullptr; + }; + + template + struct ValuePolicy { + using Shared = SharedPayload; + static const M& Get(const MatcherBase& m) { + return static_cast(m.buffer_.shared)->value; + } + template + static void Init(MatcherBase& m, Arg&& arg) { + m.buffer_.shared = new Shared(std::forward(arg)); + } + static constexpr auto shared_destroy = &Shared::Destroy; + }; + + template + struct ValuePolicy*, B> { + using M = const MatcherInterface; + using Shared = SharedPayload>; + static const M& Get(const MatcherBase& m) { + return *static_cast(m.buffer_.shared)->value; + } + static void Init(MatcherBase& m, M* impl) { + m.buffer_.shared = new Shared(std::unique_ptr(impl)); + } + + static constexpr auto shared_destroy = &Shared::Destroy; + }; + + template + void Init(M&& m) { + using MM = typename std::decay::type; + using Policy = ValuePolicy; + vtable_ = GetVTable(); + Policy::Init(*this, std::forward(m)); + } + + const VTable* vtable_; + Buffer buffer_; +}; + +} // namespace internal + +// A Matcher is a copyable and IMMUTABLE (except by assignment) +// object that can check whether a value of type T matches. The +// implementation of Matcher is just a std::shared_ptr to const +// MatcherInterface. Don't inherit from Matcher! +template +class Matcher : public internal::MatcherBase { + public: + // Constructs a null matcher. Needed for storing Matcher objects in STL + // containers. A default-constructed matcher is not yet initialized. You + // cannot use it until a valid value has been assigned to it. + explicit Matcher() {} // NOLINT + + // Constructs a matcher from its implementation. + explicit Matcher(const MatcherInterface* impl) + : internal::MatcherBase(impl) {} + + template + explicit Matcher( + const MatcherInterface* impl, + typename std::enable_if::value>::type* = + nullptr) + : internal::MatcherBase(impl) {} + + template ::type::is_gtest_matcher> + Matcher(M&& m) : internal::MatcherBase(std::forward(m)) {} // NOLINT + + // Implicit constructor here allows people to write + // EXPECT_CALL(foo, Bar(5)) instead of EXPECT_CALL(foo, Bar(Eq(5))) sometimes + Matcher(T value); // NOLINT +}; + +// The following two specializations allow the user to write str +// instead of Eq(str) and "foo" instead of Eq("foo") when a std::string +// matcher is expected. +template <> +class GTEST_API_ Matcher + : public internal::MatcherBase { + public: + Matcher() = default; + + explicit Matcher(const MatcherInterface* impl) + : internal::MatcherBase(impl) {} + + template ::type::is_gtest_matcher> + Matcher(M&& m) // NOLINT + : internal::MatcherBase(std::forward(m)) {} + + // Allows the user to write str instead of Eq(str) sometimes, where + // str is a std::string object. + Matcher(const std::string& s); // NOLINT + + // Allows the user to write "foo" instead of Eq("foo") sometimes. + Matcher(const char* s); // NOLINT +}; + +template <> +class GTEST_API_ Matcher + : public internal::MatcherBase { + public: + Matcher() = default; + + explicit Matcher(const MatcherInterface* impl) + : internal::MatcherBase(impl) {} + explicit Matcher(const MatcherInterface* impl) + : internal::MatcherBase(impl) {} + + template ::type::is_gtest_matcher> + Matcher(M&& m) // NOLINT + : internal::MatcherBase(std::forward(m)) {} + + // Allows the user to write str instead of Eq(str) sometimes, where + // str is a string object. + Matcher(const std::string& s); // NOLINT + + // Allows the user to write "foo" instead of Eq("foo") sometimes. + Matcher(const char* s); // NOLINT +}; + +#if GTEST_INTERNAL_HAS_STRING_VIEW +// The following two specializations allow the user to write str +// instead of Eq(str) and "foo" instead of Eq("foo") when a absl::string_view +// matcher is expected. +template <> +class GTEST_API_ Matcher + : public internal::MatcherBase { + public: + Matcher() = default; + + explicit Matcher(const MatcherInterface* impl) + : internal::MatcherBase(impl) {} + + template ::type::is_gtest_matcher> + Matcher(M&& m) // NOLINT + : internal::MatcherBase(std::forward(m)) { + } + + // Allows the user to write str instead of Eq(str) sometimes, where + // str is a std::string object. + Matcher(const std::string& s); // NOLINT + + // Allows the user to write "foo" instead of Eq("foo") sometimes. + Matcher(const char* s); // NOLINT + + // Allows the user to pass absl::string_views or std::string_views directly. + Matcher(internal::StringView s); // NOLINT +}; + +template <> +class GTEST_API_ Matcher + : public internal::MatcherBase { + public: + Matcher() = default; + + explicit Matcher(const MatcherInterface* impl) + : internal::MatcherBase(impl) {} + explicit Matcher(const MatcherInterface* impl) + : internal::MatcherBase(impl) {} + + template ::type::is_gtest_matcher> + Matcher(M&& m) // NOLINT + : internal::MatcherBase(std::forward(m)) {} + + // Allows the user to write str instead of Eq(str) sometimes, where + // str is a std::string object. + Matcher(const std::string& s); // NOLINT + + // Allows the user to write "foo" instead of Eq("foo") sometimes. + Matcher(const char* s); // NOLINT + + // Allows the user to pass absl::string_views or std::string_views directly. + Matcher(internal::StringView s); // NOLINT +}; +#endif // GTEST_INTERNAL_HAS_STRING_VIEW + +// Prints a matcher in a human-readable format. +template +std::ostream& operator<<(std::ostream& os, const Matcher& matcher) { + matcher.DescribeTo(&os); + return os; +} + +// The PolymorphicMatcher class template makes it easy to implement a +// polymorphic matcher (i.e. a matcher that can match values of more +// than one type, e.g. Eq(n) and NotNull()). +// +// To define a polymorphic matcher, a user should provide an Impl +// class that has a DescribeTo() method and a DescribeNegationTo() +// method, and define a member function (or member function template) +// +// bool MatchAndExplain(const Value& value, +// MatchResultListener* listener) const; +// +// See the definition of NotNull() for a complete example. +template +class PolymorphicMatcher { + public: + explicit PolymorphicMatcher(const Impl& an_impl) : impl_(an_impl) {} + + // Returns a mutable reference to the underlying matcher + // implementation object. + Impl& mutable_impl() { return impl_; } + + // Returns an immutable reference to the underlying matcher + // implementation object. + const Impl& impl() const { return impl_; } + + template + operator Matcher() const { + return Matcher(new MonomorphicImpl(impl_)); + } + + private: + template + class MonomorphicImpl : public MatcherInterface { + public: + explicit MonomorphicImpl(const Impl& impl) : impl_(impl) {} + + void DescribeTo(::std::ostream* os) const override { impl_.DescribeTo(os); } + + void DescribeNegationTo(::std::ostream* os) const override { + impl_.DescribeNegationTo(os); + } + + bool MatchAndExplain(T x, MatchResultListener* listener) const override { + return impl_.MatchAndExplain(x, listener); + } + + private: + const Impl impl_; + }; + + Impl impl_; +}; + +// Creates a matcher from its implementation. +// DEPRECATED: Especially in the generic code, prefer: +// Matcher(new MyMatcherImpl(...)); +// +// MakeMatcher may create a Matcher that accepts its argument by value, which +// leads to unnecessary copies & lack of support for non-copyable types. +template +inline Matcher MakeMatcher(const MatcherInterface* impl) { + return Matcher(impl); +} + +// Creates a polymorphic matcher from its implementation. This is +// easier to use than the PolymorphicMatcher constructor as it +// doesn't require you to explicitly write the template argument, e.g. +// +// MakePolymorphicMatcher(foo); +// vs +// PolymorphicMatcher(foo); +template +inline PolymorphicMatcher MakePolymorphicMatcher(const Impl& impl) { + return PolymorphicMatcher(impl); +} + +namespace internal { +// Implements a matcher that compares a given value with a +// pre-supplied value using one of the ==, <=, <, etc, operators. The +// two values being compared don't have to have the same type. +// +// The matcher defined here is polymorphic (for example, Eq(5) can be +// used to match an int, a short, a double, etc). Therefore we use +// a template type conversion operator in the implementation. +// +// The following template definition assumes that the Rhs parameter is +// a "bare" type (i.e. neither 'const T' nor 'T&'). +template +class ComparisonBase { + public: + explicit ComparisonBase(const Rhs& rhs) : rhs_(rhs) {} + + using is_gtest_matcher = void; + + template + bool MatchAndExplain(const Lhs& lhs, std::ostream*) const { + return Op()(lhs, Unwrap(rhs_)); + } + void DescribeTo(std::ostream* os) const { + *os << D::Desc() << " "; + UniversalPrint(Unwrap(rhs_), os); + } + void DescribeNegationTo(std::ostream* os) const { + *os << D::NegatedDesc() << " "; + UniversalPrint(Unwrap(rhs_), os); + } + + private: + template + static const T& Unwrap(const T& v) { + return v; + } + template + static const T& Unwrap(std::reference_wrapper v) { + return v; + } + + Rhs rhs_; +}; + +template +class EqMatcher : public ComparisonBase, Rhs, std::equal_to<>> { + public: + explicit EqMatcher(const Rhs& rhs) + : ComparisonBase, Rhs, std::equal_to<>>(rhs) {} + static const char* Desc() { return "is equal to"; } + static const char* NegatedDesc() { return "isn't equal to"; } +}; +template +class NeMatcher + : public ComparisonBase, Rhs, std::not_equal_to<>> { + public: + explicit NeMatcher(const Rhs& rhs) + : ComparisonBase, Rhs, std::not_equal_to<>>(rhs) {} + static const char* Desc() { return "isn't equal to"; } + static const char* NegatedDesc() { return "is equal to"; } +}; +template +class LtMatcher : public ComparisonBase, Rhs, std::less<>> { + public: + explicit LtMatcher(const Rhs& rhs) + : ComparisonBase, Rhs, std::less<>>(rhs) {} + static const char* Desc() { return "is <"; } + static const char* NegatedDesc() { return "isn't <"; } +}; +template +class GtMatcher : public ComparisonBase, Rhs, std::greater<>> { + public: + explicit GtMatcher(const Rhs& rhs) + : ComparisonBase, Rhs, std::greater<>>(rhs) {} + static const char* Desc() { return "is >"; } + static const char* NegatedDesc() { return "isn't >"; } +}; +template +class LeMatcher + : public ComparisonBase, Rhs, std::less_equal<>> { + public: + explicit LeMatcher(const Rhs& rhs) + : ComparisonBase, Rhs, std::less_equal<>>(rhs) {} + static const char* Desc() { return "is <="; } + static const char* NegatedDesc() { return "isn't <="; } +}; +template +class GeMatcher + : public ComparisonBase, Rhs, std::greater_equal<>> { + public: + explicit GeMatcher(const Rhs& rhs) + : ComparisonBase, Rhs, std::greater_equal<>>(rhs) {} + static const char* Desc() { return "is >="; } + static const char* NegatedDesc() { return "isn't >="; } +}; + +template ::value>::type> +using StringLike = T; + +// Implements polymorphic matchers MatchesRegex(regex) and +// ContainsRegex(regex), which can be used as a Matcher as long as +// T can be converted to a string. +class MatchesRegexMatcher { + public: + MatchesRegexMatcher(const RE* regex, bool full_match) + : regex_(regex), full_match_(full_match) {} + +#if GTEST_INTERNAL_HAS_STRING_VIEW + bool MatchAndExplain(const internal::StringView& s, + MatchResultListener* listener) const { + return MatchAndExplain(std::string(s), listener); + } +#endif // GTEST_INTERNAL_HAS_STRING_VIEW + + // Accepts pointer types, particularly: + // const char* + // char* + // const wchar_t* + // wchar_t* + template + bool MatchAndExplain(CharType* s, MatchResultListener* listener) const { + return s != nullptr && MatchAndExplain(std::string(s), listener); + } + + // Matches anything that can convert to std::string. + // + // This is a template, not just a plain function with const std::string&, + // because absl::string_view has some interfering non-explicit constructors. + template + bool MatchAndExplain(const MatcheeStringType& s, + MatchResultListener* /* listener */) const { + const std::string s2(s); + return full_match_ ? RE::FullMatch(s2, *regex_) + : RE::PartialMatch(s2, *regex_); + } + + void DescribeTo(::std::ostream* os) const { + *os << (full_match_ ? "matches" : "contains") << " regular expression "; + UniversalPrinter::Print(regex_->pattern(), os); + } + + void DescribeNegationTo(::std::ostream* os) const { + *os << "doesn't " << (full_match_ ? "match" : "contain") + << " regular expression "; + UniversalPrinter::Print(regex_->pattern(), os); + } + + private: + const std::shared_ptr regex_; + const bool full_match_; +}; +} // namespace internal + +// Matches a string that fully matches regular expression 'regex'. +// The matcher takes ownership of 'regex'. +inline PolymorphicMatcher MatchesRegex( + const internal::RE* regex) { + return MakePolymorphicMatcher(internal::MatchesRegexMatcher(regex, true)); +} +template +PolymorphicMatcher MatchesRegex( + const internal::StringLike& regex) { + return MatchesRegex(new internal::RE(std::string(regex))); +} + +// Matches a string that contains regular expression 'regex'. +// The matcher takes ownership of 'regex'. +inline PolymorphicMatcher ContainsRegex( + const internal::RE* regex) { + return MakePolymorphicMatcher(internal::MatchesRegexMatcher(regex, false)); +} +template +PolymorphicMatcher ContainsRegex( + const internal::StringLike& regex) { + return ContainsRegex(new internal::RE(std::string(regex))); +} + +// Creates a polymorphic matcher that matches anything equal to x. +// Note: if the parameter of Eq() were declared as const T&, Eq("foo") +// wouldn't compile. +template +inline internal::EqMatcher Eq(T x) { + return internal::EqMatcher(x); +} + +// Constructs a Matcher from a 'value' of type T. The constructed +// matcher matches any value that's equal to 'value'. +template +Matcher::Matcher(T value) { + *this = Eq(value); +} + +// Creates a monomorphic matcher that matches anything with type Lhs +// and equal to rhs. A user may need to use this instead of Eq(...) +// in order to resolve an overloading ambiguity. +// +// TypedEq(x) is just a convenient short-hand for Matcher(Eq(x)) +// or Matcher(x), but more readable than the latter. +// +// We could define similar monomorphic matchers for other comparison +// operations (e.g. TypedLt, TypedGe, and etc), but decided not to do +// it yet as those are used much less than Eq() in practice. A user +// can always write Matcher(Lt(5)) to be explicit about the type, +// for example. +template +inline Matcher TypedEq(const Rhs& rhs) { + return Eq(rhs); +} + +// Creates a polymorphic matcher that matches anything >= x. +template +inline internal::GeMatcher Ge(Rhs x) { + return internal::GeMatcher(x); +} + +// Creates a polymorphic matcher that matches anything > x. +template +inline internal::GtMatcher Gt(Rhs x) { + return internal::GtMatcher(x); +} + +// Creates a polymorphic matcher that matches anything <= x. +template +inline internal::LeMatcher Le(Rhs x) { + return internal::LeMatcher(x); +} + +// Creates a polymorphic matcher that matches anything < x. +template +inline internal::LtMatcher Lt(Rhs x) { + return internal::LtMatcher(x); +} + +// Creates a polymorphic matcher that matches anything != x. +template +inline internal::NeMatcher Ne(Rhs x) { + return internal::NeMatcher(x); +} +} // namespace testing + +GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 5046 + +#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_MATCHERS_H_ diff --git a/googletest/include/gtest/gtest-message.h b/googletest/include/gtest/gtest-message.h new file mode 100644 index 00000000..448ac6b7 --- /dev/null +++ b/googletest/include/gtest/gtest-message.h @@ -0,0 +1,251 @@ +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// The Google C++ Testing and Mocking Framework (Google Test) +// +// This header file defines the Message class. +// +// IMPORTANT NOTE: Due to limitation of the C++ language, we have to +// leave some internal implementation details in this header file. +// They are clearly marked by comments like this: +// +// // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +// +// Such code is NOT meant to be used by a user directly, and is subject +// to CHANGE WITHOUT NOTICE. Therefore DO NOT DEPEND ON IT in a user +// program! + +// IWYU pragma: private, include "gtest/gtest.h" +// IWYU pragma: friend gtest/.* +// IWYU pragma: friend gmock/.* + +#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_MESSAGE_H_ +#define GOOGLETEST_INCLUDE_GTEST_GTEST_MESSAGE_H_ + +#include +#include +#include +#include +#include + +#include "gtest/internal/gtest-port.h" + +#ifdef GTEST_HAS_ABSL +#include + +#include "absl/strings/has_absl_stringify.h" +#include "absl/strings/str_cat.h" +#endif // GTEST_HAS_ABSL + +GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ +/* class A needs to have dll-interface to be used by clients of class B */) + +// Ensures that there is at least one operator<< in the global namespace. +// See Message& operator<<(...) below for why. +void operator<<(const testing::internal::Secret&, int); + +namespace testing { + +// The Message class works like an ostream repeater. +// +// Typical usage: +// +// 1. You stream a bunch of values to a Message object. +// It will remember the text in a stringstream. +// 2. Then you stream the Message object to an ostream. +// This causes the text in the Message to be streamed +// to the ostream. +// +// For example; +// +// testing::Message foo; +// foo << 1 << " != " << 2; +// std::cout << foo; +// +// will print "1 != 2". +// +// Message is not intended to be inherited from. In particular, its +// destructor is not virtual. +// +// Note that stringstream behaves differently in gcc and in MSVC. You +// can stream a NULL char pointer to it in the former, but not in the +// latter (it causes an access violation if you do). The Message +// class hides this difference by treating a NULL char pointer as +// "(null)". +class GTEST_API_ Message { + private: + // The type of basic IO manipulators (endl, ends, and flush) for + // narrow streams. + typedef std::ostream& (*BasicNarrowIoManip)(std::ostream&); + + public: + // Constructs an empty Message. + Message(); + + // Copy constructor. + Message(const Message& msg) : ss_(new ::std::stringstream) { // NOLINT + *ss_ << msg.GetString(); + } + + // Constructs a Message from a C-string. + explicit Message(const char* str) : ss_(new ::std::stringstream) { + *ss_ << str; + } + + // Streams a non-pointer value to this object. If building a version of + // GoogleTest with ABSL, this overload is only enabled if the value does not + // have an AbslStringify definition. + template < + typename T +#ifdef GTEST_HAS_ABSL + , + typename std::enable_if::value, // NOLINT + int>::type = 0 +#endif // GTEST_HAS_ABSL + > + inline Message& operator<<(const T& val) { + // Some libraries overload << for STL containers. These + // overloads are defined in the global namespace instead of ::std. + // + // C++'s symbol lookup rule (i.e. Koenig lookup) says that these + // overloads are visible in either the std namespace or the global + // namespace, but not other namespaces, including the testing + // namespace which Google Test's Message class is in. + // + // To allow STL containers (and other types that has a << operator + // defined in the global namespace) to be used in Google Test + // assertions, testing::Message must access the custom << operator + // from the global namespace. With this using declaration, + // overloads of << defined in the global namespace and those + // visible via Koenig lookup are both exposed in this function. + using ::operator<<; + *ss_ << val; + return *this; + } + +#ifdef GTEST_HAS_ABSL + // Streams a non-pointer value with an AbslStringify definition to this + // object. + template ::value, // NOLINT + int>::type = 0> + inline Message& operator<<(const T& val) { + // ::operator<< is needed here for a similar reason as with the non-Abseil + // version above + using ::operator<<; + *ss_ << absl::StrCat(val); + return *this; + } +#endif // GTEST_HAS_ABSL + + // Streams a pointer value to this object. + // + // This function is an overload of the previous one. When you + // stream a pointer to a Message, this definition will be used as it + // is more specialized. (The C++ Standard, section + // [temp.func.order].) If you stream a non-pointer, then the + // previous definition will be used. + // + // The reason for this overload is that streaming a NULL pointer to + // ostream is undefined behavior. Depending on the compiler, you + // may get "0", "(nil)", "(null)", or an access violation. To + // ensure consistent result across compilers, we always treat NULL + // as "(null)". + template + inline Message& operator<<(T* const& pointer) { // NOLINT + if (pointer == nullptr) { + *ss_ << "(null)"; + } else { + *ss_ << pointer; + } + return *this; + } + + // Since the basic IO manipulators are overloaded for both narrow + // and wide streams, we have to provide this specialized definition + // of operator <<, even though its body is the same as the + // templatized version above. Without this definition, streaming + // endl or other basic IO manipulators to Message will confuse the + // compiler. + Message& operator<<(BasicNarrowIoManip val) { + *ss_ << val; + return *this; + } + + // Instead of 1/0, we want to see true/false for bool values. + Message& operator<<(bool b) { return *this << (b ? "true" : "false"); } + + // These two overloads allow streaming a wide C string to a Message + // using the UTF-8 encoding. + Message& operator<<(const wchar_t* wide_c_str); + Message& operator<<(wchar_t* wide_c_str); + +#if GTEST_HAS_STD_WSTRING + // Converts the given wide string to a narrow string using the UTF-8 + // encoding, and streams the result to this Message object. + Message& operator<<(const ::std::wstring& wstr); +#endif // GTEST_HAS_STD_WSTRING + + // Gets the text streamed to this object so far as an std::string. + // Each '\0' character in the buffer is replaced with "\\0". + // + // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. + std::string GetString() const; + + private: + // We'll hold the text streamed to this object here. + const std::unique_ptr< ::std::stringstream> ss_; + + // We declare (but don't implement) this to prevent the compiler + // from implementing the assignment operator. + void operator=(const Message&); +}; + +// Streams a Message to an ostream. +inline std::ostream& operator<<(std::ostream& os, const Message& sb) { + return os << sb.GetString(); +} + +namespace internal { + +// Converts a streamable value to an std::string. A NULL pointer is +// converted to "(null)". When the input value is a ::string, +// ::std::string, ::wstring, or ::std::wstring object, each NUL +// character in it is replaced with "\\0". +template +std::string StreamableToString(const T& streamable) { + return (Message() << streamable).GetString(); +} + +} // namespace internal +} // namespace testing + +GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 + +#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_MESSAGE_H_ diff --git a/googletest/include/gtest/gtest-param-test.h b/googletest/include/gtest/gtest-param-test.h new file mode 100644 index 00000000..9e023f96 --- /dev/null +++ b/googletest/include/gtest/gtest-param-test.h @@ -0,0 +1,602 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Macros and functions for implementing parameterized tests +// in Google C++ Testing and Mocking Framework (Google Test) + +// IWYU pragma: private, include "gtest/gtest.h" +// IWYU pragma: friend gtest/.* +// IWYU pragma: friend gmock/.* + +#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_ +#define GOOGLETEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_ + +// Value-parameterized tests allow you to test your code with different +// parameters without writing multiple copies of the same test. +// +// Here is how you use value-parameterized tests: + +#if 0 + +// To write value-parameterized tests, first you should define a fixture +// class. It is usually derived from testing::TestWithParam (see below for +// another inheritance scheme that's sometimes useful in more complicated +// class hierarchies), where the type of your parameter values. +// TestWithParam is itself derived from testing::Test. T can be any +// copyable type. If it's a raw pointer, you are responsible for managing the +// lifespan of the pointed values. + +class FooTest : public ::testing::TestWithParam { + // You can implement all the usual class fixture members here. +}; + +// Then, use the TEST_P macro to define as many parameterized tests +// for this fixture as you want. The _P suffix is for "parameterized" +// or "pattern", whichever you prefer to think. + +TEST_P(FooTest, DoesBlah) { + // Inside a test, access the test parameter with the GetParam() method + // of the TestWithParam class: + EXPECT_TRUE(foo.Blah(GetParam())); + ... +} + +TEST_P(FooTest, HasBlahBlah) { + ... +} + +// Finally, you can use INSTANTIATE_TEST_SUITE_P to instantiate the test +// case with any set of parameters you want. Google Test defines a number +// of functions for generating test parameters. They return what we call +// (surprise!) parameter generators. Here is a summary of them, which +// are all in the testing namespace: +// +// +// Range(begin, end [, step]) - Yields values {begin, begin+step, +// begin+step+step, ...}. The values do not +// include end. step defaults to 1. +// Values(v1, v2, ..., vN) - Yields values {v1, v2, ..., vN}. +// ValuesIn(container) - Yields values from a C-style array, an STL +// ValuesIn(begin,end) container, or an iterator range [begin, end). +// Bool() - Yields sequence {false, true}. +// Combine(g1, g2, ..., gN) - Yields all combinations (the Cartesian product +// for the math savvy) of the values generated +// by the N generators. +// +// For more details, see comments at the definitions of these functions below +// in this file. +// +// The following statement will instantiate tests from the FooTest test suite +// each with parameter values "meeny", "miny", and "moe". + +INSTANTIATE_TEST_SUITE_P(InstantiationName, + FooTest, + Values("meeny", "miny", "moe")); + +// To distinguish different instances of the pattern, (yes, you +// can instantiate it more than once) the first argument to the +// INSTANTIATE_TEST_SUITE_P macro is a prefix that will be added to the +// actual test suite name. Remember to pick unique prefixes for different +// instantiations. The tests from the instantiation above will have +// these names: +// +// * InstantiationName/FooTest.DoesBlah/0 for "meeny" +// * InstantiationName/FooTest.DoesBlah/1 for "miny" +// * InstantiationName/FooTest.DoesBlah/2 for "moe" +// * InstantiationName/FooTest.HasBlahBlah/0 for "meeny" +// * InstantiationName/FooTest.HasBlahBlah/1 for "miny" +// * InstantiationName/FooTest.HasBlahBlah/2 for "moe" +// +// You can use these names in --gtest_filter. +// +// This statement will instantiate all tests from FooTest again, each +// with parameter values "cat" and "dog": + +const char* pets[] = {"cat", "dog"}; +INSTANTIATE_TEST_SUITE_P(AnotherInstantiationName, FooTest, ValuesIn(pets)); + +// The tests from the instantiation above will have these names: +// +// * AnotherInstantiationName/FooTest.DoesBlah/0 for "cat" +// * AnotherInstantiationName/FooTest.DoesBlah/1 for "dog" +// * AnotherInstantiationName/FooTest.HasBlahBlah/0 for "cat" +// * AnotherInstantiationName/FooTest.HasBlahBlah/1 for "dog" +// +// Please note that INSTANTIATE_TEST_SUITE_P will instantiate all tests +// in the given test suite, whether their definitions come before or +// AFTER the INSTANTIATE_TEST_SUITE_P statement. +// +// Please also note that generator expressions (including parameters to the +// generators) are evaluated in InitGoogleTest(), after main() has started. +// This allows the user on one hand, to adjust generator parameters in order +// to dynamically determine a set of tests to run and on the other hand, +// give the user a chance to inspect the generated tests with Google Test +// reflection API before RUN_ALL_TESTS() is executed. +// +// You can see samples/sample7_unittest.cc and samples/sample8_unittest.cc +// for more examples. +// +// In the future, we plan to publish the API for defining new parameter +// generators. But for now this interface remains part of the internal +// implementation and is subject to change. +// +// +// A parameterized test fixture must be derived from testing::Test and from +// testing::WithParamInterface, where T is the type of the parameter +// values. Inheriting from TestWithParam satisfies that requirement because +// TestWithParam inherits from both Test and WithParamInterface. In more +// complicated hierarchies, however, it is occasionally useful to inherit +// separately from Test and WithParamInterface. For example: + +class BaseTest : public ::testing::Test { + // You can inherit all the usual members for a non-parameterized test + // fixture here. +}; + +class DerivedTest : public BaseTest, public ::testing::WithParamInterface { + // The usual test fixture members go here too. +}; + +TEST_F(BaseTest, HasFoo) { + // This is an ordinary non-parameterized test. +} + +TEST_P(DerivedTest, DoesBlah) { + // GetParam works just the same here as if you inherit from TestWithParam. + EXPECT_TRUE(foo.Blah(GetParam())); +} + +#endif // 0 + +#include +#include +#include + +#include "gtest/internal/gtest-internal.h" +#include "gtest/internal/gtest-param-util.h" // IWYU pragma: export +#include "gtest/internal/gtest-port.h" + +namespace testing { + +// Functions producing parameter generators. +// +// Google Test uses these generators to produce parameters for value- +// parameterized tests. When a parameterized test suite is instantiated +// with a particular generator, Google Test creates and runs tests +// for each element in the sequence produced by the generator. +// +// In the following sample, tests from test suite FooTest are instantiated +// each three times with parameter values 3, 5, and 8: +// +// class FooTest : public TestWithParam { ... }; +// +// TEST_P(FooTest, TestThis) { +// } +// TEST_P(FooTest, TestThat) { +// } +// INSTANTIATE_TEST_SUITE_P(TestSequence, FooTest, Values(3, 5, 8)); +// + +// Range() returns generators providing sequences of values in a range. +// +// Synopsis: +// Range(start, end) +// - returns a generator producing a sequence of values {start, start+1, +// start+2, ..., }. +// Range(start, end, step) +// - returns a generator producing a sequence of values {start, start+step, +// start+step+step, ..., }. +// Notes: +// * The generated sequences never include end. For example, Range(1, 5) +// returns a generator producing a sequence {1, 2, 3, 4}. Range(1, 9, 2) +// returns a generator producing {1, 3, 5, 7}. +// * start and end must have the same type. That type may be any integral or +// floating-point type or a user defined type satisfying these conditions: +// * It must be assignable (have operator=() defined). +// * It must have operator+() (operator+(int-compatible type) for +// two-operand version). +// * It must have operator<() defined. +// Elements in the resulting sequences will also have that type. +// * Condition start < end must be satisfied in order for resulting sequences +// to contain any elements. +// +template +internal::ParamGenerator Range(T start, T end, IncrementT step) { + return internal::ParamGenerator( + new internal::RangeGenerator(start, end, step)); +} + +template +internal::ParamGenerator Range(T start, T end) { + return Range(start, end, 1); +} + +// ValuesIn() function allows generation of tests with parameters coming from +// a container. +// +// Synopsis: +// ValuesIn(const T (&array)[N]) +// - returns a generator producing sequences with elements from +// a C-style array. +// ValuesIn(const Container& container) +// - returns a generator producing sequences with elements from +// an STL-style container. +// ValuesIn(Iterator begin, Iterator end) +// - returns a generator producing sequences with elements from +// a range [begin, end) defined by a pair of STL-style iterators. These +// iterators can also be plain C pointers. +// +// Please note that ValuesIn copies the values from the containers +// passed in and keeps them to generate tests in RUN_ALL_TESTS(). +// +// Examples: +// +// This instantiates tests from test suite StringTest +// each with C-string values of "foo", "bar", and "baz": +// +// const char* strings[] = {"foo", "bar", "baz"}; +// INSTANTIATE_TEST_SUITE_P(StringSequence, StringTest, ValuesIn(strings)); +// +// This instantiates tests from test suite StlStringTest +// each with STL strings with values "a" and "b": +// +// ::std::vector< ::std::string> GetParameterStrings() { +// ::std::vector< ::std::string> v; +// v.push_back("a"); +// v.push_back("b"); +// return v; +// } +// +// INSTANTIATE_TEST_SUITE_P(CharSequence, +// StlStringTest, +// ValuesIn(GetParameterStrings())); +// +// +// This will also instantiate tests from CharTest +// each with parameter values 'a' and 'b': +// +// ::std::list GetParameterChars() { +// ::std::list list; +// list.push_back('a'); +// list.push_back('b'); +// return list; +// } +// ::std::list l = GetParameterChars(); +// INSTANTIATE_TEST_SUITE_P(CharSequence2, +// CharTest, +// ValuesIn(l.begin(), l.end())); +// +template +internal::ParamGenerator< + typename std::iterator_traits::value_type> +ValuesIn(ForwardIterator begin, ForwardIterator end) { + typedef typename std::iterator_traits::value_type ParamType; + return internal::ParamGenerator( + new internal::ValuesInIteratorRangeGenerator(begin, end)); +} + +template +internal::ParamGenerator ValuesIn(const T (&array)[N]) { + return ValuesIn(array, array + N); +} + +template +internal::ParamGenerator ValuesIn( + const Container& container) { + return ValuesIn(container.begin(), container.end()); +} + +// Values() allows generating tests from explicitly specified list of +// parameters. +// +// Synopsis: +// Values(T v1, T v2, ..., T vN) +// - returns a generator producing sequences with elements v1, v2, ..., vN. +// +// For example, this instantiates tests from test suite BarTest each +// with values "one", "two", and "three": +// +// INSTANTIATE_TEST_SUITE_P(NumSequence, +// BarTest, +// Values("one", "two", "three")); +// +// This instantiates tests from test suite BazTest each with values 1, 2, 3.5. +// The exact type of values will depend on the type of parameter in BazTest. +// +// INSTANTIATE_TEST_SUITE_P(FloatingNumbers, BazTest, Values(1, 2, 3.5)); +// +// +template +internal::ValueArray Values(T... v) { + return internal::ValueArray(std::move(v)...); +} + +// Bool() allows generating tests with parameters in a set of (false, true). +// +// Synopsis: +// Bool() +// - returns a generator producing sequences with elements {false, true}. +// +// It is useful when testing code that depends on Boolean flags. Combinations +// of multiple flags can be tested when several Bool()'s are combined using +// Combine() function. +// +// In the following example all tests in the test suite FlagDependentTest +// will be instantiated twice with parameters false and true. +// +// class FlagDependentTest : public testing::TestWithParam { +// virtual void SetUp() { +// external_flag = GetParam(); +// } +// } +// INSTANTIATE_TEST_SUITE_P(BoolSequence, FlagDependentTest, Bool()); +// +inline internal::ParamGenerator Bool() { return Values(false, true); } + +// Combine() allows the user to combine two or more sequences to produce +// values of a Cartesian product of those sequences' elements. +// +// Synopsis: +// Combine(gen1, gen2, ..., genN) +// - returns a generator producing sequences with elements coming from +// the Cartesian product of elements from the sequences generated by +// gen1, gen2, ..., genN. The sequence elements will have a type of +// std::tuple where T1, T2, ..., TN are the types +// of elements from sequences produces by gen1, gen2, ..., genN. +// +// Example: +// +// This will instantiate tests in test suite AnimalTest each one with +// the parameter values tuple("cat", BLACK), tuple("cat", WHITE), +// tuple("dog", BLACK), and tuple("dog", WHITE): +// +// enum Color { BLACK, GRAY, WHITE }; +// class AnimalTest +// : public testing::TestWithParam > {...}; +// +// TEST_P(AnimalTest, AnimalLooksNice) {...} +// +// INSTANTIATE_TEST_SUITE_P(AnimalVariations, AnimalTest, +// Combine(Values("cat", "dog"), +// Values(BLACK, WHITE))); +// +// This will instantiate tests in FlagDependentTest with all variations of two +// Boolean flags: +// +// class FlagDependentTest +// : public testing::TestWithParam > { +// virtual void SetUp() { +// // Assigns external_flag_1 and external_flag_2 values from the tuple. +// std::tie(external_flag_1, external_flag_2) = GetParam(); +// } +// }; +// +// TEST_P(FlagDependentTest, TestFeature1) { +// // Test your code using external_flag_1 and external_flag_2 here. +// } +// INSTANTIATE_TEST_SUITE_P(TwoBoolSequence, FlagDependentTest, +// Combine(Bool(), Bool())); +// +template +internal::CartesianProductHolder Combine(const Generator&... g) { + return internal::CartesianProductHolder(g...); +} + +// ConvertGenerator() wraps a parameter generator in order to cast each produced +// value through a known type before supplying it to the test suite +// +// Synopsis: +// ConvertGenerator(gen) +// - returns a generator producing the same elements as generated by gen, but +// each T-typed element is static_cast to a type deduced from the interface +// that accepts this generator, and then returned +// +// It is useful when using the Combine() function to get the generated +// parameters in a custom type instead of std::tuple +// +// Example: +// +// This will instantiate tests in test suite AnimalTest each one with +// the parameter values tuple("cat", BLACK), tuple("cat", WHITE), +// tuple("dog", BLACK), and tuple("dog", WHITE): +// +// enum Color { BLACK, GRAY, WHITE }; +// struct ParamType { +// using TupleT = std::tuple; +// std::string animal; +// Color color; +// ParamType(TupleT t) : animal(std::get<0>(t)), color(std::get<1>(t)) {} +// }; +// class AnimalTest +// : public testing::TestWithParam {...}; +// +// TEST_P(AnimalTest, AnimalLooksNice) {...} +// +// INSTANTIATE_TEST_SUITE_P(AnimalVariations, AnimalTest, +// ConvertGenerator( +// Combine(Values("cat", "dog"), +// Values(BLACK, WHITE)))); +// +template +internal::ParamConverterGenerator ConvertGenerator( + internal::ParamGenerator gen) { + return internal::ParamConverterGenerator(std::move(gen)); +} + +// As above, but takes a callable as a second argument. The callable converts +// the generated parameter to the test fixture's parameter type. This allows you +// to use a parameter type that does not have a converting constructor from the +// generated type. +// +// Example: +// +// This will instantiate tests in test suite AnimalTest each one with +// the parameter values tuple("cat", BLACK), tuple("cat", WHITE), +// tuple("dog", BLACK), and tuple("dog", WHITE): +// +// enum Color { BLACK, GRAY, WHITE }; +// struct ParamType { +// std::string animal; +// Color color; +// }; +// class AnimalTest +// : public testing::TestWithParam {...}; +// +// TEST_P(AnimalTest, AnimalLooksNice) {...} +// +// INSTANTIATE_TEST_SUITE_P( +// AnimalVariations, AnimalTest, +// ConvertGenerator(Combine(Values("cat", "dog"), Values(BLACK, WHITE)), +// [](std::tuple t) { +// return ParamType{.animal = std::get<0>(t), +// .color = std::get<1>(t)}; +// })); +// +template ()))> +internal::ParamConverterGenerator ConvertGenerator(Gen&& gen, + Func&& f) { + return internal::ParamConverterGenerator( + std::forward(gen), std::forward(f)); +} + +// As above, but infers the T from the supplied std::function instead of +// having the caller specify it. +template ()))> +auto ConvertGenerator(Gen&& gen, Func&& f) { + constexpr bool is_single_arg_std_function = + internal::IsSingleArgStdFunction::value; + if constexpr (is_single_arg_std_function) { + return ConvertGenerator< + typename internal::FuncSingleParamType::type>( + std::forward(gen), std::forward(f)); + } else { + static_assert(is_single_arg_std_function, + "The call signature must contain a single argument."); + } +} + +#define TEST_P(test_suite_name, test_name) \ + class GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \ + : public test_suite_name, \ + private ::testing::internal::GTestNonCopyable { \ + public: \ + GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() {} \ + void TestBody() override; \ + \ + private: \ + static int AddToRegistry() { \ + ::testing::UnitTest::GetInstance() \ + ->parameterized_test_registry() \ + .GetTestSuitePatternHolder( \ + GTEST_STRINGIFY_(test_suite_name), \ + ::testing::internal::CodeLocation(__FILE__, __LINE__)) \ + ->AddTestPattern( \ + GTEST_STRINGIFY_(test_suite_name), GTEST_STRINGIFY_(test_name), \ + new ::testing::internal::TestMetaFactory(), \ + ::testing::internal::CodeLocation(__FILE__, __LINE__)); \ + return 0; \ + } \ + [[maybe_unused]] static int gtest_registering_dummy_; \ + }; \ + int GTEST_TEST_CLASS_NAME_(test_suite_name, \ + test_name)::gtest_registering_dummy_ = \ + GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)::AddToRegistry(); \ + void GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)::TestBody() + +// The last argument to INSTANTIATE_TEST_SUITE_P allows the user to specify +// generator and an optional function or functor that generates custom test name +// suffixes based on the test parameters. Such a function or functor should +// accept one argument of type testing::TestParamInfo, and +// return std::string. +// +// testing::PrintToStringParamName is a builtin test suffix generator that +// returns the value of testing::PrintToString(GetParam()). +// +// Note: test names must be non-empty, unique, and may only contain ASCII +// alphanumeric characters or underscore. Because PrintToString adds quotes +// to std::string and C strings, it won't work for these types. + +#define GTEST_EXPAND_(arg) arg +#define GTEST_GET_FIRST_(first, ...) first +#define GTEST_GET_SECOND_(first, second, ...) second + +#define INSTANTIATE_TEST_SUITE_P(prefix, test_suite_name, ...) \ + static ::testing::internal::ParamGenerator \ + gtest_##prefix##test_suite_name##_EvalGenerator_() { \ + return GTEST_EXPAND_(GTEST_GET_FIRST_(__VA_ARGS__, DUMMY_PARAM_)); \ + } \ + static ::std::string gtest_##prefix##test_suite_name##_EvalGenerateName_( \ + const ::testing::TestParamInfo& info) { \ + if (::testing::internal::AlwaysFalse()) { \ + ::testing::internal::TestNotEmpty(GTEST_EXPAND_(GTEST_GET_SECOND_( \ + __VA_ARGS__, \ + ::testing::internal::DefaultParamName, \ + DUMMY_PARAM_))); \ + auto t = std::make_tuple(__VA_ARGS__); \ + static_assert(std::tuple_size::value <= 2, \ + "Too Many Args!"); \ + } \ + return ((GTEST_EXPAND_(GTEST_GET_SECOND_( \ + __VA_ARGS__, \ + ::testing::internal::DefaultParamName, \ + DUMMY_PARAM_))))(info); \ + } \ + [[maybe_unused]] static int gtest_##prefix##test_suite_name##_dummy_ = \ + ::testing::UnitTest::GetInstance() \ + ->parameterized_test_registry() \ + .GetTestSuitePatternHolder( \ + GTEST_STRINGIFY_(test_suite_name), \ + ::testing::internal::CodeLocation(__FILE__, __LINE__)) \ + ->AddTestSuiteInstantiation( \ + GTEST_STRINGIFY_(prefix), \ + >est_##prefix##test_suite_name##_EvalGenerator_, \ + >est_##prefix##test_suite_name##_EvalGenerateName_, __FILE__, \ + __LINE__) + +// Allow Marking a Parameterized test class as not needing to be instantiated. +#define GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(T) \ + namespace gtest_do_not_use_outside_namespace_scope {} \ + static const ::testing::internal::MarkAsIgnored gtest_allow_ignore_##T( \ + GTEST_STRINGIFY_(T)) + +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ +#define INSTANTIATE_TEST_CASE_P \ + static_assert(::testing::internal::InstantiateTestCase_P_IsDeprecated(), \ + ""); \ + INSTANTIATE_TEST_SUITE_P +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + +} // namespace testing + +#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_ diff --git a/googletest/include/gtest/gtest-printers.h b/googletest/include/gtest/gtest-printers.h new file mode 100644 index 00000000..198a7693 --- /dev/null +++ b/googletest/include/gtest/gtest-printers.h @@ -0,0 +1,1236 @@ +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Google Test - The Google C++ Testing and Mocking Framework +// +// This file implements a universal value printer that can print a +// value of any type T: +// +// void ::testing::internal::UniversalPrinter::Print(value, ostream_ptr); +// +// A user can teach this function how to print a class type T by +// defining either operator<<() or PrintTo() in the namespace that +// defines T. More specifically, the FIRST defined function in the +// following list will be used (assuming T is defined in namespace +// foo): +// +// 1. foo::PrintTo(const T&, ostream*) +// 2. operator<<(ostream&, const T&) defined in either foo or the +// global namespace. +// * Prefer AbslStringify(..) to operator<<(..), per https://abseil.io/tips/215. +// * Define foo::PrintTo(..) if the type already has AbslStringify(..), but an +// alternative presentation in test results is of interest. +// +// However if T is an STL-style container then it is printed element-wise +// unless foo::PrintTo(const T&, ostream*) is defined. Note that +// operator<<() is ignored for container types. +// +// If none of the above is defined, it will print the debug string of +// the value if it is a protocol buffer, or print the raw bytes in the +// value otherwise. +// +// To aid debugging: when T is a reference type, the address of the +// value is also printed; when T is a (const) char pointer, both the +// pointer value and the NUL-terminated string it points to are +// printed. +// +// We also provide some convenient wrappers: +// +// // Prints a value to a string. For a (const or not) char +// // pointer, the NUL-terminated string (but not the pointer) is +// // printed. +// std::string ::testing::PrintToString(const T& value); +// +// // Prints a value tersely: for a reference type, the referenced +// // value (but not the address) is printed; for a (const or not) char +// // pointer, the NUL-terminated string (but not the pointer) is +// // printed. +// void ::testing::internal::UniversalTersePrint(const T& value, ostream*); +// +// // Prints value using the type inferred by the compiler. The difference +// // from UniversalTersePrint() is that this function prints both the +// // pointer and the NUL-terminated string for a (const or not) char pointer. +// void ::testing::internal::UniversalPrint(const T& value, ostream*); +// +// // Prints the fields of a tuple tersely to a string vector, one +// // element for each field. Tuple support must be enabled in +// // gtest-port.h. +// std::vector UniversalTersePrintTupleFieldsToStrings( +// const Tuple& value); +// +// Known limitation: +// +// The print primitives print the elements of an STL-style container +// using the compiler-inferred type of *iter where iter is a +// const_iterator of the container. When const_iterator is an input +// iterator but not a forward iterator, this inferred type may not +// match value_type, and the print output may be incorrect. In +// practice, this is rarely a problem as for most containers +// const_iterator is a forward iterator. We'll fix this if there's an +// actual need for it. Note that this fix cannot rely on value_type +// being defined as many user-defined container types don't have +// value_type. + +// IWYU pragma: private, include "gtest/gtest.h" +// IWYU pragma: friend gtest/.* +// IWYU pragma: friend gmock/.* + +#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_PRINTERS_H_ +#define GOOGLETEST_INCLUDE_GTEST_GTEST_PRINTERS_H_ + +#include +#include +#include // NOLINT +#include +#include +#include +#include +#include +#include +#include + +#ifdef GTEST_HAS_ABSL +#include "absl/strings/has_absl_stringify.h" +#include "absl/strings/str_cat.h" +#endif // GTEST_HAS_ABSL +#include "gtest/internal/gtest-internal.h" +#include "gtest/internal/gtest-port.h" + +#if GTEST_INTERNAL_HAS_STD_SPAN +#include // NOLINT +#endif // GTEST_INTERNAL_HAS_STD_SPAN + +#if GTEST_INTERNAL_HAS_COMPARE_LIB +#include // NOLINT +#endif // GTEST_INTERNAL_HAS_COMPARE_LIB + +namespace testing { + +// Definitions in the internal* namespaces are subject to change without notice. +// DO NOT USE THEM IN USER CODE! +namespace internal { + +template +void UniversalPrint(const T& value, ::std::ostream* os); + +template +struct IsStdSpan { + static constexpr bool value = false; +}; + +#if GTEST_INTERNAL_HAS_STD_SPAN +template +struct IsStdSpan> { + static constexpr bool value = true; +}; +#endif // GTEST_INTERNAL_HAS_STD_SPAN + +// Used to print an STL-style container when the user doesn't define +// a PrintTo() for it. +// +// NOTE: Since std::span does not have const_iterator until C++23, it would +// fail IsContainerTest before C++23. However, IsContainerTest only uses +// the presence of const_iterator to avoid treating iterators as containers +// because of iterator::iterator. Which means std::span satisfies the *intended* +// condition of IsContainerTest. +struct ContainerPrinter { + template (0)) == sizeof(IsContainer)) && + !IsRecursiveContainer::value) || + IsStdSpan::value>::type> + static void PrintValue(const T& container, std::ostream* os) { + const size_t kMaxCount = 32; // The maximum number of elements to print. + *os << '{'; + size_t count = 0; + for (auto&& elem : container) { + if (count > 0) { + *os << ','; + if (count == kMaxCount) { // Enough has been printed. + *os << " ..."; + break; + } + } + *os << ' '; + // We cannot call PrintTo(elem, os) here as PrintTo() doesn't + // handle `elem` being a native array. + internal::UniversalPrint(elem, os); + ++count; + } + + if (count > 0) { + *os << ' '; + } + *os << '}'; + } +}; + +// Used to print a pointer that is neither a char pointer nor a member +// pointer, when the user doesn't define PrintTo() for it. (A member +// variable pointer or member function pointer doesn't really point to +// a location in the address space. Their representation is +// implementation-defined. Therefore they will be printed as raw +// bytes.) +struct FunctionPointerPrinter { + template ::value>::type> + static void PrintValue(T* p, ::std::ostream* os) { + if (p == nullptr) { + *os << "NULL"; + } else { + // T is a function type, so '*os << p' doesn't do what we want + // (it just prints p as bool). We want to print p as a const + // void*. + *os << reinterpret_cast(p); + } + } +}; + +struct PointerPrinter { + template + static void PrintValue(T* p, ::std::ostream* os) { + if (p == nullptr) { + *os << "NULL"; + } else { + // T is not a function type. We just call << to print p, + // relying on ADL to pick up user-defined << for their pointer + // types, if any. + *os << p; + } + } +}; + +namespace internal_stream_operator_without_lexical_name_lookup { + +// The presence of an operator<< here will terminate lexical scope lookup +// straight away (even though it cannot be a match because of its argument +// types). Thus, the two operator<< calls in StreamPrinter will find only ADL +// candidates. +struct LookupBlocker {}; +void operator<<(LookupBlocker, LookupBlocker); + +struct StreamPrinter { + template ::value>::type> + // Only accept types for which we can find a streaming operator via + // ADL (possibly involving implicit conversions). + // (Use SFINAE via return type, because it seems GCC < 12 doesn't handle name + // lookup properly when we do it in the template parameter list.) + static auto PrintValue(const T& value, + ::std::ostream* os) -> decltype((void)(*os << value)) { + // Call streaming operator found by ADL, possibly with implicit conversions + // of the arguments. + *os << value; + } +}; + +} // namespace internal_stream_operator_without_lexical_name_lookup + +struct ProtobufPrinter { + // We print a protobuf using its ShortDebugString() when the string + // doesn't exceed this many characters; otherwise we print it using + // DebugString() for better readability. + static const size_t kProtobufOneLinerMaxLength = 50; + + template ::value>::type> + static void PrintValue(const T& value, ::std::ostream* os) { + std::string pretty_str = value.ShortDebugString(); + if (pretty_str.length() > kProtobufOneLinerMaxLength) { + pretty_str = "\n" + value.DebugString(); + } + *os << ("<" + pretty_str + ">"); + } +}; + +struct ConvertibleToIntegerPrinter { + // Since T has no << operator or PrintTo() but can be implicitly + // converted to BiggestInt, we print it as a BiggestInt. + // + // Most likely T is an enum type (either named or unnamed), in which + // case printing it as an integer is the desired behavior. In case + // T is not an enum, printing it as an integer is the best we can do + // given that it has no user-defined printer. + static void PrintValue(internal::BiggestInt value, ::std::ostream* os) { + *os << value; + } +}; + +struct ConvertibleToStringViewPrinter { +#if GTEST_INTERNAL_HAS_STRING_VIEW + static void PrintValue(internal::StringView value, ::std::ostream* os) { + internal::UniversalPrint(value, os); + } +#endif +}; + +#ifdef GTEST_HAS_ABSL +struct ConvertibleToAbslStringifyPrinter { + template ::value>::type> // NOLINT + static void PrintValue(const T& value, ::std::ostream* os) { + *os << absl::StrCat(value); + } +}; +#endif // GTEST_HAS_ABSL + +// Prints the given number of bytes in the given object to the given +// ostream. +GTEST_API_ void PrintBytesInObjectTo(const unsigned char* obj_bytes, + size_t count, ::std::ostream* os); +struct RawBytesPrinter { + // SFINAE on `sizeof` to make sure we have a complete type. + template + static void PrintValue(const T& value, ::std::ostream* os) { + PrintBytesInObjectTo( + static_cast( + // Load bearing cast to void* to support iOS + reinterpret_cast(std::addressof(value))), + sizeof(value), os); + } +}; + +struct FallbackPrinter { + template + static void PrintValue(const T&, ::std::ostream* os) { + *os << "(incomplete type)"; + } +}; + +// Try every printer in order and return the first one that works. +template +struct FindFirstPrinter : FindFirstPrinter {}; + +template +struct FindFirstPrinter< + T, decltype(Printer::PrintValue(std::declval(), nullptr)), + Printer, Printers...> { + using type = Printer; +}; + +// Select the best printer in the following order: +// - Print containers (they have begin/end/etc). +// - Print function pointers. +// - Print object pointers. +// - Print protocol buffers. +// - Use the stream operator, if available. +// - Print types convertible to BiggestInt. +// - Print types convertible to StringView, if available. +// - Fallback to printing the raw bytes of the object. +template +void PrintWithFallback(const T& value, ::std::ostream* os) { + using Printer = typename FindFirstPrinter< + T, void, ContainerPrinter, FunctionPointerPrinter, PointerPrinter, + ProtobufPrinter, +#ifdef GTEST_HAS_ABSL + ConvertibleToAbslStringifyPrinter, +#endif // GTEST_HAS_ABSL + internal_stream_operator_without_lexical_name_lookup::StreamPrinter, + ConvertibleToIntegerPrinter, ConvertibleToStringViewPrinter, + RawBytesPrinter, FallbackPrinter>::type; + Printer::PrintValue(value, os); +} + +// FormatForComparison::Format(value) formats a +// value of type ToPrint that is an operand of a comparison assertion +// (e.g. ASSERT_EQ). OtherOperand is the type of the other operand in +// the comparison, and is used to help determine the best way to +// format the value. In particular, when the value is a C string +// (char pointer) and the other operand is an STL string object, we +// want to format the C string as a string, since we know it is +// compared by value with the string object. If the value is a char +// pointer but the other operand is not an STL string object, we don't +// know whether the pointer is supposed to point to a NUL-terminated +// string, and thus want to print it as a pointer to be safe. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. + +// The default case. +template +class FormatForComparison { + public: + static ::std::string Format(const ToPrint& value) { + return ::testing::PrintToString(value); + } +}; + +// Array. +template +class FormatForComparison { + public: + static ::std::string Format(const ToPrint* value) { + return FormatForComparison::Format(value); + } +}; + +// By default, print C string as pointers to be safe, as we don't know +// whether they actually point to a NUL-terminated string. + +#define GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(CharType) \ + template \ + class FormatForComparison { \ + public: \ + static ::std::string Format(CharType* value) { \ + return ::testing::PrintToString(static_cast(value)); \ + } \ + } + +GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(char); +GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(const char); +GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(wchar_t); +GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(const wchar_t); +#ifdef __cpp_lib_char8_t +GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(char8_t); +GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(const char8_t); +#endif +GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(char16_t); +GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(const char16_t); +GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(char32_t); +GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(const char32_t); + +#undef GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_ + +// If a C string is compared with an STL string object, we know it's meant +// to point to a NUL-terminated string, and thus can print it as a string. + +#define GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(CharType, OtherStringType) \ + template <> \ + class FormatForComparison { \ + public: \ + static ::std::string Format(CharType* value) { \ + return ::testing::PrintToString(value); \ + } \ + } + +GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(char, ::std::string); +GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(const char, ::std::string); +#ifdef __cpp_lib_char8_t +GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(char8_t, ::std::u8string); +GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(const char8_t, ::std::u8string); +#endif +GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(char16_t, ::std::u16string); +GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(const char16_t, ::std::u16string); +GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(char32_t, ::std::u32string); +GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(const char32_t, ::std::u32string); + +#if GTEST_HAS_STD_WSTRING +GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(wchar_t, ::std::wstring); +GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(const wchar_t, ::std::wstring); +#endif + +#undef GTEST_IMPL_FORMAT_C_STRING_AS_STRING_ + +// Formats a comparison assertion (e.g. ASSERT_EQ, EXPECT_LT, and etc) +// operand to be used in a failure message. The type (but not value) +// of the other operand may affect the format. This allows us to +// print a char* as a raw pointer when it is compared against another +// char* or void*, and print it as a C string when it is compared +// against an std::string object, for example. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +template +std::string FormatForComparisonFailureMessage(const T1& value, + const T2& /* other_operand */) { + return FormatForComparison::Format(value); +} + +// UniversalPrinter::Print(value, ostream_ptr) prints the given +// value to the given ostream. The caller must ensure that +// 'ostream_ptr' is not NULL, or the behavior is undefined. +// +// We define UniversalPrinter as a class template (as opposed to a +// function template), as we need to partially specialize it for +// reference types, which cannot be done with function templates. +template +class UniversalPrinter; + +// Prints the given value using the << operator if it has one; +// otherwise prints the bytes in it. This is what +// UniversalPrinter::Print() does when PrintTo() is not specialized +// or overloaded for type T. +// +// A user can override this behavior for a class type Foo by defining +// an overload of PrintTo() in the namespace where Foo is defined. We +// give the user this option as sometimes defining a << operator for +// Foo is not desirable (e.g. the coding style may prevent doing it, +// or there is already a << operator but it doesn't do what the user +// wants). +template +void PrintTo(const T& value, ::std::ostream* os) { + internal::PrintWithFallback(value, os); +} + +// The following list of PrintTo() overloads tells +// UniversalPrinter::Print() how to print standard types (built-in +// types, strings, plain arrays, and pointers). + +// Overloads for various char types. +GTEST_API_ void PrintTo(unsigned char c, ::std::ostream* os); +GTEST_API_ void PrintTo(signed char c, ::std::ostream* os); +inline void PrintTo(char c, ::std::ostream* os) { + // When printing a plain char, we always treat it as unsigned. This + // way, the output won't be affected by whether the compiler thinks + // char is signed or not. + PrintTo(static_cast(c), os); +} + +// Overloads for other simple built-in types. +inline void PrintTo(bool x, ::std::ostream* os) { + *os << (x ? "true" : "false"); +} + +// Overload for wchar_t type. +// Prints a wchar_t as a symbol if it is printable or as its internal +// code otherwise and also as its decimal code (except for L'\0'). +// The L'\0' char is printed as "L'\\0'". The decimal code is printed +// as signed integer when wchar_t is implemented by the compiler +// as a signed type and is printed as an unsigned integer when wchar_t +// is implemented as an unsigned type. +GTEST_API_ void PrintTo(wchar_t wc, ::std::ostream* os); + +GTEST_API_ void PrintTo(char32_t c, ::std::ostream* os); +inline void PrintTo(char16_t c, ::std::ostream* os) { + PrintTo(ImplicitCast_(c), os); +} +#ifdef __cpp_lib_char8_t +inline void PrintTo(char8_t c, ::std::ostream* os) { + PrintTo(ImplicitCast_(c), os); +} +#endif + +// gcc/clang __{u,}int128_t +#if defined(__SIZEOF_INT128__) +GTEST_API_ void PrintTo(__uint128_t v, ::std::ostream* os); +GTEST_API_ void PrintTo(__int128_t v, ::std::ostream* os); +#endif // __SIZEOF_INT128__ + +// The default resolution used to print floating-point values uses only +// 6 digits, which can be confusing if a test compares two values whose +// difference lies in the 7th digit. So we'd like to print out numbers +// in full precision. +// However if the value is something simple like 1.1, full will print a +// long string like 1.100000001 due to floating-point numbers not using +// a base of 10. This routiune returns an appropriate resolution for a +// given floating-point number, that is, 6 if it will be accurate, or a +// max_digits10 value (full precision) if it won't, for values between +// 0.0001 and one million. +// It does this by computing what those digits would be (by multiplying +// by an appropriate power of 10), then dividing by that power again to +// see if gets the original value back. +// A similar algorithm applies for values larger than one million; note +// that for those values, we must divide to get a six-digit number, and +// then multiply to possibly get the original value again. +template +int AppropriateResolution(FloatType val) { + int full = std::numeric_limits::max_digits10; + if (val < 0) val = -val; + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + if (val < 1000000) { + FloatType mulfor6 = 1e10; + // Without these static casts, the template instantiation for float would + // fail to compile when -Wdouble-promotion is enabled, as the arithmetic and + // comparison logic would promote floats to doubles. + if (val >= static_cast(100000.0)) { // 100,000 to 999,999 + mulfor6 = 1.0; + } else if (val >= static_cast(10000.0)) { + mulfor6 = 1e1; + } else if (val >= static_cast(1000.0)) { + mulfor6 = 1e2; + } else if (val >= static_cast(100.0)) { + mulfor6 = 1e3; + } else if (val >= static_cast(10.0)) { + mulfor6 = 1e4; + } else if (val >= static_cast(1.0)) { + mulfor6 = 1e5; + } else if (val >= static_cast(0.1)) { + mulfor6 = 1e6; + } else if (val >= static_cast(0.01)) { + mulfor6 = 1e7; + } else if (val >= static_cast(0.001)) { + mulfor6 = 1e8; + } else if (val >= static_cast(0.0001)) { + mulfor6 = 1e9; + } + if (static_cast(static_cast( + val * mulfor6 + (static_cast(0.5)))) / + mulfor6 == + val) + return 6; + } else if (val < static_cast(1e10)) { + FloatType divfor6 = static_cast(1.0); + if (val >= static_cast(1e9)) { // 1,000,000,000 to 9,999,999,999 + divfor6 = 10000; + } else if (val >= + static_cast(1e8)) { // 100,000,000 to 999,999,999 + divfor6 = 1000; + } else if (val >= + static_cast(1e7)) { // 10,000,000 to 99,999,999 + divfor6 = 100; + } else if (val >= static_cast(1e6)) { // 1,000,000 to 9,999,999 + divfor6 = 10; + } + if (static_cast(static_cast( + val / divfor6 + (static_cast(0.5)))) * + divfor6 == + val) + return 6; + } +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + return full; +} + +inline void PrintTo(float f, ::std::ostream* os) { + auto old_precision = os->precision(); + os->precision(AppropriateResolution(f)); + *os << f; + os->precision(old_precision); +} + +inline void PrintTo(double d, ::std::ostream* os) { + auto old_precision = os->precision(); + os->precision(AppropriateResolution(d)); + *os << d; + os->precision(old_precision); +} + +// Overloads for C strings. +GTEST_API_ void PrintTo(const char* s, ::std::ostream* os); +inline void PrintTo(char* s, ::std::ostream* os) { + PrintTo(ImplicitCast_(s), os); +} + +// signed/unsigned char is often used for representing binary data, so +// we print pointers to it as void* to be safe. +inline void PrintTo(const signed char* s, ::std::ostream* os) { + PrintTo(ImplicitCast_(s), os); +} +inline void PrintTo(signed char* s, ::std::ostream* os) { + PrintTo(ImplicitCast_(s), os); +} +inline void PrintTo(const unsigned char* s, ::std::ostream* os) { + PrintTo(ImplicitCast_(s), os); +} +inline void PrintTo(unsigned char* s, ::std::ostream* os) { + PrintTo(ImplicitCast_(s), os); +} +#ifdef __cpp_lib_char8_t +// Overloads for u8 strings. +GTEST_API_ void PrintTo(const char8_t* s, ::std::ostream* os); +inline void PrintTo(char8_t* s, ::std::ostream* os) { + PrintTo(ImplicitCast_(s), os); +} +#endif +// Overloads for u16 strings. +GTEST_API_ void PrintTo(const char16_t* s, ::std::ostream* os); +inline void PrintTo(char16_t* s, ::std::ostream* os) { + PrintTo(ImplicitCast_(s), os); +} +// Overloads for u32 strings. +GTEST_API_ void PrintTo(const char32_t* s, ::std::ostream* os); +inline void PrintTo(char32_t* s, ::std::ostream* os) { + PrintTo(ImplicitCast_(s), os); +} + +// MSVC can be configured to define wchar_t as a typedef of unsigned +// short. It defines _NATIVE_WCHAR_T_DEFINED when wchar_t is a native +// type. When wchar_t is a typedef, defining an overload for const +// wchar_t* would cause unsigned short* be printed as a wide string, +// possibly causing invalid memory accesses. +#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED) +// Overloads for wide C strings +GTEST_API_ void PrintTo(const wchar_t* s, ::std::ostream* os); +inline void PrintTo(wchar_t* s, ::std::ostream* os) { + PrintTo(ImplicitCast_(s), os); +} +#endif + +// Overload for C arrays. Multi-dimensional arrays are printed +// properly. + +// Prints the given number of elements in an array, without printing +// the curly braces. +template +void PrintRawArrayTo(const T a[], size_t count, ::std::ostream* os) { + UniversalPrint(a[0], os); + for (size_t i = 1; i != count; i++) { + *os << ", "; + UniversalPrint(a[i], os); + } +} + +// Overloads for ::std::string. +GTEST_API_ void PrintStringTo(const ::std::string& s, ::std::ostream* os); +inline void PrintTo(const ::std::string& s, ::std::ostream* os) { + PrintStringTo(s, os); +} + +// Overloads for ::std::u8string +#ifdef __cpp_lib_char8_t +GTEST_API_ void PrintU8StringTo(const ::std::u8string& s, ::std::ostream* os); +inline void PrintTo(const ::std::u8string& s, ::std::ostream* os) { + PrintU8StringTo(s, os); +} +#endif + +// Overloads for ::std::u16string +GTEST_API_ void PrintU16StringTo(const ::std::u16string& s, ::std::ostream* os); +inline void PrintTo(const ::std::u16string& s, ::std::ostream* os) { + PrintU16StringTo(s, os); +} + +// Overloads for ::std::u32string +GTEST_API_ void PrintU32StringTo(const ::std::u32string& s, ::std::ostream* os); +inline void PrintTo(const ::std::u32string& s, ::std::ostream* os) { + PrintU32StringTo(s, os); +} + +// Overloads for ::std::wstring. +#if GTEST_HAS_STD_WSTRING +GTEST_API_ void PrintWideStringTo(const ::std::wstring& s, ::std::ostream* os); +inline void PrintTo(const ::std::wstring& s, ::std::ostream* os) { + PrintWideStringTo(s, os); +} +#endif // GTEST_HAS_STD_WSTRING + +#if GTEST_INTERNAL_HAS_STRING_VIEW +// Overload for internal::StringView. +inline void PrintTo(internal::StringView sp, ::std::ostream* os) { + PrintTo(::std::string(sp), os); +} +#endif // GTEST_INTERNAL_HAS_STRING_VIEW + +inline void PrintTo(std::nullptr_t, ::std::ostream* os) { *os << "(nullptr)"; } + +#if GTEST_HAS_RTTI +inline void PrintTo(const std::type_info& info, std::ostream* os) { + *os << internal::GetTypeName(info); +} +#endif // GTEST_HAS_RTTI + +template +void PrintTo(std::reference_wrapper ref, ::std::ostream* os) { + UniversalPrinter::Print(ref.get(), os); +} + +inline const void* VoidifyPointer(const void* p) { return p; } +inline const void* VoidifyPointer(volatile const void* p) { + return const_cast(p); +} + +template +void PrintSmartPointer(const Ptr& ptr, std::ostream* os, char) { + if (ptr == nullptr) { + *os << "(nullptr)"; + } else { + // We can't print the value. Just print the pointer.. + *os << "(" << (VoidifyPointer)(ptr.get()) << ")"; + } +} +template ::value && + !std::is_array::value>::type> +void PrintSmartPointer(const Ptr& ptr, std::ostream* os, int) { + if (ptr == nullptr) { + *os << "(nullptr)"; + } else { + *os << "(ptr = " << (VoidifyPointer)(ptr.get()) << ", value = "; + UniversalPrinter::Print(*ptr, os); + *os << ")"; + } +} + +template +void PrintTo(const std::unique_ptr& ptr, std::ostream* os) { + (PrintSmartPointer)(ptr, os, 0); +} + +template +void PrintTo(const std::shared_ptr& ptr, std::ostream* os) { + (PrintSmartPointer)(ptr, os, 0); +} + +#if GTEST_INTERNAL_HAS_COMPARE_LIB +template +void PrintOrderingHelper(T ordering, std::ostream* os) { + if (ordering == T::less) { + *os << "(less)"; + } else if (ordering == T::greater) { + *os << "(greater)"; + } else if (ordering == T::equivalent) { + *os << "(equivalent)"; + } else { + *os << "(unknown ordering)"; + } +} + +inline void PrintTo(std::strong_ordering ordering, std::ostream* os) { + if (ordering == std::strong_ordering::equal) { + *os << "(equal)"; + } else { + PrintOrderingHelper(ordering, os); + } +} + +inline void PrintTo(std::partial_ordering ordering, std::ostream* os) { + if (ordering == std::partial_ordering::unordered) { + *os << "(unordered)"; + } else { + PrintOrderingHelper(ordering, os); + } +} + +inline void PrintTo(std::weak_ordering ordering, std::ostream* os) { + PrintOrderingHelper(ordering, os); +} +#endif + +// Helper function for printing a tuple. T must be instantiated with +// a tuple type. +template +void PrintTupleTo(const T&, std::integral_constant, + ::std::ostream*) {} + +template +void PrintTupleTo(const T& t, std::integral_constant, + ::std::ostream* os) { + PrintTupleTo(t, std::integral_constant(), os); + GTEST_INTENTIONAL_CONST_COND_PUSH_() + if (I > 1) { + GTEST_INTENTIONAL_CONST_COND_POP_() + *os << ", "; + } + UniversalPrinter::type>::Print( + std::get(t), os); +} + +template +void PrintTo(const ::std::tuple& t, ::std::ostream* os) { + *os << "("; + PrintTupleTo(t, std::integral_constant(), os); + *os << ")"; +} + +// Overload for std::pair. +template +void PrintTo(const ::std::pair& value, ::std::ostream* os) { + *os << '('; + // We cannot use UniversalPrint(value.first, os) here, as T1 may be + // a reference type. The same for printing value.second. + UniversalPrinter::Print(value.first, os); + *os << ", "; + UniversalPrinter::Print(value.second, os); + *os << ')'; +} + +// Implements printing a non-reference type T by letting the compiler +// pick the right overload of PrintTo() for T. +template +class UniversalPrinter { + public: + // MSVC warns about adding const to a function type, so we want to + // disable the warning. + GTEST_DISABLE_MSC_WARNINGS_PUSH_(4180) + + // Note: we deliberately don't call this PrintTo(), as that name + // conflicts with ::testing::internal::PrintTo in the body of the + // function. + static void Print(const T& value, ::std::ostream* os) { + // By default, ::testing::internal::PrintTo() is used for printing + // the value. + // + // Thanks to Koenig look-up, if T is a class and has its own + // PrintTo() function defined in its namespace, that function will + // be visible here. Since it is more specific than the generic ones + // in ::testing::internal, it will be picked by the compiler in the + // following statement - exactly what we want. + PrintTo(value, os); + } + + GTEST_DISABLE_MSC_WARNINGS_POP_() +}; + +// Remove any const-qualifiers before passing a type to UniversalPrinter. +template +class UniversalPrinter : public UniversalPrinter {}; + +#if GTEST_INTERNAL_HAS_ANY + +// Printer for std::any / absl::any + +template <> +class UniversalPrinter { + public: + static void Print(const Any& value, ::std::ostream* os) { + if (value.has_value()) { + *os << "value of type " << GetTypeName(value); + } else { + *os << "no value"; + } + } + + private: + static std::string GetTypeName(const Any& value) { +#if GTEST_HAS_RTTI + return internal::GetTypeName(value.type()); +#else + static_cast(value); // possibly unused + return ""; +#endif // GTEST_HAS_RTTI + } +}; + +#endif // GTEST_INTERNAL_HAS_ANY + +#if GTEST_INTERNAL_HAS_OPTIONAL + +// Printer for std::optional / absl::optional + +template +class UniversalPrinter> { + public: + static void Print(const Optional& value, ::std::ostream* os) { + *os << '('; + if (!value) { + *os << "nullopt"; + } else { + UniversalPrint(*value, os); + } + *os << ')'; + } +}; + +template <> +class UniversalPrinter { + public: + static void Print(decltype(Nullopt()), ::std::ostream* os) { + *os << "(nullopt)"; + } +}; + +#endif // GTEST_INTERNAL_HAS_OPTIONAL + +#if GTEST_INTERNAL_HAS_VARIANT + +// Printer for std::variant / absl::variant + +template +class UniversalPrinter> { + public: + static void Print(const Variant& value, ::std::ostream* os) { + *os << '('; +#ifdef GTEST_HAS_ABSL + absl::visit(Visitor{os, value.index()}, value); +#else + std::visit(Visitor{os, value.index()}, value); +#endif // GTEST_HAS_ABSL + *os << ')'; + } + + private: + struct Visitor { + template + void operator()(const U& u) const { + *os << "'" << GetTypeName() << "(index = " << index + << ")' with value "; + UniversalPrint(u, os); + } + ::std::ostream* os; + std::size_t index; + }; +}; + +#endif // GTEST_INTERNAL_HAS_VARIANT + +// UniversalPrintArray(begin, len, os) prints an array of 'len' +// elements, starting at address 'begin'. +template +void UniversalPrintArray(const T* begin, size_t len, ::std::ostream* os) { + if (len == 0) { + *os << "{}"; + } else { + *os << "{ "; + const size_t kThreshold = 18; + const size_t kChunkSize = 8; + // If the array has more than kThreshold elements, we'll have to + // omit some details by printing only the first and the last + // kChunkSize elements. + if (len <= kThreshold) { + PrintRawArrayTo(begin, len, os); + } else { + PrintRawArrayTo(begin, kChunkSize, os); + *os << ", ..., "; + PrintRawArrayTo(begin + len - kChunkSize, kChunkSize, os); + } + *os << " }"; + } +} +// This overload prints a (const) char array compactly. +GTEST_API_ void UniversalPrintArray(const char* begin, size_t len, + ::std::ostream* os); + +#ifdef __cpp_lib_char8_t +// This overload prints a (const) char8_t array compactly. +GTEST_API_ void UniversalPrintArray(const char8_t* begin, size_t len, + ::std::ostream* os); +#endif + +// This overload prints a (const) char16_t array compactly. +GTEST_API_ void UniversalPrintArray(const char16_t* begin, size_t len, + ::std::ostream* os); + +// This overload prints a (const) char32_t array compactly. +GTEST_API_ void UniversalPrintArray(const char32_t* begin, size_t len, + ::std::ostream* os); + +// This overload prints a (const) wchar_t array compactly. +GTEST_API_ void UniversalPrintArray(const wchar_t* begin, size_t len, + ::std::ostream* os); + +// Implements printing an array type T[N]. +template +class UniversalPrinter { + public: + // Prints the given array, omitting some elements when there are too + // many. + static void Print(const T (&a)[N], ::std::ostream* os) { + UniversalPrintArray(a, N, os); + } +}; + +// Implements printing a reference type T&. +template +class UniversalPrinter { + public: + // MSVC warns about adding const to a function type, so we want to + // disable the warning. + GTEST_DISABLE_MSC_WARNINGS_PUSH_(4180) + + static void Print(const T& value, ::std::ostream* os) { + // Prints the address of the value. We use reinterpret_cast here + // as static_cast doesn't compile when T is a function type. + *os << "@" << reinterpret_cast(&value) << " "; + + // Then prints the value itself. + UniversalPrint(value, os); + } + + GTEST_DISABLE_MSC_WARNINGS_POP_() +}; + +// Prints a value tersely: for a reference type, the referenced value +// (but not the address) is printed; for a (const) char pointer, the +// NUL-terminated string (but not the pointer) is printed. + +template +class UniversalTersePrinter { + public: + static void Print(const T& value, ::std::ostream* os) { + UniversalPrint(value, os); + } +}; +template +class UniversalTersePrinter { + public: + static void Print(const T& value, ::std::ostream* os) { + UniversalPrint(value, os); + } +}; +template +class UniversalTersePrinter> { + public: + static void Print(std::reference_wrapper value, ::std::ostream* os) { + UniversalTersePrinter::Print(value.get(), os); + } +}; +template +class UniversalTersePrinter { + public: + static void Print(const T (&value)[N], ::std::ostream* os) { + UniversalPrinter::Print(value, os); + } +}; +template <> +class UniversalTersePrinter { + public: + static void Print(const char* str, ::std::ostream* os) { + if (str == nullptr) { + *os << "NULL"; + } else { + UniversalPrint(std::string(str), os); + } + } +}; +template <> +class UniversalTersePrinter : public UniversalTersePrinter { +}; + +#ifdef __cpp_lib_char8_t +template <> +class UniversalTersePrinter { + public: + static void Print(const char8_t* str, ::std::ostream* os) { + if (str == nullptr) { + *os << "NULL"; + } else { + UniversalPrint(::std::u8string(str), os); + } + } +}; +template <> +class UniversalTersePrinter + : public UniversalTersePrinter {}; +#endif + +template <> +class UniversalTersePrinter { + public: + static void Print(const char16_t* str, ::std::ostream* os) { + if (str == nullptr) { + *os << "NULL"; + } else { + UniversalPrint(::std::u16string(str), os); + } + } +}; +template <> +class UniversalTersePrinter + : public UniversalTersePrinter {}; + +template <> +class UniversalTersePrinter { + public: + static void Print(const char32_t* str, ::std::ostream* os) { + if (str == nullptr) { + *os << "NULL"; + } else { + UniversalPrint(::std::u32string(str), os); + } + } +}; +template <> +class UniversalTersePrinter + : public UniversalTersePrinter {}; + +#if GTEST_HAS_STD_WSTRING +template <> +class UniversalTersePrinter { + public: + static void Print(const wchar_t* str, ::std::ostream* os) { + if (str == nullptr) { + *os << "NULL"; + } else { + UniversalPrint(::std::wstring(str), os); + } + } +}; +#endif + +template <> +class UniversalTersePrinter { + public: + static void Print(wchar_t* str, ::std::ostream* os) { + UniversalTersePrinter::Print(str, os); + } +}; + +template +void UniversalTersePrint(const T& value, ::std::ostream* os) { + UniversalTersePrinter::Print(value, os); +} + +// Prints a value using the type inferred by the compiler. The +// difference between this and UniversalTersePrint() is that for a +// (const) char pointer, this prints both the pointer and the +// NUL-terminated string. +template +void UniversalPrint(const T& value, ::std::ostream* os) { + // A workarond for the bug in VC++ 7.1 that prevents us from instantiating + // UniversalPrinter with T directly. + typedef T T1; + UniversalPrinter::Print(value, os); +} + +typedef ::std::vector<::std::string> Strings; + +// Tersely prints the first N fields of a tuple to a string vector, +// one element for each field. +template +void TersePrintPrefixToStrings(const Tuple&, std::integral_constant, + Strings*) {} +template +void TersePrintPrefixToStrings(const Tuple& t, + std::integral_constant, + Strings* strings) { + TersePrintPrefixToStrings(t, std::integral_constant(), + strings); + ::std::stringstream ss; + UniversalTersePrint(std::get(t), &ss); + strings->push_back(ss.str()); +} + +// Prints the fields of a tuple tersely to a string vector, one +// element for each field. See the comment before +// UniversalTersePrint() for how we define "tersely". +template +Strings UniversalTersePrintTupleFieldsToStrings(const Tuple& value) { + Strings result; + TersePrintPrefixToStrings( + value, std::integral_constant::value>(), + &result); + return result; +} + +} // namespace internal + +template +::std::string PrintToString(const T& value) { + ::std::stringstream ss; + internal::UniversalTersePrinter::Print(value, &ss); + return ss.str(); +} + +} // namespace testing + +// Include any custom printer added by the local installation. +// We must include this header at the end to make sure it can use the +// declarations from this file. +#include "gtest/internal/custom/gtest-printers.h" + +#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_PRINTERS_H_ diff --git a/googletest/include/gtest/gtest-spi.h b/googletest/include/gtest/gtest-spi.h new file mode 100644 index 00000000..c0613b69 --- /dev/null +++ b/googletest/include/gtest/gtest-spi.h @@ -0,0 +1,250 @@ +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Utilities for testing Google Test itself and code that uses Google Test +// (e.g. frameworks built on top of Google Test). + +#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_SPI_H_ +#define GOOGLETEST_INCLUDE_GTEST_GTEST_SPI_H_ + +#include + +#include "gtest/gtest.h" + +GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ +/* class A needs to have dll-interface to be used by clients of class B */) + +namespace testing { + +// This helper class can be used to mock out Google Test failure reporting +// so that we can test Google Test or code that builds on Google Test. +// +// An object of this class appends a TestPartResult object to the +// TestPartResultArray object given in the constructor whenever a Google Test +// failure is reported. It can either intercept only failures that are +// generated in the same thread that created this object or it can intercept +// all generated failures. The scope of this mock object can be controlled with +// the second argument to the two arguments constructor. +class GTEST_API_ ScopedFakeTestPartResultReporter + : public TestPartResultReporterInterface { + public: + // The two possible mocking modes of this object. + enum InterceptMode { + INTERCEPT_ONLY_CURRENT_THREAD, // Intercepts only thread local failures. + INTERCEPT_ALL_THREADS // Intercepts all failures. + }; + + // The c'tor sets this object as the test part result reporter used + // by Google Test. The 'result' parameter specifies where to report the + // results. This reporter will only catch failures generated in the current + // thread. DEPRECATED + explicit ScopedFakeTestPartResultReporter(TestPartResultArray* result); + + // Same as above, but you can choose the interception scope of this object. + ScopedFakeTestPartResultReporter(InterceptMode intercept_mode, + TestPartResultArray* result); + + // The d'tor restores the previous test part result reporter. + ~ScopedFakeTestPartResultReporter() override; + + // Appends the TestPartResult object to the TestPartResultArray + // received in the constructor. + // + // This method is from the TestPartResultReporterInterface + // interface. + void ReportTestPartResult(const TestPartResult& result) override; + + private: + void Init(); + + const InterceptMode intercept_mode_; + TestPartResultReporterInterface* old_reporter_; + TestPartResultArray* const result_; + + ScopedFakeTestPartResultReporter(const ScopedFakeTestPartResultReporter&) = + delete; + ScopedFakeTestPartResultReporter& operator=( + const ScopedFakeTestPartResultReporter&) = delete; +}; + +namespace internal { + +// A helper class for implementing EXPECT_FATAL_FAILURE() and +// EXPECT_NONFATAL_FAILURE(). Its destructor verifies that the given +// TestPartResultArray contains exactly one failure that has the given +// type and contains the given substring. If that's not the case, a +// non-fatal failure will be generated. +class GTEST_API_ SingleFailureChecker { + public: + // The constructor remembers the arguments. + SingleFailureChecker(const TestPartResultArray* results, + TestPartResult::Type type, const std::string& substr); + ~SingleFailureChecker(); + + private: + const TestPartResultArray* const results_; + const TestPartResult::Type type_; + const std::string substr_; + + SingleFailureChecker(const SingleFailureChecker&) = delete; + SingleFailureChecker& operator=(const SingleFailureChecker&) = delete; +}; + +} // namespace internal + +} // namespace testing + +GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 + +// A set of macros for testing Google Test assertions or code that's expected +// to generate Google Test fatal failures (e.g. a failure from an ASSERT_EQ, but +// not a non-fatal failure, as from EXPECT_EQ). It verifies that the given +// statement will cause exactly one fatal Google Test failure with 'substr' +// being part of the failure message. +// +// There are two different versions of this macro. EXPECT_FATAL_FAILURE only +// affects and considers failures generated in the current thread and +// EXPECT_FATAL_FAILURE_ON_ALL_THREADS does the same but for all threads. +// +// The verification of the assertion is done correctly even when the statement +// throws an exception or aborts the current function. +// +// Known restrictions: +// - 'statement' cannot reference local non-static variables or +// non-static members of the current object. +// - 'statement' cannot return a value. +// - You cannot stream a failure message to this macro. +// +// Note that even though the implementations of the following two +// macros are much alike, we cannot refactor them to use a common +// helper macro, due to some peculiarity in how the preprocessor +// works. The AcceptsMacroThatExpandsToUnprotectedComma test in +// gtest_unittest.cc will fail to compile if we do that. +#define EXPECT_FATAL_FAILURE(statement, substr) \ + do { \ + class GTestExpectFatalFailureHelper { \ + public: \ + static void Execute() { statement; } \ + }; \ + ::testing::TestPartResultArray gtest_failures; \ + ::testing::internal::SingleFailureChecker gtest_checker( \ + >est_failures, ::testing::TestPartResult::kFatalFailure, (substr)); \ + { \ + ::testing::ScopedFakeTestPartResultReporter gtest_reporter( \ + ::testing::ScopedFakeTestPartResultReporter:: \ + INTERCEPT_ONLY_CURRENT_THREAD, \ + >est_failures); \ + GTestExpectFatalFailureHelper::Execute(); \ + } \ + } while (::testing::internal::AlwaysFalse()) + +#define EXPECT_FATAL_FAILURE_ON_ALL_THREADS(statement, substr) \ + do { \ + class GTestExpectFatalFailureHelper { \ + public: \ + static void Execute() { statement; } \ + }; \ + ::testing::TestPartResultArray gtest_failures; \ + ::testing::internal::SingleFailureChecker gtest_checker( \ + >est_failures, ::testing::TestPartResult::kFatalFailure, (substr)); \ + { \ + ::testing::ScopedFakeTestPartResultReporter gtest_reporter( \ + ::testing::ScopedFakeTestPartResultReporter::INTERCEPT_ALL_THREADS, \ + >est_failures); \ + GTestExpectFatalFailureHelper::Execute(); \ + } \ + } while (::testing::internal::AlwaysFalse()) + +// A macro for testing Google Test assertions or code that's expected to +// generate Google Test non-fatal failures (e.g. a failure from an EXPECT_EQ, +// but not from an ASSERT_EQ). It asserts that the given statement will cause +// exactly one non-fatal Google Test failure with 'substr' being part of the +// failure message. +// +// There are two different versions of this macro. EXPECT_NONFATAL_FAILURE only +// affects and considers failures generated in the current thread and +// EXPECT_NONFATAL_FAILURE_ON_ALL_THREADS does the same but for all threads. +// +// 'statement' is allowed to reference local variables and members of +// the current object. +// +// The verification of the assertion is done correctly even when the statement +// throws an exception or aborts the current function. +// +// Known restrictions: +// - You cannot stream a failure message to this macro. +// +// Note that even though the implementations of the following two +// macros are much alike, we cannot refactor them to use a common +// helper macro, due to some peculiarity in how the preprocessor +// works. If we do that, the code won't compile when the user gives +// EXPECT_NONFATAL_FAILURE() a statement that contains a macro that +// expands to code containing an unprotected comma. The +// AcceptsMacroThatExpandsToUnprotectedComma test in gtest_unittest.cc +// catches that. +// +// For the same reason, we have to write +// if (::testing::internal::AlwaysTrue()) { statement; } +// instead of +// GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement) +// to avoid an MSVC warning on unreachable code. +#define EXPECT_NONFATAL_FAILURE(statement, substr) \ + do { \ + ::testing::TestPartResultArray gtest_failures; \ + ::testing::internal::SingleFailureChecker gtest_checker( \ + >est_failures, ::testing::TestPartResult::kNonFatalFailure, \ + (substr)); \ + { \ + ::testing::ScopedFakeTestPartResultReporter gtest_reporter( \ + ::testing::ScopedFakeTestPartResultReporter:: \ + INTERCEPT_ONLY_CURRENT_THREAD, \ + >est_failures); \ + if (::testing::internal::AlwaysTrue()) { \ + statement; \ + } \ + } \ + } while (::testing::internal::AlwaysFalse()) + +#define EXPECT_NONFATAL_FAILURE_ON_ALL_THREADS(statement, substr) \ + do { \ + ::testing::TestPartResultArray gtest_failures; \ + ::testing::internal::SingleFailureChecker gtest_checker( \ + >est_failures, ::testing::TestPartResult::kNonFatalFailure, \ + (substr)); \ + { \ + ::testing::ScopedFakeTestPartResultReporter gtest_reporter( \ + ::testing::ScopedFakeTestPartResultReporter::INTERCEPT_ALL_THREADS, \ + >est_failures); \ + if (::testing::internal::AlwaysTrue()) { \ + statement; \ + } \ + } \ + } while (::testing::internal::AlwaysFalse()) + +#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_SPI_H_ diff --git a/googletest/include/gtest/gtest-test-part.h b/googletest/include/gtest/gtest-test-part.h new file mode 100644 index 00000000..41c8a9a0 --- /dev/null +++ b/googletest/include/gtest/gtest-test-part.h @@ -0,0 +1,192 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// IWYU pragma: private, include "gtest/gtest.h" +// IWYU pragma: friend gtest/.* +// IWYU pragma: friend gmock/.* + +#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_TEST_PART_H_ +#define GOOGLETEST_INCLUDE_GTEST_GTEST_TEST_PART_H_ + +#include +#include +#include +#include + +#include "gtest/internal/gtest-internal.h" +#include "gtest/internal/gtest-string.h" + +GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ +/* class A needs to have dll-interface to be used by clients of class B */) + +namespace testing { + +// A copyable object representing the result of a test part (i.e. an +// assertion or an explicit FAIL(), ADD_FAILURE(), or SUCCESS()). +// +// Don't inherit from TestPartResult as its destructor is not virtual. +class GTEST_API_ TestPartResult { + public: + // The possible outcomes of a test part (i.e. an assertion or an + // explicit SUCCEED(), FAIL(), or ADD_FAILURE()). + enum Type { + kSuccess, // Succeeded. + kNonFatalFailure, // Failed but the test can continue. + kFatalFailure, // Failed and the test should be terminated. + kSkip // Skipped. + }; + + // C'tor. TestPartResult does NOT have a default constructor. + // Always use this constructor (with parameters) to create a + // TestPartResult object. + TestPartResult(Type a_type, const char* a_file_name, int a_line_number, + const char* a_message) + : type_(a_type), + file_name_(a_file_name == nullptr ? "" : a_file_name), + line_number_(a_line_number), + summary_(ExtractSummary(a_message)), + message_(a_message) {} + + // Gets the outcome of the test part. + Type type() const { return type_; } + + // Gets the name of the source file where the test part took place, or + // NULL if it's unknown. + const char* file_name() const { + return file_name_.empty() ? nullptr : file_name_.c_str(); + } + + // Gets the line in the source file where the test part took place, + // or -1 if it's unknown. + int line_number() const { return line_number_; } + + // Gets the summary of the failure message. + const char* summary() const { return summary_.c_str(); } + + // Gets the message associated with the test part. + const char* message() const { return message_.c_str(); } + + // Returns true if and only if the test part was skipped. + bool skipped() const { return type_ == kSkip; } + + // Returns true if and only if the test part passed. + bool passed() const { return type_ == kSuccess; } + + // Returns true if and only if the test part non-fatally failed. + bool nonfatally_failed() const { return type_ == kNonFatalFailure; } + + // Returns true if and only if the test part fatally failed. + bool fatally_failed() const { return type_ == kFatalFailure; } + + // Returns true if and only if the test part failed. + bool failed() const { return fatally_failed() || nonfatally_failed(); } + + private: + Type type_; + + // Gets the summary of the failure message by omitting the stack + // trace in it. + static std::string ExtractSummary(const char* message); + + // The name of the source file where the test part took place, or + // "" if the source file is unknown. + std::string file_name_; + // The line in the source file where the test part took place, or -1 + // if the line number is unknown. + int line_number_; + std::string summary_; // The test failure summary. + std::string message_; // The test failure message. +}; + +// Prints a TestPartResult object. +std::ostream& operator<<(std::ostream& os, const TestPartResult& result); + +// An array of TestPartResult objects. +// +// Don't inherit from TestPartResultArray as its destructor is not +// virtual. +class GTEST_API_ TestPartResultArray { + public: + TestPartResultArray() = default; + + // Appends the given TestPartResult to the array. + void Append(const TestPartResult& result); + + // Returns the TestPartResult at the given index (0-based). + const TestPartResult& GetTestPartResult(int index) const; + + // Returns the number of TestPartResult objects in the array. + int size() const; + + private: + std::vector array_; + + TestPartResultArray(const TestPartResultArray&) = delete; + TestPartResultArray& operator=(const TestPartResultArray&) = delete; +}; + +// This interface knows how to report a test part result. +class GTEST_API_ TestPartResultReporterInterface { + public: + virtual ~TestPartResultReporterInterface() = default; + + virtual void ReportTestPartResult(const TestPartResult& result) = 0; +}; + +namespace internal { + +// This helper class is used by {ASSERT|EXPECT}_NO_FATAL_FAILURE to check if a +// statement generates new fatal failures. To do so it registers itself as the +// current test part result reporter. Besides checking if fatal failures were +// reported, it only delegates the reporting to the former result reporter. +// The original result reporter is restored in the destructor. +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +class GTEST_API_ HasNewFatalFailureHelper + : public TestPartResultReporterInterface { + public: + HasNewFatalFailureHelper(); + ~HasNewFatalFailureHelper() override; + void ReportTestPartResult(const TestPartResult& result) override; + bool has_new_fatal_failure() const { return has_new_fatal_failure_; } + + private: + bool has_new_fatal_failure_; + TestPartResultReporterInterface* original_reporter_; + + HasNewFatalFailureHelper(const HasNewFatalFailureHelper&) = delete; + HasNewFatalFailureHelper& operator=(const HasNewFatalFailureHelper&) = delete; +}; + +} // namespace internal + +} // namespace testing + +GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 + +#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_TEST_PART_H_ diff --git a/googletest/include/gtest/gtest-typed-test.h b/googletest/include/gtest/gtest-typed-test.h new file mode 100644 index 00000000..442e00bd --- /dev/null +++ b/googletest/include/gtest/gtest-typed-test.h @@ -0,0 +1,331 @@ +// Copyright 2008 Google Inc. +// All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// IWYU pragma: private, include "gtest/gtest.h" +// IWYU pragma: friend gtest/.* +// IWYU pragma: friend gmock/.* + +#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_TYPED_TEST_H_ +#define GOOGLETEST_INCLUDE_GTEST_GTEST_TYPED_TEST_H_ + +// This header implements typed tests and type-parameterized tests. + +// Typed (aka type-driven) tests repeat the same test for types in a +// list. You must know which types you want to test with when writing +// typed tests. Here's how you do it: + +#if 0 + +// First, define a fixture class template. It should be parameterized +// by a type. Remember to derive it from testing::Test. +template +class FooTest : public testing::Test { + public: + ... + typedef std::list List; + static T shared_; + T value_; +}; + +// Next, associate a list of types with the test suite, which will be +// repeated for each type in the list. The typedef is necessary for +// the macro to parse correctly. +typedef testing::Types MyTypes; +TYPED_TEST_SUITE(FooTest, MyTypes); + +// If the type list contains only one type, you can write that type +// directly without Types<...>: +// TYPED_TEST_SUITE(FooTest, int); + +// Then, use TYPED_TEST() instead of TEST_F() to define as many typed +// tests for this test suite as you want. +TYPED_TEST(FooTest, DoesBlah) { + // Inside a test, refer to the special name TypeParam to get the type + // parameter. Since we are inside a derived class template, C++ requires + // us to visit the members of FooTest via 'this'. + TypeParam n = this->value_; + + // To visit static members of the fixture, add the TestFixture:: + // prefix. + n += TestFixture::shared_; + + // To refer to typedefs in the fixture, add the "typename + // TestFixture::" prefix. + typename TestFixture::List values; + values.push_back(n); + ... +} + +TYPED_TEST(FooTest, HasPropertyA) { ... } + +// TYPED_TEST_SUITE takes an optional third argument which allows to specify a +// class that generates custom test name suffixes based on the type. This should +// be a class which has a static template function GetName(int index) returning +// a string for each type. The provided integer index equals the index of the +// type in the provided type list. In many cases the index can be ignored. +// +// For example: +// class MyTypeNames { +// public: +// template +// static std::string GetName(int) { +// if (std::is_same()) return "char"; +// if (std::is_same()) return "int"; +// if (std::is_same()) return "unsignedInt"; +// } +// }; +// TYPED_TEST_SUITE(FooTest, MyTypes, MyTypeNames); + +#endif // 0 + +// Type-parameterized tests are abstract test patterns parameterized +// by a type. Compared with typed tests, type-parameterized tests +// allow you to define the test pattern without knowing what the type +// parameters are. The defined pattern can be instantiated with +// different types any number of times, in any number of translation +// units. +// +// If you are designing an interface or concept, you can define a +// suite of type-parameterized tests to verify properties that any +// valid implementation of the interface/concept should have. Then, +// each implementation can easily instantiate the test suite to verify +// that it conforms to the requirements, without having to write +// similar tests repeatedly. Here's an example: + +#if 0 + +// First, define a fixture class template. It should be parameterized +// by a type. Remember to derive it from testing::Test. +template +class FooTest : public testing::Test { + ... +}; + +// Next, declare that you will define a type-parameterized test suite +// (the _P suffix is for "parameterized" or "pattern", whichever you +// prefer): +TYPED_TEST_SUITE_P(FooTest); + +// Then, use TYPED_TEST_P() to define as many type-parameterized tests +// for this type-parameterized test suite as you want. +TYPED_TEST_P(FooTest, DoesBlah) { + // Inside a test, refer to TypeParam to get the type parameter. + TypeParam n = 0; + ... +} + +TYPED_TEST_P(FooTest, HasPropertyA) { ... } + +// Now the tricky part: you need to register all test patterns before +// you can instantiate them. The first argument of the macro is the +// test suite name; the rest are the names of the tests in this test +// case. +REGISTER_TYPED_TEST_SUITE_P(FooTest, + DoesBlah, HasPropertyA); + +// Finally, you are free to instantiate the pattern with the types you +// want. If you put the above code in a header file, you can #include +// it in multiple C++ source files and instantiate it multiple times. +// +// To distinguish different instances of the pattern, the first +// argument to the INSTANTIATE_* macro is a prefix that will be added +// to the actual test suite name. Remember to pick unique prefixes for +// different instances. +typedef testing::Types MyTypes; +INSTANTIATE_TYPED_TEST_SUITE_P(My, FooTest, MyTypes); + +// If the type list contains only one type, you can write that type +// directly without Types<...>: +// INSTANTIATE_TYPED_TEST_SUITE_P(My, FooTest, int); +// +// Similar to the optional argument of TYPED_TEST_SUITE above, +// INSTANTIATE_TEST_SUITE_P takes an optional fourth argument which allows to +// generate custom names. +// INSTANTIATE_TYPED_TEST_SUITE_P(My, FooTest, MyTypes, MyTypeNames); + +#endif // 0 + +#include "gtest/internal/gtest-internal.h" +#include "gtest/internal/gtest-port.h" +#include "gtest/internal/gtest-type-util.h" + +// Implements typed tests. + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Expands to the name of the typedef for the type parameters of the +// given test suite. +#define GTEST_TYPE_PARAMS_(TestSuiteName) gtest_type_params_##TestSuiteName##_ + +// Expands to the name of the typedef for the NameGenerator, responsible for +// creating the suffixes of the name. +#define GTEST_NAME_GENERATOR_(TestSuiteName) \ + gtest_type_params_##TestSuiteName##_NameGenerator + +#define TYPED_TEST_SUITE(CaseName, Types, ...) \ + typedef ::testing::internal::GenerateTypeList::type \ + GTEST_TYPE_PARAMS_(CaseName); \ + typedef ::testing::internal::NameGeneratorSelector<__VA_ARGS__>::type \ + GTEST_NAME_GENERATOR_(CaseName) + +#define TYPED_TEST(CaseName, TestName) \ + static_assert(sizeof(GTEST_STRINGIFY_(TestName)) > 1, \ + "test-name must not be empty"); \ + template \ + class GTEST_TEST_CLASS_NAME_(CaseName, TestName) \ + : public CaseName { \ + private: \ + typedef CaseName TestFixture; \ + typedef gtest_TypeParam_ TypeParam; \ + void TestBody() override; \ + }; \ + [[maybe_unused]] static bool gtest_##CaseName##_##TestName##_registered_ = \ + ::testing::internal::TypeParameterizedTest< \ + CaseName, \ + ::testing::internal::TemplateSel, \ + GTEST_TYPE_PARAMS_( \ + CaseName)>::Register("", \ + ::testing::internal::CodeLocation( \ + __FILE__, __LINE__), \ + GTEST_STRINGIFY_(CaseName), \ + GTEST_STRINGIFY_(TestName), 0, \ + ::testing::internal::GenerateNames< \ + GTEST_NAME_GENERATOR_(CaseName), \ + GTEST_TYPE_PARAMS_(CaseName)>()); \ + template \ + void GTEST_TEST_CLASS_NAME_(CaseName, \ + TestName)::TestBody() + +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ +#define TYPED_TEST_CASE \ + static_assert(::testing::internal::TypedTestCaseIsDeprecated(), ""); \ + TYPED_TEST_SUITE +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + +// Implements type-parameterized tests. + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Expands to the namespace name that the type-parameterized tests for +// the given type-parameterized test suite are defined in. The exact +// name of the namespace is subject to change without notice. +#define GTEST_SUITE_NAMESPACE_(TestSuiteName) gtest_suite_##TestSuiteName##_ + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Expands to the name of the variable used to remember the names of +// the defined tests in the given test suite. +#define GTEST_TYPED_TEST_SUITE_P_STATE_(TestSuiteName) \ + gtest_typed_test_suite_p_state_##TestSuiteName##_ + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE DIRECTLY. +// +// Expands to the name of the variable used to remember the names of +// the registered tests in the given test suite. +#define GTEST_REGISTERED_TEST_NAMES_(TestSuiteName) \ + gtest_registered_test_names_##TestSuiteName##_ + +// The variables defined in the type-parameterized test macros are +// static as typically these macros are used in a .h file that can be +// #included in multiple translation units linked together. +#define TYPED_TEST_SUITE_P(SuiteName) \ + static ::testing::internal::TypedTestSuitePState \ + GTEST_TYPED_TEST_SUITE_P_STATE_(SuiteName) + +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ +#define TYPED_TEST_CASE_P \ + static_assert(::testing::internal::TypedTestCase_P_IsDeprecated(), ""); \ + TYPED_TEST_SUITE_P +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + +#define TYPED_TEST_P(SuiteName, TestName) \ + namespace GTEST_SUITE_NAMESPACE_(SuiteName) { \ + template \ + class TestName : public SuiteName { \ + private: \ + typedef SuiteName TestFixture; \ + typedef gtest_TypeParam_ TypeParam; \ + void TestBody() override; \ + }; \ + [[maybe_unused]] static bool gtest_##TestName##_defined_ = \ + GTEST_TYPED_TEST_SUITE_P_STATE_(SuiteName).AddTestName( \ + __FILE__, __LINE__, GTEST_STRINGIFY_(SuiteName), \ + GTEST_STRINGIFY_(TestName)); \ + } \ + template \ + void GTEST_SUITE_NAMESPACE_( \ + SuiteName)::TestName::TestBody() + +// Note: this won't work correctly if the trailing arguments are macros. +#define REGISTER_TYPED_TEST_SUITE_P(SuiteName, ...) \ + namespace GTEST_SUITE_NAMESPACE_(SuiteName) { \ + typedef ::testing::internal::Templates<__VA_ARGS__> gtest_AllTests_; \ + } \ + [[maybe_unused]] static const char* const GTEST_REGISTERED_TEST_NAMES_( \ + SuiteName) = \ + GTEST_TYPED_TEST_SUITE_P_STATE_(SuiteName).VerifyRegisteredTestNames( \ + GTEST_STRINGIFY_(SuiteName), __FILE__, __LINE__, #__VA_ARGS__) + +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ +#define REGISTER_TYPED_TEST_CASE_P \ + static_assert(::testing::internal::RegisterTypedTestCase_P_IsDeprecated(), \ + ""); \ + REGISTER_TYPED_TEST_SUITE_P +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + +#define INSTANTIATE_TYPED_TEST_SUITE_P(Prefix, SuiteName, Types, ...) \ + static_assert(sizeof(GTEST_STRINGIFY_(Prefix)) > 1, \ + "test-suit-prefix must not be empty"); \ + [[maybe_unused]] static bool gtest_##Prefix##_##SuiteName = \ + ::testing::internal::TypeParameterizedTestSuite< \ + SuiteName, GTEST_SUITE_NAMESPACE_(SuiteName)::gtest_AllTests_, \ + ::testing::internal::GenerateTypeList::type>:: \ + Register(GTEST_STRINGIFY_(Prefix), \ + ::testing::internal::CodeLocation(__FILE__, __LINE__), \ + >EST_TYPED_TEST_SUITE_P_STATE_(SuiteName), \ + GTEST_STRINGIFY_(SuiteName), \ + GTEST_REGISTERED_TEST_NAMES_(SuiteName), \ + ::testing::internal::GenerateNames< \ + ::testing::internal::NameGeneratorSelector< \ + __VA_ARGS__>::type, \ + ::testing::internal::GenerateTypeList::type>()) + +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ +#define INSTANTIATE_TYPED_TEST_CASE_P \ + static_assert( \ + ::testing::internal::InstantiateTypedTestCase_P_IsDeprecated(), ""); \ + INSTANTIATE_TYPED_TEST_SUITE_P +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + +#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_TYPED_TEST_H_ diff --git a/googletest/include/gtest/gtest.h b/googletest/include/gtest/gtest.h new file mode 100644 index 00000000..7be0caaf --- /dev/null +++ b/googletest/include/gtest/gtest.h @@ -0,0 +1,2338 @@ +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// The Google C++ Testing and Mocking Framework (Google Test) +// +// This header file defines the public API for Google Test. It should be +// included by any test program that uses Google Test. +// +// IMPORTANT NOTE: Due to limitation of the C++ language, we have to +// leave some internal implementation details in this header file. +// They are clearly marked by comments like this: +// +// // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +// +// Such code is NOT meant to be used by a user directly, and is subject +// to CHANGE WITHOUT NOTICE. Therefore DO NOT DEPEND ON IT in a user +// program! +// +// Acknowledgment: Google Test borrowed the idea of automatic test +// registration from Barthelemy Dagenais' (barthelemy@prologique.com) +// easyUnit framework. + +#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_H_ +#define GOOGLETEST_INCLUDE_GTEST_GTEST_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gtest/gtest-assertion-result.h" // IWYU pragma: export +#include "gtest/gtest-death-test.h" // IWYU pragma: export +#include "gtest/gtest-matchers.h" // IWYU pragma: export +#include "gtest/gtest-message.h" // IWYU pragma: export +#include "gtest/gtest-param-test.h" // IWYU pragma: export +#include "gtest/gtest-printers.h" // IWYU pragma: export +#include "gtest/gtest-test-part.h" // IWYU pragma: export +#include "gtest/gtest-typed-test.h" // IWYU pragma: export +#include "gtest/gtest_pred_impl.h" // IWYU pragma: export +#include "gtest/gtest_prod.h" // IWYU pragma: export +#include "gtest/internal/gtest-internal.h" +#include "gtest/internal/gtest-string.h" + +GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ +/* class A needs to have dll-interface to be used by clients of class B */) + +// Declares the flags. + +// This flag temporary enables the disabled tests. +GTEST_DECLARE_bool_(also_run_disabled_tests); + +// This flag brings the debugger on an assertion failure. +GTEST_DECLARE_bool_(break_on_failure); + +// This flag controls whether Google Test catches all test-thrown exceptions +// and logs them as failures. +GTEST_DECLARE_bool_(catch_exceptions); + +// This flag enables using colors in terminal output. Available values are +// "yes" to enable colors, "no" (disable colors), or "auto" (the default) +// to let Google Test decide. +GTEST_DECLARE_string_(color); + +// This flag controls whether the test runner should continue execution past +// first failure. +GTEST_DECLARE_bool_(fail_fast); + +// This flag sets up the filter to select by name using a glob pattern +// the tests to run. If the filter is not given all tests are executed. +GTEST_DECLARE_string_(filter); + +// This flag controls whether Google Test installs a signal handler that dumps +// debugging information when fatal signals are raised. +GTEST_DECLARE_bool_(install_failure_signal_handler); + +// This flag causes the Google Test to list tests. None of the tests listed +// are actually run if the flag is provided. +GTEST_DECLARE_bool_(list_tests); + +// This flag controls whether Google Test emits a detailed XML report to a file +// in addition to its normal textual output. +GTEST_DECLARE_string_(output); + +// This flags control whether Google Test prints only test failures. +GTEST_DECLARE_bool_(brief); + +// This flags control whether Google Test prints the elapsed time for each +// test. +GTEST_DECLARE_bool_(print_time); + +// This flags control whether Google Test prints UTF8 characters as text. +GTEST_DECLARE_bool_(print_utf8); + +// This flag specifies the random number seed. +GTEST_DECLARE_int32_(random_seed); + +// This flag sets how many times the tests are repeated. The default value +// is 1. If the value is -1 the tests are repeating forever. +GTEST_DECLARE_int32_(repeat); + +// This flag controls whether Google Test Environments are recreated for each +// repeat of the tests. The default value is true. If set to false the global +// test Environment objects are only set up once, for the first iteration, and +// only torn down once, for the last. +GTEST_DECLARE_bool_(recreate_environments_when_repeating); + +// This flag controls whether Google Test includes Google Test internal +// stack frames in failure stack traces. +GTEST_DECLARE_bool_(show_internal_stack_frames); + +// When this flag is specified, tests' order is randomized on every iteration. +GTEST_DECLARE_bool_(shuffle); + +// This flag specifies the maximum number of stack frames to be +// printed in a failure message. +GTEST_DECLARE_int32_(stack_trace_depth); + +// When this flag is specified, a failed assertion will throw an +// exception if exceptions are enabled, or exit the program with a +// non-zero code otherwise. For use with an external test framework. +GTEST_DECLARE_bool_(throw_on_failure); + +// When this flag is set with a "host:port" string, on supported +// platforms test results are streamed to the specified port on +// the specified host machine. +GTEST_DECLARE_string_(stream_result_to); + +#if GTEST_USE_OWN_FLAGFILE_FLAG_ +GTEST_DECLARE_string_(flagfile); +#endif // GTEST_USE_OWN_FLAGFILE_FLAG_ + +namespace testing { + +// Silence C4100 (unreferenced formal parameter) and 4805 +// unsafe mix of type 'const int' and type 'const bool' +GTEST_DISABLE_MSC_WARNINGS_PUSH_(4805 4100) + +// The upper limit for valid stack trace depths. +const int kMaxStackTraceDepth = 100; + +namespace internal { + +class AssertHelper; +class DefaultGlobalTestPartResultReporter; +class ExecDeathTest; +class NoExecDeathTest; +class FinalSuccessChecker; +class GTestFlagSaver; +class StreamingListenerTest; +class TestResultAccessor; +class TestEventListenersAccessor; +class TestEventRepeater; +class UnitTestRecordPropertyTestHelper; +class WindowsDeathTest; +class FuchsiaDeathTest; +class UnitTestImpl* GetUnitTestImpl(); +void ReportFailureInUnknownLocation(TestPartResult::Type result_type, + const std::string& message); +std::set* GetIgnoredParameterizedTestSuites(); + +// A base class that prevents subclasses from being copyable. +// We do this instead of using '= delete' so as to avoid triggering warnings +// inside user code regarding any of our declarations. +class GTestNonCopyable { + public: + GTestNonCopyable() = default; + GTestNonCopyable(const GTestNonCopyable&) = delete; + GTestNonCopyable& operator=(const GTestNonCopyable&) = delete; + ~GTestNonCopyable() = default; +}; + +} // namespace internal + +// The friend relationship of some of these classes is cyclic. +// If we don't forward declare them the compiler might confuse the classes +// in friendship clauses with same named classes on the scope. +class Test; +class TestSuite; + +// Old API is still available but deprecated +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ +using TestCase = TestSuite; +#endif +class TestInfo; +class UnitTest; + +// The abstract class that all tests inherit from. +// +// In Google Test, a unit test program contains one or many TestSuites, and +// each TestSuite contains one or many Tests. +// +// When you define a test using the TEST macro, you don't need to +// explicitly derive from Test - the TEST macro automatically does +// this for you. +// +// The only time you derive from Test is when defining a test fixture +// to be used in a TEST_F. For example: +// +// class FooTest : public testing::Test { +// protected: +// void SetUp() override { ... } +// void TearDown() override { ... } +// ... +// }; +// +// TEST_F(FooTest, Bar) { ... } +// TEST_F(FooTest, Baz) { ... } +// +// Test is not copyable. +class GTEST_API_ Test { + public: + friend class TestInfo; + + // The d'tor is virtual as we intend to inherit from Test. + virtual ~Test(); + + // Sets up the stuff shared by all tests in this test suite. + // + // Google Test will call Foo::SetUpTestSuite() before running the first + // test in test suite Foo. Hence a sub-class can define its own + // SetUpTestSuite() method to shadow the one defined in the super + // class. + static void SetUpTestSuite() {} + + // Tears down the stuff shared by all tests in this test suite. + // + // Google Test will call Foo::TearDownTestSuite() after running the last + // test in test suite Foo. Hence a sub-class can define its own + // TearDownTestSuite() method to shadow the one defined in the super + // class. + static void TearDownTestSuite() {} + + // Legacy API is deprecated but still available. Use SetUpTestSuite and + // TearDownTestSuite instead. +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + static void TearDownTestCase() {} + static void SetUpTestCase() {} +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + + // Returns true if and only if the current test has a fatal failure. + static bool HasFatalFailure(); + + // Returns true if and only if the current test has a non-fatal failure. + static bool HasNonfatalFailure(); + + // Returns true if and only if the current test was skipped. + static bool IsSkipped(); + + // Returns true if and only if the current test has a (either fatal or + // non-fatal) failure. + static bool HasFailure() { return HasFatalFailure() || HasNonfatalFailure(); } + + // Logs a property for the current test, test suite, or for the entire + // invocation of the test program when used outside of the context of a + // test suite. Only the last value for a given key is remembered. These + // are public static so they can be called from utility functions that are + // not members of the test fixture. Calls to RecordProperty made during + // lifespan of the test (from the moment its constructor starts to the + // moment its destructor finishes) will be output in XML as attributes of + // the element. Properties recorded from fixture's + // SetUpTestSuite or TearDownTestSuite are logged as attributes of the + // corresponding element. Calls to RecordProperty made in the + // global context (before or after invocation of RUN_ALL_TESTS and from + // SetUp/TearDown method of Environment objects registered with Google + // Test) will be output as attributes of the element. + static void RecordProperty(const std::string& key, const std::string& value); + // We do not define a custom serialization except for values that can be + // converted to int64_t, but other values could be logged in this way. + template ::value, + bool> = true> + static void RecordProperty(const std::string& key, const T& value) { + RecordProperty(key, (Message() << value).GetString()); + } + + protected: + // Creates a Test object. + Test(); + + // Sets up the test fixture. + virtual void SetUp(); + + // Tears down the test fixture. + virtual void TearDown(); + + private: + // Returns true if and only if the current test has the same fixture class + // as the first test in the current test suite. + static bool HasSameFixtureClass(); + + // Runs the test after the test fixture has been set up. + // + // A sub-class must implement this to define the test logic. + // + // DO NOT OVERRIDE THIS FUNCTION DIRECTLY IN A USER PROGRAM. + // Instead, use the TEST or TEST_F macro. + virtual void TestBody() = 0; + + // Sets up, executes, and tears down the test. + void Run(); + + // Deletes self. We deliberately pick an unusual name for this + // internal method to avoid clashing with names used in user TESTs. + void DeleteSelf_() { delete this; } + + const std::unique_ptr gtest_flag_saver_; + + // Often a user misspells SetUp() as Setup() and spends a long time + // wondering why it is never called by Google Test. The declaration of + // the following method is solely for catching such an error at + // compile time: + // + // - The return type is deliberately chosen to be not void, so it + // will be a conflict if void Setup() is declared in the user's + // test fixture. + // + // - This method is private, so it will be another compiler error + // if the method is called from the user's test fixture. + // + // DO NOT OVERRIDE THIS FUNCTION. + // + // If you see an error about overriding the following function or + // about it being private, you have mis-spelled SetUp() as Setup(). + struct Setup_should_be_spelled_SetUp {}; + virtual Setup_should_be_spelled_SetUp* Setup() { return nullptr; } + + // We disallow copying Tests. + Test(const Test&) = delete; + Test& operator=(const Test&) = delete; +}; + +typedef internal::TimeInMillis TimeInMillis; + +// A copyable object representing a user specified test property which can be +// output as a key/value string pair. +// +// Don't inherit from TestProperty as its destructor is not virtual. +class TestProperty { + public: + // C'tor. TestProperty does NOT have a default constructor. + // Always use this constructor (with parameters) to create a + // TestProperty object. + TestProperty(const std::string& a_key, const std::string& a_value) + : key_(a_key), value_(a_value) {} + + // Gets the user supplied key. + const char* key() const { return key_.c_str(); } + + // Gets the user supplied value. + const char* value() const { return value_.c_str(); } + + // Sets a new value, overriding the one supplied in the constructor. + void SetValue(const std::string& new_value) { value_ = new_value; } + + private: + // The key supplied by the user. + std::string key_; + // The value supplied by the user. + std::string value_; +}; + +// The result of a single Test. This includes a list of +// TestPartResults, a list of TestProperties, a count of how many +// death tests there are in the Test, and how much time it took to run +// the Test. +// +// TestResult is not copyable. +class GTEST_API_ TestResult { + public: + // Creates an empty TestResult. + TestResult(); + + // D'tor. Do not inherit from TestResult. + ~TestResult(); + + // Gets the number of all test parts. This is the sum of the number + // of successful test parts and the number of failed test parts. + int total_part_count() const; + + // Returns the number of the test properties. + int test_property_count() const; + + // Returns true if and only if the test passed (i.e. no test part failed). + bool Passed() const { return !Skipped() && !Failed(); } + + // Returns true if and only if the test was skipped. + bool Skipped() const; + + // Returns true if and only if the test failed. + bool Failed() const; + + // Returns true if and only if the test fatally failed. + bool HasFatalFailure() const; + + // Returns true if and only if the test has a non-fatal failure. + bool HasNonfatalFailure() const; + + // Returns the elapsed time, in milliseconds. + TimeInMillis elapsed_time() const { return elapsed_time_; } + + // Gets the time of the test case start, in ms from the start of the + // UNIX epoch. + TimeInMillis start_timestamp() const { return start_timestamp_; } + + // Returns the i-th test part result among all the results. i can range from 0 + // to total_part_count() - 1. If i is not in that range, aborts the program. + const TestPartResult& GetTestPartResult(int i) const; + + // Returns the i-th test property. i can range from 0 to + // test_property_count() - 1. If i is not in that range, aborts the + // program. + const TestProperty& GetTestProperty(int i) const; + + private: + friend class TestInfo; + friend class TestSuite; + friend class UnitTest; + friend class internal::DefaultGlobalTestPartResultReporter; + friend class internal::ExecDeathTest; + friend class internal::TestResultAccessor; + friend class internal::UnitTestImpl; + friend class internal::WindowsDeathTest; + friend class internal::FuchsiaDeathTest; + + // Gets the vector of TestPartResults. + const std::vector& test_part_results() const { + return test_part_results_; + } + + // Gets the vector of TestProperties. + const std::vector& test_properties() const { + return test_properties_; + } + + // Sets the start time. + void set_start_timestamp(TimeInMillis start) { start_timestamp_ = start; } + + // Sets the elapsed time. + void set_elapsed_time(TimeInMillis elapsed) { elapsed_time_ = elapsed; } + + // Adds a test property to the list. The property is validated and may add + // a non-fatal failure if invalid (e.g., if it conflicts with reserved + // key names). If a property is already recorded for the same key, the + // value will be updated, rather than storing multiple values for the same + // key. xml_element specifies the element for which the property is being + // recorded and is used for validation. + void RecordProperty(const std::string& xml_element, + const TestProperty& test_property); + + // Adds a failure if the key is a reserved attribute of Google Test + // testsuite tags. Returns true if the property is valid. + // FIXME: Validate attribute names are legal and human readable. + static bool ValidateTestProperty(const std::string& xml_element, + const TestProperty& test_property); + + // Adds a test part result to the list. + void AddTestPartResult(const TestPartResult& test_part_result); + + // Returns the death test count. + int death_test_count() const { return death_test_count_; } + + // Increments the death test count, returning the new count. + int increment_death_test_count() { return ++death_test_count_; } + + // Clears the test part results. + void ClearTestPartResults(); + + // Clears the object. + void Clear(); + + // Protects mutable state of the property vector and of owned + // properties, whose values may be updated. + internal::Mutex test_properties_mutex_; + + // The vector of TestPartResults + std::vector test_part_results_; + // The vector of TestProperties + std::vector test_properties_; + // Running count of death tests. + int death_test_count_; + // The start time, in milliseconds since UNIX Epoch. + TimeInMillis start_timestamp_; + // The elapsed time, in milliseconds. + TimeInMillis elapsed_time_; + + // We disallow copying TestResult. + TestResult(const TestResult&) = delete; + TestResult& operator=(const TestResult&) = delete; +}; // class TestResult + +// A TestInfo object stores the following information about a test: +// +// Test suite name +// Test name +// Whether the test should be run +// A function pointer that creates the test object when invoked +// Test result +// +// The constructor of TestInfo registers itself with the UnitTest +// singleton such that the RUN_ALL_TESTS() macro knows which tests to +// run. +class GTEST_API_ TestInfo { + public: + // Destructs a TestInfo object. This function is not virtual, so + // don't inherit from TestInfo. + ~TestInfo(); + + // Returns the test suite name. + const char* test_suite_name() const { return test_suite_name_.c_str(); } + +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + const char* test_case_name() const { return test_suite_name(); } +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + + // Returns the test name. + const char* name() const { return name_.c_str(); } + + // Returns the name of the parameter type, or NULL if this is not a typed + // or a type-parameterized test. + const char* type_param() const { + if (type_param_ != nullptr) return type_param_->c_str(); + return nullptr; + } + + // Returns the text representation of the value parameter, or NULL if this + // is not a value-parameterized test. + const char* value_param() const { + if (value_param_ != nullptr) return value_param_->c_str(); + return nullptr; + } + + // Returns the file name where this test is defined. + const char* file() const { return location_.file.c_str(); } + + // Returns the line where this test is defined. + int line() const { return location_.line; } + + // Return true if this test should not be run because it's in another shard. + bool is_in_another_shard() const { return is_in_another_shard_; } + + // Returns true if this test should run, that is if the test is not + // disabled (or it is disabled but the also_run_disabled_tests flag has + // been specified) and its full name matches the user-specified filter. + // + // Google Test allows the user to filter the tests by their full names. + // The full name of a test Bar in test suite Foo is defined as + // "Foo.Bar". Only the tests that match the filter will run. + // + // A filter is a colon-separated list of glob (not regex) patterns, + // optionally followed by a '-' and a colon-separated list of + // negative patterns (tests to exclude). A test is run if it + // matches one of the positive patterns and does not match any of + // the negative patterns. + // + // For example, *A*:Foo.* is a filter that matches any string that + // contains the character 'A' or starts with "Foo.". + bool should_run() const { return should_run_; } + + // Returns true if and only if this test will appear in the XML report. + bool is_reportable() const { + // The XML report includes tests matching the filter, excluding those + // run in other shards. + return matches_filter_ && !is_in_another_shard_; + } + + // Returns the result of the test. + const TestResult* result() const { return &result_; } + + private: +#ifdef GTEST_HAS_DEATH_TEST + friend class internal::DefaultDeathTestFactory; +#endif // GTEST_HAS_DEATH_TEST + friend class Test; + friend class TestSuite; + friend class internal::UnitTestImpl; + friend class internal::StreamingListenerTest; + friend TestInfo* internal::MakeAndRegisterTestInfo( + std::string test_suite_name, const char* name, const char* type_param, + const char* value_param, internal::CodeLocation code_location, + internal::TypeId fixture_class_id, internal::SetUpTestSuiteFunc set_up_tc, + internal::TearDownTestSuiteFunc tear_down_tc, + internal::TestFactoryBase* factory); + + // Constructs a TestInfo object. The newly constructed instance assumes + // ownership of the factory object. + TestInfo(std::string test_suite_name, std::string name, + const char* a_type_param, // NULL if not a type-parameterized test + const char* a_value_param, // NULL if not a value-parameterized test + internal::CodeLocation a_code_location, + internal::TypeId fixture_class_id, + internal::TestFactoryBase* factory); + + // Increments the number of death tests encountered in this test so + // far. + int increment_death_test_count() { + return result_.increment_death_test_count(); + } + + // Creates the test object, runs it, records its result, and then + // deletes it. + void Run(); + + // Skip and records the test result for this object. + void Skip(); + + static void ClearTestResult(TestInfo* test_info) { + test_info->result_.Clear(); + } + + // These fields are immutable properties of the test. + const std::string test_suite_name_; // test suite name + const std::string name_; // Test name + // Name of the parameter type, or NULL if this is not a typed or a + // type-parameterized test. + const std::unique_ptr type_param_; + // Text representation of the value parameter, or NULL if this is not a + // value-parameterized test. + const std::unique_ptr value_param_; + internal::CodeLocation location_; + const internal::TypeId fixture_class_id_; // ID of the test fixture class + bool should_run_; // True if and only if this test should run + bool is_disabled_; // True if and only if this test is disabled + bool matches_filter_; // True if this test matches the + // user-specified filter. + bool is_in_another_shard_; // Will be run in another shard. + internal::TestFactoryBase* const factory_; // The factory that creates + // the test object + + // This field is mutable and needs to be reset before running the + // test for the second time. + TestResult result_; + + TestInfo(const TestInfo&) = delete; + TestInfo& operator=(const TestInfo&) = delete; +}; + +// A test suite, which consists of a vector of TestInfos. +// +// TestSuite is not copyable. +class GTEST_API_ TestSuite { + public: + // Creates a TestSuite with the given name. + // + // TestSuite does NOT have a default constructor. Always use this + // constructor to create a TestSuite object. + // + // Arguments: + // + // name: name of the test suite + // a_type_param: the name of the test's type parameter, or NULL if + // this is not a type-parameterized test. + // set_up_tc: pointer to the function that sets up the test suite + // tear_down_tc: pointer to the function that tears down the test suite + TestSuite(const std::string& name, const char* a_type_param, + internal::SetUpTestSuiteFunc set_up_tc, + internal::TearDownTestSuiteFunc tear_down_tc); + + // Destructor of TestSuite. + virtual ~TestSuite(); + + // Gets the name of the TestSuite. + const char* name() const { return name_.c_str(); } + + // Returns the name of the parameter type, or NULL if this is not a + // type-parameterized test suite. + const char* type_param() const { + if (type_param_ != nullptr) return type_param_->c_str(); + return nullptr; + } + + // Returns true if any test in this test suite should run. + bool should_run() const { return should_run_; } + + // Gets the number of successful tests in this test suite. + int successful_test_count() const; + + // Gets the number of skipped tests in this test suite. + int skipped_test_count() const; + + // Gets the number of failed tests in this test suite. + int failed_test_count() const; + + // Gets the number of disabled tests that will be reported in the XML report. + int reportable_disabled_test_count() const; + + // Gets the number of disabled tests in this test suite. + int disabled_test_count() const; + + // Gets the number of tests to be printed in the XML report. + int reportable_test_count() const; + + // Get the number of tests in this test suite that should run. + int test_to_run_count() const; + + // Gets the number of all tests in this test suite. + int total_test_count() const; + + // Returns true if and only if the test suite passed. + bool Passed() const { return !Failed(); } + + // Returns true if and only if the test suite failed. + bool Failed() const { + return failed_test_count() > 0 || ad_hoc_test_result().Failed(); + } + + // Returns the elapsed time, in milliseconds. + TimeInMillis elapsed_time() const { return elapsed_time_; } + + // Gets the time of the test suite start, in ms from the start of the + // UNIX epoch. + TimeInMillis start_timestamp() const { return start_timestamp_; } + + // Returns the i-th test among all the tests. i can range from 0 to + // total_test_count() - 1. If i is not in that range, returns NULL. + const TestInfo* GetTestInfo(int i) const; + + // Returns the TestResult that holds test properties recorded during + // execution of SetUpTestSuite and TearDownTestSuite. + const TestResult& ad_hoc_test_result() const { return ad_hoc_test_result_; } + + private: + friend class Test; + friend class internal::UnitTestImpl; + + // Gets the (mutable) vector of TestInfos in this TestSuite. + std::vector& test_info_list() { return test_info_list_; } + + // Gets the (immutable) vector of TestInfos in this TestSuite. + const std::vector& test_info_list() const { + return test_info_list_; + } + + // Returns the i-th test among all the tests. i can range from 0 to + // total_test_count() - 1. If i is not in that range, returns NULL. + TestInfo* GetMutableTestInfo(int i); + + // Sets the should_run member. + void set_should_run(bool should) { should_run_ = should; } + + // Adds a TestInfo to this test suite. Will delete the TestInfo upon + // destruction of the TestSuite object. + void AddTestInfo(TestInfo* test_info); + + // Clears the results of all tests in this test suite. + void ClearResult(); + + // Clears the results of all tests in the given test suite. + static void ClearTestSuiteResult(TestSuite* test_suite) { + test_suite->ClearResult(); + } + + // Runs every test in this TestSuite. + void Run(); + + // Skips the execution of tests under this TestSuite + void Skip(); + + // Runs SetUpTestSuite() for this TestSuite. This wrapper is needed + // for catching exceptions thrown from SetUpTestSuite(). + void RunSetUpTestSuite() { + if (set_up_tc_ != nullptr) { + (*set_up_tc_)(); + } + } + + // Runs TearDownTestSuite() for this TestSuite. This wrapper is + // needed for catching exceptions thrown from TearDownTestSuite(). + void RunTearDownTestSuite() { + if (tear_down_tc_ != nullptr) { + (*tear_down_tc_)(); + } + } + + // Returns true if and only if test passed. + static bool TestPassed(const TestInfo* test_info) { + return test_info->should_run() && test_info->result()->Passed(); + } + + // Returns true if and only if test skipped. + static bool TestSkipped(const TestInfo* test_info) { + return test_info->should_run() && test_info->result()->Skipped(); + } + + // Returns true if and only if test failed. + static bool TestFailed(const TestInfo* test_info) { + return test_info->should_run() && test_info->result()->Failed(); + } + + // Returns true if and only if the test is disabled and will be reported in + // the XML report. + static bool TestReportableDisabled(const TestInfo* test_info) { + return test_info->is_reportable() && test_info->is_disabled_; + } + + // Returns true if and only if test is disabled. + static bool TestDisabled(const TestInfo* test_info) { + return test_info->is_disabled_; + } + + // Returns true if and only if this test will appear in the XML report. + static bool TestReportable(const TestInfo* test_info) { + return test_info->is_reportable(); + } + + // Returns true if the given test should run. + static bool ShouldRunTest(const TestInfo* test_info) { + return test_info->should_run(); + } + + // Shuffles the tests in this test suite. + void ShuffleTests(internal::Random* random); + + // Restores the test order to before the first shuffle. + void UnshuffleTests(); + + // Name of the test suite. + std::string name_; + // Name of the parameter type, or NULL if this is not a typed or a + // type-parameterized test. + const std::unique_ptr type_param_; + // The vector of TestInfos in their original order. It owns the + // elements in the vector. + std::vector test_info_list_; + // Provides a level of indirection for the test list to allow easy + // shuffling and restoring the test order. The i-th element in this + // vector is the index of the i-th test in the shuffled test list. + std::vector test_indices_; + // Pointer to the function that sets up the test suite. + internal::SetUpTestSuiteFunc set_up_tc_; + // Pointer to the function that tears down the test suite. + internal::TearDownTestSuiteFunc tear_down_tc_; + // True if and only if any test in this test suite should run. + bool should_run_; + // The start time, in milliseconds since UNIX Epoch. + TimeInMillis start_timestamp_; + // Elapsed time, in milliseconds. + TimeInMillis elapsed_time_; + // Holds test properties recorded during execution of SetUpTestSuite and + // TearDownTestSuite. + TestResult ad_hoc_test_result_; + + // We disallow copying TestSuites. + TestSuite(const TestSuite&) = delete; + TestSuite& operator=(const TestSuite&) = delete; +}; + +// An Environment object is capable of setting up and tearing down an +// environment. You should subclass this to define your own +// environment(s). +// +// An Environment object does the set-up and tear-down in virtual +// methods SetUp() and TearDown() instead of the constructor and the +// destructor, as: +// +// 1. You cannot safely throw from a destructor. This is a problem +// as in some cases Google Test is used where exceptions are enabled, and +// we may want to implement ASSERT_* using exceptions where they are +// available. +// 2. You cannot use ASSERT_* directly in a constructor or +// destructor. +class Environment { + public: + // The d'tor is virtual as we need to subclass Environment. + virtual ~Environment() = default; + + // Override this to define how to set up the environment. + virtual void SetUp() {} + + // Override this to define how to tear down the environment. + virtual void TearDown() {} + + private: + // If you see an error about overriding the following function or + // about it being private, you have mis-spelled SetUp() as Setup(). + struct Setup_should_be_spelled_SetUp {}; + virtual Setup_should_be_spelled_SetUp* Setup() { return nullptr; } +}; + +#if GTEST_HAS_EXCEPTIONS + +// Exception which can be thrown from TestEventListener::OnTestPartResult. +class GTEST_API_ AssertionException + : public internal::GoogleTestFailureException { + public: + explicit AssertionException(const TestPartResult& result) + : GoogleTestFailureException(result) {} +}; + +#endif // GTEST_HAS_EXCEPTIONS + +// The interface for tracing execution of tests. The methods are organized in +// the order the corresponding events are fired. +class TestEventListener { + public: + virtual ~TestEventListener() = default; + + // Fired before any test activity starts. + virtual void OnTestProgramStart(const UnitTest& unit_test) = 0; + + // Fired before each iteration of tests starts. There may be more than + // one iteration if GTEST_FLAG(repeat) is set. iteration is the iteration + // index, starting from 0. + virtual void OnTestIterationStart(const UnitTest& unit_test, + int iteration) = 0; + + // Fired before environment set-up for each iteration of tests starts. + virtual void OnEnvironmentsSetUpStart(const UnitTest& unit_test) = 0; + + // Fired after environment set-up for each iteration of tests ends. + virtual void OnEnvironmentsSetUpEnd(const UnitTest& unit_test) = 0; + + // Fired before the test suite starts. + virtual void OnTestSuiteStart(const TestSuite& /*test_suite*/) {} + + // Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + virtual void OnTestCaseStart(const TestCase& /*test_case*/) {} +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + + // Fired before the test starts. + virtual void OnTestStart(const TestInfo& test_info) = 0; + + // Fired when a test is disabled + virtual void OnTestDisabled(const TestInfo& /*test_info*/) {} + + // Fired after a failed assertion or a SUCCEED() invocation. + // If you want to throw an exception from this function to skip to the next + // TEST, it must be AssertionException defined above, or inherited from it. + virtual void OnTestPartResult(const TestPartResult& test_part_result) = 0; + + // Fired after the test ends. + virtual void OnTestEnd(const TestInfo& test_info) = 0; + + // Fired after the test suite ends. + virtual void OnTestSuiteEnd(const TestSuite& /*test_suite*/) {} + +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + virtual void OnTestCaseEnd(const TestCase& /*test_case*/) {} +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + + // Fired before environment tear-down for each iteration of tests starts. + virtual void OnEnvironmentsTearDownStart(const UnitTest& unit_test) = 0; + + // Fired after environment tear-down for each iteration of tests ends. + virtual void OnEnvironmentsTearDownEnd(const UnitTest& unit_test) = 0; + + // Fired after each iteration of tests finishes. + virtual void OnTestIterationEnd(const UnitTest& unit_test, int iteration) = 0; + + // Fired after all test activities have ended. + virtual void OnTestProgramEnd(const UnitTest& unit_test) = 0; +}; + +// The convenience class for users who need to override just one or two +// methods and are not concerned that a possible change to a signature of +// the methods they override will not be caught during the build. For +// comments about each method please see the definition of TestEventListener +// above. +class EmptyTestEventListener : public TestEventListener { + public: + void OnTestProgramStart(const UnitTest& /*unit_test*/) override {} + void OnTestIterationStart(const UnitTest& /*unit_test*/, + int /*iteration*/) override {} + void OnEnvironmentsSetUpStart(const UnitTest& /*unit_test*/) override {} + void OnEnvironmentsSetUpEnd(const UnitTest& /*unit_test*/) override {} + void OnTestSuiteStart(const TestSuite& /*test_suite*/) override {} +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + void OnTestCaseStart(const TestCase& /*test_case*/) override {} +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + + void OnTestStart(const TestInfo& /*test_info*/) override {} + void OnTestDisabled(const TestInfo& /*test_info*/) override {} + void OnTestPartResult(const TestPartResult& /*test_part_result*/) override {} + void OnTestEnd(const TestInfo& /*test_info*/) override {} + void OnTestSuiteEnd(const TestSuite& /*test_suite*/) override {} +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + void OnTestCaseEnd(const TestCase& /*test_case*/) override {} +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + + void OnEnvironmentsTearDownStart(const UnitTest& /*unit_test*/) override {} + void OnEnvironmentsTearDownEnd(const UnitTest& /*unit_test*/) override {} + void OnTestIterationEnd(const UnitTest& /*unit_test*/, + int /*iteration*/) override {} + void OnTestProgramEnd(const UnitTest& /*unit_test*/) override {} +}; + +// TestEventListeners lets users add listeners to track events in Google Test. +class GTEST_API_ TestEventListeners { + public: + TestEventListeners(); + ~TestEventListeners(); + + // Appends an event listener to the end of the list. Google Test assumes + // the ownership of the listener (i.e. it will delete the listener when + // the test program finishes). + void Append(TestEventListener* listener); + + // Removes the given event listener from the list and returns it. It then + // becomes the caller's responsibility to delete the listener. Returns + // NULL if the listener is not found in the list. + TestEventListener* Release(TestEventListener* listener); + + // Returns the standard listener responsible for the default console + // output. Can be removed from the listeners list to shut down default + // console output. Note that removing this object from the listener list + // with Release transfers its ownership to the caller and makes this + // function return NULL the next time. + TestEventListener* default_result_printer() const { + return default_result_printer_; + } + + // Returns the standard listener responsible for the default XML output + // controlled by the --gtest_output=xml flag. Can be removed from the + // listeners list by users who want to shut down the default XML output + // controlled by this flag and substitute it with custom one. Note that + // removing this object from the listener list with Release transfers its + // ownership to the caller and makes this function return NULL the next + // time. + TestEventListener* default_xml_generator() const { + return default_xml_generator_; + } + + // Controls whether events will be forwarded by the repeater to the + // listeners in the list. + void SuppressEventForwarding(bool); + + private: + friend class TestSuite; + friend class TestInfo; + friend class internal::DefaultGlobalTestPartResultReporter; + friend class internal::NoExecDeathTest; + friend class internal::TestEventListenersAccessor; + friend class internal::UnitTestImpl; + + // Returns repeater that broadcasts the TestEventListener events to all + // subscribers. + TestEventListener* repeater(); + + // Sets the default_result_printer attribute to the provided listener. + // The listener is also added to the listener list and previous + // default_result_printer is removed from it and deleted. The listener can + // also be NULL in which case it will not be added to the list. Does + // nothing if the previous and the current listener objects are the same. + void SetDefaultResultPrinter(TestEventListener* listener); + + // Sets the default_xml_generator attribute to the provided listener. The + // listener is also added to the listener list and previous + // default_xml_generator is removed from it and deleted. The listener can + // also be NULL in which case it will not be added to the list. Does + // nothing if the previous and the current listener objects are the same. + void SetDefaultXmlGenerator(TestEventListener* listener); + + // Controls whether events will be forwarded by the repeater to the + // listeners in the list. + bool EventForwardingEnabled() const; + + // The actual list of listeners. + internal::TestEventRepeater* repeater_; + // Listener responsible for the standard result output. + TestEventListener* default_result_printer_; + // Listener responsible for the creation of the XML output file. + TestEventListener* default_xml_generator_; + + // We disallow copying TestEventListeners. + TestEventListeners(const TestEventListeners&) = delete; + TestEventListeners& operator=(const TestEventListeners&) = delete; +}; + +// A UnitTest consists of a vector of TestSuites. +// +// This is a singleton class. The only instance of UnitTest is +// created when UnitTest::GetInstance() is first called. This +// instance is never deleted. +// +// UnitTest is not copyable. +// +// This class is thread-safe as long as the methods are called +// according to their specification. +class GTEST_API_ UnitTest { + public: + // Gets the singleton UnitTest object. The first time this method + // is called, a UnitTest object is constructed and returned. + // Consecutive calls will return the same object. + static UnitTest* GetInstance(); + + // Runs all tests in this UnitTest object and prints the result. + // Returns 0 if successful, or 1 otherwise. + // + // This method can only be called from the main thread. + // + // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. + [[nodiscard]] int Run(); + + // Returns the working directory when the first TEST() or TEST_F() + // was executed. The UnitTest object owns the string. + const char* original_working_dir() const; + + // Returns the TestSuite object for the test that's currently running, + // or NULL if no test is running. + const TestSuite* current_test_suite() const GTEST_LOCK_EXCLUDED_(mutex_); + +// Legacy API is still available but deprecated +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + const TestCase* current_test_case() const GTEST_LOCK_EXCLUDED_(mutex_); +#endif + + // Returns the TestInfo object for the test that's currently running, + // or NULL if no test is running. + const TestInfo* current_test_info() const GTEST_LOCK_EXCLUDED_(mutex_); + + // Returns the random seed used at the start of the current test run. + int random_seed() const; + + // Returns the ParameterizedTestSuiteRegistry object used to keep track of + // value-parameterized tests and instantiate and register them. + // + // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. + internal::ParameterizedTestSuiteRegistry& parameterized_test_registry() + GTEST_LOCK_EXCLUDED_(mutex_); + + // Gets the number of successful test suites. + int successful_test_suite_count() const; + + // Gets the number of failed test suites. + int failed_test_suite_count() const; + + // Gets the number of all test suites. + int total_test_suite_count() const; + + // Gets the number of all test suites that contain at least one test + // that should run. + int test_suite_to_run_count() const; + + // Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + int successful_test_case_count() const; + int failed_test_case_count() const; + int total_test_case_count() const; + int test_case_to_run_count() const; +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + + // Gets the number of successful tests. + int successful_test_count() const; + + // Gets the number of skipped tests. + int skipped_test_count() const; + + // Gets the number of failed tests. + int failed_test_count() const; + + // Gets the number of disabled tests that will be reported in the XML report. + int reportable_disabled_test_count() const; + + // Gets the number of disabled tests. + int disabled_test_count() const; + + // Gets the number of tests to be printed in the XML report. + int reportable_test_count() const; + + // Gets the number of all tests. + int total_test_count() const; + + // Gets the number of tests that should run. + int test_to_run_count() const; + + // Gets the time of the test program start, in ms from the start of the + // UNIX epoch. + TimeInMillis start_timestamp() const; + + // Gets the elapsed time, in milliseconds. + TimeInMillis elapsed_time() const; + + // Returns true if and only if the unit test passed (i.e. all test suites + // passed). + bool Passed() const; + + // Returns true if and only if the unit test failed (i.e. some test suite + // failed or something outside of all tests failed). + bool Failed() const; + + // Gets the i-th test suite among all the test suites. i can range from 0 to + // total_test_suite_count() - 1. If i is not in that range, returns NULL. + const TestSuite* GetTestSuite(int i) const; + +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + const TestCase* GetTestCase(int i) const; +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + + // Returns the TestResult containing information on test failures and + // properties logged outside of individual test suites. + const TestResult& ad_hoc_test_result() const; + + // Returns the list of event listeners that can be used to track events + // inside Google Test. + TestEventListeners& listeners(); + + private: + // Registers and returns a global test environment. When a test + // program is run, all global test environments will be set-up in + // the order they were registered. After all tests in the program + // have finished, all global test environments will be torn-down in + // the *reverse* order they were registered. + // + // The UnitTest object takes ownership of the given environment. + // + // This method can only be called from the main thread. + Environment* AddEnvironment(Environment* env); + + // Adds a TestPartResult to the current TestResult object. All + // Google Test assertion macros (e.g. ASSERT_TRUE, EXPECT_EQ, etc) + // eventually call this to report their results. The user code + // should use the assertion macros instead of calling this directly. + void AddTestPartResult(TestPartResult::Type result_type, + const char* file_name, int line_number, + const std::string& message, + const std::string& os_stack_trace) + GTEST_LOCK_EXCLUDED_(mutex_); + + // Adds a TestProperty to the current TestResult object when invoked from + // inside a test, to current TestSuite's ad_hoc_test_result_ when invoked + // from SetUpTestSuite or TearDownTestSuite, or to the global property set + // when invoked elsewhere. If the result already contains a property with + // the same key, the value will be updated. + void RecordProperty(const std::string& key, const std::string& value); + + // Gets the i-th test suite among all the test suites. i can range from 0 to + // total_test_suite_count() - 1. If i is not in that range, returns NULL. + TestSuite* GetMutableTestSuite(int i); + + // Invokes OsStackTrackGetterInterface::UponLeavingGTest. UponLeavingGTest() + // should be called immediately before Google Test calls user code. It saves + // some information about the current stack that CurrentStackTrace() will use + // to find and hide Google Test stack frames. + void UponLeavingGTest(); + + // Sets the TestSuite object for the test that's currently running. + void set_current_test_suite(TestSuite* a_current_test_suite) + GTEST_LOCK_EXCLUDED_(mutex_); + + // Sets the TestInfo object for the test that's currently running. + void set_current_test_info(TestInfo* a_current_test_info) + GTEST_LOCK_EXCLUDED_(mutex_); + + // Accessors for the implementation object. + internal::UnitTestImpl* impl() { return impl_; } + const internal::UnitTestImpl* impl() const { return impl_; } + + // These classes and functions are friends as they need to access private + // members of UnitTest. + friend class ScopedTrace; + friend class Test; + friend class TestInfo; + friend class TestSuite; + friend class internal::AssertHelper; + friend class internal::StreamingListenerTest; + friend class internal::UnitTestRecordPropertyTestHelper; + friend Environment* AddGlobalTestEnvironment(Environment* env); + friend std::set* internal::GetIgnoredParameterizedTestSuites(); + friend internal::UnitTestImpl* internal::GetUnitTestImpl(); + friend void internal::ReportFailureInUnknownLocation( + TestPartResult::Type result_type, const std::string& message); + + // Creates an empty UnitTest. + UnitTest(); + + // D'tor + virtual ~UnitTest(); + + // Pushes a trace defined by SCOPED_TRACE() on to the per-thread + // Google Test trace stack. + void PushGTestTrace(const internal::TraceInfo& trace) + GTEST_LOCK_EXCLUDED_(mutex_); + + // Pops a trace from the per-thread Google Test trace stack. + void PopGTestTrace() GTEST_LOCK_EXCLUDED_(mutex_); + + // Protects mutable state in *impl_. This is mutable as some const + // methods need to lock it too. + mutable internal::Mutex mutex_; + + // Opaque implementation object. This field is never changed once + // the object is constructed. We don't mark it as const here, as + // doing so will cause a warning in the constructor of UnitTest. + // Mutable state in *impl_ is protected by mutex_. + internal::UnitTestImpl* impl_; + + // We disallow copying UnitTest. + UnitTest(const UnitTest&) = delete; + UnitTest& operator=(const UnitTest&) = delete; +}; + +// A convenient wrapper for adding an environment for the test +// program. +// +// You should call this before RUN_ALL_TESTS() is called, probably in +// main(). If you use gtest_main, you need to call this before main() +// starts for it to take effect. For example, you can define a global +// variable like this: +// +// testing::Environment* const foo_env = +// testing::AddGlobalTestEnvironment(new FooEnvironment); +// +// However, we strongly recommend you to write your own main() and +// call AddGlobalTestEnvironment() there, as relying on initialization +// of global variables makes the code harder to read and may cause +// problems when you register multiple environments from different +// translation units and the environments have dependencies among them +// (remember that the compiler doesn't guarantee the order in which +// global variables from different translation units are initialized). +inline Environment* AddGlobalTestEnvironment(Environment* env) { + return UnitTest::GetInstance()->AddEnvironment(env); +} + +// Initializes Google Test. This must be called before calling +// RUN_ALL_TESTS(). In particular, it parses a command line for the +// flags that Google Test recognizes. Whenever a Google Test flag is +// seen, it is removed from argv, and *argc is decremented. +// +// No value is returned. Instead, the Google Test flag variables are +// updated. +// +// Calling the function for the second time has no user-visible effect. +GTEST_API_ void InitGoogleTest(int* argc, char** argv); + +// This overloaded version can be used in Windows programs compiled in +// UNICODE mode. +GTEST_API_ void InitGoogleTest(int* argc, wchar_t** argv); + +// This overloaded version can be used on Arduino/embedded platforms where +// there is no argc/argv. +GTEST_API_ void InitGoogleTest(); + +namespace internal { + +// Separate the error generating code from the code path to reduce the stack +// frame size of CmpHelperEQ. This helps reduce the overhead of some sanitizers +// when calling EXPECT_* in a tight loop. +template +AssertionResult CmpHelperEQFailure(const char* lhs_expression, + const char* rhs_expression, const T1& lhs, + const T2& rhs) { + return EqFailure(lhs_expression, rhs_expression, + FormatForComparisonFailureMessage(lhs, rhs), + FormatForComparisonFailureMessage(rhs, lhs), false); +} + +// This block of code defines operator==/!= +// to block lexical scope lookup. +// It prevents using invalid operator==/!= defined at namespace scope. +struct faketype {}; +inline bool operator==(faketype, faketype) { return true; } +inline bool operator!=(faketype, faketype) { return false; } + +// The helper function for {ASSERT|EXPECT}_EQ. +template +AssertionResult CmpHelperEQ(const char* lhs_expression, + const char* rhs_expression, const T1& lhs, + const T2& rhs) { + if (lhs == rhs) { + return AssertionSuccess(); + } + + return CmpHelperEQFailure(lhs_expression, rhs_expression, lhs, rhs); +} + +class EqHelper { + public: + // This templatized version is for the general case. + template < + typename T1, typename T2, + // Disable this overload for cases where one argument is a pointer + // and the other is the null pointer constant. + typename std::enable_if::value || + !std::is_pointer::value>::type* = nullptr> + static AssertionResult Compare(const char* lhs_expression, + const char* rhs_expression, const T1& lhs, + const T2& rhs) { + return CmpHelperEQ(lhs_expression, rhs_expression, lhs, rhs); + } + + // With this overloaded version, we allow anonymous enums to be used + // in {ASSERT|EXPECT}_EQ when compiled with gcc 4, as anonymous + // enums can be implicitly cast to BiggestInt. + // + // Even though its body looks the same as the above version, we + // cannot merge the two, as it will make anonymous enums unhappy. + static AssertionResult Compare(const char* lhs_expression, + const char* rhs_expression, BiggestInt lhs, + BiggestInt rhs) { + return CmpHelperEQ(lhs_expression, rhs_expression, lhs, rhs); + } + + template + static AssertionResult Compare( + const char* lhs_expression, const char* rhs_expression, + // Handle cases where '0' is used as a null pointer literal. + std::nullptr_t /* lhs */, T* rhs) { + // We already know that 'lhs' is a null pointer. + return CmpHelperEQ(lhs_expression, rhs_expression, static_cast(nullptr), + rhs); + } +}; + +// Separate the error generating code from the code path to reduce the stack +// frame size of CmpHelperOP. This helps reduce the overhead of some sanitizers +// when calling EXPECT_OP in a tight loop. +template +AssertionResult CmpHelperOpFailure(const char* expr1, const char* expr2, + const T1& val1, const T2& val2, + const char* op) { + return AssertionFailure() + << "Expected: (" << expr1 << ") " << op << " (" << expr2 + << "), actual: " << FormatForComparisonFailureMessage(val1, val2) + << " vs " << FormatForComparisonFailureMessage(val2, val1); +} + +// A macro for implementing the helper functions needed to implement +// ASSERT_?? and EXPECT_??. It is here just to avoid copy-and-paste +// of similar code. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. + +#define GTEST_IMPL_CMP_HELPER_(op_name, op) \ + template \ + AssertionResult CmpHelper##op_name(const char* expr1, const char* expr2, \ + const T1& val1, const T2& val2) { \ + if (val1 op val2) { \ + return AssertionSuccess(); \ + } else { \ + return CmpHelperOpFailure(expr1, expr2, val1, val2, #op); \ + } \ + } + +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. + +// Implements the helper function for {ASSERT|EXPECT}_NE +GTEST_IMPL_CMP_HELPER_(NE, !=) +// Implements the helper function for {ASSERT|EXPECT}_LE +GTEST_IMPL_CMP_HELPER_(LE, <=) +// Implements the helper function for {ASSERT|EXPECT}_LT +GTEST_IMPL_CMP_HELPER_(LT, <) +// Implements the helper function for {ASSERT|EXPECT}_GE +GTEST_IMPL_CMP_HELPER_(GE, >=) +// Implements the helper function for {ASSERT|EXPECT}_GT +GTEST_IMPL_CMP_HELPER_(GT, >) + +#undef GTEST_IMPL_CMP_HELPER_ + +// The helper function for {ASSERT|EXPECT}_STREQ. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +GTEST_API_ AssertionResult CmpHelperSTREQ(const char* s1_expression, + const char* s2_expression, + const char* s1, const char* s2); + +// The helper function for {ASSERT|EXPECT}_STRCASEEQ. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +GTEST_API_ AssertionResult CmpHelperSTRCASEEQ(const char* s1_expression, + const char* s2_expression, + const char* s1, const char* s2); + +// The helper function for {ASSERT|EXPECT}_STRNE. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +GTEST_API_ AssertionResult CmpHelperSTRNE(const char* s1_expression, + const char* s2_expression, + const char* s1, const char* s2); + +// The helper function for {ASSERT|EXPECT}_STRCASENE. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +GTEST_API_ AssertionResult CmpHelperSTRCASENE(const char* s1_expression, + const char* s2_expression, + const char* s1, const char* s2); + +// Helper function for *_STREQ on wide strings. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +GTEST_API_ AssertionResult CmpHelperSTREQ(const char* s1_expression, + const char* s2_expression, + const wchar_t* s1, const wchar_t* s2); + +// Helper function for *_STRNE on wide strings. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +GTEST_API_ AssertionResult CmpHelperSTRNE(const char* s1_expression, + const char* s2_expression, + const wchar_t* s1, const wchar_t* s2); + +} // namespace internal + +// IsSubstring() and IsNotSubstring() are intended to be used as the +// first argument to {EXPECT,ASSERT}_PRED_FORMAT2(), not by +// themselves. They check whether needle is a substring of haystack +// (NULL is considered a substring of itself only), and return an +// appropriate error message when they fail. +// +// The {needle,haystack}_expr arguments are the stringified +// expressions that generated the two real arguments. +GTEST_API_ AssertionResult IsSubstring(const char* needle_expr, + const char* haystack_expr, + const char* needle, + const char* haystack); +GTEST_API_ AssertionResult IsSubstring(const char* needle_expr, + const char* haystack_expr, + const wchar_t* needle, + const wchar_t* haystack); +GTEST_API_ AssertionResult IsNotSubstring(const char* needle_expr, + const char* haystack_expr, + const char* needle, + const char* haystack); +GTEST_API_ AssertionResult IsNotSubstring(const char* needle_expr, + const char* haystack_expr, + const wchar_t* needle, + const wchar_t* haystack); +GTEST_API_ AssertionResult IsSubstring(const char* needle_expr, + const char* haystack_expr, + const ::std::string& needle, + const ::std::string& haystack); +GTEST_API_ AssertionResult IsNotSubstring(const char* needle_expr, + const char* haystack_expr, + const ::std::string& needle, + const ::std::string& haystack); + +#if GTEST_HAS_STD_WSTRING +GTEST_API_ AssertionResult IsSubstring(const char* needle_expr, + const char* haystack_expr, + const ::std::wstring& needle, + const ::std::wstring& haystack); +GTEST_API_ AssertionResult IsNotSubstring(const char* needle_expr, + const char* haystack_expr, + const ::std::wstring& needle, + const ::std::wstring& haystack); +#endif // GTEST_HAS_STD_WSTRING + +namespace internal { + +// Helper template function for comparing floating-points. +// +// Template parameter: +// +// RawType: the raw floating-point type (either float or double) +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +template +AssertionResult CmpHelperFloatingPointEQ(const char* lhs_expression, + const char* rhs_expression, + RawType lhs_value, RawType rhs_value) { + const FloatingPoint lhs(lhs_value), rhs(rhs_value); + + if (lhs.AlmostEquals(rhs)) { + return AssertionSuccess(); + } + + ::std::stringstream lhs_ss; + lhs_ss.precision(std::numeric_limits::digits10 + 2); + lhs_ss << lhs_value; + + ::std::stringstream rhs_ss; + rhs_ss.precision(std::numeric_limits::digits10 + 2); + rhs_ss << rhs_value; + + return EqFailure(lhs_expression, rhs_expression, + StringStreamToString(&lhs_ss), StringStreamToString(&rhs_ss), + false); +} + +// Helper function for implementing ASSERT_NEAR. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +GTEST_API_ AssertionResult DoubleNearPredFormat(const char* expr1, + const char* expr2, + const char* abs_error_expr, + double val1, double val2, + double abs_error); + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// A class that enables one to stream messages to assertion macros +class GTEST_API_ AssertHelper { + public: + // Constructor. + AssertHelper(TestPartResult::Type type, const char* file, int line, + const char* message); + ~AssertHelper(); + + // Message assignment is a semantic trick to enable assertion + // streaming; see the GTEST_MESSAGE_ macro below. + void operator=(const Message& message) const; + + private: + // We put our data in a struct so that the size of the AssertHelper class can + // be as small as possible. This is important because gcc is incapable of + // re-using stack space even for temporary variables, so every EXPECT_EQ + // reserves stack space for another AssertHelper. + struct AssertHelperData { + AssertHelperData(TestPartResult::Type t, const char* srcfile, int line_num, + const char* msg) + : type(t), file(srcfile), line(line_num), message(msg) {} + + TestPartResult::Type const type; + const char* const file; + int const line; + std::string const message; + + private: + AssertHelperData(const AssertHelperData&) = delete; + AssertHelperData& operator=(const AssertHelperData&) = delete; + }; + + AssertHelperData* const data_; + + AssertHelper(const AssertHelper&) = delete; + AssertHelper& operator=(const AssertHelper&) = delete; +}; + +} // namespace internal + +// The pure interface class that all value-parameterized tests inherit from. +// A value-parameterized class must inherit from both ::testing::Test and +// ::testing::WithParamInterface. In most cases that just means inheriting +// from ::testing::TestWithParam, but more complicated test hierarchies +// may need to inherit from Test and WithParamInterface at different levels. +// +// This interface has support for accessing the test parameter value via +// the GetParam() method. +// +// Use it with one of the parameter generator defining functions, like Range(), +// Values(), ValuesIn(), Bool(), Combine(), and ConvertGenerator(). +// +// class FooTest : public ::testing::TestWithParam { +// protected: +// FooTest() { +// // Can use GetParam() here. +// } +// ~FooTest() override { +// // Can use GetParam() here. +// } +// void SetUp() override { +// // Can use GetParam() here. +// } +// void TearDown override { +// // Can use GetParam() here. +// } +// }; +// TEST_P(FooTest, DoesBar) { +// // Can use GetParam() method here. +// Foo foo; +// ASSERT_TRUE(foo.DoesBar(GetParam())); +// } +// INSTANTIATE_TEST_SUITE_P(OneToTenRange, FooTest, ::testing::Range(1, 10)); + +template +class WithParamInterface { + public: + typedef T ParamType; + virtual ~WithParamInterface() = default; + + // The current parameter value. Is also available in the test fixture's + // constructor. + static const ParamType& GetParam() { + GTEST_CHECK_(parameter_ != nullptr) + << "GetParam() can only be called inside a value-parameterized test " + << "-- did you intend to write TEST_P instead of TEST_F?"; + return *parameter_; + } + + private: + // Sets parameter value. The caller is responsible for making sure the value + // remains alive and unchanged throughout the current test. + static void SetParam(const ParamType* parameter) { parameter_ = parameter; } + + // Static value used for accessing parameter during a test lifetime. + static const ParamType* parameter_; + + // TestClass must be a subclass of WithParamInterface and Test. + template + friend class internal::ParameterizedTestFactory; +}; + +template +const T* WithParamInterface::parameter_ = nullptr; + +// Most value-parameterized classes can ignore the existence of +// WithParamInterface, and can just inherit from ::testing::TestWithParam. + +template +class TestWithParam : public Test, public WithParamInterface {}; + +// Macros for indicating success/failure in test code. + +// Skips test in runtime. +// Skipping test aborts current function. +// Skipped tests are neither successful nor failed. +#define GTEST_SKIP() GTEST_SKIP_("") + +// ADD_FAILURE unconditionally adds a failure to the current test. +// SUCCEED generates a success - it doesn't automatically make the +// current test successful, as a test is only successful when it has +// no failure. +// +// EXPECT_* verifies that a certain condition is satisfied. If not, +// it behaves like ADD_FAILURE. In particular: +// +// EXPECT_TRUE verifies that a Boolean condition is true. +// EXPECT_FALSE verifies that a Boolean condition is false. +// +// FAIL and ASSERT_* are similar to ADD_FAILURE and EXPECT_*, except +// that they will also abort the current function on failure. People +// usually want the fail-fast behavior of FAIL and ASSERT_*, but those +// writing data-driven tests often find themselves using ADD_FAILURE +// and EXPECT_* more. + +// Generates a nonfatal failure with a generic message. +#define ADD_FAILURE() GTEST_NONFATAL_FAILURE_("Failed") + +// Generates a nonfatal failure at the given source file location with +// a generic message. +#define ADD_FAILURE_AT(file, line) \ + GTEST_MESSAGE_AT_(file, line, "Failed", \ + ::testing::TestPartResult::kNonFatalFailure) + +// Generates a fatal failure with a generic message. +#define GTEST_FAIL() GTEST_FATAL_FAILURE_("Failed") + +// Like GTEST_FAIL(), but at the given source file location. +#define GTEST_FAIL_AT(file, line) \ + return GTEST_MESSAGE_AT_(file, line, "Failed", \ + ::testing::TestPartResult::kFatalFailure) + +// Define this macro to 1 to omit the definition of FAIL(), which is a +// generic name and clashes with some other libraries. +#if !(defined(GTEST_DONT_DEFINE_FAIL) && GTEST_DONT_DEFINE_FAIL) +#define FAIL() GTEST_FAIL() +#define FAIL_AT(file, line) GTEST_FAIL_AT(file, line) +#endif + +// Generates a success with a generic message. +#define GTEST_SUCCEED() GTEST_SUCCESS_("Succeeded") + +// Define this macro to 1 to omit the definition of SUCCEED(), which +// is a generic name and clashes with some other libraries. +#if !(defined(GTEST_DONT_DEFINE_SUCCEED) && GTEST_DONT_DEFINE_SUCCEED) +#define SUCCEED() GTEST_SUCCEED() +#endif + +// Macros for testing exceptions. +// +// * {ASSERT|EXPECT}_THROW(statement, expected_exception): +// Tests that the statement throws the expected exception. +// * {ASSERT|EXPECT}_NO_THROW(statement): +// Tests that the statement doesn't throw any exception. +// * {ASSERT|EXPECT}_ANY_THROW(statement): +// Tests that the statement throws an exception. + +#define EXPECT_THROW(statement, expected_exception) \ + GTEST_TEST_THROW_(statement, expected_exception, GTEST_NONFATAL_FAILURE_) +#define EXPECT_NO_THROW(statement) \ + GTEST_TEST_NO_THROW_(statement, GTEST_NONFATAL_FAILURE_) +#define EXPECT_ANY_THROW(statement) \ + GTEST_TEST_ANY_THROW_(statement, GTEST_NONFATAL_FAILURE_) +#define ASSERT_THROW(statement, expected_exception) \ + GTEST_TEST_THROW_(statement, expected_exception, GTEST_FATAL_FAILURE_) +#define ASSERT_NO_THROW(statement) \ + GTEST_TEST_NO_THROW_(statement, GTEST_FATAL_FAILURE_) +#define ASSERT_ANY_THROW(statement) \ + GTEST_TEST_ANY_THROW_(statement, GTEST_FATAL_FAILURE_) + +// Boolean assertions. Condition can be either a Boolean expression or an +// AssertionResult. For more information on how to use AssertionResult with +// these macros see comments on that class. +#define GTEST_EXPECT_TRUE(condition) \ + GTEST_TEST_BOOLEAN_(condition, #condition, false, true, \ + GTEST_NONFATAL_FAILURE_) +#define GTEST_EXPECT_FALSE(condition) \ + GTEST_TEST_BOOLEAN_(!(condition), #condition, true, false, \ + GTEST_NONFATAL_FAILURE_) +#define GTEST_ASSERT_TRUE(condition) \ + GTEST_TEST_BOOLEAN_(condition, #condition, false, true, GTEST_FATAL_FAILURE_) +#define GTEST_ASSERT_FALSE(condition) \ + GTEST_TEST_BOOLEAN_(!(condition), #condition, true, false, \ + GTEST_FATAL_FAILURE_) + +// Define these macros to 1 to omit the definition of the corresponding +// EXPECT or ASSERT, which clashes with some users' own code. + +#if !(defined(GTEST_DONT_DEFINE_EXPECT_TRUE) && GTEST_DONT_DEFINE_EXPECT_TRUE) +#define EXPECT_TRUE(condition) GTEST_EXPECT_TRUE(condition) +#endif + +#if !(defined(GTEST_DONT_DEFINE_EXPECT_FALSE) && GTEST_DONT_DEFINE_EXPECT_FALSE) +#define EXPECT_FALSE(condition) GTEST_EXPECT_FALSE(condition) +#endif + +#if !(defined(GTEST_DONT_DEFINE_ASSERT_TRUE) && GTEST_DONT_DEFINE_ASSERT_TRUE) +#define ASSERT_TRUE(condition) GTEST_ASSERT_TRUE(condition) +#endif + +#if !(defined(GTEST_DONT_DEFINE_ASSERT_FALSE) && GTEST_DONT_DEFINE_ASSERT_FALSE) +#define ASSERT_FALSE(condition) GTEST_ASSERT_FALSE(condition) +#endif + +// Macros for testing equalities and inequalities. +// +// * {ASSERT|EXPECT}_EQ(v1, v2): Tests that v1 == v2 +// * {ASSERT|EXPECT}_NE(v1, v2): Tests that v1 != v2 +// * {ASSERT|EXPECT}_LT(v1, v2): Tests that v1 < v2 +// * {ASSERT|EXPECT}_LE(v1, v2): Tests that v1 <= v2 +// * {ASSERT|EXPECT}_GT(v1, v2): Tests that v1 > v2 +// * {ASSERT|EXPECT}_GE(v1, v2): Tests that v1 >= v2 +// +// When they are not, Google Test prints both the tested expressions and +// their actual values. The values must be compatible built-in types, +// or you will get a compiler error. By "compatible" we mean that the +// values can be compared by the respective operator. +// +// Note: +// +// 1. It is possible to make a user-defined type work with +// {ASSERT|EXPECT}_??(), but that requires overloading the +// comparison operators and is thus discouraged by the Google C++ +// Usage Guide. Therefore, you are advised to use the +// {ASSERT|EXPECT}_TRUE() macro to assert that two objects are +// equal. +// +// 2. The {ASSERT|EXPECT}_??() macros do pointer comparisons on +// pointers (in particular, C strings). Therefore, if you use it +// with two C strings, you are testing how their locations in memory +// are related, not how their content is related. To compare two C +// strings by content, use {ASSERT|EXPECT}_STR*(). +// +// 3. {ASSERT|EXPECT}_EQ(v1, v2) is preferred to +// {ASSERT|EXPECT}_TRUE(v1 == v2), as the former tells you +// what the actual value is when it fails, and similarly for the +// other comparisons. +// +// 4. Do not depend on the order in which {ASSERT|EXPECT}_??() +// evaluate their arguments, which is undefined. +// +// 5. These macros evaluate their arguments exactly once. +// +// Examples: +// +// EXPECT_NE(Foo(), 5); +// EXPECT_EQ(a_pointer, NULL); +// ASSERT_LT(i, array_size); +// ASSERT_GT(records.size(), 0) << "There is no record left."; + +#define EXPECT_EQ(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::EqHelper::Compare, val1, val2) +#define EXPECT_NE(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperNE, val1, val2) +#define EXPECT_LE(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperLE, val1, val2) +#define EXPECT_LT(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperLT, val1, val2) +#define EXPECT_GE(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperGE, val1, val2) +#define EXPECT_GT(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperGT, val1, val2) + +#define GTEST_ASSERT_EQ(val1, val2) \ + ASSERT_PRED_FORMAT2(::testing::internal::EqHelper::Compare, val1, val2) +#define GTEST_ASSERT_NE(val1, val2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperNE, val1, val2) +#define GTEST_ASSERT_LE(val1, val2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperLE, val1, val2) +#define GTEST_ASSERT_LT(val1, val2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperLT, val1, val2) +#define GTEST_ASSERT_GE(val1, val2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperGE, val1, val2) +#define GTEST_ASSERT_GT(val1, val2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperGT, val1, val2) + +// Define macro GTEST_DONT_DEFINE_ASSERT_XY to 1 to omit the definition of +// ASSERT_XY(), which clashes with some users' own code. + +#if !(defined(GTEST_DONT_DEFINE_ASSERT_EQ) && GTEST_DONT_DEFINE_ASSERT_EQ) +#define ASSERT_EQ(val1, val2) GTEST_ASSERT_EQ(val1, val2) +#endif + +#if !(defined(GTEST_DONT_DEFINE_ASSERT_NE) && GTEST_DONT_DEFINE_ASSERT_NE) +#define ASSERT_NE(val1, val2) GTEST_ASSERT_NE(val1, val2) +#endif + +#if !(defined(GTEST_DONT_DEFINE_ASSERT_LE) && GTEST_DONT_DEFINE_ASSERT_LE) +#define ASSERT_LE(val1, val2) GTEST_ASSERT_LE(val1, val2) +#endif + +#if !(defined(GTEST_DONT_DEFINE_ASSERT_LT) && GTEST_DONT_DEFINE_ASSERT_LT) +#define ASSERT_LT(val1, val2) GTEST_ASSERT_LT(val1, val2) +#endif + +#if !(defined(GTEST_DONT_DEFINE_ASSERT_GE) && GTEST_DONT_DEFINE_ASSERT_GE) +#define ASSERT_GE(val1, val2) GTEST_ASSERT_GE(val1, val2) +#endif + +#if !(defined(GTEST_DONT_DEFINE_ASSERT_GT) && GTEST_DONT_DEFINE_ASSERT_GT) +#define ASSERT_GT(val1, val2) GTEST_ASSERT_GT(val1, val2) +#endif + +// C-string Comparisons. All tests treat NULL and any non-NULL string +// as different. Two NULLs are equal. +// +// * {ASSERT|EXPECT}_STREQ(s1, s2): Tests that s1 == s2 +// * {ASSERT|EXPECT}_STRNE(s1, s2): Tests that s1 != s2 +// * {ASSERT|EXPECT}_STRCASEEQ(s1, s2): Tests that s1 == s2, ignoring case +// * {ASSERT|EXPECT}_STRCASENE(s1, s2): Tests that s1 != s2, ignoring case +// +// For wide or narrow string objects, you can use the +// {ASSERT|EXPECT}_??() macros. +// +// Don't depend on the order in which the arguments are evaluated, +// which is undefined. +// +// These macros evaluate their arguments exactly once. + +#define EXPECT_STREQ(s1, s2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTREQ, s1, s2) +#define EXPECT_STRNE(s1, s2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTRNE, s1, s2) +#define EXPECT_STRCASEEQ(s1, s2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASEEQ, s1, s2) +#define EXPECT_STRCASENE(s1, s2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASENE, s1, s2) + +#define ASSERT_STREQ(s1, s2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTREQ, s1, s2) +#define ASSERT_STRNE(s1, s2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTRNE, s1, s2) +#define ASSERT_STRCASEEQ(s1, s2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASEEQ, s1, s2) +#define ASSERT_STRCASENE(s1, s2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASENE, s1, s2) + +// Macros for comparing floating-point numbers. +// +// * {ASSERT|EXPECT}_FLOAT_EQ(val1, val2): +// Tests that two float values are almost equal. +// * {ASSERT|EXPECT}_DOUBLE_EQ(val1, val2): +// Tests that two double values are almost equal. +// * {ASSERT|EXPECT}_NEAR(v1, v2, abs_error): +// Tests that v1 and v2 are within the given distance to each other. +// +// Google Test uses ULP-based comparison to automatically pick a default +// error bound that is appropriate for the operands. See the +// FloatingPoint template class in gtest-internal.h if you are +// interested in the implementation details. + +#define EXPECT_FLOAT_EQ(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ, \ + val1, val2) + +#define EXPECT_DOUBLE_EQ(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ, \ + val1, val2) + +#define ASSERT_FLOAT_EQ(val1, val2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ, \ + val1, val2) + +#define ASSERT_DOUBLE_EQ(val1, val2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ, \ + val1, val2) + +#define EXPECT_NEAR(val1, val2, abs_error) \ + EXPECT_PRED_FORMAT3(::testing::internal::DoubleNearPredFormat, val1, val2, \ + abs_error) + +#define ASSERT_NEAR(val1, val2, abs_error) \ + ASSERT_PRED_FORMAT3(::testing::internal::DoubleNearPredFormat, val1, val2, \ + abs_error) + +// These predicate format functions work on floating-point values, and +// can be used in {ASSERT|EXPECT}_PRED_FORMAT2*(), e.g. +// +// EXPECT_PRED_FORMAT2(testing::DoubleLE, Foo(), 5.0); + +// Asserts that val1 is less than, or almost equal to, val2. Fails +// otherwise. In particular, it fails if either val1 or val2 is NaN. +GTEST_API_ AssertionResult FloatLE(const char* expr1, const char* expr2, + float val1, float val2); +GTEST_API_ AssertionResult DoubleLE(const char* expr1, const char* expr2, + double val1, double val2); + +#ifdef GTEST_OS_WINDOWS + +// Macros that test for HRESULT failure and success, these are only useful +// on Windows, and rely on Windows SDK macros and APIs to compile. +// +// * {ASSERT|EXPECT}_HRESULT_{SUCCEEDED|FAILED}(expr) +// +// When expr unexpectedly fails or succeeds, Google Test prints the +// expected result and the actual result with both a human-readable +// string representation of the error, if available, as well as the +// hex result code. +#define EXPECT_HRESULT_SUCCEEDED(expr) \ + EXPECT_PRED_FORMAT1(::testing::internal::IsHRESULTSuccess, (expr)) + +#define ASSERT_HRESULT_SUCCEEDED(expr) \ + ASSERT_PRED_FORMAT1(::testing::internal::IsHRESULTSuccess, (expr)) + +#define EXPECT_HRESULT_FAILED(expr) \ + EXPECT_PRED_FORMAT1(::testing::internal::IsHRESULTFailure, (expr)) + +#define ASSERT_HRESULT_FAILED(expr) \ + ASSERT_PRED_FORMAT1(::testing::internal::IsHRESULTFailure, (expr)) + +#endif // GTEST_OS_WINDOWS + +// Macros that execute statement and check that it doesn't generate new fatal +// failures in the current thread. +// +// * {ASSERT|EXPECT}_NO_FATAL_FAILURE(statement); +// +// Examples: +// +// EXPECT_NO_FATAL_FAILURE(Process()); +// ASSERT_NO_FATAL_FAILURE(Process()) << "Process() failed"; +// +#define ASSERT_NO_FATAL_FAILURE(statement) \ + GTEST_TEST_NO_FATAL_FAILURE_(statement, GTEST_FATAL_FAILURE_) +#define EXPECT_NO_FATAL_FAILURE(statement) \ + GTEST_TEST_NO_FATAL_FAILURE_(statement, GTEST_NONFATAL_FAILURE_) + +// Causes a trace (including the given source file path and line number, +// and the given message) to be included in every test failure message generated +// by code in the scope of the lifetime of an instance of this class. The effect +// is undone with the destruction of the instance. +// +// The message argument can be anything streamable to std::ostream. +// +// Example: +// testing::ScopedTrace trace("file.cc", 123, "message"); +// +class GTEST_API_ ScopedTrace { + public: + // The c'tor pushes the given source file location and message onto + // a trace stack maintained by Google Test. + + // Template version. Uses Message() to convert the values into strings. + // Slow, but flexible. + template + ScopedTrace(const char* file, int line, const T& message) { + PushTrace(file, line, (Message() << message).GetString()); + } + + // Optimize for some known types. + ScopedTrace(const char* file, int line, const char* message) { + PushTrace(file, line, message ? message : "(null)"); + } + + ScopedTrace(const char* file, int line, const std::string& message) { + PushTrace(file, line, message); + } + + // The d'tor pops the info pushed by the c'tor. + // + // Note that the d'tor is not virtual in order to be efficient. + // Don't inherit from ScopedTrace! + ~ScopedTrace(); + + private: + void PushTrace(const char* file, int line, std::string message); + + ScopedTrace(const ScopedTrace&) = delete; + ScopedTrace& operator=(const ScopedTrace&) = delete; +}; + +// Causes a trace (including the source file path, the current line +// number, and the given message) to be included in every test failure +// message generated by code in the current scope. The effect is +// undone when the control leaves the current scope. +// +// The message argument can be anything streamable to std::ostream. +// +// In the implementation, we include the current line number as part +// of the dummy variable name, thus allowing multiple SCOPED_TRACE()s +// to appear in the same block - as long as they are on different +// lines. +// +// Assuming that each thread maintains its own stack of traces. +// Therefore, a SCOPED_TRACE() would (correctly) only affect the +// assertions in its own thread. +#define SCOPED_TRACE(message) \ + const ::testing::ScopedTrace GTEST_CONCAT_TOKEN_(gtest_trace_, __LINE__)( \ + __FILE__, __LINE__, (message)) + +// Compile-time assertion for type equality. +// StaticAssertTypeEq() compiles if and only if type1 and type2 +// are the same type. The value it returns is not interesting. +// +// Instead of making StaticAssertTypeEq a class template, we make it a +// function template that invokes a helper class template. This +// prevents a user from misusing StaticAssertTypeEq by +// defining objects of that type. +// +// CAVEAT: +// +// When used inside a method of a class template, +// StaticAssertTypeEq() is effective ONLY IF the method is +// instantiated. For example, given: +// +// template class Foo { +// public: +// void Bar() { testing::StaticAssertTypeEq(); } +// }; +// +// the code: +// +// void Test1() { Foo foo; } +// +// will NOT generate a compiler error, as Foo::Bar() is never +// actually instantiated. Instead, you need: +// +// void Test2() { Foo foo; foo.Bar(); } +// +// to cause a compiler error. +template +constexpr bool StaticAssertTypeEq() noexcept { + static_assert(std::is_same::value, "T1 and T2 are not the same type"); + return true; +} + +// Defines a test. +// +// The first parameter is the name of the test suite, and the second +// parameter is the name of the test within the test suite. +// +// The convention is to end the test suite name with "Test". For +// example, a test suite for the Foo class can be named FooTest. +// +// Test code should appear between braces after an invocation of +// this macro. Example: +// +// TEST(FooTest, InitializesCorrectly) { +// Foo foo; +// EXPECT_TRUE(foo.StatusIsOK()); +// } + +// Note that we call GetTestTypeId() instead of GetTypeId< +// ::testing::Test>() here to get the type ID of testing::Test. This +// is to work around a suspected linker bug when using Google Test as +// a framework on Mac OS X. The bug causes GetTypeId< +// ::testing::Test>() to return different values depending on whether +// the call is from the Google Test framework itself or from user test +// code. GetTestTypeId() is guaranteed to always return the same +// value, as it always calls GetTypeId<>() from the Google Test +// framework. +#define GTEST_TEST(test_suite_name, test_name) \ + GTEST_TEST_(test_suite_name, test_name, ::testing::Test, \ + ::testing::internal::GetTestTypeId()) + +// Define this macro to 1 to omit the definition of TEST(), which +// is a generic name and clashes with some other libraries. +#if !(defined(GTEST_DONT_DEFINE_TEST) && GTEST_DONT_DEFINE_TEST) +#define TEST(test_suite_name, test_name) GTEST_TEST(test_suite_name, test_name) +#endif + +// Defines a test that uses a test fixture. +// +// The first parameter is the name of the test fixture class, which +// also doubles as the test suite name. The second parameter is the +// name of the test within the test suite. +// +// A test fixture class must be declared earlier. The user should put +// the test code between braces after using this macro. Example: +// +// class FooTest : public testing::Test { +// protected: +// void SetUp() override { b_.AddElement(3); } +// +// Foo a_; +// Foo b_; +// }; +// +// TEST_F(FooTest, InitializesCorrectly) { +// EXPECT_TRUE(a_.StatusIsOK()); +// } +// +// TEST_F(FooTest, ReturnsElementCountCorrectly) { +// EXPECT_EQ(a_.size(), 0); +// EXPECT_EQ(b_.size(), 1); +// } +#define GTEST_TEST_F(test_fixture, test_name) \ + GTEST_TEST_(test_fixture, test_name, test_fixture, \ + ::testing::internal::GetTypeId()) +#if !(defined(GTEST_DONT_DEFINE_TEST_F) && GTEST_DONT_DEFINE_TEST_F) +#define TEST_F(test_fixture, test_name) GTEST_TEST_F(test_fixture, test_name) +#endif + +// Returns a path to a temporary directory, which should be writable. It is +// implementation-dependent whether or not the path is terminated by the +// directory-separator character. +GTEST_API_ std::string TempDir(); + +// Returns a path to a directory that contains ancillary data files that might +// be used by tests. It is implementation dependent whether or not the path is +// terminated by the directory-separator character. The directory and the files +// in it should be considered read-only. +GTEST_API_ std::string SrcDir(); + +GTEST_DISABLE_MSC_WARNINGS_POP_() // 4805 4100 + +// Dynamically registers a test with the framework. +// +// This is an advanced API only to be used when the `TEST` macros are +// insufficient. The macros should be preferred when possible, as they avoid +// most of the complexity of calling this function. +// +// The `factory` argument is a factory callable (move-constructible) object or +// function pointer that creates a new instance of the Test object. It +// handles ownership to the caller. The signature of the callable is +// `Fixture*()`, where `Fixture` is the test fixture class for the test. All +// tests registered with the same `test_suite_name` must return the same +// fixture type. This is checked at runtime. +// +// The framework will infer the fixture class from the factory and will call +// the `SetUpTestSuite` and `TearDownTestSuite` for it. +// +// Must be called before `RUN_ALL_TESTS()` is invoked, otherwise behavior is +// undefined. +// +// Use case example: +// +// class MyFixture : public ::testing::Test { +// public: +// // All of these optional, just like in regular macro usage. +// static void SetUpTestSuite() { ... } +// static void TearDownTestSuite() { ... } +// void SetUp() override { ... } +// void TearDown() override { ... } +// }; +// +// class MyTest : public MyFixture { +// public: +// explicit MyTest(int data) : data_(data) {} +// void TestBody() override { ... } +// +// private: +// int data_; +// }; +// +// void RegisterMyTests(const std::vector& values) { +// for (int v : values) { +// ::testing::RegisterTest( +// "MyFixture", ("Test" + std::to_string(v)).c_str(), nullptr, +// std::to_string(v).c_str(), +// __FILE__, __LINE__, +// // Important to use the fixture type as the return type here. +// [=]() -> MyFixture* { return new MyTest(v); }); +// } +// } +// ... +// int main(int argc, char** argv) { +// ::testing::InitGoogleTest(&argc, argv); +// std::vector values_to_test = LoadValuesFromConfig(); +// RegisterMyTests(values_to_test); +// ... +// return RUN_ALL_TESTS(); +// } +// +template +TestInfo* RegisterTest(const char* test_suite_name, const char* test_name, + const char* type_param, const char* value_param, + const char* file, int line, Factory factory) { + using TestT = typename std::remove_pointer::type; + + class FactoryImpl : public internal::TestFactoryBase { + public: + explicit FactoryImpl(Factory f) : factory_(std::move(f)) {} + Test* CreateTest() override { return factory_(); } + + private: + Factory factory_; + }; + + return internal::MakeAndRegisterTestInfo( + test_suite_name, test_name, type_param, value_param, + internal::CodeLocation(file, line), internal::GetTypeId(), + internal::SuiteApiResolver::GetSetUpCaseOrSuite(file, line), + internal::SuiteApiResolver::GetTearDownCaseOrSuite(file, line), + new FactoryImpl{std::move(factory)}); +} + +} // namespace testing + +// Use this function in main() to run all tests. It returns 0 if all +// tests are successful, or 1 otherwise. +// +// RUN_ALL_TESTS() should be invoked after the command line has been +// parsed by InitGoogleTest(). RUN_ALL_TESTS will tear down and delete any +// installed environments and should only be called once per binary. +// +// This function was formerly a macro; thus, it is in the global +// namespace and has an all-caps name. +[[nodiscard]] int RUN_ALL_TESTS(); + +inline int RUN_ALL_TESTS() { return ::testing::UnitTest::GetInstance()->Run(); } + +GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 + +#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_H_ diff --git a/googletest/include/gtest/gtest_pred_impl.h b/googletest/include/gtest/gtest_pred_impl.h new file mode 100644 index 00000000..47a24aa6 --- /dev/null +++ b/googletest/include/gtest/gtest_pred_impl.h @@ -0,0 +1,279 @@ +// Copyright 2006, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Implements a family of generic predicate assertion macros. + +// IWYU pragma: private, include "gtest/gtest.h" +// IWYU pragma: friend gtest/.* +// IWYU pragma: friend gmock/.* + +#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_PRED_IMPL_H_ +#define GOOGLETEST_INCLUDE_GTEST_GTEST_PRED_IMPL_H_ + +#include "gtest/gtest-assertion-result.h" +#include "gtest/internal/gtest-internal.h" +#include "gtest/internal/gtest-port.h" + +namespace testing { + +// This header implements a family of generic predicate assertion +// macros: +// +// ASSERT_PRED_FORMAT1(pred_format, v1) +// ASSERT_PRED_FORMAT2(pred_format, v1, v2) +// ... +// +// where pred_format is a function or functor that takes n (in the +// case of ASSERT_PRED_FORMATn) values and their source expression +// text, and returns a testing::AssertionResult. See the definition +// of ASSERT_EQ in gtest.h for an example. +// +// If you don't care about formatting, you can use the more +// restrictive version: +// +// ASSERT_PRED1(pred, v1) +// ASSERT_PRED2(pred, v1, v2) +// ... +// +// where pred is an n-ary function or functor that returns bool, +// and the values v1, v2, ..., must support the << operator for +// streaming to std::ostream. +// +// We also define the EXPECT_* variations. +// +// For now we only support predicates whose arity is at most 5. +// Please email googletestframework@googlegroups.com if you need +// support for higher arities. + +// GTEST_ASSERT_ is the basic statement to which all of the assertions +// in this file reduce. Don't use this in your code. + +#define GTEST_ASSERT_(expression, on_failure) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (const ::testing::AssertionResult gtest_ar = (expression)) \ + ; \ + else \ + on_failure(gtest_ar.failure_message()) + +// Helper function for implementing {EXPECT|ASSERT}_PRED1. Don't use +// this in your code. +template +AssertionResult AssertPred1Helper(const char* pred_text, const char* e1, + Pred pred, const T1& v1) { + if (pred(v1)) return AssertionSuccess(); + + return AssertionFailure() + << pred_text << "(" << e1 << ") evaluates to false, where" + << "\n" + << e1 << " evaluates to " << ::testing::PrintToString(v1); +} + +// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT1. +// Don't use this in your code. +#define GTEST_PRED_FORMAT1_(pred_format, v1, on_failure) \ + GTEST_ASSERT_(pred_format(#v1, v1), on_failure) + +// Internal macro for implementing {EXPECT|ASSERT}_PRED1. Don't use +// this in your code. +#define GTEST_PRED1_(pred, v1, on_failure) \ + GTEST_ASSERT_(::testing::AssertPred1Helper(#pred, #v1, pred, v1), on_failure) + +// Unary predicate assertion macros. +#define EXPECT_PRED_FORMAT1(pred_format, v1) \ + GTEST_PRED_FORMAT1_(pred_format, v1, GTEST_NONFATAL_FAILURE_) +#define EXPECT_PRED1(pred, v1) GTEST_PRED1_(pred, v1, GTEST_NONFATAL_FAILURE_) +#define ASSERT_PRED_FORMAT1(pred_format, v1) \ + GTEST_PRED_FORMAT1_(pred_format, v1, GTEST_FATAL_FAILURE_) +#define ASSERT_PRED1(pred, v1) GTEST_PRED1_(pred, v1, GTEST_FATAL_FAILURE_) + +// Helper function for implementing {EXPECT|ASSERT}_PRED2. Don't use +// this in your code. +template +AssertionResult AssertPred2Helper(const char* pred_text, const char* e1, + const char* e2, Pred pred, const T1& v1, + const T2& v2) { + if (pred(v1, v2)) return AssertionSuccess(); + + return AssertionFailure() + << pred_text << "(" << e1 << ", " << e2 + << ") evaluates to false, where" + << "\n" + << e1 << " evaluates to " << ::testing::PrintToString(v1) << "\n" + << e2 << " evaluates to " << ::testing::PrintToString(v2); +} + +// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT2. +// Don't use this in your code. +#define GTEST_PRED_FORMAT2_(pred_format, v1, v2, on_failure) \ + GTEST_ASSERT_(pred_format(#v1, #v2, v1, v2), on_failure) + +// Internal macro for implementing {EXPECT|ASSERT}_PRED2. Don't use +// this in your code. +#define GTEST_PRED2_(pred, v1, v2, on_failure) \ + GTEST_ASSERT_(::testing::AssertPred2Helper(#pred, #v1, #v2, pred, v1, v2), \ + on_failure) + +// Binary predicate assertion macros. +#define EXPECT_PRED_FORMAT2(pred_format, v1, v2) \ + GTEST_PRED_FORMAT2_(pred_format, v1, v2, GTEST_NONFATAL_FAILURE_) +#define EXPECT_PRED2(pred, v1, v2) \ + GTEST_PRED2_(pred, v1, v2, GTEST_NONFATAL_FAILURE_) +#define ASSERT_PRED_FORMAT2(pred_format, v1, v2) \ + GTEST_PRED_FORMAT2_(pred_format, v1, v2, GTEST_FATAL_FAILURE_) +#define ASSERT_PRED2(pred, v1, v2) \ + GTEST_PRED2_(pred, v1, v2, GTEST_FATAL_FAILURE_) + +// Helper function for implementing {EXPECT|ASSERT}_PRED3. Don't use +// this in your code. +template +AssertionResult AssertPred3Helper(const char* pred_text, const char* e1, + const char* e2, const char* e3, Pred pred, + const T1& v1, const T2& v2, const T3& v3) { + if (pred(v1, v2, v3)) return AssertionSuccess(); + + return AssertionFailure() + << pred_text << "(" << e1 << ", " << e2 << ", " << e3 + << ") evaluates to false, where" + << "\n" + << e1 << " evaluates to " << ::testing::PrintToString(v1) << "\n" + << e2 << " evaluates to " << ::testing::PrintToString(v2) << "\n" + << e3 << " evaluates to " << ::testing::PrintToString(v3); +} + +// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT3. +// Don't use this in your code. +#define GTEST_PRED_FORMAT3_(pred_format, v1, v2, v3, on_failure) \ + GTEST_ASSERT_(pred_format(#v1, #v2, #v3, v1, v2, v3), on_failure) + +// Internal macro for implementing {EXPECT|ASSERT}_PRED3. Don't use +// this in your code. +#define GTEST_PRED3_(pred, v1, v2, v3, on_failure) \ + GTEST_ASSERT_( \ + ::testing::AssertPred3Helper(#pred, #v1, #v2, #v3, pred, v1, v2, v3), \ + on_failure) + +// Ternary predicate assertion macros. +#define EXPECT_PRED_FORMAT3(pred_format, v1, v2, v3) \ + GTEST_PRED_FORMAT3_(pred_format, v1, v2, v3, GTEST_NONFATAL_FAILURE_) +#define EXPECT_PRED3(pred, v1, v2, v3) \ + GTEST_PRED3_(pred, v1, v2, v3, GTEST_NONFATAL_FAILURE_) +#define ASSERT_PRED_FORMAT3(pred_format, v1, v2, v3) \ + GTEST_PRED_FORMAT3_(pred_format, v1, v2, v3, GTEST_FATAL_FAILURE_) +#define ASSERT_PRED3(pred, v1, v2, v3) \ + GTEST_PRED3_(pred, v1, v2, v3, GTEST_FATAL_FAILURE_) + +// Helper function for implementing {EXPECT|ASSERT}_PRED4. Don't use +// this in your code. +template +AssertionResult AssertPred4Helper(const char* pred_text, const char* e1, + const char* e2, const char* e3, + const char* e4, Pred pred, const T1& v1, + const T2& v2, const T3& v3, const T4& v4) { + if (pred(v1, v2, v3, v4)) return AssertionSuccess(); + + return AssertionFailure() + << pred_text << "(" << e1 << ", " << e2 << ", " << e3 << ", " << e4 + << ") evaluates to false, where" + << "\n" + << e1 << " evaluates to " << ::testing::PrintToString(v1) << "\n" + << e2 << " evaluates to " << ::testing::PrintToString(v2) << "\n" + << e3 << " evaluates to " << ::testing::PrintToString(v3) << "\n" + << e4 << " evaluates to " << ::testing::PrintToString(v4); +} + +// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT4. +// Don't use this in your code. +#define GTEST_PRED_FORMAT4_(pred_format, v1, v2, v3, v4, on_failure) \ + GTEST_ASSERT_(pred_format(#v1, #v2, #v3, #v4, v1, v2, v3, v4), on_failure) + +// Internal macro for implementing {EXPECT|ASSERT}_PRED4. Don't use +// this in your code. +#define GTEST_PRED4_(pred, v1, v2, v3, v4, on_failure) \ + GTEST_ASSERT_(::testing::AssertPred4Helper(#pred, #v1, #v2, #v3, #v4, pred, \ + v1, v2, v3, v4), \ + on_failure) + +// 4-ary predicate assertion macros. +#define EXPECT_PRED_FORMAT4(pred_format, v1, v2, v3, v4) \ + GTEST_PRED_FORMAT4_(pred_format, v1, v2, v3, v4, GTEST_NONFATAL_FAILURE_) +#define EXPECT_PRED4(pred, v1, v2, v3, v4) \ + GTEST_PRED4_(pred, v1, v2, v3, v4, GTEST_NONFATAL_FAILURE_) +#define ASSERT_PRED_FORMAT4(pred_format, v1, v2, v3, v4) \ + GTEST_PRED_FORMAT4_(pred_format, v1, v2, v3, v4, GTEST_FATAL_FAILURE_) +#define ASSERT_PRED4(pred, v1, v2, v3, v4) \ + GTEST_PRED4_(pred, v1, v2, v3, v4, GTEST_FATAL_FAILURE_) + +// Helper function for implementing {EXPECT|ASSERT}_PRED5. Don't use +// this in your code. +template +AssertionResult AssertPred5Helper(const char* pred_text, const char* e1, + const char* e2, const char* e3, + const char* e4, const char* e5, Pred pred, + const T1& v1, const T2& v2, const T3& v3, + const T4& v4, const T5& v5) { + if (pred(v1, v2, v3, v4, v5)) return AssertionSuccess(); + + return AssertionFailure() + << pred_text << "(" << e1 << ", " << e2 << ", " << e3 << ", " << e4 + << ", " << e5 << ") evaluates to false, where" + << "\n" + << e1 << " evaluates to " << ::testing::PrintToString(v1) << "\n" + << e2 << " evaluates to " << ::testing::PrintToString(v2) << "\n" + << e3 << " evaluates to " << ::testing::PrintToString(v3) << "\n" + << e4 << " evaluates to " << ::testing::PrintToString(v4) << "\n" + << e5 << " evaluates to " << ::testing::PrintToString(v5); +} + +// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT5. +// Don't use this in your code. +#define GTEST_PRED_FORMAT5_(pred_format, v1, v2, v3, v4, v5, on_failure) \ + GTEST_ASSERT_(pred_format(#v1, #v2, #v3, #v4, #v5, v1, v2, v3, v4, v5), \ + on_failure) + +// Internal macro for implementing {EXPECT|ASSERT}_PRED5. Don't use +// this in your code. +#define GTEST_PRED5_(pred, v1, v2, v3, v4, v5, on_failure) \ + GTEST_ASSERT_(::testing::AssertPred5Helper(#pred, #v1, #v2, #v3, #v4, #v5, \ + pred, v1, v2, v3, v4, v5), \ + on_failure) + +// 5-ary predicate assertion macros. +#define EXPECT_PRED_FORMAT5(pred_format, v1, v2, v3, v4, v5) \ + GTEST_PRED_FORMAT5_(pred_format, v1, v2, v3, v4, v5, GTEST_NONFATAL_FAILURE_) +#define EXPECT_PRED5(pred, v1, v2, v3, v4, v5) \ + GTEST_PRED5_(pred, v1, v2, v3, v4, v5, GTEST_NONFATAL_FAILURE_) +#define ASSERT_PRED_FORMAT5(pred_format, v1, v2, v3, v4, v5) \ + GTEST_PRED_FORMAT5_(pred_format, v1, v2, v3, v4, v5, GTEST_FATAL_FAILURE_) +#define ASSERT_PRED5(pred, v1, v2, v3, v4, v5) \ + GTEST_PRED5_(pred, v1, v2, v3, v4, v5, GTEST_FATAL_FAILURE_) + +} // namespace testing + +#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_PRED_IMPL_H_ diff --git a/googletest/include/gtest/gtest_prod.h b/googletest/include/gtest/gtest_prod.h new file mode 100644 index 00000000..1f37dc31 --- /dev/null +++ b/googletest/include/gtest/gtest_prod.h @@ -0,0 +1,60 @@ +// Copyright 2006, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Google C++ Testing and Mocking Framework definitions useful in production +// code. + +#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_PROD_H_ +#define GOOGLETEST_INCLUDE_GTEST_GTEST_PROD_H_ + +// When you need to test the private or protected members of a class, +// use the FRIEND_TEST macro to declare your tests as friends of the +// class. For example: +// +// class MyClass { +// private: +// void PrivateMethod(); +// FRIEND_TEST(MyClassTest, PrivateMethodWorks); +// }; +// +// class MyClassTest : public testing::Test { +// // ... +// }; +// +// TEST_F(MyClassTest, PrivateMethodWorks) { +// // Can call MyClass::PrivateMethod() here. +// } +// +// Note: The test class must be in the same namespace as the class being tested. +// For example, putting MyClassTest in an anonymous namespace will not work. + +#define FRIEND_TEST(test_case_name, test_name) \ + friend class test_case_name##_##test_name##_Test + +#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_PROD_H_ diff --git a/googletest/include/gtest/internal/custom/README.md b/googletest/include/gtest/internal/custom/README.md new file mode 100644 index 00000000..cb49e2c7 --- /dev/null +++ b/googletest/include/gtest/internal/custom/README.md @@ -0,0 +1,44 @@ +# Customization Points + +The custom directory is an injection point for custom user configurations. + +## Header `gtest.h` + +### The following macros can be defined: + +* `GTEST_OS_STACK_TRACE_GETTER_` - The name of an implementation of + `OsStackTraceGetterInterface`. +* `GTEST_CUSTOM_TEMPDIR_FUNCTION_` - An override for `testing::TempDir()`. See + `testing::TempDir` for semantics and signature. + +## Header `gtest-port.h` + +The following macros can be defined: + +### Logging: + +* `GTEST_LOG_(severity)` +* `GTEST_CHECK_(condition)` +* Functions `LogToStderr()` and `FlushInfoLog()` have to be provided too. + +### Threading: + +* `GTEST_HAS_NOTIFICATION_` - Enabled if Notification is already provided. +* `GTEST_HAS_MUTEX_AND_THREAD_LOCAL_` - Enabled if `Mutex` and `ThreadLocal` + are already provided. Must also provide `GTEST_DECLARE_STATIC_MUTEX_(mutex)` + and `GTEST_DEFINE_STATIC_MUTEX_(mutex)` +* `GTEST_EXCLUSIVE_LOCK_REQUIRED_(locks)` +* `GTEST_LOCK_EXCLUDED_(locks)` + +### Underlying library support features + +* `GTEST_HAS_CXXABI_H_` + +### Exporting API symbols: + +* `GTEST_API_` - Specifier for exported symbols. + +## Header `gtest-printers.h` + +* See documentation at `gtest/gtest-printers.h` for details on how to define a + custom printer. diff --git a/googletest/include/gtest/internal/custom/gtest-port.h b/googletest/include/gtest/internal/custom/gtest-port.h new file mode 100644 index 00000000..db02881c --- /dev/null +++ b/googletest/include/gtest/internal/custom/gtest-port.h @@ -0,0 +1,37 @@ +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Injection point for custom user configurations. See README for details +// +// ** Custom implementation starts here ** + +#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PORT_H_ +#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PORT_H_ + +#endif // GOOGLETEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PORT_H_ diff --git a/googletest/include/gtest/internal/custom/gtest-printers.h b/googletest/include/gtest/internal/custom/gtest-printers.h new file mode 100644 index 00000000..b9495d83 --- /dev/null +++ b/googletest/include/gtest/internal/custom/gtest-printers.h @@ -0,0 +1,42 @@ +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// This file provides an injection point for custom printers in a local +// installation of gTest. +// It will be included from gtest-printers.h and the overrides in this file +// will be visible to everyone. +// +// Injection point for custom user configurations. See README for details +// +// ** Custom implementation starts here ** + +#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PRINTERS_H_ +#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PRINTERS_H_ + +#endif // GOOGLETEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PRINTERS_H_ diff --git a/googletest/include/gtest/internal/custom/gtest.h b/googletest/include/gtest/internal/custom/gtest.h new file mode 100644 index 00000000..afaaf17b --- /dev/null +++ b/googletest/include/gtest/internal/custom/gtest.h @@ -0,0 +1,37 @@ +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Injection point for custom user configurations. See README for details +// +// ** Custom implementation starts here ** + +#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_H_ +#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_H_ + +#endif // GOOGLETEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_H_ diff --git a/googletest/include/gtest/internal/gtest-death-test-internal.h b/googletest/include/gtest/internal/gtest-death-test-internal.h new file mode 100644 index 00000000..b363259e --- /dev/null +++ b/googletest/include/gtest/internal/gtest-death-test-internal.h @@ -0,0 +1,306 @@ +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// The Google C++ Testing and Mocking Framework (Google Test) +// +// This header file defines internal utilities needed for implementing +// death tests. They are subject to change without notice. + +// IWYU pragma: private, include "gtest/gtest.h" +// IWYU pragma: friend gtest/.* +// IWYU pragma: friend gmock/.* + +#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_DEATH_TEST_INTERNAL_H_ +#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_DEATH_TEST_INTERNAL_H_ + +#include + +#include +#include + +#include "gtest/gtest-matchers.h" +#include "gtest/internal/gtest-internal.h" +#include "gtest/internal/gtest-port.h" + +GTEST_DECLARE_string_(internal_run_death_test); + +namespace testing { +namespace internal { + +// Name of the flag (needed for parsing Google Test flag). +const char kInternalRunDeathTestFlag[] = "internal_run_death_test"; + +// A string passed to EXPECT_DEATH (etc.) is caught by one of these overloads +// and interpreted as a regex (rather than an Eq matcher) for legacy +// compatibility. +inline Matcher MakeDeathTestMatcher( + ::testing::internal::RE regex) { + return ContainsRegex(regex.pattern()); +} +inline Matcher MakeDeathTestMatcher(const char* regex) { + return ContainsRegex(regex); +} +inline Matcher MakeDeathTestMatcher( + const ::std::string& regex) { + return ContainsRegex(regex); +} + +// If a Matcher is passed to EXPECT_DEATH (etc.), it's +// used directly. +inline Matcher MakeDeathTestMatcher( + Matcher matcher) { + return matcher; +} + +#ifdef GTEST_HAS_DEATH_TEST + +GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ +/* class A needs to have dll-interface to be used by clients of class B */) + +// DeathTest is a class that hides much of the complexity of the +// GTEST_DEATH_TEST_ macro. It is abstract; its static Create method +// returns a concrete class that depends on the prevailing death test +// style, as defined by the --gtest_death_test_style and/or +// --gtest_internal_run_death_test flags. + +// In describing the results of death tests, these terms are used with +// the corresponding definitions: +// +// exit status: The integer exit information in the format specified +// by wait(2) +// exit code: The integer code passed to exit(3), _Exit(2), or +// returned from main() +class GTEST_API_ DeathTest { + public: + // Create returns false if there was an error determining the + // appropriate action to take for the current death test; for example, + // if the gtest_death_test_style flag is set to an invalid value. + // The LastMessage method will return a more detailed message in that + // case. Otherwise, the DeathTest pointer pointed to by the "test" + // argument is set. If the death test should be skipped, the pointer + // is set to NULL; otherwise, it is set to the address of a new concrete + // DeathTest object that controls the execution of the current test. + static bool Create(const char* statement, Matcher matcher, + const char* file, int line, DeathTest** test); + DeathTest(); + virtual ~DeathTest() = default; + + // A helper class that aborts a death test when it's deleted. + class ReturnSentinel { + public: + explicit ReturnSentinel(DeathTest* test) : test_(test) {} + ~ReturnSentinel() { test_->Abort(TEST_ENCOUNTERED_RETURN_STATEMENT); } + + private: + DeathTest* const test_; + ReturnSentinel(const ReturnSentinel&) = delete; + ReturnSentinel& operator=(const ReturnSentinel&) = delete; + }; + + // An enumeration of possible roles that may be taken when a death + // test is encountered. EXECUTE means that the death test logic should + // be executed immediately. OVERSEE means that the program should prepare + // the appropriate environment for a child process to execute the death + // test, then wait for it to complete. + enum TestRole { OVERSEE_TEST, EXECUTE_TEST }; + + // An enumeration of the three reasons that a test might be aborted. + enum AbortReason { + TEST_ENCOUNTERED_RETURN_STATEMENT, + TEST_THREW_EXCEPTION, + TEST_DID_NOT_DIE + }; + + // Assumes one of the above roles. + virtual TestRole AssumeRole() = 0; + + // Waits for the death test to finish and returns its status. + virtual int Wait() = 0; + + // Returns true if the death test passed; that is, the test process + // exited during the test, its exit status matches a user-supplied + // predicate, and its stderr output matches a user-supplied regular + // expression. + // The user-supplied predicate may be a macro expression rather + // than a function pointer or functor, or else Wait and Passed could + // be combined. + virtual bool Passed(bool exit_status_ok) = 0; + + // Signals that the death test did not die as expected. + virtual void Abort(AbortReason reason) = 0; + + // Returns a human-readable outcome message regarding the outcome of + // the last death test. + static const char* LastMessage(); + + static void set_last_death_test_message(const std::string& message); + + private: + // A string containing a description of the outcome of the last death test. + static std::string last_death_test_message_; + + DeathTest(const DeathTest&) = delete; + DeathTest& operator=(const DeathTest&) = delete; +}; + +GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 + +// Factory interface for death tests. May be mocked out for testing. +class DeathTestFactory { + public: + virtual ~DeathTestFactory() = default; + virtual bool Create(const char* statement, + Matcher matcher, const char* file, + int line, DeathTest** test) = 0; +}; + +// A concrete DeathTestFactory implementation for normal use. +class DefaultDeathTestFactory : public DeathTestFactory { + public: + bool Create(const char* statement, Matcher matcher, + const char* file, int line, DeathTest** test) override; +}; + +// Returns true if exit_status describes a process that was terminated +// by a signal, or exited normally with a nonzero exit code. +GTEST_API_ bool ExitedUnsuccessfully(int exit_status); + +// Traps C++ exceptions escaping statement and reports them as test +// failures. Note that trapping SEH exceptions is not implemented here. +#if GTEST_HAS_EXCEPTIONS +#define GTEST_EXECUTE_DEATH_TEST_STATEMENT_(statement, death_test) \ + try { \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + } catch (const ::std::exception& gtest_exception) { \ + fprintf( \ + stderr, \ + "\n%s: Caught std::exception-derived exception escaping the " \ + "death test statement. Exception message: %s\n", \ + ::testing::internal::FormatFileLocation(__FILE__, __LINE__).c_str(), \ + gtest_exception.what()); \ + fflush(stderr); \ + death_test->Abort(::testing::internal::DeathTest::TEST_THREW_EXCEPTION); \ + } catch (...) { \ + death_test->Abort(::testing::internal::DeathTest::TEST_THREW_EXCEPTION); \ + } + +#else +#define GTEST_EXECUTE_DEATH_TEST_STATEMENT_(statement, death_test) \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement) + +#endif + +// This macro is for implementing ASSERT_DEATH*, EXPECT_DEATH*, +// ASSERT_EXIT*, and EXPECT_EXIT*. +#define GTEST_DEATH_TEST_(statement, predicate, regex_or_matcher, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::testing::internal::AlwaysTrue()) { \ + ::testing::internal::DeathTest* gtest_dt; \ + if (!::testing::internal::DeathTest::Create( \ + #statement, \ + ::testing::internal::MakeDeathTestMatcher(regex_or_matcher), \ + __FILE__, __LINE__, >est_dt)) { \ + goto GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__); \ + } \ + if (gtest_dt != nullptr) { \ + std::unique_ptr< ::testing::internal::DeathTest> gtest_dt_ptr(gtest_dt); \ + switch (gtest_dt->AssumeRole()) { \ + case ::testing::internal::DeathTest::OVERSEE_TEST: \ + if (!gtest_dt->Passed(predicate(gtest_dt->Wait()))) { \ + goto GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__); \ + } \ + break; \ + case ::testing::internal::DeathTest::EXECUTE_TEST: { \ + const ::testing::internal::DeathTest::ReturnSentinel gtest_sentinel( \ + gtest_dt); \ + GTEST_EXECUTE_DEATH_TEST_STATEMENT_(statement, gtest_dt); \ + gtest_dt->Abort(::testing::internal::DeathTest::TEST_DID_NOT_DIE); \ + break; \ + } \ + } \ + } \ + } else \ + GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__) \ + : fail(::testing::internal::DeathTest::LastMessage()) +// The symbol "fail" here expands to something into which a message +// can be streamed. + +// This macro is for implementing ASSERT/EXPECT_DEBUG_DEATH when compiled in +// NDEBUG mode. In this case we need the statements to be executed and the macro +// must accept a streamed message even though the message is never printed. +// The regex object is not evaluated, but it is used to prevent "unused" +// warnings and to avoid an expression that doesn't compile in debug mode. +#define GTEST_EXECUTE_STATEMENT_(statement, regex_or_matcher) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::testing::internal::AlwaysTrue()) { \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + } else if (!::testing::internal::AlwaysTrue()) { \ + ::testing::internal::MakeDeathTestMatcher(regex_or_matcher); \ + } else \ + ::testing::Message() + +// A class representing the parsed contents of the +// --gtest_internal_run_death_test flag, as it existed when +// RUN_ALL_TESTS was called. +class InternalRunDeathTestFlag { + public: + InternalRunDeathTestFlag(const std::string& a_file, int a_line, int an_index, + int a_write_fd) + : file_(a_file), line_(a_line), index_(an_index), write_fd_(a_write_fd) {} + + ~InternalRunDeathTestFlag() { + if (write_fd_ >= 0) posix::Close(write_fd_); + } + + const std::string& file() const { return file_; } + int line() const { return line_; } + int index() const { return index_; } + int write_fd() const { return write_fd_; } + + private: + std::string file_; + int line_; + int index_; + int write_fd_; + + InternalRunDeathTestFlag(const InternalRunDeathTestFlag&) = delete; + InternalRunDeathTestFlag& operator=(const InternalRunDeathTestFlag&) = delete; +}; + +// Returns a newly created InternalRunDeathTestFlag object with fields +// initialized from the GTEST_FLAG(internal_run_death_test) flag if +// the flag is specified; otherwise returns NULL. +InternalRunDeathTestFlag* ParseInternalRunDeathTestFlag(); + +#endif // GTEST_HAS_DEATH_TEST + +} // namespace internal +} // namespace testing + +#endif // GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_DEATH_TEST_INTERNAL_H_ diff --git a/googletest/include/gtest/internal/gtest-filepath.h b/googletest/include/gtest/internal/gtest-filepath.h new file mode 100644 index 00000000..6dc47be5 --- /dev/null +++ b/googletest/include/gtest/internal/gtest-filepath.h @@ -0,0 +1,233 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Google Test filepath utilities +// +// This header file declares classes and functions used internally by +// Google Test. They are subject to change without notice. +// +// This file is #included in gtest/internal/gtest-internal.h. +// Do not include this header file separately! + +// IWYU pragma: private, include "gtest/gtest.h" +// IWYU pragma: friend gtest/.* +// IWYU pragma: friend gmock/.* + +#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_FILEPATH_H_ +#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_FILEPATH_H_ + +#include +#include + +#include "gtest/internal/gtest-port.h" +#include "gtest/internal/gtest-string.h" + +GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ +/* class A needs to have dll-interface to be used by clients of class B */) + +#if GTEST_HAS_FILE_SYSTEM + +namespace testing { +namespace internal { + +// FilePath - a class for file and directory pathname manipulation which +// handles platform-specific conventions (like the pathname separator). +// Used for helper functions for naming files in a directory for xml output. +// Except for Set methods, all methods are const or static, which provides an +// "immutable value object" -- useful for peace of mind. +// A FilePath with a value ending in a path separator ("like/this/") represents +// a directory, otherwise it is assumed to represent a file. In either case, +// it may or may not represent an actual file or directory in the file system. +// Names are NOT checked for syntax correctness -- no checking for illegal +// characters, malformed paths, etc. + +class GTEST_API_ FilePath { + public: + FilePath() : pathname_("") {} + FilePath(const FilePath& rhs) : pathname_(rhs.pathname_) {} + FilePath(FilePath&& rhs) noexcept : pathname_(std::move(rhs.pathname_)) {} + + explicit FilePath(std::string pathname) : pathname_(std::move(pathname)) { + Normalize(); + } + + FilePath& operator=(const FilePath& rhs) { + Set(rhs); + return *this; + } + FilePath& operator=(FilePath&& rhs) noexcept { + pathname_ = std::move(rhs.pathname_); + return *this; + } + + void Set(const FilePath& rhs) { pathname_ = rhs.pathname_; } + + const std::string& string() const { return pathname_; } + const char* c_str() const { return pathname_.c_str(); } + + // Returns the current working directory, or "" if unsuccessful. + static FilePath GetCurrentDir(); + + // Given directory = "dir", base_name = "test", number = 0, + // extension = "xml", returns "dir/test.xml". If number is greater + // than zero (e.g., 12), returns "dir/test_12.xml". + // On Windows platform, uses \ as the separator rather than /. + static FilePath MakeFileName(const FilePath& directory, + const FilePath& base_name, int number, + const char* extension); + + // Given directory = "dir", relative_path = "test.xml", + // returns "dir/test.xml". + // On Windows, uses \ as the separator rather than /. + static FilePath ConcatPaths(const FilePath& directory, + const FilePath& relative_path); + + // Returns a pathname for a file that does not currently exist. The pathname + // will be directory/base_name.extension or + // directory/base_name_.extension if directory/base_name.extension + // already exists. The number will be incremented until a pathname is found + // that does not already exist. + // Examples: 'dir/foo_test.xml' or 'dir/foo_test_1.xml'. + // There could be a race condition if two or more processes are calling this + // function at the same time -- they could both pick the same filename. + static FilePath GenerateUniqueFileName(const FilePath& directory, + const FilePath& base_name, + const char* extension); + + // Returns true if and only if the path is "". + bool IsEmpty() const { return pathname_.empty(); } + + // If input name has a trailing separator character, removes it and returns + // the name, otherwise return the name string unmodified. + // On Windows platform, uses \ as the separator, other platforms use /. + FilePath RemoveTrailingPathSeparator() const; + + // Returns a copy of the FilePath with the directory part removed. + // Example: FilePath("path/to/file").RemoveDirectoryName() returns + // FilePath("file"). If there is no directory part ("just_a_file"), it returns + // the FilePath unmodified. If there is no file part ("just_a_dir/") it + // returns an empty FilePath (""). + // On Windows platform, '\' is the path separator, otherwise it is '/'. + FilePath RemoveDirectoryName() const; + + // RemoveFileName returns the directory path with the filename removed. + // Example: FilePath("path/to/file").RemoveFileName() returns "path/to/". + // If the FilePath is "a_file" or "/a_file", RemoveFileName returns + // FilePath("./") or, on Windows, FilePath(".\\"). If the filepath does + // not have a file, like "just/a/dir/", it returns the FilePath unmodified. + // On Windows platform, '\' is the path separator, otherwise it is '/'. + FilePath RemoveFileName() const; + + // Returns a copy of the FilePath with the case-insensitive extension removed. + // Example: FilePath("dir/file.exe").RemoveExtension("EXE") returns + // FilePath("dir/file"). If a case-insensitive extension is not + // found, returns a copy of the original FilePath. + FilePath RemoveExtension(const char* extension) const; + + // Creates directories so that path exists. Returns true if successful or if + // the directories already exist; returns false if unable to create + // directories for any reason. Will also return false if the FilePath does + // not represent a directory (that is, it doesn't end with a path separator). + bool CreateDirectoriesRecursively() const; + + // Create the directory so that path exists. Returns true if successful or + // if the directory already exists; returns false if unable to create the + // directory for any reason, including if the parent directory does not + // exist. Not named "CreateDirectory" because that's a macro on Windows. + bool CreateFolder() const; + + // Returns true if FilePath describes something in the file-system, + // either a file, directory, or whatever, and that something exists. + bool FileOrDirectoryExists() const; + + // Returns true if pathname describes a directory in the file-system + // that exists. + bool DirectoryExists() const; + + // Returns true if FilePath ends with a path separator, which indicates that + // it is intended to represent a directory. Returns false otherwise. + // This does NOT check that a directory (or file) actually exists. + bool IsDirectory() const; + + // Returns true if pathname describes a root directory. (Windows has one + // root directory per disk drive.) + bool IsRootDirectory() const; + + // Returns true if pathname describes an absolute path. + bool IsAbsolutePath() const; + + private: + // Replaces multiple consecutive separators with a single separator. + // For example, "bar///foo" becomes "bar/foo". Does not eliminate other + // redundancies that might be in a pathname involving "." or "..". + // + // A pathname with multiple consecutive separators may occur either through + // user error or as a result of some scripts or APIs that generate a pathname + // with a trailing separator. On other platforms the same API or script + // may NOT generate a pathname with a trailing "/". Then elsewhere that + // pathname may have another "/" and pathname components added to it, + // without checking for the separator already being there. + // The script language and operating system may allow paths like "foo//bar" + // but some of the functions in FilePath will not handle that correctly. In + // particular, RemoveTrailingPathSeparator() only removes one separator, and + // it is called in CreateDirectoriesRecursively() assuming that it will change + // a pathname from directory syntax (trailing separator) to filename syntax. + // + // On Windows this method also replaces the alternate path separator '/' with + // the primary path separator '\\', so that for example "bar\\/\\foo" becomes + // "bar\\foo". + + void Normalize(); + + // Returns a pointer to the last occurrence of a valid path separator in + // the FilePath. On Windows, for example, both '/' and '\' are valid path + // separators. Returns NULL if no path separator was found. + const char* FindLastPathSeparator() const; + + // Returns the length of the path root, including the directory separator at + // the end of the prefix. Returns zero by definition if the path is relative. + // Examples: + // - [Windows] "..\Sibling" => 0 + // - [Windows] "\Windows" => 1 + // - [Windows] "C:/Windows\Notepad.exe" => 3 + // - [Windows] "\\Host\Share\C$/Windows" => 13 + // - [UNIX] "/bin" => 1 + size_t CalculateRootLength() const; + + std::string pathname_; +}; // class FilePath + +} // namespace internal +} // namespace testing + +GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 + +#endif // GTEST_HAS_FILE_SYSTEM + +#endif // GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_FILEPATH_H_ diff --git a/googletest/include/gtest/internal/gtest-internal.h b/googletest/include/gtest/internal/gtest-internal.h new file mode 100644 index 00000000..808d89be --- /dev/null +++ b/googletest/include/gtest/internal/gtest-internal.h @@ -0,0 +1,1517 @@ +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// The Google C++ Testing and Mocking Framework (Google Test) +// +// This header file declares functions and macros used internally by +// Google Test. They are subject to change without notice. + +// IWYU pragma: private, include "gtest/gtest.h" +// IWYU pragma: friend gtest/.* +// IWYU pragma: friend gmock/.* + +#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_INTERNAL_H_ +#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_INTERNAL_H_ + +#include "gtest/internal/gtest-port.h" + +#ifdef GTEST_OS_LINUX +#include +#include +#include +#include +#endif // GTEST_OS_LINUX + +#if GTEST_HAS_EXCEPTIONS +#include +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gtest/gtest-message.h" +#include "gtest/internal/gtest-filepath.h" +#include "gtest/internal/gtest-string.h" +#include "gtest/internal/gtest-type-util.h" + +// Due to C++ preprocessor weirdness, we need double indirection to +// concatenate two tokens when one of them is __LINE__. Writing +// +// foo ## __LINE__ +// +// will result in the token foo__LINE__, instead of foo followed by +// the current line number. For more details, see +// https://www.parashift.com/c++-faq-lite/misc-technical-issues.html#faq-39.6 +#define GTEST_CONCAT_TOKEN_(foo, bar) GTEST_CONCAT_TOKEN_IMPL_(foo, bar) +#define GTEST_CONCAT_TOKEN_IMPL_(foo, bar) foo##bar + +// Stringifies its argument. +// Work around a bug in visual studio which doesn't accept code like this: +// +// #define GTEST_STRINGIFY_(name) #name +// #define MACRO(a, b, c) ... GTEST_STRINGIFY_(a) ... +// MACRO(, x, y) +// +// Complaining about the argument to GTEST_STRINGIFY_ being empty. +// This is allowed by the spec. +#define GTEST_STRINGIFY_HELPER_(name, ...) #name +#define GTEST_STRINGIFY_(...) GTEST_STRINGIFY_HELPER_(__VA_ARGS__, ) + +namespace proto2 { +class MessageLite; +} + +namespace testing { + +// Forward declarations. + +class AssertionResult; // Result of an assertion. +class Message; // Represents a failure message. +class Test; // Represents a test. +class TestInfo; // Information about a test. +class TestPartResult; // Result of a test part. +class UnitTest; // A collection of test suites. + +template +::std::string PrintToString(const T& value); + +namespace internal { + +struct TraceInfo; // Information about a trace point. +class TestInfoImpl; // Opaque implementation of TestInfo +class UnitTestImpl; // Opaque implementation of UnitTest + +// The text used in failure messages to indicate the start of the +// stack trace. +GTEST_API_ extern const char kStackTraceMarker[]; + +// An IgnoredValue object can be implicitly constructed from ANY value. +class IgnoredValue { + struct Sink {}; + + public: + // This constructor template allows any value to be implicitly + // converted to IgnoredValue. The object has no data member and + // doesn't try to remember anything about the argument. We + // deliberately omit the 'explicit' keyword in order to allow the + // conversion to be implicit. + // Disable the conversion if T already has a magical conversion operator. + // Otherwise we get ambiguity. + template ::value, + int>::type = 0> + IgnoredValue(const T& /* ignored */) {} // NOLINT(runtime/explicit) +}; + +// Appends the user-supplied message to the Google-Test-generated message. +GTEST_API_ std::string AppendUserMessage(const std::string& gtest_msg, + const Message& user_msg); + +#if GTEST_HAS_EXCEPTIONS + +GTEST_DISABLE_MSC_WARNINGS_PUSH_( + 4275 /* an exported class was derived from a class that was not exported */) + +// This exception is thrown by (and only by) a failed Google Test +// assertion when GTEST_FLAG(throw_on_failure) is true (if exceptions +// are enabled). We derive it from std::runtime_error, which is for +// errors presumably detectable only at run time. Since +// std::runtime_error inherits from std::exception, many testing +// frameworks know how to extract and print the message inside it. +class GTEST_API_ GoogleTestFailureException : public ::std::runtime_error { + public: + explicit GoogleTestFailureException(const TestPartResult& failure); +}; + +GTEST_DISABLE_MSC_WARNINGS_POP_() // 4275 + +#endif // GTEST_HAS_EXCEPTIONS + +namespace edit_distance { +// Returns the optimal edits to go from 'left' to 'right'. +// All edits cost the same, with replace having lower priority than +// add/remove. +// Simple implementation of the Wagner-Fischer algorithm. +// See https://en.wikipedia.org/wiki/Wagner-Fischer_algorithm +enum EditType { kMatch, kAdd, kRemove, kReplace }; +GTEST_API_ std::vector CalculateOptimalEdits( + const std::vector& left, const std::vector& right); + +// Same as above, but the input is represented as strings. +GTEST_API_ std::vector CalculateOptimalEdits( + const std::vector& left, + const std::vector& right); + +// Create a diff of the input strings in Unified diff format. +GTEST_API_ std::string CreateUnifiedDiff(const std::vector& left, + const std::vector& right, + size_t context = 2); + +} // namespace edit_distance + +// Constructs and returns the message for an equality assertion +// (e.g. ASSERT_EQ, EXPECT_STREQ, etc) failure. +// +// The first four parameters are the expressions used in the assertion +// and their values, as strings. For example, for ASSERT_EQ(foo, bar) +// where foo is 5 and bar is 6, we have: +// +// expected_expression: "foo" +// actual_expression: "bar" +// expected_value: "5" +// actual_value: "6" +// +// The ignoring_case parameter is true if and only if the assertion is a +// *_STRCASEEQ*. When it's true, the string " (ignoring case)" will +// be inserted into the message. +GTEST_API_ AssertionResult EqFailure(const char* expected_expression, + const char* actual_expression, + const std::string& expected_value, + const std::string& actual_value, + bool ignoring_case); + +// Constructs a failure message for Boolean assertions such as EXPECT_TRUE. +GTEST_API_ std::string GetBoolAssertionFailureMessage( + const AssertionResult& assertion_result, const char* expression_text, + const char* actual_predicate_value, const char* expected_predicate_value); + +// This template class represents an IEEE floating-point number +// (either single-precision or double-precision, depending on the +// template parameters). +// +// The purpose of this class is to do more sophisticated number +// comparison. (Due to round-off error, etc, it's very unlikely that +// two floating-points will be equal exactly. Hence a naive +// comparison by the == operation often doesn't work.) +// +// Format of IEEE floating-point: +// +// The most-significant bit being the leftmost, an IEEE +// floating-point looks like +// +// sign_bit exponent_bits fraction_bits +// +// Here, sign_bit is a single bit that designates the sign of the +// number. +// +// For float, there are 8 exponent bits and 23 fraction bits. +// +// For double, there are 11 exponent bits and 52 fraction bits. +// +// More details can be found at +// https://en.wikipedia.org/wiki/IEEE_floating-point_standard. +// +// Template parameter: +// +// RawType: the raw floating-point type (either float or double) +template +class FloatingPoint { + public: + // Defines the unsigned integer type that has the same size as the + // floating point number. + typedef typename TypeWithSize::UInt Bits; + + // Constants. + + // # of bits in a number. + static const size_t kBitCount = 8 * sizeof(RawType); + + // # of fraction bits in a number. + static const size_t kFractionBitCount = + std::numeric_limits::digits - 1; + + // # of exponent bits in a number. + static const size_t kExponentBitCount = kBitCount - 1 - kFractionBitCount; + + // The mask for the sign bit. + static const Bits kSignBitMask = static_cast(1) << (kBitCount - 1); + + // The mask for the fraction bits. + static const Bits kFractionBitMask = ~static_cast(0) >> + (kExponentBitCount + 1); + + // The mask for the exponent bits. + static const Bits kExponentBitMask = ~(kSignBitMask | kFractionBitMask); + + // How many ULP's (Units in the Last Place) we want to tolerate when + // comparing two numbers. The larger the value, the more error we + // allow. A 0 value means that two numbers must be exactly the same + // to be considered equal. + // + // The maximum error of a single floating-point operation is 0.5 + // units in the last place. On Intel CPU's, all floating-point + // calculations are done with 80-bit precision, while double has 64 + // bits. Therefore, 4 should be enough for ordinary use. + // + // See the following article for more details on ULP: + // https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ + static const uint32_t kMaxUlps = 4; + + // Constructs a FloatingPoint from a raw floating-point number. + // + // On an Intel CPU, passing a non-normalized NAN (Not a Number) + // around may change its bits, although the new value is guaranteed + // to be also a NAN. Therefore, don't expect this constructor to + // preserve the bits in x when x is a NAN. + explicit FloatingPoint(RawType x) { memcpy(&bits_, &x, sizeof(x)); } + + // Static methods + + // Reinterprets a bit pattern as a floating-point number. + // + // This function is needed to test the AlmostEquals() method. + static RawType ReinterpretBits(Bits bits) { + RawType fp; + memcpy(&fp, &bits, sizeof(fp)); + return fp; + } + + // Returns the floating-point number that represent positive infinity. + static RawType Infinity() { return ReinterpretBits(kExponentBitMask); } + + // Non-static methods + + // Returns the bits that represents this number. + const Bits& bits() const { return bits_; } + + // Returns the exponent bits of this number. + Bits exponent_bits() const { return kExponentBitMask & bits_; } + + // Returns the fraction bits of this number. + Bits fraction_bits() const { return kFractionBitMask & bits_; } + + // Returns the sign bit of this number. + Bits sign_bit() const { return kSignBitMask & bits_; } + + // Returns true if and only if this is NAN (not a number). + bool is_nan() const { + // It's a NAN if the exponent bits are all ones and the fraction + // bits are not entirely zeros. + return (exponent_bits() == kExponentBitMask) && (fraction_bits() != 0); + } + + // Returns true if and only if this number is at most kMaxUlps ULP's away + // from rhs. In particular, this function: + // + // - returns false if either number is (or both are) NAN. + // - treats really large numbers as almost equal to infinity. + // - thinks +0.0 and -0.0 are 0 ULP's apart. + bool AlmostEquals(const FloatingPoint& rhs) const { + // The IEEE standard says that any comparison operation involving + // a NAN must return false. + if (is_nan() || rhs.is_nan()) return false; + + return DistanceBetweenSignAndMagnitudeNumbers(bits_, rhs.bits_) <= kMaxUlps; + } + + private: + // Converts an integer from the sign-and-magnitude representation to + // the biased representation. More precisely, let N be 2 to the + // power of (kBitCount - 1), an integer x is represented by the + // unsigned number x + N. + // + // For instance, + // + // -N + 1 (the most negative number representable using + // sign-and-magnitude) is represented by 1; + // 0 is represented by N; and + // N - 1 (the biggest number representable using + // sign-and-magnitude) is represented by 2N - 1. + // + // Read https://en.wikipedia.org/wiki/Signed_number_representations + // for more details on signed number representations. + static Bits SignAndMagnitudeToBiased(Bits sam) { + if (kSignBitMask & sam) { + // sam represents a negative number. + return ~sam + 1; + } else { + // sam represents a positive number. + return kSignBitMask | sam; + } + } + + // Given two numbers in the sign-and-magnitude representation, + // returns the distance between them as an unsigned number. + static Bits DistanceBetweenSignAndMagnitudeNumbers(Bits sam1, Bits sam2) { + const Bits biased1 = SignAndMagnitudeToBiased(sam1); + const Bits biased2 = SignAndMagnitudeToBiased(sam2); + return (biased1 >= biased2) ? (biased1 - biased2) : (biased2 - biased1); + } + + Bits bits_; // The bits that represent the number. +}; + +// Typedefs the instances of the FloatingPoint template class that we +// care to use. +typedef FloatingPoint Float; +typedef FloatingPoint Double; + +// In order to catch the mistake of putting tests that use different +// test fixture classes in the same test suite, we need to assign +// unique IDs to fixture classes and compare them. The TypeId type is +// used to hold such IDs. The user should treat TypeId as an opaque +// type: the only operation allowed on TypeId values is to compare +// them for equality using the == operator. +typedef const void* TypeId; + +template +class TypeIdHelper { + public: + // dummy_ must not have a const type. Otherwise an overly eager + // compiler (e.g. MSVC 7.1 & 8.0) may try to merge + // TypeIdHelper::dummy_ for different Ts as an "optimization". + static bool dummy_; +}; + +template +bool TypeIdHelper::dummy_ = false; + +// GetTypeId() returns the ID of type T. Different values will be +// returned for different types. Calling the function twice with the +// same type argument is guaranteed to return the same ID. +template +TypeId GetTypeId() { + // The compiler is required to allocate a different + // TypeIdHelper::dummy_ variable for each T used to instantiate + // the template. Therefore, the address of dummy_ is guaranteed to + // be unique. + return &(TypeIdHelper::dummy_); +} + +// Returns the type ID of ::testing::Test. Always call this instead +// of GetTypeId< ::testing::Test>() to get the type ID of +// ::testing::Test, as the latter may give the wrong result due to a +// suspected linker bug when compiling Google Test as a Mac OS X +// framework. +GTEST_API_ TypeId GetTestTypeId(); + +// Defines the abstract factory interface that creates instances +// of a Test object. +class TestFactoryBase { + public: + virtual ~TestFactoryBase() = default; + + // Creates a test instance to run. The instance is both created and destroyed + // within TestInfoImpl::Run() + virtual Test* CreateTest() = 0; + + protected: + TestFactoryBase() {} + + private: + TestFactoryBase(const TestFactoryBase&) = delete; + TestFactoryBase& operator=(const TestFactoryBase&) = delete; +}; + +// This class provides implementation of TestFactoryBase interface. +// It is used in TEST and TEST_F macros. +template +class TestFactoryImpl : public TestFactoryBase { + public: + Test* CreateTest() override { return new TestClass; } +}; + +#ifdef GTEST_OS_WINDOWS + +// Predicate-formatters for implementing the HRESULT checking macros +// {ASSERT|EXPECT}_HRESULT_{SUCCEEDED|FAILED} +// We pass a long instead of HRESULT to avoid causing an +// include dependency for the HRESULT type. +GTEST_API_ AssertionResult IsHRESULTSuccess(const char* expr, + long hr); // NOLINT +GTEST_API_ AssertionResult IsHRESULTFailure(const char* expr, + long hr); // NOLINT + +#endif // GTEST_OS_WINDOWS + +// Types of SetUpTestSuite() and TearDownTestSuite() functions. +using SetUpTestSuiteFunc = void (*)(); +using TearDownTestSuiteFunc = void (*)(); + +struct CodeLocation { + CodeLocation(std::string a_file, int a_line) + : file(std::move(a_file)), line(a_line) {} + + std::string file; + int line; +}; + +// Helper to identify which setup function for TestCase / TestSuite to call. +// Only one function is allowed, either TestCase or TestSute but not both. + +// Utility functions to help SuiteApiResolver +using SetUpTearDownSuiteFuncType = void (*)(); + +inline SetUpTearDownSuiteFuncType GetNotDefaultOrNull( + SetUpTearDownSuiteFuncType a, SetUpTearDownSuiteFuncType def) { + return a == def ? nullptr : a; +} + +template +// Note that SuiteApiResolver inherits from T because +// SetUpTestSuite()/TearDownTestSuite() could be protected. This way +// SuiteApiResolver can access them. +struct SuiteApiResolver : T { + // testing::Test is only forward declared at this point. So we make it a + // dependent class for the compiler to be OK with it. + using Test = + typename std::conditional::type; + + static SetUpTearDownSuiteFuncType GetSetUpCaseOrSuite(const char* filename, + int line_num) { +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + SetUpTearDownSuiteFuncType test_case_fp = + GetNotDefaultOrNull(&T::SetUpTestCase, &Test::SetUpTestCase); + SetUpTearDownSuiteFuncType test_suite_fp = + GetNotDefaultOrNull(&T::SetUpTestSuite, &Test::SetUpTestSuite); + + GTEST_CHECK_(!test_case_fp || !test_suite_fp) + << "Test can not provide both SetUpTestSuite and SetUpTestCase, please " + "make sure there is only one present at " + << filename << ":" << line_num; + + return test_case_fp != nullptr ? test_case_fp : test_suite_fp; +#else + (void)(filename); + (void)(line_num); + return &T::SetUpTestSuite; +#endif + } + + static SetUpTearDownSuiteFuncType GetTearDownCaseOrSuite(const char* filename, + int line_num) { +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + SetUpTearDownSuiteFuncType test_case_fp = + GetNotDefaultOrNull(&T::TearDownTestCase, &Test::TearDownTestCase); + SetUpTearDownSuiteFuncType test_suite_fp = + GetNotDefaultOrNull(&T::TearDownTestSuite, &Test::TearDownTestSuite); + + GTEST_CHECK_(!test_case_fp || !test_suite_fp) + << "Test can not provide both TearDownTestSuite and TearDownTestCase," + " please make sure there is only one present at" + << filename << ":" << line_num; + + return test_case_fp != nullptr ? test_case_fp : test_suite_fp; +#else + (void)(filename); + (void)(line_num); + return &T::TearDownTestSuite; +#endif + } +}; + +// Creates a new TestInfo object and registers it with Google Test; +// returns the created object. +// +// Arguments: +// +// test_suite_name: name of the test suite +// name: name of the test +// type_param: the name of the test's type parameter, or NULL if +// this is not a typed or a type-parameterized test. +// value_param: text representation of the test's value parameter, +// or NULL if this is not a value-parameterized test. +// code_location: code location where the test is defined +// fixture_class_id: ID of the test fixture class +// set_up_tc: pointer to the function that sets up the test suite +// tear_down_tc: pointer to the function that tears down the test suite +// factory: pointer to the factory that creates a test object. +// The newly created TestInfo instance will assume +// ownership of the factory object. +GTEST_API_ TestInfo* MakeAndRegisterTestInfo( + std::string test_suite_name, const char* name, const char* type_param, + const char* value_param, CodeLocation code_location, + TypeId fixture_class_id, SetUpTestSuiteFunc set_up_tc, + TearDownTestSuiteFunc tear_down_tc, TestFactoryBase* factory); + +// If *pstr starts with the given prefix, modifies *pstr to be right +// past the prefix and returns true; otherwise leaves *pstr unchanged +// and returns false. None of pstr, *pstr, and prefix can be NULL. +GTEST_API_ bool SkipPrefix(const char* prefix, const char** pstr); + +GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ +/* class A needs to have dll-interface to be used by clients of class B */) + +// State of the definition of a type-parameterized test suite. +class GTEST_API_ TypedTestSuitePState { + public: + TypedTestSuitePState() : registered_(false) {} + + // Adds the given test name to defined_test_names_ and return true + // if the test suite hasn't been registered; otherwise aborts the + // program. + bool AddTestName(const char* file, int line, const char* case_name, + const char* test_name) { + if (registered_) { + fprintf(stderr, + "%s Test %s must be defined before " + "REGISTER_TYPED_TEST_SUITE_P(%s, ...).\n", + FormatFileLocation(file, line).c_str(), test_name, case_name); + fflush(stderr); + posix::Abort(); + } + registered_tests_.emplace(test_name, CodeLocation(file, line)); + return true; + } + + bool TestExists(const std::string& test_name) const { + return registered_tests_.count(test_name) > 0; + } + + const CodeLocation& GetCodeLocation(const std::string& test_name) const { + RegisteredTestsMap::const_iterator it = registered_tests_.find(test_name); + GTEST_CHECK_(it != registered_tests_.end()); + return it->second; + } + + // Verifies that registered_tests match the test names in + // defined_test_names_; returns registered_tests if successful, or + // aborts the program otherwise. + const char* VerifyRegisteredTestNames(const char* test_suite_name, + const char* file, int line, + const char* registered_tests); + + private: + typedef ::std::map> RegisteredTestsMap; + + bool registered_; + RegisteredTestsMap registered_tests_; +}; + +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ +using TypedTestCasePState = TypedTestSuitePState; +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + +GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 + +// Skips to the first non-space char after the first comma in 'str'; +// returns NULL if no comma is found in 'str'. +inline const char* SkipComma(const char* str) { + const char* comma = strchr(str, ','); + if (comma == nullptr) { + return nullptr; + } + while (IsSpace(*(++comma))) { + } + return comma; +} + +// Returns the prefix of 'str' before the first comma in it; returns +// the entire string if it contains no comma. +inline std::string GetPrefixUntilComma(const char* str) { + const char* comma = strchr(str, ','); + return comma == nullptr ? str : std::string(str, comma); +} + +// Splits a given string on a given delimiter, populating a given +// vector with the fields. +void SplitString(const ::std::string& str, char delimiter, + ::std::vector<::std::string>* dest); + +// The default argument to the template below for the case when the user does +// not provide a name generator. +struct DefaultNameGenerator { + template + static std::string GetName(int i) { + return StreamableToString(i); + } +}; + +template +struct NameGeneratorSelector { + typedef Provided type; +}; + +template +void GenerateNamesRecursively(internal::None, std::vector*, int) {} + +template +void GenerateNamesRecursively(Types, std::vector* result, int i) { + result->push_back(NameGenerator::template GetName(i)); + GenerateNamesRecursively(typename Types::Tail(), result, + i + 1); +} + +template +std::vector GenerateNames() { + std::vector result; + GenerateNamesRecursively(Types(), &result, 0); + return result; +} + +// TypeParameterizedTest::Register() +// registers a list of type-parameterized tests with Google Test. The +// return value is insignificant - we just need to return something +// such that we can call this function in a namespace scope. +// +// Implementation note: The GTEST_TEMPLATE_ macro declares a template +// template parameter. It's defined in gtest-type-util.h. +template +class TypeParameterizedTest { + public: + // 'index' is the index of the test in the type list 'Types' + // specified in INSTANTIATE_TYPED_TEST_SUITE_P(Prefix, TestSuite, + // Types). Valid values for 'index' are [0, N - 1] where N is the + // length of Types. + static bool Register(const char* prefix, CodeLocation code_location, + const char* case_name, const char* test_names, int index, + const std::vector& type_names = + GenerateNames()) { + typedef typename Types::Head Type; + typedef Fixture FixtureClass; + typedef typename GTEST_BIND_(TestSel, Type) TestClass; + + // First, registers the first type-parameterized test in the type + // list. + MakeAndRegisterTestInfo( + (std::string(prefix) + (prefix[0] == '\0' ? "" : "/") + case_name + + "/" + type_names[static_cast(index)]), + StripTrailingSpaces(GetPrefixUntilComma(test_names)).c_str(), + GetTypeName().c_str(), + nullptr, // No value parameter. + code_location, GetTypeId(), + SuiteApiResolver::GetSetUpCaseOrSuite( + code_location.file.c_str(), code_location.line), + SuiteApiResolver::GetTearDownCaseOrSuite( + code_location.file.c_str(), code_location.line), + new TestFactoryImpl); + + // Next, recurses (at compile time) with the tail of the type list. + return TypeParameterizedTest:: + Register(prefix, std::move(code_location), case_name, test_names, + index + 1, type_names); + } +}; + +// The base case for the compile time recursion. +template +class TypeParameterizedTest { + public: + static bool Register(const char* /*prefix*/, CodeLocation, + const char* /*case_name*/, const char* /*test_names*/, + int /*index*/, + const std::vector& = + std::vector() /*type_names*/) { + return true; + } +}; + +GTEST_API_ void RegisterTypeParameterizedTestSuite(const char* test_suite_name, + CodeLocation code_location); +GTEST_API_ void RegisterTypeParameterizedTestSuiteInstantiation( + const char* case_name); + +// TypeParameterizedTestSuite::Register() +// registers *all combinations* of 'Tests' and 'Types' with Google +// Test. The return value is insignificant - we just need to return +// something such that we can call this function in a namespace scope. +template +class TypeParameterizedTestSuite { + public: + static bool Register(const char* prefix, CodeLocation code_location, + const TypedTestSuitePState* state, const char* case_name, + const char* test_names, + const std::vector& type_names = + GenerateNames()) { + RegisterTypeParameterizedTestSuiteInstantiation(case_name); + std::string test_name = + StripTrailingSpaces(GetPrefixUntilComma(test_names)); + if (!state->TestExists(test_name)) { + fprintf(stderr, "Failed to get code location for test %s.%s at %s.", + case_name, test_name.c_str(), + FormatFileLocation(code_location.file.c_str(), code_location.line) + .c_str()); + fflush(stderr); + posix::Abort(); + } + const CodeLocation& test_location = state->GetCodeLocation(test_name); + + typedef typename Tests::Head Head; + + // First, register the first test in 'Test' for each type in 'Types'. + TypeParameterizedTest::Register( + prefix, test_location, case_name, test_names, 0, type_names); + + // Next, recurses (at compile time) with the tail of the test list. + return TypeParameterizedTestSuite::Register(prefix, + std::move(code_location), + state, case_name, + SkipComma(test_names), + type_names); + } +}; + +// The base case for the compile time recursion. +template +class TypeParameterizedTestSuite { + public: + static bool Register(const char* /*prefix*/, const CodeLocation&, + const TypedTestSuitePState* /*state*/, + const char* /*case_name*/, const char* /*test_names*/, + const std::vector& = + std::vector() /*type_names*/) { + return true; + } +}; + +// Returns the current OS stack trace as an std::string. +// +// The maximum number of stack frames to be included is specified by +// the gtest_stack_trace_depth flag. The skip_count parameter +// specifies the number of top frames to be skipped, which doesn't +// count against the number of frames to be included. +// +// For example, if Foo() calls Bar(), which in turn calls +// GetCurrentOsStackTraceExceptTop(..., 1), Foo() will be included in +// the trace but Bar() and GetCurrentOsStackTraceExceptTop() won't. +GTEST_API_ std::string GetCurrentOsStackTraceExceptTop(int skip_count); + +// Helpers for suppressing warnings on unreachable code or constant +// condition. + +// Always returns true. +GTEST_API_ bool AlwaysTrue(); + +// Always returns false. +inline bool AlwaysFalse() { return !AlwaysTrue(); } + +// Helper for suppressing false warning from Clang on a const char* +// variable declared in a conditional expression always being NULL in +// the else branch. +struct GTEST_API_ ConstCharPtr { + ConstCharPtr(const char* str) : value(str) {} + operator bool() const { return true; } + const char* value; +}; + +// Helper for declaring std::string within 'if' statement +// in pre C++17 build environment. +struct TrueWithString { + TrueWithString() = default; + explicit TrueWithString(const char* str) : value(str) {} + explicit TrueWithString(const std::string& str) : value(str) {} + explicit operator bool() const { return true; } + std::string value; +}; + +// A simple Linear Congruential Generator for generating random +// numbers with a uniform distribution. Unlike rand() and srand(), it +// doesn't use global state (and therefore can't interfere with user +// code). Unlike rand_r(), it's portable. An LCG isn't very random, +// but it's good enough for our purposes. +class GTEST_API_ Random { + public: + static const uint32_t kMaxRange = 1u << 31; + + explicit Random(uint32_t seed) : state_(seed) {} + + void Reseed(uint32_t seed) { state_ = seed; } + + // Generates a random number from [0, range). Crashes if 'range' is + // 0 or greater than kMaxRange. + uint32_t Generate(uint32_t range); + + private: + uint32_t state_; + Random(const Random&) = delete; + Random& operator=(const Random&) = delete; +}; + +// Turns const U&, U&, const U, and U all into U. +#define GTEST_REMOVE_REFERENCE_AND_CONST_(T) \ + typename std::remove_const::type>::type + +// HasDebugStringAndShortDebugString::value is a compile-time bool constant +// that's true if and only if T has methods DebugString() and ShortDebugString() +// that return std::string. +template +class HasDebugStringAndShortDebugString { + private: + template + static auto CheckDebugString(C*) -> typename std::is_same< + std::string, decltype(std::declval().DebugString())>::type; + template + static std::false_type CheckDebugString(...); + + template + static auto CheckShortDebugString(C*) -> typename std::is_same< + std::string, decltype(std::declval().ShortDebugString())>::type; + template + static std::false_type CheckShortDebugString(...); + + using HasDebugStringType = decltype(CheckDebugString(nullptr)); + using HasShortDebugStringType = decltype(CheckShortDebugString(nullptr)); + + public: + static constexpr bool value = + HasDebugStringType::value && HasShortDebugStringType::value; +}; + +// When the compiler sees expression IsContainerTest(0), if C is an +// STL-style container class, the first overload of IsContainerTest +// will be viable (since both C::iterator* and C::const_iterator* are +// valid types and NULL can be implicitly converted to them). It will +// be picked over the second overload as 'int' is a perfect match for +// the type of argument 0. If C::iterator or C::const_iterator is not +// a valid type, the first overload is not viable, and the second +// overload will be picked. Therefore, we can determine whether C is +// a container class by checking the type of IsContainerTest(0). +// The value of the expression is insignificant. +// +// In C++11 mode we check the existence of a const_iterator and that an +// iterator is properly implemented for the container. +// +// For pre-C++11 that we look for both C::iterator and C::const_iterator. +// The reason is that C++ injects the name of a class as a member of the +// class itself (e.g. you can refer to class iterator as either +// 'iterator' or 'iterator::iterator'). If we look for C::iterator +// only, for example, we would mistakenly think that a class named +// iterator is an STL container. +// +// Also note that the simpler approach of overloading +// IsContainerTest(typename C::const_iterator*) and +// IsContainerTest(...) doesn't work with Visual Age C++ and Sun C++. +typedef int IsContainer; +template ().begin()), + class = decltype(::std::declval().end()), + class = decltype(++::std::declval()), + class = decltype(*::std::declval()), + class = typename C::const_iterator> +IsContainer IsContainerTest(int /* dummy */) { + return 0; +} + +typedef char IsNotContainer; +template +IsNotContainer IsContainerTest(long /* dummy */) { + return '\0'; +} + +// Trait to detect whether a type T is a hash table. +// The heuristic used is that the type contains an inner type `hasher` and does +// not contain an inner type `reverse_iterator`. +// If the container is iterable in reverse, then order might actually matter. +template +struct IsHashTable { + private: + template + static char test(typename U::hasher*, typename U::reverse_iterator*); + template + static int test(typename U::hasher*, ...); + template + static char test(...); + + public: + static const bool value = sizeof(test(nullptr, nullptr)) == sizeof(int); +}; + +template +const bool IsHashTable::value; + +template (0)) == sizeof(IsContainer)> +struct IsRecursiveContainerImpl; + +template +struct IsRecursiveContainerImpl : public std::false_type {}; + +// Since the IsRecursiveContainerImpl depends on the IsContainerTest we need to +// obey the same inconsistencies as the IsContainerTest, namely check if +// something is a container is relying on only const_iterator in C++11 and +// is relying on both const_iterator and iterator otherwise +template +struct IsRecursiveContainerImpl { + using value_type = decltype(*std::declval()); + using type = + std::is_same::type>::type, + C>; +}; + +// IsRecursiveContainer is a unary compile-time predicate that +// evaluates whether C is a recursive container type. A recursive container +// type is a container type whose value_type is equal to the container type +// itself. An example for a recursive container type is +// boost::filesystem::path, whose iterator has a value_type that is equal to +// boost::filesystem::path. +template +struct IsRecursiveContainer : public IsRecursiveContainerImpl::type {}; + +// Utilities for native arrays. + +// ArrayEq() compares two k-dimensional native arrays using the +// elements' operator==, where k can be any integer >= 0. When k is +// 0, ArrayEq() degenerates into comparing a single pair of values. + +template +bool ArrayEq(const T* lhs, size_t size, const U* rhs); + +// This generic version is used when k is 0. +template +inline bool ArrayEq(const T& lhs, const U& rhs) { + return lhs == rhs; +} + +// This overload is used when k >= 1. +template +inline bool ArrayEq(const T (&lhs)[N], const U (&rhs)[N]) { + return internal::ArrayEq(lhs, N, rhs); +} + +// This helper reduces code bloat. If we instead put its logic inside +// the previous ArrayEq() function, arrays with different sizes would +// lead to different copies of the template code. +template +bool ArrayEq(const T* lhs, size_t size, const U* rhs) { + for (size_t i = 0; i != size; i++) { + if (!internal::ArrayEq(lhs[i], rhs[i])) return false; + } + return true; +} + +// Finds the first element in the iterator range [begin, end) that +// equals elem. Element may be a native array type itself. +template +Iter ArrayAwareFind(Iter begin, Iter end, const Element& elem) { + for (Iter it = begin; it != end; ++it) { + if (internal::ArrayEq(*it, elem)) return it; + } + return end; +} + +// CopyArray() copies a k-dimensional native array using the elements' +// operator=, where k can be any integer >= 0. When k is 0, +// CopyArray() degenerates into copying a single value. + +template +void CopyArray(const T* from, size_t size, U* to); + +// This generic version is used when k is 0. +template +inline void CopyArray(const T& from, U* to) { + *to = from; +} + +// This overload is used when k >= 1. +template +inline void CopyArray(const T (&from)[N], U (*to)[N]) { + internal::CopyArray(from, N, *to); +} + +// This helper reduces code bloat. If we instead put its logic inside +// the previous CopyArray() function, arrays with different sizes +// would lead to different copies of the template code. +template +void CopyArray(const T* from, size_t size, U* to) { + for (size_t i = 0; i != size; i++) { + internal::CopyArray(from[i], to + i); + } +} + +// The relation between an NativeArray object (see below) and the +// native array it represents. +// We use 2 different structs to allow non-copyable types to be used, as long +// as RelationToSourceReference() is passed. +struct RelationToSourceReference {}; +struct RelationToSourceCopy {}; + +// Adapts a native array to a read-only STL-style container. Instead +// of the complete STL container concept, this adaptor only implements +// members useful for Google Mock's container matchers. New members +// should be added as needed. To simplify the implementation, we only +// support Element being a raw type (i.e. having no top-level const or +// reference modifier). It's the client's responsibility to satisfy +// this requirement. Element can be an array type itself (hence +// multi-dimensional arrays are supported). +template +class NativeArray { + public: + // STL-style container typedefs. + typedef Element value_type; + typedef Element* iterator; + typedef const Element* const_iterator; + + // Constructs from a native array. References the source. + NativeArray(const Element* array, size_t count, RelationToSourceReference) { + InitRef(array, count); + } + + // Constructs from a native array. Copies the source. + NativeArray(const Element* array, size_t count, RelationToSourceCopy) { + InitCopy(array, count); + } + + // Copy constructor. + NativeArray(const NativeArray& rhs) { + (this->*rhs.clone_)(rhs.array_, rhs.size_); + } + + ~NativeArray() { + if (clone_ != &NativeArray::InitRef) delete[] array_; + } + + // STL-style container methods. + size_t size() const { return size_; } + const_iterator begin() const { return array_; } + const_iterator end() const { return array_ + size_; } + bool operator==(const NativeArray& rhs) const { + return size() == rhs.size() && ArrayEq(begin(), size(), rhs.begin()); + } + + private: + static_assert(!std::is_const::value, "Type must not be const"); + static_assert(!std::is_reference::value, + "Type must not be a reference"); + + // Initializes this object with a copy of the input. + void InitCopy(const Element* array, size_t a_size) { + Element* const copy = new Element[a_size]; + CopyArray(array, a_size, copy); + array_ = copy; + size_ = a_size; + clone_ = &NativeArray::InitCopy; + } + + // Initializes this object with a reference of the input. + void InitRef(const Element* array, size_t a_size) { + array_ = array; + size_ = a_size; + clone_ = &NativeArray::InitRef; + } + + const Element* array_; + size_t size_; + void (NativeArray::*clone_)(const Element*, size_t); +}; + +template +struct Ignore { + Ignore(...); // NOLINT +}; + +template +struct ElemFromListImpl; +template +struct ElemFromListImpl> { + // We make Ignore a template to solve a problem with MSVC. + // A non-template Ignore would work fine with `decltype(Ignore(I))...`, but + // MSVC doesn't understand how to deal with that pack expansion. + // Use `0 * I` to have a single instantiation of Ignore. + template + static R Apply(Ignore<0 * I>..., R (*)(), ...); +}; + +template +struct ElemFromList { + using type = decltype(ElemFromListImpl>::Apply( + static_cast(nullptr)...)); +}; + +struct FlatTupleConstructTag {}; + +template +class FlatTuple; + +template +struct FlatTupleElemBase; + +template +struct FlatTupleElemBase, I> { + using value_type = typename ElemFromList::type; + FlatTupleElemBase() = default; + template + explicit FlatTupleElemBase(FlatTupleConstructTag, Arg&& t) + : value(std::forward(t)) {} + value_type value; +}; + +template +struct FlatTupleBase; + +template +struct FlatTupleBase, std::index_sequence> + : FlatTupleElemBase, Idx>... { + using Indices = std::index_sequence; + FlatTupleBase() = default; + template + explicit FlatTupleBase(FlatTupleConstructTag, Args&&... args) + : FlatTupleElemBase, Idx>(FlatTupleConstructTag{}, + std::forward(args))... {} + + template + const typename ElemFromList::type& Get() const { + return FlatTupleElemBase, I>::value; + } + + template + typename ElemFromList::type& Get() { + return FlatTupleElemBase, I>::value; + } + + template + auto Apply(F&& f) -> decltype(std::forward(f)(this->Get()...)) { + return std::forward(f)(Get()...); + } + + template + auto Apply(F&& f) const -> decltype(std::forward(f)(this->Get()...)) { + return std::forward(f)(Get()...); + } +}; + +// Analog to std::tuple but with different tradeoffs. +// This class minimizes the template instantiation depth, thus allowing more +// elements than std::tuple would. std::tuple has been seen to require an +// instantiation depth of more than 10x the number of elements in some +// implementations. +// FlatTuple and ElemFromList are not recursive and have a fixed depth +// regardless of T... +// std::make_index_sequence, on the other hand, it is recursive but with an +// instantiation depth of O(ln(N)). +template +class FlatTuple + : private FlatTupleBase, + std::make_index_sequence> { + using Indices = + typename FlatTupleBase, + std::make_index_sequence>::Indices; + + public: + FlatTuple() = default; + template + explicit FlatTuple(FlatTupleConstructTag tag, Args&&... args) + : FlatTuple::FlatTupleBase(tag, std::forward(args)...) {} + + using FlatTuple::FlatTupleBase::Apply; + using FlatTuple::FlatTupleBase::Get; +}; + +// Utility functions to be called with static_assert to induce deprecation +// warnings. +[[deprecated( + "INSTANTIATE_TEST_CASE_P is deprecated, please use " + "INSTANTIATE_TEST_SUITE_P")]] +constexpr bool InstantiateTestCase_P_IsDeprecated() { + return true; +} + +[[deprecated( + "TYPED_TEST_CASE_P is deprecated, please use " + "TYPED_TEST_SUITE_P")]] +constexpr bool TypedTestCase_P_IsDeprecated() { + return true; +} + +[[deprecated( + "TYPED_TEST_CASE is deprecated, please use " + "TYPED_TEST_SUITE")]] +constexpr bool TypedTestCaseIsDeprecated() { + return true; +} + +[[deprecated( + "REGISTER_TYPED_TEST_CASE_P is deprecated, please use " + "REGISTER_TYPED_TEST_SUITE_P")]] +constexpr bool RegisterTypedTestCase_P_IsDeprecated() { + return true; +} + +[[deprecated( + "INSTANTIATE_TYPED_TEST_CASE_P is deprecated, please use " + "INSTANTIATE_TYPED_TEST_SUITE_P")]] +constexpr bool InstantiateTypedTestCase_P_IsDeprecated() { + return true; +} + +} // namespace internal +} // namespace testing + +namespace std { +// Some standard library implementations use `struct tuple_size` and some use +// `class tuple_size`. Clang warns about the mismatch. +// https://reviews.llvm.org/D55466 +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmismatched-tags" +#endif +template +struct tuple_size> + : std::integral_constant {}; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +} // namespace std + +#define GTEST_MESSAGE_AT_(file, line, message, result_type) \ + ::testing::internal::AssertHelper(result_type, file, line, message) = \ + ::testing::Message() + +#define GTEST_MESSAGE_(message, result_type) \ + GTEST_MESSAGE_AT_(__FILE__, __LINE__, message, result_type) + +#define GTEST_FATAL_FAILURE_(message) \ + return GTEST_MESSAGE_(message, ::testing::TestPartResult::kFatalFailure) + +#define GTEST_NONFATAL_FAILURE_(message) \ + GTEST_MESSAGE_(message, ::testing::TestPartResult::kNonFatalFailure) + +#define GTEST_SUCCESS_(message) \ + GTEST_MESSAGE_(message, ::testing::TestPartResult::kSuccess) + +#define GTEST_SKIP_(message) \ + return GTEST_MESSAGE_(message, ::testing::TestPartResult::kSkip) + +// Suppress MSVC warning 4072 (unreachable code) for the code following +// statement if it returns or throws (or doesn't return or throw in some +// situations). +// NOTE: The "else" is important to keep this expansion to prevent a top-level +// "else" from attaching to our "if". +#define GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement) \ + if (::testing::internal::AlwaysTrue()) { \ + statement; \ + } else /* NOLINT */ \ + static_assert(true, "") // User must have a semicolon after expansion. + +#if GTEST_HAS_EXCEPTIONS + +namespace testing { +namespace internal { + +class NeverThrown { + public: + const char* what() const noexcept { + return "this exception should never be thrown"; + } +}; + +} // namespace internal +} // namespace testing + +#if GTEST_HAS_RTTI + +#define GTEST_EXCEPTION_TYPE_(e) ::testing::internal::GetTypeName(typeid(e)) + +#else // GTEST_HAS_RTTI + +#define GTEST_EXCEPTION_TYPE_(e) \ + std::string { "an std::exception-derived error" } + +#endif // GTEST_HAS_RTTI + +#define GTEST_TEST_THROW_CATCH_STD_EXCEPTION_(statement, expected_exception) \ + catch (typename std::conditional< \ + std::is_same::type>::type, \ + std::exception>::value, \ + const ::testing::internal::NeverThrown&, const std::exception&>::type \ + e) { \ + gtest_msg.value = "Expected: " #statement \ + " throws an exception of type " #expected_exception \ + ".\n Actual: it throws "; \ + gtest_msg.value += GTEST_EXCEPTION_TYPE_(e); \ + gtest_msg.value += " with description \""; \ + gtest_msg.value += e.what(); \ + gtest_msg.value += "\"."; \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__); \ + } + +#else // GTEST_HAS_EXCEPTIONS + +#define GTEST_TEST_THROW_CATCH_STD_EXCEPTION_(statement, expected_exception) + +#endif // GTEST_HAS_EXCEPTIONS + +#define GTEST_TEST_THROW_(statement, expected_exception, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::testing::internal::TrueWithString gtest_msg{}) { \ + bool gtest_caught_expected = false; \ + try { \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + } catch (expected_exception const&) { \ + gtest_caught_expected = true; \ + } \ + GTEST_TEST_THROW_CATCH_STD_EXCEPTION_(statement, expected_exception) \ + catch (...) { \ + gtest_msg.value = "Expected: " #statement \ + " throws an exception of type " #expected_exception \ + ".\n Actual: it throws a different type."; \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__); \ + } \ + if (!gtest_caught_expected) { \ + gtest_msg.value = "Expected: " #statement \ + " throws an exception of type " #expected_exception \ + ".\n Actual: it throws nothing."; \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__); \ + } \ + } else /*NOLINT*/ \ + GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__) \ + : fail(gtest_msg.value.c_str()) + +#if GTEST_HAS_EXCEPTIONS + +#define GTEST_TEST_NO_THROW_CATCH_STD_EXCEPTION_() \ + catch (std::exception const& e) { \ + gtest_msg.value = "it throws "; \ + gtest_msg.value += GTEST_EXCEPTION_TYPE_(e); \ + gtest_msg.value += " with description \""; \ + gtest_msg.value += e.what(); \ + gtest_msg.value += "\"."; \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testnothrow_, __LINE__); \ + } + +#else // GTEST_HAS_EXCEPTIONS + +#define GTEST_TEST_NO_THROW_CATCH_STD_EXCEPTION_() + +#endif // GTEST_HAS_EXCEPTIONS + +#define GTEST_TEST_NO_THROW_(statement, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::testing::internal::TrueWithString gtest_msg{}) { \ + try { \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + } \ + GTEST_TEST_NO_THROW_CATCH_STD_EXCEPTION_() \ + catch (...) { \ + gtest_msg.value = "it throws."; \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testnothrow_, __LINE__); \ + } \ + } else \ + GTEST_CONCAT_TOKEN_(gtest_label_testnothrow_, __LINE__) \ + : fail(("Expected: " #statement " doesn't throw an exception.\n" \ + " Actual: " + \ + gtest_msg.value) \ + .c_str()) + +#define GTEST_TEST_ANY_THROW_(statement, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::testing::internal::AlwaysTrue()) { \ + bool gtest_caught_any = false; \ + try { \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + } catch (...) { \ + gtest_caught_any = true; \ + } \ + if (!gtest_caught_any) { \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testanythrow_, __LINE__); \ + } \ + } else \ + GTEST_CONCAT_TOKEN_(gtest_label_testanythrow_, __LINE__) \ + : fail("Expected: " #statement \ + " throws an exception.\n" \ + " Actual: it doesn't.") + +// Implements Boolean test assertions such as EXPECT_TRUE. expression can be +// either a boolean expression or an AssertionResult. text is a textual +// representation of expression as it was passed into the EXPECT_TRUE. +#define GTEST_TEST_BOOLEAN_(expression, text, actual, expected, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (const ::testing::AssertionResult gtest_ar_ = \ + ::testing::AssertionResult(expression)) \ + ; \ + else \ + fail(::testing::internal::GetBoolAssertionFailureMessage( \ + gtest_ar_, text, #actual, #expected) \ + .c_str()) + +#define GTEST_TEST_NO_FATAL_FAILURE_(statement, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::testing::internal::AlwaysTrue()) { \ + const ::testing::internal::HasNewFatalFailureHelper \ + gtest_fatal_failure_checker; \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + if (gtest_fatal_failure_checker.has_new_fatal_failure()) { \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testnofatal_, __LINE__); \ + } \ + } else /* NOLINT */ \ + GTEST_CONCAT_TOKEN_(gtest_label_testnofatal_, __LINE__) \ + : fail("Expected: " #statement \ + " doesn't generate new fatal " \ + "failures in the current thread.\n" \ + " Actual: it does.") + +// Expands to the name of the class that implements the given test. +#define GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \ + test_suite_name##_##test_name##_Test + +// Helper macro for defining tests. +#define GTEST_TEST_(test_suite_name, test_name, parent_class, parent_id) \ + static_assert(sizeof(GTEST_STRINGIFY_(test_suite_name)) > 1, \ + "test_suite_name must not be empty"); \ + static_assert(sizeof(GTEST_STRINGIFY_(test_name)) > 1, \ + "test_name must not be empty"); \ + class GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \ + : public parent_class { \ + public: \ + GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() = default; \ + ~GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() override = default; \ + GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \ + (const GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &) = delete; \ + GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) & operator=( \ + const GTEST_TEST_CLASS_NAME_(test_suite_name, \ + test_name) &) = delete; /* NOLINT */ \ + GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \ + (GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &&) noexcept = delete; \ + GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) & operator=( \ + GTEST_TEST_CLASS_NAME_(test_suite_name, \ + test_name) &&) noexcept = delete; /* NOLINT */ \ + \ + private: \ + void TestBody() override; \ + [[maybe_unused]] static ::testing::TestInfo* const test_info_; \ + }; \ + \ + ::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(test_suite_name, \ + test_name)::test_info_ = \ + ::testing::internal::MakeAndRegisterTestInfo( \ + #test_suite_name, #test_name, nullptr, nullptr, \ + ::testing::internal::CodeLocation(__FILE__, __LINE__), (parent_id), \ + ::testing::internal::SuiteApiResolver< \ + parent_class>::GetSetUpCaseOrSuite(__FILE__, __LINE__), \ + ::testing::internal::SuiteApiResolver< \ + parent_class>::GetTearDownCaseOrSuite(__FILE__, __LINE__), \ + new ::testing::internal::TestFactoryImpl); \ + void GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)::TestBody() + +#endif // GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_INTERNAL_H_ diff --git a/googletest/include/gtest/internal/gtest-param-util.h b/googletest/include/gtest/internal/gtest-param-util.h new file mode 100644 index 00000000..a092a86a --- /dev/null +++ b/googletest/include/gtest/internal/gtest-param-util.h @@ -0,0 +1,1064 @@ +// Copyright 2008 Google Inc. +// All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Type and function utilities for implementing parameterized tests. + +// IWYU pragma: private, include "gtest/gtest.h" +// IWYU pragma: friend gtest/.* +// IWYU pragma: friend gmock/.* + +#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_H_ +#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_H_ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gtest/gtest-printers.h" +#include "gtest/gtest-test-part.h" +#include "gtest/internal/gtest-internal.h" +#include "gtest/internal/gtest-port.h" + +namespace testing { +// Input to a parameterized test name generator, describing a test parameter. +// Consists of the parameter value and the integer parameter index. +template +struct TestParamInfo { + TestParamInfo(const ParamType& a_param, size_t an_index) + : param(a_param), index(an_index) {} + ParamType param; + size_t index; +}; + +// A builtin parameterized test name generator which returns the result of +// testing::PrintToString. +struct PrintToStringParamName { + template + std::string operator()(const TestParamInfo& info) const { + return PrintToString(info.param); + } +}; + +namespace internal { + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// Utility Functions + +// Outputs a message explaining invalid registration of different +// fixture class for the same test suite. This may happen when +// TEST_P macro is used to define two tests with the same name +// but in different namespaces. +GTEST_API_ void ReportInvalidTestSuiteType(const char* test_suite_name, + const CodeLocation& code_location); + +template +class ParamGeneratorInterface; +template +class ParamGenerator; + +// Interface for iterating over elements provided by an implementation +// of ParamGeneratorInterface. +template +class ParamIteratorInterface { + public: + virtual ~ParamIteratorInterface() = default; + // A pointer to the base generator instance. + // Used only for the purposes of iterator comparison + // to make sure that two iterators belong to the same generator. + virtual const ParamGeneratorInterface* BaseGenerator() const = 0; + // Advances iterator to point to the next element + // provided by the generator. The caller is responsible + // for not calling Advance() on an iterator equal to + // BaseGenerator()->End(). + virtual void Advance() = 0; + // Clones the iterator object. Used for implementing copy semantics + // of ParamIterator. + virtual ParamIteratorInterface* Clone() const = 0; + // Dereferences the current iterator and provides (read-only) access + // to the pointed value. It is the caller's responsibility not to call + // Current() on an iterator equal to BaseGenerator()->End(). + // Used for implementing ParamGenerator::operator*(). + virtual const T* Current() const = 0; + // Determines whether the given iterator and other point to the same + // element in the sequence generated by the generator. + // Used for implementing ParamGenerator::operator==(). + virtual bool Equals(const ParamIteratorInterface& other) const = 0; +}; + +// Class iterating over elements provided by an implementation of +// ParamGeneratorInterface. It wraps ParamIteratorInterface +// and implements the const forward iterator concept. +template +class ParamIterator { + public: + typedef T value_type; + typedef const T& reference; + typedef ptrdiff_t difference_type; + + // ParamIterator assumes ownership of the impl_ pointer. + ParamIterator(const ParamIterator& other) : impl_(other.impl_->Clone()) {} + ParamIterator& operator=(const ParamIterator& other) { + if (this != &other) impl_.reset(other.impl_->Clone()); + return *this; + } + + const T& operator*() const { return *impl_->Current(); } + const T* operator->() const { return impl_->Current(); } + // Prefix version of operator++. + ParamIterator& operator++() { + impl_->Advance(); + return *this; + } + // Postfix version of operator++. + ParamIterator operator++(int /*unused*/) { + ParamIteratorInterface* clone = impl_->Clone(); + impl_->Advance(); + return ParamIterator(clone); + } + bool operator==(const ParamIterator& other) const { + return impl_.get() == other.impl_.get() || impl_->Equals(*other.impl_); + } + bool operator!=(const ParamIterator& other) const { + return !(*this == other); + } + + private: + friend class ParamGenerator; + explicit ParamIterator(ParamIteratorInterface* impl) : impl_(impl) {} + std::unique_ptr> impl_; +}; + +// ParamGeneratorInterface is the binary interface to access generators +// defined in other translation units. +template +class ParamGeneratorInterface { + public: + typedef T ParamType; + + virtual ~ParamGeneratorInterface() = default; + + // Generator interface definition + virtual ParamIteratorInterface* Begin() const = 0; + virtual ParamIteratorInterface* End() const = 0; +}; + +// Wraps ParamGeneratorInterface and provides general generator syntax +// compatible with the STL Container concept. +// This class implements copy initialization semantics and the contained +// ParamGeneratorInterface instance is shared among all copies +// of the original object. This is possible because that instance is immutable. +template +class ParamGenerator { + public: + typedef ParamIterator iterator; + + explicit ParamGenerator(ParamGeneratorInterface* impl) : impl_(impl) {} + ParamGenerator(const ParamGenerator& other) : impl_(other.impl_) {} + + ParamGenerator& operator=(const ParamGenerator& other) { + impl_ = other.impl_; + return *this; + } + + iterator begin() const { return iterator(impl_->Begin()); } + iterator end() const { return iterator(impl_->End()); } + + private: + std::shared_ptr> impl_; +}; + +// Generates values from a range of two comparable values. Can be used to +// generate sequences of user-defined types that implement operator+() and +// operator<(). +// This class is used in the Range() function. +template +class RangeGenerator : public ParamGeneratorInterface { + public: + RangeGenerator(T begin, T end, IncrementT step) + : begin_(begin), + end_(end), + step_(step), + end_index_(CalculateEndIndex(begin, end, step)) {} + ~RangeGenerator() override = default; + + ParamIteratorInterface* Begin() const override { + return new Iterator(this, begin_, 0, step_); + } + ParamIteratorInterface* End() const override { + return new Iterator(this, end_, end_index_, step_); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, T value, int index, + IncrementT step) + : base_(base), value_(value), index_(index), step_(step) {} + ~Iterator() override = default; + + const ParamGeneratorInterface* BaseGenerator() const override { + return base_; + } + void Advance() override { + value_ = static_cast(value_ + step_); + index_++; + } + ParamIteratorInterface* Clone() const override { + return new Iterator(*this); + } + const T* Current() const override { return &value_; } + bool Equals(const ParamIteratorInterface& other) const override { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const int other_index = + CheckedDowncastToActualType(&other)->index_; + return index_ == other_index; + } + + private: + Iterator(const Iterator& other) + : ParamIteratorInterface(), + base_(other.base_), + value_(other.value_), + index_(other.index_), + step_(other.step_) {} + + // No implementation - assignment is unsupported. + void operator=(const Iterator& other); + + const ParamGeneratorInterface* const base_; + T value_; + int index_; + const IncrementT step_; + }; // class RangeGenerator::Iterator + + static int CalculateEndIndex(const T& begin, const T& end, + const IncrementT& step) { + int end_index = 0; + for (T i = begin; i < end; i = static_cast(i + step)) end_index++; + return end_index; + } + + // No implementation - assignment is unsupported. + void operator=(const RangeGenerator& other); + + const T begin_; + const T end_; + const IncrementT step_; + // The index for the end() iterator. All the elements in the generated + // sequence are indexed (0-based) to aid iterator comparison. + const int end_index_; +}; // class RangeGenerator + +// Generates values from a pair of STL-style iterators. Used in the +// ValuesIn() function. The elements are copied from the source range +// since the source can be located on the stack, and the generator +// is likely to persist beyond that stack frame. +template +class ValuesInIteratorRangeGenerator : public ParamGeneratorInterface { + public: + template + ValuesInIteratorRangeGenerator(ForwardIterator begin, ForwardIterator end) + : container_(begin, end) {} + ~ValuesInIteratorRangeGenerator() override = default; + + ParamIteratorInterface* Begin() const override { + return new Iterator(this, container_.begin()); + } + ParamIteratorInterface* End() const override { + return new Iterator(this, container_.end()); + } + + private: + typedef typename ::std::vector ContainerType; + + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + typename ContainerType::const_iterator iterator) + : base_(base), iterator_(iterator) {} + ~Iterator() override = default; + + const ParamGeneratorInterface* BaseGenerator() const override { + return base_; + } + void Advance() override { + ++iterator_; + value_.reset(); + } + ParamIteratorInterface* Clone() const override { + return new Iterator(*this); + } + // We need to use cached value referenced by iterator_ because *iterator_ + // can return a temporary object (and of type other then T), so just + // having "return &*iterator_;" doesn't work. + // value_ is updated here and not in Advance() because Advance() + // can advance iterator_ beyond the end of the range, and we cannot + // detect that fact. The client code, on the other hand, is + // responsible for not calling Current() on an out-of-range iterator. + const T* Current() const override { + if (value_.get() == nullptr) value_.reset(new T(*iterator_)); + return value_.get(); + } + bool Equals(const ParamIteratorInterface& other) const override { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + return iterator_ == + CheckedDowncastToActualType(&other)->iterator_; + } + + private: + Iterator(const Iterator& other) + // The explicit constructor call suppresses a false warning + // emitted by gcc when supplied with the -Wextra option. + : ParamIteratorInterface(), + base_(other.base_), + iterator_(other.iterator_) {} + + const ParamGeneratorInterface* const base_; + typename ContainerType::const_iterator iterator_; + // A cached value of *iterator_. We keep it here to allow access by + // pointer in the wrapping iterator's operator->(). + // value_ needs to be mutable to be accessed in Current(). + // Use of std::unique_ptr helps manage cached value's lifetime, + // which is bound by the lifespan of the iterator itself. + mutable std::unique_ptr value_; + }; // class ValuesInIteratorRangeGenerator::Iterator + + // No implementation - assignment is unsupported. + void operator=(const ValuesInIteratorRangeGenerator& other); + + const ContainerType container_; +}; // class ValuesInIteratorRangeGenerator + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Default parameterized test name generator, returns a string containing the +// integer test parameter index. +template +std::string DefaultParamName(const TestParamInfo& info) { + return std::to_string(info.index); +} + +template +void TestNotEmpty() { + static_assert(sizeof(T) == 0, "Empty arguments are not allowed."); +} +template +void TestNotEmpty(const T&) {} + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Stores a parameter value and later creates tests parameterized with that +// value. +template +class ParameterizedTestFactory : public TestFactoryBase { + public: + typedef typename TestClass::ParamType ParamType; + explicit ParameterizedTestFactory(ParamType parameter) + : parameter_(parameter) {} + Test* CreateTest() override { + TestClass::SetParam(¶meter_); + return new TestClass(); + } + + private: + const ParamType parameter_; + + ParameterizedTestFactory(const ParameterizedTestFactory&) = delete; + ParameterizedTestFactory& operator=(const ParameterizedTestFactory&) = delete; +}; + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// TestMetaFactoryBase is a base class for meta-factories that create +// test factories for passing into MakeAndRegisterTestInfo function. +template +class TestMetaFactoryBase { + public: + virtual ~TestMetaFactoryBase() = default; + + virtual TestFactoryBase* CreateTestFactory(ParamType parameter) = 0; +}; + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// TestMetaFactory creates test factories for passing into +// MakeAndRegisterTestInfo function. Since MakeAndRegisterTestInfo receives +// ownership of test factory pointer, same factory object cannot be passed +// into that method twice. But ParameterizedTestSuiteInfo is going to call +// it for each Test/Parameter value combination. Thus it needs meta factory +// creator class. +template +class TestMetaFactory + : public TestMetaFactoryBase { + public: + using ParamType = typename TestSuite::ParamType; + + TestMetaFactory() = default; + + TestFactoryBase* CreateTestFactory(ParamType parameter) override { + return new ParameterizedTestFactory(parameter); + } + + private: + TestMetaFactory(const TestMetaFactory&) = delete; + TestMetaFactory& operator=(const TestMetaFactory&) = delete; +}; + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// ParameterizedTestSuiteInfoBase is a generic interface +// to ParameterizedTestSuiteInfo classes. ParameterizedTestSuiteInfoBase +// accumulates test information provided by TEST_P macro invocations +// and generators provided by INSTANTIATE_TEST_SUITE_P macro invocations +// and uses that information to register all resulting test instances +// in RegisterTests method. The ParameterizeTestSuiteRegistry class holds +// a collection of pointers to the ParameterizedTestSuiteInfo objects +// and calls RegisterTests() on each of them when asked. +class ParameterizedTestSuiteInfoBase { + public: + virtual ~ParameterizedTestSuiteInfoBase() = default; + + // Base part of test suite name for display purposes. + virtual const std::string& GetTestSuiteName() const = 0; + // Test suite id to verify identity. + virtual TypeId GetTestSuiteTypeId() const = 0; + // UnitTest class invokes this method to register tests in this + // test suite right before running them in RUN_ALL_TESTS macro. + // This method should not be called more than once on any single + // instance of a ParameterizedTestSuiteInfoBase derived class. + virtual void RegisterTests() = 0; + + protected: + ParameterizedTestSuiteInfoBase() {} + + private: + ParameterizedTestSuiteInfoBase(const ParameterizedTestSuiteInfoBase&) = + delete; + ParameterizedTestSuiteInfoBase& operator=( + const ParameterizedTestSuiteInfoBase&) = delete; +}; + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Report a the name of a test_suit as safe to ignore +// as the side effect of construction of this type. +struct GTEST_API_ MarkAsIgnored { + explicit MarkAsIgnored(const char* test_suite); +}; + +GTEST_API_ void InsertSyntheticTestCase(const std::string& name, + CodeLocation location, bool has_test_p); + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// ParameterizedTestSuiteInfo accumulates tests obtained from TEST_P +// macro invocations for a particular test suite and generators +// obtained from INSTANTIATE_TEST_SUITE_P macro invocations for that +// test suite. It registers tests with all values generated by all +// generators when asked. +template +class ParameterizedTestSuiteInfo : public ParameterizedTestSuiteInfoBase { + public: + // ParamType and GeneratorCreationFunc are private types but are required + // for declarations of public methods AddTestPattern() and + // AddTestSuiteInstantiation(). + using ParamType = typename TestSuite::ParamType; + // A function that returns an instance of appropriate generator type. + typedef ParamGenerator(GeneratorCreationFunc)(); + using ParamNameGeneratorFunc = std::string(const TestParamInfo&); + + explicit ParameterizedTestSuiteInfo(std::string name, + CodeLocation code_location) + : test_suite_name_(std::move(name)), + code_location_(std::move(code_location)) {} + + // Test suite base name for display purposes. + const std::string& GetTestSuiteName() const override { + return test_suite_name_; + } + // Test suite id to verify identity. + TypeId GetTestSuiteTypeId() const override { return GetTypeId(); } + // TEST_P macro uses AddTestPattern() to record information + // about a single test in a LocalTestInfo structure. + // test_suite_name is the base name of the test suite (without invocation + // prefix). test_base_name is the name of an individual test without + // parameter index. For the test SequenceA/FooTest.DoBar/1 FooTest is + // test suite base name and DoBar is test base name. + void AddTestPattern(const char*, const char* test_base_name, + TestMetaFactoryBase* meta_factory, + CodeLocation code_location) { + tests_.emplace_back( + new TestInfo(test_base_name, meta_factory, std::move(code_location))); + } + // INSTANTIATE_TEST_SUITE_P macro uses AddGenerator() to record information + // about a generator. + int AddTestSuiteInstantiation(std::string instantiation_name, + GeneratorCreationFunc* func, + ParamNameGeneratorFunc* name_func, + const char* file, int line) { + instantiations_.emplace_back(std::move(instantiation_name), func, name_func, + file, line); + return 0; // Return value used only to run this method in namespace scope. + } + // UnitTest class invokes this method to register tests in this test suite + // right before running tests in RUN_ALL_TESTS macro. + // This method should not be called more than once on any single + // instance of a ParameterizedTestSuiteInfoBase derived class. + // UnitTest has a guard to prevent from calling this method more than once. + void RegisterTests() override { + bool generated_instantiations = false; + + std::string test_suite_name; + std::string test_name; + for (const std::shared_ptr& test_info : tests_) { + for (const InstantiationInfo& instantiation : instantiations_) { + const std::string& instantiation_name = instantiation.name; + ParamGenerator generator((*instantiation.generator)()); + ParamNameGeneratorFunc* name_func = instantiation.name_func; + const char* file = instantiation.file; + int line = instantiation.line; + + if (!instantiation_name.empty()) + test_suite_name = instantiation_name + "/"; + else + test_suite_name.clear(); + test_suite_name += test_suite_name_; + + size_t i = 0; + std::set test_param_names; + for (const auto& param : generator) { + generated_instantiations = true; + + test_name.clear(); + + std::string param_name = + name_func(TestParamInfo(param, i)); + + GTEST_CHECK_(IsValidParamName(param_name)) + << "Parameterized test name '" << param_name + << "' is invalid (contains spaces, dashes, or any " + "non-alphanumeric characters other than underscores), in " + << file << " line " << line << "" << std::endl; + + GTEST_CHECK_(test_param_names.count(param_name) == 0) + << "Duplicate parameterized test name '" << param_name << "', in " + << file << " line " << line << std::endl; + + if (!test_info->test_base_name.empty()) { + test_name.append(test_info->test_base_name).append("/"); + } + test_name += param_name; + + test_param_names.insert(std::move(param_name)); + + MakeAndRegisterTestInfo( + test_suite_name, test_name.c_str(), + nullptr, // No type parameter. + PrintToString(param).c_str(), test_info->code_location, + GetTestSuiteTypeId(), + SuiteApiResolver::GetSetUpCaseOrSuite(file, line), + SuiteApiResolver::GetTearDownCaseOrSuite(file, line), + test_info->test_meta_factory->CreateTestFactory(param)); + ++i; + } // for param + } // for instantiation + } // for test_info + + if (!generated_instantiations) { + // There are no generaotrs, or they all generate nothing ... + InsertSyntheticTestCase(GetTestSuiteName(), code_location_, + !tests_.empty()); + } + } // RegisterTests + + private: + // LocalTestInfo structure keeps information about a single test registered + // with TEST_P macro. + struct TestInfo { + TestInfo(const char* a_test_base_name, + TestMetaFactoryBase* a_test_meta_factory, + CodeLocation a_code_location) + : test_base_name(a_test_base_name), + test_meta_factory(a_test_meta_factory), + code_location(std::move(a_code_location)) {} + + const std::string test_base_name; + const std::unique_ptr> test_meta_factory; + const CodeLocation code_location; + }; + using TestInfoContainer = ::std::vector>; + // Records data received from INSTANTIATE_TEST_SUITE_P macros: + // + struct InstantiationInfo { + InstantiationInfo(std::string name_in, GeneratorCreationFunc* generator_in, + ParamNameGeneratorFunc* name_func_in, const char* file_in, + int line_in) + : name(std::move(name_in)), + generator(generator_in), + name_func(name_func_in), + file(file_in), + line(line_in) {} + + std::string name; + GeneratorCreationFunc* generator; + ParamNameGeneratorFunc* name_func; + const char* file; + int line; + }; + typedef ::std::vector InstantiationContainer; + + static bool IsValidParamName(const std::string& name) { + // Check for empty string + if (name.empty()) return false; + + // Check for invalid characters + for (std::string::size_type index = 0; index < name.size(); ++index) { + if (!IsAlNum(name[index]) && name[index] != '_') return false; + } + + return true; + } + + const std::string test_suite_name_; + CodeLocation code_location_; + TestInfoContainer tests_; + InstantiationContainer instantiations_; + + ParameterizedTestSuiteInfo(const ParameterizedTestSuiteInfo&) = delete; + ParameterizedTestSuiteInfo& operator=(const ParameterizedTestSuiteInfo&) = + delete; +}; // class ParameterizedTestSuiteInfo + +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ +template +using ParameterizedTestCaseInfo = ParameterizedTestSuiteInfo; +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// ParameterizedTestSuiteRegistry contains a map of +// ParameterizedTestSuiteInfoBase classes accessed by test suite names. TEST_P +// and INSTANTIATE_TEST_SUITE_P macros use it to locate their corresponding +// ParameterizedTestSuiteInfo descriptors. +class ParameterizedTestSuiteRegistry { + public: + ParameterizedTestSuiteRegistry() = default; + ~ParameterizedTestSuiteRegistry() { + for (auto& test_suite_info : test_suite_infos_) { + delete test_suite_info; + } + } + + // Looks up or creates and returns a structure containing information about + // tests and instantiations of a particular test suite. + template + ParameterizedTestSuiteInfo* GetTestSuitePatternHolder( + std::string test_suite_name, CodeLocation code_location) { + ParameterizedTestSuiteInfo* typed_test_info = nullptr; + + auto item_it = suite_name_to_info_index_.find(test_suite_name); + if (item_it != suite_name_to_info_index_.end()) { + auto* test_suite_info = test_suite_infos_[item_it->second]; + if (test_suite_info->GetTestSuiteTypeId() != GetTypeId()) { + // Complain about incorrect usage of Google Test facilities + // and terminate the program since we cannot guaranty correct + // test suite setup and tear-down in this case. + ReportInvalidTestSuiteType(test_suite_name.c_str(), code_location); + posix::Abort(); + } else { + // At this point we are sure that the object we found is of the same + // type we are looking for, so we downcast it to that type + // without further checks. + typed_test_info = + CheckedDowncastToActualType>( + test_suite_info); + } + } + if (typed_test_info == nullptr) { + typed_test_info = new ParameterizedTestSuiteInfo( + test_suite_name, std::move(code_location)); + suite_name_to_info_index_.emplace(std::move(test_suite_name), + test_suite_infos_.size()); + test_suite_infos_.push_back(typed_test_info); + } + return typed_test_info; + } + void RegisterTests() { + for (auto& test_suite_info : test_suite_infos_) { + test_suite_info->RegisterTests(); + } + } +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + template + ParameterizedTestCaseInfo* GetTestCasePatternHolder( + std::string test_case_name, CodeLocation code_location) { + return GetTestSuitePatternHolder(std::move(test_case_name), + std::move(code_location)); + } + +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + + private: + using TestSuiteInfoContainer = ::std::vector; + + TestSuiteInfoContainer test_suite_infos_; + ::std::unordered_map suite_name_to_info_index_; + + ParameterizedTestSuiteRegistry(const ParameterizedTestSuiteRegistry&) = + delete; + ParameterizedTestSuiteRegistry& operator=( + const ParameterizedTestSuiteRegistry&) = delete; +}; + +// Keep track of what type-parameterized test suite are defined and +// where as well as which are intatiated. This allows susequently +// identifying suits that are defined but never used. +class TypeParameterizedTestSuiteRegistry { + public: + // Add a suite definition + void RegisterTestSuite(const char* test_suite_name, + CodeLocation code_location); + + // Add an instantiation of a suit. + void RegisterInstantiation(const char* test_suite_name); + + // For each suit repored as defined but not reported as instantiation, + // emit a test that reports that fact (configurably, as an error). + void CheckForInstantiations(); + + private: + struct TypeParameterizedTestSuiteInfo { + explicit TypeParameterizedTestSuiteInfo(CodeLocation c) + : code_location(std::move(c)), instantiated(false) {} + + CodeLocation code_location; + bool instantiated; + }; + + std::map suites_; +}; + +} // namespace internal + +// Forward declarations of ValuesIn(), which is implemented in +// include/gtest/gtest-param-test.h. +template +internal::ParamGenerator ValuesIn( + const Container& container); + +namespace internal { +// Used in the Values() function to provide polymorphic capabilities. + +GTEST_DISABLE_MSC_WARNINGS_PUSH_(4100) + +template +class ValueArray { + public: + explicit ValueArray(Ts... v) : v_(FlatTupleConstructTag{}, std::move(v)...) {} + + template + operator ParamGenerator() const { // NOLINT + return ValuesIn(MakeVector(std::make_index_sequence())); + } + + private: + template + std::vector MakeVector(std::index_sequence) const { + return std::vector{static_cast(v_.template Get())...}; + } + + FlatTuple v_; +}; + +GTEST_DISABLE_MSC_WARNINGS_POP_() // 4100 + +template +class CartesianProductGenerator + : public ParamGeneratorInterface<::std::tuple> { + public: + typedef ::std::tuple ParamType; + + CartesianProductGenerator(const std::tuple...>& g) + : generators_(g) {} + ~CartesianProductGenerator() override = default; + + ParamIteratorInterface* Begin() const override { + return new Iterator(this, generators_, false); + } + ParamIteratorInterface* End() const override { + return new Iterator(this, generators_, true); + } + + private: + template + class IteratorImpl; + template + class IteratorImpl> + : public ParamIteratorInterface { + public: + IteratorImpl(const ParamGeneratorInterface* base, + const std::tuple...>& generators, + bool is_end) + : base_(base), + begin_(std::get(generators).begin()...), + end_(std::get(generators).end()...), + current_(is_end ? end_ : begin_) { + ComputeCurrentValue(); + } + ~IteratorImpl() override = default; + + const ParamGeneratorInterface* BaseGenerator() const override { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + void Advance() override { + assert(!AtEnd()); + // Advance the last iterator. + ++std::get(current_); + // if that reaches end, propagate that up. + AdvanceIfEnd(); + ComputeCurrentValue(); + } + ParamIteratorInterface* Clone() const override { + return new IteratorImpl(*this); + } + + const ParamType* Current() const override { return current_value_.get(); } + + bool Equals(const ParamIteratorInterface& other) const override { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const IteratorImpl* typed_other = + CheckedDowncastToActualType(&other); + + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + if (AtEnd() && typed_other->AtEnd()) return true; + + bool same = true; + bool dummy[] = { + (same = same && std::get(current_) == + std::get(typed_other->current_))...}; + (void)dummy; + return same; + } + + private: + template + void AdvanceIfEnd() { + if (std::get(current_) != std::get(end_)) return; + + bool last = ThisI == 0; + if (last) { + // We are done. Nothing else to propagate. + return; + } + + constexpr size_t NextI = ThisI - (ThisI != 0); + std::get(current_) = std::get(begin_); + ++std::get(current_); + AdvanceIfEnd(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = std::make_shared(*std::get(current_)...); + } + bool AtEnd() const { + bool at_end = false; + bool dummy[] = { + (at_end = at_end || std::get(current_) == std::get(end_))...}; + (void)dummy; + return at_end; + } + + const ParamGeneratorInterface* const base_; + std::tuple::iterator...> begin_; + std::tuple::iterator...> end_; + std::tuple::iterator...> current_; + std::shared_ptr current_value_; + }; + + using Iterator = IteratorImpl>; + + std::tuple...> generators_; +}; + +template +class CartesianProductHolder { + public: + CartesianProductHolder(const Gen&... g) : generators_(g...) {} + template + operator ParamGenerator<::std::tuple>() const { + return ParamGenerator<::std::tuple>( + new CartesianProductGenerator(generators_)); + } + + private: + std::tuple generators_; +}; + +template +class ParamGeneratorConverter : public ParamGeneratorInterface { + public: + ParamGeneratorConverter(ParamGenerator gen, Func converter) // NOLINT + : generator_(std::move(gen)), converter_(std::move(converter)) {} + + ParamIteratorInterface* Begin() const override { + return new Iterator(this, generator_.begin(), generator_.end()); + } + ParamIteratorInterface* End() const override { + return new Iterator(this, generator_.end(), generator_.end()); + } + + // Returns the std::function wrapping the user-supplied converter callable. It + // is used by the iterator (see class Iterator below) to convert the object + // (of type FROM) returned by the ParamGenerator to an object of a type that + // can be static_cast to type TO. + const Func& TypeConverter() const { return converter_; } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorConverter* base, ParamIterator it, + ParamIterator end) + : base_(base), it_(it), end_(end) { + if (it_ != end_) + value_ = + std::make_shared(static_cast(base->TypeConverter()(*it_))); + } + ~Iterator() override = default; + + const ParamGeneratorInterface* BaseGenerator() const override { + return base_; + } + void Advance() override { + ++it_; + if (it_ != end_) + value_ = + std::make_shared(static_cast(base_->TypeConverter()(*it_))); + } + ParamIteratorInterface* Clone() const override { + return new Iterator(*this); + } + const To* Current() const override { return value_.get(); } + bool Equals(const ParamIteratorInterface& other) const override { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const ParamIterator other_it = + CheckedDowncastToActualType(&other)->it_; + return it_ == other_it; + } + + private: + Iterator(const Iterator& other) = default; + + const ParamGeneratorConverter* const base_; + ParamIterator it_; + ParamIterator end_; + std::shared_ptr value_; + }; // class ParamGeneratorConverter::Iterator + + ParamGenerator generator_; + Func converter_; +}; // class ParamGeneratorConverter + +template > +class ParamConverterGenerator { + public: + ParamConverterGenerator(ParamGenerator g) // NOLINT + : generator_(std::move(g)), converter_(Identity) {} + + ParamConverterGenerator(ParamGenerator g, StdFunction converter) + : generator_(std::move(g)), converter_(std::move(converter)) {} + + template + operator ParamGenerator() const { // NOLINT + return ParamGenerator( + new ParamGeneratorConverter(generator_, + converter_)); + } + + private: + static const GeneratedT& Identity(const GeneratedT& v) { return v; } + + ParamGenerator generator_; + StdFunction converter_; +}; + +// Template to determine the param type of a single-param std::function. +template +struct FuncSingleParamType; +template +struct FuncSingleParamType> { + using type = std::remove_cv_t>; +}; + +template +struct IsSingleArgStdFunction : public std::false_type {}; +template +struct IsSingleArgStdFunction> : public std::true_type {}; + +} // namespace internal +} // namespace testing + +#endif // GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_H_ diff --git a/googletest/include/gtest/internal/gtest-port-arch.h b/googletest/include/gtest/internal/gtest-port-arch.h new file mode 100644 index 00000000..7ec968f3 --- /dev/null +++ b/googletest/include/gtest/internal/gtest-port-arch.h @@ -0,0 +1,124 @@ +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// The Google C++ Testing and Mocking Framework (Google Test) +// +// This header file defines the GTEST_OS_* macro. +// It is separate from gtest-port.h so that custom/gtest-port.h can include it. + +#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_ARCH_H_ +#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_ARCH_H_ + +// Determines the platform on which Google Test is compiled. +#ifdef __CYGWIN__ +#define GTEST_OS_CYGWIN 1 +#elif defined(__MINGW__) || defined(__MINGW32__) || defined(__MINGW64__) +#define GTEST_OS_WINDOWS_MINGW 1 +#define GTEST_OS_WINDOWS 1 +#elif defined _WIN32 +#define GTEST_OS_WINDOWS 1 +#ifdef _WIN32_WCE +#define GTEST_OS_WINDOWS_MOBILE 1 +#elif defined(WINAPI_FAMILY) +#include +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) +#define GTEST_OS_WINDOWS_DESKTOP 1 +#elif WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_PHONE_APP) +#define GTEST_OS_WINDOWS_PHONE 1 +#elif WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) +#define GTEST_OS_WINDOWS_RT 1 +#elif WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_TV_TITLE) +#define GTEST_OS_WINDOWS_PHONE 1 +#define GTEST_OS_WINDOWS_TV_TITLE 1 +#elif WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_GAMES) +#define GTEST_OS_WINDOWS_GAMES 1 +#else +// WINAPI_FAMILY defined but no known partition matched. +// Default to desktop. +#define GTEST_OS_WINDOWS_DESKTOP 1 +#endif +#else +#define GTEST_OS_WINDOWS_DESKTOP 1 +#endif // _WIN32_WCE +#elif defined __OS2__ +#define GTEST_OS_OS2 1 +#elif defined __APPLE__ +#define GTEST_OS_MAC 1 +#include +#if TARGET_OS_IPHONE +#define GTEST_OS_IOS 1 +#endif +#elif defined __DragonFly__ +#define GTEST_OS_DRAGONFLY 1 +#elif defined __FreeBSD__ +#define GTEST_OS_FREEBSD 1 +#elif defined __Fuchsia__ +#define GTEST_OS_FUCHSIA 1 +#elif defined(__GNU__) +#define GTEST_OS_GNU_HURD 1 +#elif defined(__GLIBC__) && defined(__FreeBSD_kernel__) +#define GTEST_OS_GNU_KFREEBSD 1 +#elif defined __linux__ +#define GTEST_OS_LINUX 1 +#if defined __ANDROID__ +#define GTEST_OS_LINUX_ANDROID 1 +#endif +#elif defined __MVS__ +#define GTEST_OS_ZOS 1 +#elif defined(__sun) && defined(__SVR4) +#define GTEST_OS_SOLARIS 1 +#elif defined(_AIX) +#define GTEST_OS_AIX 1 +#elif defined(__hpux) +#define GTEST_OS_HPUX 1 +#elif defined __native_client__ +#define GTEST_OS_NACL 1 +#elif defined __NetBSD__ +#define GTEST_OS_NETBSD 1 +#elif defined __OpenBSD__ +#define GTEST_OS_OPENBSD 1 +#elif defined __QNX__ +#define GTEST_OS_QNX 1 +#elif defined(__HAIKU__) +#define GTEST_OS_HAIKU 1 +#elif defined ESP8266 +#define GTEST_OS_ESP8266 1 +#elif defined ESP32 +#define GTEST_OS_ESP32 1 +#elif defined(__XTENSA__) +#define GTEST_OS_XTENSA 1 +#elif defined(__hexagon__) +#define GTEST_OS_QURT 1 +#elif defined(CPU_QN9090) || defined(CPU_QN9090HN) +#define GTEST_OS_NXP_QN9090 1 +#elif defined(NRF52) +#define GTEST_OS_NRF52 1 +#endif // __CYGWIN__ + +#endif // GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_ARCH_H_ diff --git a/googletest/include/gtest/internal/gtest-port.h b/googletest/include/gtest/internal/gtest-port.h new file mode 100644 index 00000000..25b7d194 --- /dev/null +++ b/googletest/include/gtest/internal/gtest-port.h @@ -0,0 +1,2486 @@ +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Low-level types and utilities for porting Google Test to various +// platforms. All macros ending with _ and symbols defined in an +// internal namespace are subject to change without notice. Code +// outside Google Test MUST NOT USE THEM DIRECTLY. Macros that don't +// end with _ are part of Google Test's public API and can be used by +// code outside Google Test. +// +// This file is fundamental to Google Test. All other Google Test source +// files are expected to #include this. Therefore, it cannot #include +// any other Google Test header. + +// IWYU pragma: private, include "gtest/gtest.h" +// IWYU pragma: friend gtest/.* +// IWYU pragma: friend gmock/.* + +#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_H_ +#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_H_ + +// Environment-describing macros +// ----------------------------- +// +// Google Test can be used in many different environments. Macros in +// this section tell Google Test what kind of environment it is being +// used in, such that Google Test can provide environment-specific +// features and implementations. +// +// Google Test tries to automatically detect the properties of its +// environment, so users usually don't need to worry about these +// macros. However, the automatic detection is not perfect. +// Sometimes it's necessary for a user to define some of the following +// macros in the build script to override Google Test's decisions. +// +// If the user doesn't define a macro in the list, Google Test will +// provide a default definition. After this header is #included, all +// macros in this list will be defined to either 1 or 0. +// +// Notes to maintainers: +// - Each macro here is a user-tweakable knob; do not grow the list +// lightly. +// - Use #if to key off these macros. Don't use #ifdef or "#if +// defined(...)", which will not work as these macros are ALWAYS +// defined. +// +// GTEST_HAS_CLONE - Define it to 1/0 to indicate that clone(2) +// is/isn't available. +// GTEST_HAS_EXCEPTIONS - Define it to 1/0 to indicate that exceptions +// are enabled. +// GTEST_HAS_POSIX_RE - Define it to 1/0 to indicate that POSIX regular +// expressions are/aren't available. +// GTEST_HAS_PTHREAD - Define it to 1/0 to indicate that +// is/isn't available. +// GTEST_HAS_RTTI - Define it to 1/0 to indicate that RTTI is/isn't +// enabled. +// GTEST_HAS_STD_WSTRING - Define it to 1/0 to indicate that +// std::wstring does/doesn't work (Google Test can +// be used where std::wstring is unavailable). +// GTEST_HAS_FILE_SYSTEM - Define it to 1/0 to indicate whether or not a +// file system is/isn't available. +// GTEST_HAS_SEH - Define it to 1/0 to indicate whether the +// compiler supports Microsoft's "Structured +// Exception Handling". +// GTEST_HAS_STREAM_REDIRECTION +// - Define it to 1/0 to indicate whether the +// platform supports I/O stream redirection using +// dup() and dup2(). +// GTEST_LINKED_AS_SHARED_LIBRARY +// - Define to 1 when compiling tests that use +// Google Test as a shared library (known as +// DLL on Windows). +// GTEST_CREATE_SHARED_LIBRARY +// - Define to 1 when compiling Google Test itself +// as a shared library. +// GTEST_DEFAULT_DEATH_TEST_STYLE +// - The default value of --gtest_death_test_style. +// The legacy default has been "fast" in the open +// source version since 2008. The recommended value +// is "threadsafe", and can be set in +// custom/gtest-port.h. + +// Platform-indicating macros +// -------------------------- +// +// Macros indicating the platform on which Google Test is being used +// (a macro is defined to 1 if compiled on the given platform; +// otherwise UNDEFINED -- it's never defined to 0.). Google Test +// defines these macros automatically. Code outside Google Test MUST +// NOT define them. +// +// GTEST_OS_AIX - IBM AIX +// GTEST_OS_CYGWIN - Cygwin +// GTEST_OS_DRAGONFLY - DragonFlyBSD +// GTEST_OS_FREEBSD - FreeBSD +// GTEST_OS_FUCHSIA - Fuchsia +// GTEST_OS_GNU_HURD - GNU/Hurd +// GTEST_OS_GNU_KFREEBSD - GNU/kFreeBSD +// GTEST_OS_HAIKU - Haiku +// GTEST_OS_HPUX - HP-UX +// GTEST_OS_LINUX - Linux +// GTEST_OS_LINUX_ANDROID - Google Android +// GTEST_OS_MAC - Mac OS X +// GTEST_OS_IOS - iOS +// GTEST_OS_NACL - Google Native Client (NaCl) +// GTEST_OS_NETBSD - NetBSD +// GTEST_OS_OPENBSD - OpenBSD +// GTEST_OS_OS2 - OS/2 +// GTEST_OS_QNX - QNX +// GTEST_OS_SOLARIS - Sun Solaris +// GTEST_OS_WINDOWS - Windows (Desktop, MinGW, or Mobile) +// GTEST_OS_WINDOWS_DESKTOP - Windows Desktop +// GTEST_OS_WINDOWS_MINGW - MinGW +// GTEST_OS_WINDOWS_MOBILE - Windows Mobile +// GTEST_OS_WINDOWS_PHONE - Windows Phone +// GTEST_OS_WINDOWS_RT - Windows Store App/WinRT +// GTEST_OS_ZOS - z/OS +// +// Among the platforms, Cygwin, Linux, Mac OS X, and Windows have the +// most stable support. Since core members of the Google Test project +// don't have access to other platforms, support for them may be less +// stable. If you notice any problems on your platform, please notify +// googletestframework@googlegroups.com (patches for fixing them are +// even more welcome!). +// +// It is possible that none of the GTEST_OS_* macros are defined. + +// Feature-indicating macros +// ------------------------- +// +// Macros indicating which Google Test features are available (a macro +// is defined to 1 if the corresponding feature is supported; +// otherwise UNDEFINED -- it's never defined to 0.). Google Test +// defines these macros automatically. Code outside Google Test MUST +// NOT define them. +// +// These macros are public so that portable tests can be written. +// Such tests typically surround code using a feature with an #ifdef +// which controls that code. For example: +// +// #ifdef GTEST_HAS_DEATH_TEST +// EXPECT_DEATH(DoSomethingDeadly()); +// #endif +// +// GTEST_HAS_DEATH_TEST - death tests +// GTEST_HAS_TYPED_TEST - typed tests +// GTEST_HAS_TYPED_TEST_P - type-parameterized tests +// GTEST_IS_THREADSAFE - Google Test is thread-safe. +// GTEST_USES_RE2 - the RE2 regular expression library is used +// GTEST_USES_POSIX_RE - enhanced POSIX regex is used. Do not confuse with +// GTEST_HAS_POSIX_RE (see above) which users can +// define themselves. +// GTEST_USES_SIMPLE_RE - our own simple regex is used; +// the above RE\b(s) are mutually exclusive. +// GTEST_HAS_ABSL - Google Test is compiled with Abseil. + +// Misc public macros +// ------------------ +// +// GTEST_FLAG(flag_name) - references the variable corresponding to +// the given Google Test flag. + +// Internal utilities +// ------------------ +// +// The following macros and utilities are for Google Test's INTERNAL +// use only. Code outside Google Test MUST NOT USE THEM DIRECTLY. +// +// Macros for basic C++ coding: +// GTEST_AMBIGUOUS_ELSE_BLOCKER_ - for disabling a gcc warning. +// GTEST_INTENTIONAL_CONST_COND_PUSH_ - start code section where MSVC C4127 is +// suppressed (constant conditional). +// GTEST_INTENTIONAL_CONST_COND_POP_ - finish code section where MSVC C4127 +// is suppressed. +// GTEST_INTERNAL_HAS_ANY - for enabling UniversalPrinter or +// UniversalPrinter specializations. +// Always defined to 0 or 1. +// GTEST_INTERNAL_HAS_OPTIONAL - for enabling UniversalPrinter +// or +// UniversalPrinter +// specializations. Always defined to 0 or 1. +// GTEST_INTERNAL_HAS_STD_SPAN - for enabling UniversalPrinter +// specializations. Always defined to 0 or 1 +// GTEST_INTERNAL_HAS_STRING_VIEW - for enabling Matcher or +// Matcher +// specializations. Always defined to 0 or 1. +// GTEST_INTERNAL_HAS_VARIANT - for enabling UniversalPrinter or +// UniversalPrinter +// specializations. Always defined to 0 or 1. +// GTEST_USE_OWN_FLAGFILE_FLAG_ - Always defined to 0 or 1. +// GTEST_HAS_CXXABI_H_ - Always defined to 0 or 1. +// GTEST_CAN_STREAM_RESULTS_ - Always defined to 0 or 1. +// GTEST_HAS_ALT_PATH_SEP_ - Always defined to 0 or 1. +// GTEST_WIDE_STRING_USES_UTF16_ - Always defined to 0 or 1. +// GTEST_HAS_MUTEX_AND_THREAD_LOCAL_ - Always defined to 0 or 1. +// GTEST_HAS_NOTIFICATION_- Always defined to 0 or 1. +// +// Synchronization: +// Mutex, MutexLock, ThreadLocal, GetThreadCount() +// - synchronization primitives. +// +// Regular expressions: +// RE - a simple regular expression class using +// 1) the RE2 syntax on all platforms when built with RE2 +// and Abseil as dependencies +// 2) the POSIX Extended Regular Expression syntax on +// UNIX-like platforms, +// 3) A reduced regular exception syntax on other platforms, +// including Windows. +// Logging: +// GTEST_LOG_() - logs messages at the specified severity level. +// LogToStderr() - directs all log messages to stderr. +// FlushInfoLog() - flushes informational log messages. +// +// Stdout and stderr capturing: +// CaptureStdout() - starts capturing stdout. +// GetCapturedStdout() - stops capturing stdout and returns the captured +// string. +// CaptureStderr() - starts capturing stderr. +// GetCapturedStderr() - stops capturing stderr and returns the captured +// string. +// +// Integer types: +// TypeWithSize - maps an integer to a int type. +// TimeInMillis - integers of known sizes. +// BiggestInt - the biggest signed integer type. +// +// Command-line utilities: +// GetInjectableArgvs() - returns the command line as a vector of strings. +// +// Environment variable utilities: +// GetEnv() - gets the value of an environment variable. +// BoolFromGTestEnv() - parses a bool environment variable. +// Int32FromGTestEnv() - parses an int32_t environment variable. +// StringFromGTestEnv() - parses a string environment variable. + +// The definition of GTEST_INTERNAL_CPLUSPLUS_LANG comes first because it can +// potentially be used as an #include guard. +#if defined(_MSVC_LANG) +#define GTEST_INTERNAL_CPLUSPLUS_LANG _MSVC_LANG +#elif defined(__cplusplus) +#define GTEST_INTERNAL_CPLUSPLUS_LANG __cplusplus +#endif + +#if !defined(GTEST_INTERNAL_CPLUSPLUS_LANG) || \ + GTEST_INTERNAL_CPLUSPLUS_LANG < 201703L +#error C++ versions less than C++17 are not supported. +#endif + +// MSVC >= 19.11 (VS 2017 Update 3) supports __has_include. +#ifdef __has_include +#define GTEST_INTERNAL_HAS_INCLUDE __has_include +#else +#define GTEST_INTERNAL_HAS_INCLUDE(...) 0 +#endif + +// Detect C++ feature test macros as gracefully as possible. +// MSVC >= 19.15, Clang >= 3.4.1, and GCC >= 4.1.2 support feature test macros. +// +// GCC15 warns that is deprecated in C++17 and suggests using +// instead, even though is not available in C++17 mode prior +// to GCC9. +#if GTEST_INTERNAL_CPLUSPLUS_LANG >= 202002L || \ + GTEST_INTERNAL_HAS_INCLUDE() +#include // C++20 or support. +#else +#include // Pre-C++20 +#endif + +#include // for isspace, etc +#include // for ptrdiff_t +#include +#include +#include + +#include +// #include // Guarded by GTEST_IS_THREADSAFE below +#include +#include +#include +#include +#include +#include +#include +// #include // Guarded by GTEST_IS_THREADSAFE below +#include +#include +#include + +#ifndef _WIN32_WCE +#include +#include +#endif // !_WIN32_WCE + +#if defined __APPLE__ +#include +#include +#endif + +#include "gtest/internal/custom/gtest-port.h" +#include "gtest/internal/gtest-port-arch.h" + +#ifndef GTEST_HAS_MUTEX_AND_THREAD_LOCAL_ +#define GTEST_HAS_MUTEX_AND_THREAD_LOCAL_ 0 +#endif + +#ifndef GTEST_HAS_NOTIFICATION_ +#define GTEST_HAS_NOTIFICATION_ 0 +#endif + +#if defined(GTEST_HAS_ABSL) && !defined(GTEST_NO_ABSL_FLAGS) +#define GTEST_INTERNAL_HAS_ABSL_FLAGS // Used only in this file. +#include "absl/flags/declare.h" +#include "absl/flags/flag.h" +#include "absl/flags/reflection.h" +#endif + +#if !defined(GTEST_DEV_EMAIL_) +#define GTEST_DEV_EMAIL_ "googletestframework@@googlegroups.com" +#define GTEST_FLAG_PREFIX_ "gtest_" +#define GTEST_FLAG_PREFIX_DASH_ "gtest-" +#define GTEST_FLAG_PREFIX_UPPER_ "GTEST_" +#define GTEST_NAME_ "Google Test" +#define GTEST_PROJECT_URL_ "https://github.com/google/googletest/" +#endif // !defined(GTEST_DEV_EMAIL_) + +#if !defined(GTEST_INIT_GOOGLE_TEST_NAME_) +#define GTEST_INIT_GOOGLE_TEST_NAME_ "testing::InitGoogleTest" +#endif // !defined(GTEST_INIT_GOOGLE_TEST_NAME_) + +// Determines the version of gcc that is used to compile this. +#ifdef __GNUC__ +// 40302 means version 4.3.2. +#define GTEST_GCC_VER_ \ + (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) +#endif // __GNUC__ + +// Macros for disabling Microsoft Visual C++ warnings. +// +// GTEST_DISABLE_MSC_WARNINGS_PUSH_(4800 4385) +// /* code that triggers warnings C4800 and C4385 */ +// GTEST_DISABLE_MSC_WARNINGS_POP_() +#if defined(_MSC_VER) +#define GTEST_DISABLE_MSC_WARNINGS_PUSH_(warnings) \ + __pragma(warning(push)) __pragma(warning(disable : warnings)) +#define GTEST_DISABLE_MSC_WARNINGS_POP_() __pragma(warning(pop)) +#else +// Not all compilers are MSVC +#define GTEST_DISABLE_MSC_WARNINGS_PUSH_(warnings) +#define GTEST_DISABLE_MSC_WARNINGS_POP_() +#endif + +// Clang on Windows does not understand MSVC's pragma warning. +// We need clang-specific way to disable function deprecation warning. +#ifdef __clang__ +#define GTEST_DISABLE_MSC_DEPRECATED_PUSH_() \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") \ + _Pragma("clang diagnostic ignored \"-Wdeprecated-implementations\"") +#define GTEST_DISABLE_MSC_DEPRECATED_POP_() _Pragma("clang diagnostic pop") +#else +#define GTEST_DISABLE_MSC_DEPRECATED_PUSH_() \ + GTEST_DISABLE_MSC_WARNINGS_PUSH_(4996) +#define GTEST_DISABLE_MSC_DEPRECATED_POP_() GTEST_DISABLE_MSC_WARNINGS_POP_() +#endif + +// Brings in definitions for functions used in the testing::internal::posix +// namespace (read, write, close, chdir, isatty, stat). We do not currently +// use them on Windows Mobile. +#ifdef GTEST_OS_WINDOWS +#ifndef GTEST_OS_WINDOWS_MOBILE +#include +#include +#endif +// In order to avoid having to include , use forward declaration +#if defined(GTEST_OS_WINDOWS_MINGW) && !defined(__MINGW64_VERSION_MAJOR) +// MinGW defined _CRITICAL_SECTION and _RTL_CRITICAL_SECTION as two +// separate (equivalent) structs, instead of using typedef +typedef struct _CRITICAL_SECTION GTEST_CRITICAL_SECTION; +#else +// Assume CRITICAL_SECTION is a typedef of _RTL_CRITICAL_SECTION. +// This assumption is verified by +// WindowsTypesTest.CRITICAL_SECTIONIs_RTL_CRITICAL_SECTION. +typedef struct _RTL_CRITICAL_SECTION GTEST_CRITICAL_SECTION; +#endif +#elif defined(GTEST_OS_XTENSA) +#include +// Xtensa toolchains define strcasecmp in the string.h header instead of +// strings.h. string.h is already included. +#else +// This assumes that non-Windows OSes provide unistd.h. For OSes where this +// is not the case, we need to include headers that provide the functions +// mentioned above. +#include +#include +#endif // GTEST_OS_WINDOWS + +#ifdef GTEST_OS_LINUX_ANDROID +// Used to define __ANDROID_API__ matching the target NDK API level. +#include // NOLINT +#endif + +// Defines this to true if and only if Google Test can use POSIX regular +// expressions. +#ifndef GTEST_HAS_POSIX_RE +#ifdef GTEST_OS_LINUX_ANDROID +// On Android, is only available starting with Gingerbread. +#define GTEST_HAS_POSIX_RE (__ANDROID_API__ >= 9) +#else +#if !(defined(GTEST_OS_WINDOWS) || defined(GTEST_OS_XTENSA) || \ + defined(GTEST_OS_QURT)) +#define GTEST_HAS_POSIX_RE 1 +#else +#define GTEST_HAS_POSIX_RE 0 +#endif +#endif // GTEST_OS_LINUX_ANDROID +#endif + +// Select the regular expression implementation. +#ifdef GTEST_HAS_ABSL +// When using Abseil, RE2 is required. +#include "absl/strings/string_view.h" +#include "re2/re2.h" +#define GTEST_USES_RE2 1 +#elif GTEST_HAS_POSIX_RE +#include // NOLINT +#define GTEST_USES_POSIX_RE 1 +#else +// Use our own simple regex implementation. +#define GTEST_USES_SIMPLE_RE 1 +#endif + +#ifndef GTEST_HAS_EXCEPTIONS +// The user didn't tell us whether exceptions are enabled, so we need +// to figure it out. +#if defined(_MSC_VER) && defined(_CPPUNWIND) +// MSVC defines _CPPUNWIND to 1 if and only if exceptions are enabled. +#define GTEST_HAS_EXCEPTIONS 1 +#elif defined(__BORLANDC__) +// C++Builder's implementation of the STL uses the _HAS_EXCEPTIONS +// macro to enable exceptions, so we'll do the same. +// Assumes that exceptions are enabled by default. +#ifndef _HAS_EXCEPTIONS +#define _HAS_EXCEPTIONS 1 +#endif // _HAS_EXCEPTIONS +#define GTEST_HAS_EXCEPTIONS _HAS_EXCEPTIONS +#elif defined(__clang__) +// clang defines __EXCEPTIONS if and only if exceptions are enabled before clang +// 220714, but if and only if cleanups are enabled after that. In Obj-C++ files, +// there can be cleanups for ObjC exceptions which also need cleanups, even if +// C++ exceptions are disabled. clang has __has_feature(cxx_exceptions) which +// checks for C++ exceptions starting at clang r206352, but which checked for +// cleanups prior to that. To reliably check for C++ exception availability with +// clang, check for +// __EXCEPTIONS && __has_feature(cxx_exceptions). +#if defined(__EXCEPTIONS) && __EXCEPTIONS && __has_feature(cxx_exceptions) +#define GTEST_HAS_EXCEPTIONS 1 +#else +#define GTEST_HAS_EXCEPTIONS 0 +#endif +#elif defined(__GNUC__) && defined(__EXCEPTIONS) && __EXCEPTIONS +// gcc defines __EXCEPTIONS to 1 if and only if exceptions are enabled. +#define GTEST_HAS_EXCEPTIONS 1 +#elif defined(__SUNPRO_CC) +// Sun Pro CC supports exceptions. However, there is no compile-time way of +// detecting whether they are enabled or not. Therefore, we assume that +// they are enabled unless the user tells us otherwise. +#define GTEST_HAS_EXCEPTIONS 1 +#elif defined(__IBMCPP__) && defined(__EXCEPTIONS) && __EXCEPTIONS +// xlC defines __EXCEPTIONS to 1 if and only if exceptions are enabled. +#define GTEST_HAS_EXCEPTIONS 1 +#elif defined(__HP_aCC) +// Exception handling is in effect by default in HP aCC compiler. It has to +// be turned of by +noeh compiler option if desired. +#define GTEST_HAS_EXCEPTIONS 1 +#else +// For other compilers, we assume exceptions are disabled to be +// conservative. +#define GTEST_HAS_EXCEPTIONS 0 +#endif // defined(_MSC_VER) || defined(__BORLANDC__) +#endif // GTEST_HAS_EXCEPTIONS + +#ifndef GTEST_HAS_STD_WSTRING +// The user didn't tell us whether ::std::wstring is available, so we need +// to figure it out. +// Cygwin 1.7 and below doesn't support ::std::wstring. +// Solaris' libc++ doesn't support it either. Android has +// no support for it at least as recent as Froyo (2.2). +#if (!(defined(GTEST_OS_LINUX_ANDROID) || defined(GTEST_OS_CYGWIN) || \ + defined(GTEST_OS_SOLARIS) || defined(GTEST_OS_HAIKU) || \ + defined(GTEST_OS_ESP32) || defined(GTEST_OS_ESP8266) || \ + defined(GTEST_OS_XTENSA) || defined(GTEST_OS_QURT) || \ + defined(GTEST_OS_NXP_QN9090) || defined(GTEST_OS_NRF52))) +#define GTEST_HAS_STD_WSTRING 1 +#else +#define GTEST_HAS_STD_WSTRING 0 +#endif +#endif // GTEST_HAS_STD_WSTRING + +#ifndef GTEST_HAS_FILE_SYSTEM +// Most platforms support a file system. +#define GTEST_HAS_FILE_SYSTEM 1 +#endif // GTEST_HAS_FILE_SYSTEM + +// Determines whether RTTI is available. +#ifndef GTEST_HAS_RTTI +// The user didn't tell us whether RTTI is enabled, so we need to +// figure it out. + +#ifdef _MSC_VER + +#ifdef _CPPRTTI // MSVC defines this macro if and only if RTTI is enabled. +#define GTEST_HAS_RTTI 1 +#else +#define GTEST_HAS_RTTI 0 +#endif + +// Starting with version 4.3.2, gcc defines __GXX_RTTI if and only if RTTI is +// enabled. +#elif defined(__GNUC__) + +#ifdef __GXX_RTTI +// When building against STLport with the Android NDK and with +// -frtti -fno-exceptions, the build fails at link time with undefined +// references to __cxa_bad_typeid. Note sure if STL or toolchain bug, +// so disable RTTI when detected. +#if defined(GTEST_OS_LINUX_ANDROID) && defined(_STLPORT_MAJOR) && \ + !defined(__EXCEPTIONS) +#define GTEST_HAS_RTTI 0 +#else +#define GTEST_HAS_RTTI 1 +#endif // GTEST_OS_LINUX_ANDROID && __STLPORT_MAJOR && !__EXCEPTIONS +#else +#define GTEST_HAS_RTTI 0 +#endif // __GXX_RTTI + +// Clang defines __GXX_RTTI starting with version 3.0, but its manual recommends +// using has_feature instead. has_feature(cxx_rtti) is supported since 2.7, the +// first version with C++ support. +#elif defined(__clang__) + +#define GTEST_HAS_RTTI __has_feature(cxx_rtti) + +// Starting with version 9.0 IBM Visual Age defines __RTTI_ALL__ to 1 if +// both the typeid and dynamic_cast features are present. +#elif defined(__IBMCPP__) && (__IBMCPP__ >= 900) + +#ifdef __RTTI_ALL__ +#define GTEST_HAS_RTTI 1 +#else +#define GTEST_HAS_RTTI 0 +#endif + +#else + +// For all other compilers, we assume RTTI is enabled. +#define GTEST_HAS_RTTI 1 + +#endif // _MSC_VER + +#endif // GTEST_HAS_RTTI + +// It's this header's responsibility to #include when RTTI +// is enabled. +#if GTEST_HAS_RTTI +#include +#endif + +// Determines whether Google Test can use the pthreads library. +#ifndef GTEST_HAS_PTHREAD +// The user didn't tell us explicitly, so we make reasonable assumptions about +// which platforms have pthreads support. +// +// To disable threading support in Google Test, add -DGTEST_HAS_PTHREAD=0 +// to your compiler flags. +#if (defined(GTEST_OS_LINUX) || defined(GTEST_OS_MAC) || \ + defined(GTEST_OS_HPUX) || defined(GTEST_OS_QNX) || \ + defined(GTEST_OS_FREEBSD) || defined(GTEST_OS_NACL) || \ + defined(GTEST_OS_NETBSD) || defined(GTEST_OS_FUCHSIA) || \ + defined(GTEST_OS_DRAGONFLY) || defined(GTEST_OS_GNU_KFREEBSD) || \ + defined(GTEST_OS_OPENBSD) || defined(GTEST_OS_HAIKU) || \ + defined(GTEST_OS_GNU_HURD) || defined(GTEST_OS_SOLARIS) || \ + defined(GTEST_OS_AIX) || defined(GTEST_OS_ZOS)) +#define GTEST_HAS_PTHREAD 1 +#else +#define GTEST_HAS_PTHREAD 0 +#endif +#endif // GTEST_HAS_PTHREAD + +#if GTEST_HAS_PTHREAD +// gtest-port.h guarantees to #include when GTEST_HAS_PTHREAD is +// true. +#include // NOLINT + +// For timespec and nanosleep, used below. +#include // NOLINT +#endif + +// Determines whether clone(2) is supported. +// Usually it will only be available on Linux, excluding +// Linux on the Itanium architecture. +// Also see https://linux.die.net/man/2/clone. +#ifndef GTEST_HAS_CLONE +// The user didn't tell us, so we need to figure it out. + +#if defined(GTEST_OS_LINUX) && !defined(__ia64__) +#if defined(GTEST_OS_LINUX_ANDROID) +// On Android, clone() became available at different API levels for each 32-bit +// architecture. +#if defined(__LP64__) || (defined(__arm__) && __ANDROID_API__ >= 9) || \ + (defined(__mips__) && __ANDROID_API__ >= 12) || \ + (defined(__i386__) && __ANDROID_API__ >= 17) +#define GTEST_HAS_CLONE 1 +#else +#define GTEST_HAS_CLONE 0 +#endif +#else +#define GTEST_HAS_CLONE 1 +#endif +#else +#define GTEST_HAS_CLONE 0 +#endif // GTEST_OS_LINUX && !defined(__ia64__) + +#endif // GTEST_HAS_CLONE + +// Determines whether to support stream redirection. This is used to test +// output correctness and to implement death tests. +#ifndef GTEST_HAS_STREAM_REDIRECTION +// By default, we assume that stream redirection is supported on all +// platforms except known mobile / embedded ones. Also, if the port doesn't have +// a file system, stream redirection is not supported. +#if defined(GTEST_OS_WINDOWS_MOBILE) || defined(GTEST_OS_WINDOWS_PHONE) || \ + defined(GTEST_OS_WINDOWS_RT) || defined(GTEST_OS_WINDOWS_GAMES) || \ + defined(GTEST_OS_ESP8266) || defined(GTEST_OS_XTENSA) || \ + defined(GTEST_OS_QURT) || !GTEST_HAS_FILE_SYSTEM +#define GTEST_HAS_STREAM_REDIRECTION 0 +#else +#define GTEST_HAS_STREAM_REDIRECTION 1 +#endif // !GTEST_OS_WINDOWS_MOBILE +#endif // GTEST_HAS_STREAM_REDIRECTION + +// Determines whether to support death tests. +// pops up a dialog window that cannot be suppressed programmatically. +#if (defined(GTEST_OS_LINUX) || defined(GTEST_OS_CYGWIN) || \ + defined(GTEST_OS_SOLARIS) || defined(GTEST_OS_ZOS) || \ + (defined(GTEST_OS_MAC) && !defined(GTEST_OS_IOS)) || \ + (defined(GTEST_OS_WINDOWS_DESKTOP) && _MSC_VER) || \ + defined(GTEST_OS_WINDOWS_MINGW) || defined(GTEST_OS_AIX) || \ + defined(GTEST_OS_HPUX) || defined(GTEST_OS_OPENBSD) || \ + defined(GTEST_OS_QNX) || defined(GTEST_OS_FREEBSD) || \ + defined(GTEST_OS_NETBSD) || defined(GTEST_OS_FUCHSIA) || \ + defined(GTEST_OS_DRAGONFLY) || defined(GTEST_OS_GNU_KFREEBSD) || \ + defined(GTEST_OS_HAIKU) || defined(GTEST_OS_GNU_HURD)) +// Death tests require a file system to work properly. +#if GTEST_HAS_FILE_SYSTEM +#define GTEST_HAS_DEATH_TEST 1 +#endif // GTEST_HAS_FILE_SYSTEM +#endif + +// Determines whether to support type-driven tests. + +// Typed tests need and variadic macros, which GCC, VC++ 8.0, +// Sun Pro CC, IBM Visual Age, and HP aCC support. +#if defined(__GNUC__) || defined(_MSC_VER) || defined(__SUNPRO_CC) || \ + defined(__IBMCPP__) || defined(__HP_aCC) +#define GTEST_HAS_TYPED_TEST 1 +#define GTEST_HAS_TYPED_TEST_P 1 +#endif + +// Determines whether the system compiler uses UTF-16 for encoding wide strings. +#if defined(GTEST_OS_WINDOWS) || defined(GTEST_OS_CYGWIN) || \ + defined(GTEST_OS_AIX) || defined(GTEST_OS_OS2) +#define GTEST_WIDE_STRING_USES_UTF16_ 1 +#else +#define GTEST_WIDE_STRING_USES_UTF16_ 0 +#endif + +// Determines whether test results can be streamed to a socket. +#if defined(GTEST_OS_LINUX) || defined(GTEST_OS_GNU_KFREEBSD) || \ + defined(GTEST_OS_DRAGONFLY) || defined(GTEST_OS_FREEBSD) || \ + defined(GTEST_OS_NETBSD) || defined(GTEST_OS_OPENBSD) || \ + defined(GTEST_OS_GNU_HURD) || defined(GTEST_OS_MAC) +#define GTEST_CAN_STREAM_RESULTS_ 1 +#else +#define GTEST_CAN_STREAM_RESULTS_ 0 +#endif + +// Defines some utility macros. + +// The GNU compiler emits a warning if nested "if" statements are followed by +// an "else" statement and braces are not used to explicitly disambiguate the +// "else" binding. This leads to problems with code like: +// +// if (gate) +// ASSERT_*(condition) << "Some message"; +// +// The "switch (0) case 0:" idiom is used to suppress this. +#ifdef __INTEL_COMPILER +#define GTEST_AMBIGUOUS_ELSE_BLOCKER_ +#else +#define GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + switch (0) \ + case 0: \ + default: // NOLINT +#endif + +// GTEST_HAVE_ATTRIBUTE_ +// +// A function-like feature checking macro that is a wrapper around +// `__has_attribute`, which is defined by GCC 5+ and Clang and evaluates to a +// nonzero constant integer if the attribute is supported or 0 if not. +// +// It evaluates to zero if `__has_attribute` is not defined by the compiler. +// +// GCC: https://gcc.gnu.org/gcc-5/changes.html +// Clang: https://clang.llvm.org/docs/LanguageExtensions.html +#ifdef __has_attribute +#define GTEST_HAVE_ATTRIBUTE_(x) __has_attribute(x) +#else +#define GTEST_HAVE_ATTRIBUTE_(x) 0 +#endif + +// GTEST_INTERNAL_HAVE_CPP_ATTRIBUTE +// +// A function-like feature checking macro that accepts C++11 style attributes. +// It's a wrapper around `__has_cpp_attribute`, defined by ISO C++ SD-6 +// (https://en.cppreference.com/w/cpp/experimental/feature_test). If we don't +// find `__has_cpp_attribute`, will evaluate to 0. +#if defined(__has_cpp_attribute) +// NOTE: requiring __cplusplus above should not be necessary, but +// works around https://bugs.llvm.org/show_bug.cgi?id=23435. +#define GTEST_INTERNAL_HAVE_CPP_ATTRIBUTE(x) __has_cpp_attribute(x) +#else +#define GTEST_INTERNAL_HAVE_CPP_ATTRIBUTE(x) 0 +#endif + +// GTEST_HAVE_FEATURE_ +// +// A function-like feature checking macro that is a wrapper around +// `__has_feature`. +#ifdef __has_feature +#define GTEST_HAVE_FEATURE_(x) __has_feature(x) +#else +#define GTEST_HAVE_FEATURE_(x) 0 +#endif + +// Use this annotation before a function that takes a printf format string. +#if GTEST_HAVE_ATTRIBUTE_(format) && defined(__MINGW_PRINTF_FORMAT) +// MinGW has two different printf implementations. Ensure the format macro +// matches the selected implementation. See +// https://sourceforge.net/p/mingw-w64/wiki2/gnu%20printf/. +#define GTEST_ATTRIBUTE_PRINTF_(string_index, first_to_check) \ + __attribute__((format(__MINGW_PRINTF_FORMAT, string_index, first_to_check))) +#elif GTEST_HAVE_ATTRIBUTE_(format) +#define GTEST_ATTRIBUTE_PRINTF_(string_index, first_to_check) \ + __attribute__((format(printf, string_index, first_to_check))) +#else +#define GTEST_ATTRIBUTE_PRINTF_(string_index, first_to_check) +#endif + +// MS C++ compiler emits warning when a conditional expression is compile time +// constant. In some contexts this warning is false positive and needs to be +// suppressed. Use the following two macros in such cases: +// +// GTEST_INTENTIONAL_CONST_COND_PUSH_() +// while (true) { +// GTEST_INTENTIONAL_CONST_COND_POP_() +// } +#define GTEST_INTENTIONAL_CONST_COND_PUSH_() \ + GTEST_DISABLE_MSC_WARNINGS_PUSH_(4127) +#define GTEST_INTENTIONAL_CONST_COND_POP_() GTEST_DISABLE_MSC_WARNINGS_POP_() + +// Determine whether the compiler supports Microsoft's Structured Exception +// Handling. This is supported by several Windows compilers but generally +// does not exist on any other system. +#ifndef GTEST_HAS_SEH +// The user didn't tell us, so we need to figure it out. + +#if defined(_MSC_VER) || defined(__BORLANDC__) +// These two compilers are known to support SEH. +#define GTEST_HAS_SEH 1 +#else +// Assume no SEH. +#define GTEST_HAS_SEH 0 +#endif + +#endif // GTEST_HAS_SEH + +#ifndef GTEST_IS_THREADSAFE + +#if (GTEST_HAS_MUTEX_AND_THREAD_LOCAL_ || \ + (defined(GTEST_OS_WINDOWS) && !defined(GTEST_OS_WINDOWS_PHONE) && \ + !defined(GTEST_OS_WINDOWS_RT)) || \ + GTEST_HAS_PTHREAD) +#define GTEST_IS_THREADSAFE 1 +#endif + +#endif // GTEST_IS_THREADSAFE + +#ifdef GTEST_IS_THREADSAFE +// Some platforms don't support including these threading related headers. +#include // NOLINT +#include // NOLINT +#endif // GTEST_IS_THREADSAFE + +// GTEST_API_ qualifies all symbols that must be exported. The definitions below +// are guarded by #ifndef to give embedders a chance to define GTEST_API_ in +// gtest/internal/custom/gtest-port.h +#ifndef GTEST_API_ + +#ifdef _MSC_VER +#if defined(GTEST_LINKED_AS_SHARED_LIBRARY) && GTEST_LINKED_AS_SHARED_LIBRARY +#define GTEST_API_ __declspec(dllimport) +#elif defined(GTEST_CREATE_SHARED_LIBRARY) && GTEST_CREATE_SHARED_LIBRARY +#define GTEST_API_ __declspec(dllexport) +#endif +#elif GTEST_HAVE_ATTRIBUTE_(visibility) +#define GTEST_API_ __attribute__((visibility("default"))) +#endif // _MSC_VER + +#endif // GTEST_API_ + +#ifndef GTEST_API_ +#define GTEST_API_ +#endif // GTEST_API_ + +#ifndef GTEST_DEFAULT_DEATH_TEST_STYLE +#define GTEST_DEFAULT_DEATH_TEST_STYLE "fast" +#endif // GTEST_DEFAULT_DEATH_TEST_STYLE + +#if GTEST_HAVE_ATTRIBUTE_(noinline) +// Ask the compiler to never inline a given function. +#define GTEST_NO_INLINE_ __attribute__((noinline)) +#else +#define GTEST_NO_INLINE_ +#endif + +#if GTEST_HAVE_ATTRIBUTE_(disable_tail_calls) +// Ask the compiler not to perform tail call optimization inside +// the marked function. +#define GTEST_NO_TAIL_CALL_ __attribute__((disable_tail_calls)) +#elif defined(__GNUC__) && !defined(__NVCOMPILER) +#define GTEST_NO_TAIL_CALL_ \ + __attribute__((optimize("no-optimize-sibling-calls"))) +#else +#define GTEST_NO_TAIL_CALL_ +#endif + +// _LIBCPP_VERSION is defined by the libc++ library from the LLVM project. +#if !defined(GTEST_HAS_CXXABI_H_) +#if defined(__GLIBCXX__) || (defined(_LIBCPP_VERSION) && !defined(_MSC_VER)) +#define GTEST_HAS_CXXABI_H_ 1 +#else +#define GTEST_HAS_CXXABI_H_ 0 +#endif +#endif + +// A function level attribute to disable checking for use of uninitialized +// memory when built with MemorySanitizer. +#if GTEST_HAVE_ATTRIBUTE_(no_sanitize_memory) +#define GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_ __attribute__((no_sanitize_memory)) +#else +#define GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_ +#endif + +// A function level attribute to disable AddressSanitizer instrumentation. +#if GTEST_HAVE_ATTRIBUTE_(no_sanitize_address) +#define GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_ \ + __attribute__((no_sanitize_address)) +#else +#define GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_ +#endif + +// A function level attribute to disable HWAddressSanitizer instrumentation. +#if GTEST_HAVE_FEATURE_(hwaddress_sanitizer) && \ + GTEST_HAVE_ATTRIBUTE_(no_sanitize) +#define GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_ \ + __attribute__((no_sanitize("hwaddress"))) +#else +#define GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_ +#endif + +// A function level attribute to disable ThreadSanitizer instrumentation. +#if GTEST_HAVE_ATTRIBUTE_(no_sanitize_thread) +#define GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_ __attribute((no_sanitize_thread)) +#else +#define GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_ +#endif + +namespace testing { + +class Message; + +// Legacy imports for backwards compatibility. +// New code should use std:: names directly. +using std::get; +using std::make_tuple; +using std::tuple; +using std::tuple_element; +using std::tuple_size; + +namespace internal { + +// A secret type that Google Test users don't know about. It has no +// accessible constructors on purpose. Therefore it's impossible to create a +// Secret object, which is what we want. +class Secret { + Secret(const Secret&) = delete; +}; + +// A helper for suppressing warnings on constant condition. It just +// returns 'condition'. +GTEST_API_ bool IsTrue(bool condition); + +// Defines RE. + +#ifdef GTEST_USES_RE2 + +// This is almost `using RE = ::RE2`, except it is copy-constructible, and it +// needs to disambiguate the `std::string`, `absl::string_view`, and `const +// char*` constructors. +class GTEST_API_ RE { + public: + RE(absl::string_view regex) : regex_(regex) {} // NOLINT + RE(const char* regex) : RE(absl::string_view(regex)) {} // NOLINT + RE(const std::string& regex) : RE(absl::string_view(regex)) {} // NOLINT + RE(const RE& other) : RE(other.pattern()) {} + + const std::string& pattern() const { return regex_.pattern(); } + + static bool FullMatch(absl::string_view str, const RE& re) { + return RE2::FullMatch(str, re.regex_); + } + static bool PartialMatch(absl::string_view str, const RE& re) { + return RE2::PartialMatch(str, re.regex_); + } + + private: + RE2 regex_; +}; + +#elif defined(GTEST_USES_POSIX_RE) || defined(GTEST_USES_SIMPLE_RE) +GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ +/* class A needs to have dll-interface to be used by clients of class B */) + +// A simple C++ wrapper for . It uses the POSIX Extended +// Regular Expression syntax. +class GTEST_API_ RE { + public: + // A copy constructor is required by the Standard to initialize object + // references from r-values. + RE(const RE& other) { Init(other.pattern()); } + + // Constructs an RE from a string. + RE(const ::std::string& regex) { Init(regex.c_str()); } // NOLINT + + RE(const char* regex) { Init(regex); } // NOLINT + ~RE(); + + // Returns the string representation of the regex. + const char* pattern() const { return pattern_.c_str(); } + + // FullMatch(str, re) returns true if and only if regular expression re + // matches the entire str. + // PartialMatch(str, re) returns true if and only if regular expression re + // matches a substring of str (including str itself). + static bool FullMatch(const ::std::string& str, const RE& re) { + return FullMatch(str.c_str(), re); + } + static bool PartialMatch(const ::std::string& str, const RE& re) { + return PartialMatch(str.c_str(), re); + } + + static bool FullMatch(const char* str, const RE& re); + static bool PartialMatch(const char* str, const RE& re); + + private: + void Init(const char* regex); + std::string pattern_; + bool is_valid_; + +#ifdef GTEST_USES_POSIX_RE + + regex_t full_regex_; // For FullMatch(). + regex_t partial_regex_; // For PartialMatch(). + +#else // GTEST_USES_SIMPLE_RE + + std::string full_pattern_; // For FullMatch(); + +#endif +}; +GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 +#endif // ::testing::internal::RE implementation + +// Formats a source file path and a line number as they would appear +// in an error message from the compiler used to compile this code. +GTEST_API_ ::std::string FormatFileLocation(const char* file, int line); + +// Formats a file location for compiler-independent XML output. +// Although this function is not platform dependent, we put it next to +// FormatFileLocation in order to contrast the two functions. +GTEST_API_ ::std::string FormatCompilerIndependentFileLocation(const char* file, + int line); + +// Defines logging utilities: +// GTEST_LOG_(severity) - logs messages at the specified severity level. The +// message itself is streamed into the macro. +// LogToStderr() - directs all log messages to stderr. +// FlushInfoLog() - flushes informational log messages. + +enum GTestLogSeverity { GTEST_INFO, GTEST_WARNING, GTEST_ERROR, GTEST_FATAL }; + +// Formats log entry severity, provides a stream object for streaming the +// log message, and terminates the message with a newline when going out of +// scope. +class GTEST_API_ GTestLog { + public: + GTestLog(GTestLogSeverity severity, const char* file, int line); + + // Flushes the buffers and, if severity is GTEST_FATAL, aborts the program. + ~GTestLog(); + + ::std::ostream& GetStream() { return ::std::cerr; } + + private: + const GTestLogSeverity severity_; + + GTestLog(const GTestLog&) = delete; + GTestLog& operator=(const GTestLog&) = delete; +}; + +#if !defined(GTEST_LOG_) + +#define GTEST_LOG_(severity) \ + ::testing::internal::GTestLog(::testing::internal::GTEST_##severity, \ + __FILE__, __LINE__) \ + .GetStream() + +inline void LogToStderr() {} +inline void FlushInfoLog() { fflush(nullptr); } + +#endif // !defined(GTEST_LOG_) + +#if !defined(GTEST_CHECK_) +// INTERNAL IMPLEMENTATION - DO NOT USE. +// +// GTEST_CHECK_ is an all-mode assert. It aborts the program if the condition +// is not satisfied. +// Synopsis: +// GTEST_CHECK_(boolean_condition); +// or +// GTEST_CHECK_(boolean_condition) << "Additional message"; +// +// This checks the condition and if the condition is not satisfied +// it prints message about the condition violation, including the +// condition itself, plus additional message streamed into it, if any, +// and then it aborts the program. It aborts the program irrespective of +// whether it is built in the debug mode or not. +#define GTEST_CHECK_(condition) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::testing::internal::IsTrue(condition)) \ + ; \ + else \ + GTEST_LOG_(FATAL) << "Condition " #condition " failed. " +#endif // !defined(GTEST_CHECK_) + +// An all-mode assert to verify that the given POSIX-style function +// call returns 0 (indicating success). Known limitation: this +// doesn't expand to a balanced 'if' statement, so enclose the macro +// in {} if you need to use it as the only statement in an 'if' +// branch. +#define GTEST_CHECK_POSIX_SUCCESS_(posix_call) \ + if (const int gtest_error = (posix_call)) \ + GTEST_LOG_(FATAL) << #posix_call << "failed with error " << gtest_error + +// Transforms "T" into "const T&" according to standard reference collapsing +// rules (this is only needed as a backport for C++98 compilers that do not +// support reference collapsing). Specifically, it transforms: +// +// char ==> const char& +// const char ==> const char& +// char& ==> char& +// const char& ==> const char& +// +// Note that the non-const reference will not have "const" added. This is +// standard, and necessary so that "T" can always bind to "const T&". +template +struct ConstRef { + typedef const T& type; +}; +template +struct ConstRef { + typedef T& type; +}; + +// The argument T must depend on some template parameters. +#define GTEST_REFERENCE_TO_CONST_(T) \ + typename ::testing::internal::ConstRef::type + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Use ImplicitCast_ as a safe version of static_cast for upcasting in +// the type hierarchy (e.g. casting a Foo* to a SuperclassOfFoo* or a +// const Foo*). When you use ImplicitCast_, the compiler checks that +// the cast is safe. Such explicit ImplicitCast_s are necessary in +// surprisingly many situations where C++ demands an exact type match +// instead of an argument type convertible to a target type. +// +// The syntax for using ImplicitCast_ is the same as for static_cast: +// +// ImplicitCast_(expr) +// +// ImplicitCast_ would have been part of the C++ standard library, +// but the proposal was submitted too late. It will probably make +// its way into the language in the future. +// +// This relatively ugly name is intentional. It prevents clashes with +// similar functions users may have (e.g., implicit_cast). The internal +// namespace alone is not enough because the function can be found by ADL. +template +inline To ImplicitCast_(To x) { + return x; +} + +// Downcasts the pointer of type Base to Derived. +// Derived must be a subclass of Base. The parameter MUST +// point to a class of type Derived, not any subclass of it. +// When RTTI is available, the function performs a runtime +// check to enforce this. +template +Derived* CheckedDowncastToActualType(Base* base) { + static_assert(std::is_base_of::value, + "target type not derived from source type"); +#if GTEST_HAS_RTTI + GTEST_CHECK_(base == nullptr || dynamic_cast(base) != nullptr); +#endif + return static_cast(base); +} + +#if GTEST_HAS_STREAM_REDIRECTION + +// Defines the stderr capturer: +// CaptureStdout - starts capturing stdout. +// GetCapturedStdout - stops capturing stdout and returns the captured string. +// CaptureStderr - starts capturing stderr. +// GetCapturedStderr - stops capturing stderr and returns the captured string. +// +GTEST_API_ void CaptureStdout(); +GTEST_API_ std::string GetCapturedStdout(); +GTEST_API_ void CaptureStderr(); +GTEST_API_ std::string GetCapturedStderr(); + +#endif // GTEST_HAS_STREAM_REDIRECTION +// Returns the size (in bytes) of a file. +GTEST_API_ size_t GetFileSize(FILE* file); + +// Reads the entire content of a file as a string. +GTEST_API_ std::string ReadEntireFile(FILE* file); + +// All command line arguments. +GTEST_API_ std::vector GetArgvs(); + +#ifdef GTEST_HAS_DEATH_TEST + +std::vector GetInjectableArgvs(); +// Deprecated: pass the args vector by value instead. +void SetInjectableArgvs(const std::vector* new_argvs); +void SetInjectableArgvs(const std::vector& new_argvs); +void ClearInjectableArgvs(); + +#endif // GTEST_HAS_DEATH_TEST + +// Defines synchronization primitives. +#ifdef GTEST_IS_THREADSAFE + +#ifdef GTEST_OS_WINDOWS +// Provides leak-safe Windows kernel handle ownership. +// Used in death tests and in threading support. +class GTEST_API_ AutoHandle { + public: + // Assume that Win32 HANDLE type is equivalent to void*. Doing so allows us to + // avoid including in this header file. Including is + // undesirable because it defines a lot of symbols and macros that tend to + // conflict with client code. This assumption is verified by + // WindowsTypesTest.HANDLEIsVoidStar. + typedef void* Handle; + AutoHandle(); + explicit AutoHandle(Handle handle); + + ~AutoHandle(); + + Handle Get() const; + void Reset(); + void Reset(Handle handle); + + private: + // Returns true if and only if the handle is a valid handle object that can be + // closed. + bool IsCloseable() const; + + Handle handle_; + + AutoHandle(const AutoHandle&) = delete; + AutoHandle& operator=(const AutoHandle&) = delete; +}; +#endif + +#if GTEST_HAS_NOTIFICATION_ +// Notification has already been imported into the namespace. +// Nothing to do here. + +#else +GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ +/* class A needs to have dll-interface to be used by clients of class B */) + +// Allows a controller thread to pause execution of newly created +// threads until notified. Instances of this class must be created +// and destroyed in the controller thread. +// +// This class is only for testing Google Test's own constructs. Do not +// use it in user tests, either directly or indirectly. +// TODO(b/203539622): Replace unconditionally with absl::Notification. +class GTEST_API_ Notification { + public: + Notification() : notified_(false) {} + Notification(const Notification&) = delete; + Notification& operator=(const Notification&) = delete; + + // Notifies all threads created with this notification to start. Must + // be called from the controller thread. + void Notify() { + std::lock_guard lock(mu_); + notified_ = true; + cv_.notify_all(); + } + + // Blocks until the controller thread notifies. Must be called from a test + // thread. + void WaitForNotification() { + std::unique_lock lock(mu_); + cv_.wait(lock, [this]() { return notified_; }); + } + + private: + std::mutex mu_; + std::condition_variable cv_; + bool notified_; +}; +GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 +#endif // GTEST_HAS_NOTIFICATION_ + +// On MinGW, we can have both GTEST_OS_WINDOWS and GTEST_HAS_PTHREAD +// defined, but we don't want to use MinGW's pthreads implementation, which +// has conformance problems with some versions of the POSIX standard. +#if GTEST_HAS_PTHREAD && !defined(GTEST_OS_WINDOWS_MINGW) + +// As a C-function, ThreadFuncWithCLinkage cannot be templated itself. +// Consequently, it cannot select a correct instantiation of ThreadWithParam +// in order to call its Run(). Introducing ThreadWithParamBase as a +// non-templated base class for ThreadWithParam allows us to bypass this +// problem. +class ThreadWithParamBase { + public: + virtual ~ThreadWithParamBase() = default; + virtual void Run() = 0; +}; + +// pthread_create() accepts a pointer to a function type with the C linkage. +// According to the Standard (7.5/1), function types with different linkages +// are different even if they are otherwise identical. Some compilers (for +// example, SunStudio) treat them as different types. Since class methods +// cannot be defined with C-linkage we need to define a free C-function to +// pass into pthread_create(). +extern "C" inline void* ThreadFuncWithCLinkage(void* thread) { + static_cast(thread)->Run(); + return nullptr; +} + +// Helper class for testing Google Test's multi-threading constructs. +// To use it, write: +// +// void ThreadFunc(int param) { /* Do things with param */ } +// Notification thread_can_start; +// ... +// // The thread_can_start parameter is optional; you can supply NULL. +// ThreadWithParam thread(&ThreadFunc, 5, &thread_can_start); +// thread_can_start.Notify(); +// +// These classes are only for testing Google Test's own constructs. Do +// not use them in user tests, either directly or indirectly. +template +class ThreadWithParam : public ThreadWithParamBase { + public: + typedef void UserThreadFunc(T); + + ThreadWithParam(UserThreadFunc* func, T param, Notification* thread_can_start) + : func_(func), + param_(param), + thread_can_start_(thread_can_start), + finished_(false) { + ThreadWithParamBase* const base = this; + // The thread can be created only after all fields except thread_ + // have been initialized. + GTEST_CHECK_POSIX_SUCCESS_( + pthread_create(&thread_, nullptr, &ThreadFuncWithCLinkage, base)); + } + ~ThreadWithParam() override { Join(); } + + void Join() { + if (!finished_) { + GTEST_CHECK_POSIX_SUCCESS_(pthread_join(thread_, nullptr)); + finished_ = true; + } + } + + void Run() override { + if (thread_can_start_ != nullptr) thread_can_start_->WaitForNotification(); + func_(param_); + } + + private: + UserThreadFunc* const func_; // User-supplied thread function. + const T param_; // User-supplied parameter to the thread function. + // When non-NULL, used to block execution until the controller thread + // notifies. + Notification* const thread_can_start_; + bool finished_; // true if and only if we know that the thread function has + // finished. + pthread_t thread_; // The native thread object. + + ThreadWithParam(const ThreadWithParam&) = delete; + ThreadWithParam& operator=(const ThreadWithParam&) = delete; +}; +#endif // !GTEST_OS_WINDOWS && GTEST_HAS_PTHREAD || + // GTEST_HAS_MUTEX_AND_THREAD_LOCAL_ + +#if GTEST_HAS_MUTEX_AND_THREAD_LOCAL_ +// Mutex and ThreadLocal have already been imported into the namespace. +// Nothing to do here. + +#elif defined(GTEST_OS_WINDOWS) && !defined(GTEST_OS_WINDOWS_PHONE) && \ + !defined(GTEST_OS_WINDOWS_RT) + +// Mutex implements mutex on Windows platforms. It is used in conjunction +// with class MutexLock: +// +// Mutex mutex; +// ... +// MutexLock lock(&mutex); // Acquires the mutex and releases it at the +// // end of the current scope. +// +// A static Mutex *must* be defined or declared using one of the following +// macros: +// GTEST_DEFINE_STATIC_MUTEX_(g_some_mutex); +// GTEST_DECLARE_STATIC_MUTEX_(g_some_mutex); +// +// (A non-static Mutex is defined/declared in the usual way). +class GTEST_API_ Mutex { + public: + enum MutexType { kStatic = 0, kDynamic = 1 }; + // We rely on kStaticMutex being 0 as it is to what the linker initializes + // type_ in static mutexes. critical_section_ will be initialized lazily + // in ThreadSafeLazyInit(). + enum StaticConstructorSelector { kStaticMutex = 0 }; + + // This constructor intentionally does nothing. It relies on type_ being + // statically initialized to 0 (effectively setting it to kStatic) and on + // ThreadSafeLazyInit() to lazily initialize the rest of the members. + explicit Mutex(StaticConstructorSelector /*dummy*/) {} + + Mutex(); + ~Mutex(); + + void Lock(); + + void Unlock(); + + // Does nothing if the current thread holds the mutex. Otherwise, crashes + // with high probability. + void AssertHeld(); + + private: + // Initializes owner_thread_id_ and critical_section_ in static mutexes. + void ThreadSafeLazyInit(); + + // Per https://blogs.msdn.microsoft.com/oldnewthing/20040223-00/?p=40503, + // we assume that 0 is an invalid value for thread IDs. + unsigned int owner_thread_id_; + + // For static mutexes, we rely on these members being initialized to zeros + // by the linker. + MutexType type_; + long critical_section_init_phase_; // NOLINT + GTEST_CRITICAL_SECTION* critical_section_; + + Mutex(const Mutex&) = delete; + Mutex& operator=(const Mutex&) = delete; +}; + +#define GTEST_DECLARE_STATIC_MUTEX_(mutex) \ + extern ::testing::internal::Mutex mutex + +#define GTEST_DEFINE_STATIC_MUTEX_(mutex) \ + ::testing::internal::Mutex mutex(::testing::internal::Mutex::kStaticMutex) + +// We cannot name this class MutexLock because the ctor declaration would +// conflict with a macro named MutexLock, which is defined on some +// platforms. That macro is used as a defensive measure to prevent against +// inadvertent misuses of MutexLock like "MutexLock(&mu)" rather than +// "MutexLock l(&mu)". Hence the typedef trick below. +class GTestMutexLock { + public: + explicit GTestMutexLock(Mutex* mutex) : mutex_(mutex) { mutex_->Lock(); } + + ~GTestMutexLock() { mutex_->Unlock(); } + + private: + Mutex* const mutex_; + + GTestMutexLock(const GTestMutexLock&) = delete; + GTestMutexLock& operator=(const GTestMutexLock&) = delete; +}; + +typedef GTestMutexLock MutexLock; + +// Base class for ValueHolder. Allows a caller to hold and delete a value +// without knowing its type. +class ThreadLocalValueHolderBase { + public: + virtual ~ThreadLocalValueHolderBase() {} +}; + +// Provides a way for a thread to send notifications to a ThreadLocal +// regardless of its parameter type. +class ThreadLocalBase { + public: + // Creates a new ValueHolder object holding a default value passed to + // this ThreadLocal's constructor and returns it. It is the caller's + // responsibility not to call this when the ThreadLocal instance already + // has a value on the current thread. + virtual ThreadLocalValueHolderBase* NewValueForCurrentThread() const = 0; + + protected: + ThreadLocalBase() {} + virtual ~ThreadLocalBase() {} + + private: + ThreadLocalBase(const ThreadLocalBase&) = delete; + ThreadLocalBase& operator=(const ThreadLocalBase&) = delete; +}; + +// Maps a thread to a set of ThreadLocals that have values instantiated on that +// thread and notifies them when the thread exits. A ThreadLocal instance is +// expected to persist until all threads it has values on have terminated. +class GTEST_API_ ThreadLocalRegistry { + public: + // Registers thread_local_instance as having value on the current thread. + // Returns a value that can be used to identify the thread from other threads. + static ThreadLocalValueHolderBase* GetValueOnCurrentThread( + const ThreadLocalBase* thread_local_instance); + + // Invoked when a ThreadLocal instance is destroyed. + static void OnThreadLocalDestroyed( + const ThreadLocalBase* thread_local_instance); +}; + +class GTEST_API_ ThreadWithParamBase { + public: + void Join(); + + protected: + class Runnable { + public: + virtual ~Runnable() {} + virtual void Run() = 0; + }; + + ThreadWithParamBase(Runnable* runnable, Notification* thread_can_start); + virtual ~ThreadWithParamBase(); + + private: + AutoHandle thread_; +}; + +// Helper class for testing Google Test's multi-threading constructs. +template +class ThreadWithParam : public ThreadWithParamBase { + public: + typedef void UserThreadFunc(T); + + ThreadWithParam(UserThreadFunc* func, T param, Notification* thread_can_start) + : ThreadWithParamBase(new RunnableImpl(func, param), thread_can_start) {} + virtual ~ThreadWithParam() {} + + private: + class RunnableImpl : public Runnable { + public: + RunnableImpl(UserThreadFunc* func, T param) : func_(func), param_(param) {} + virtual ~RunnableImpl() {} + virtual void Run() { func_(param_); } + + private: + UserThreadFunc* const func_; + const T param_; + + RunnableImpl(const RunnableImpl&) = delete; + RunnableImpl& operator=(const RunnableImpl&) = delete; + }; + + ThreadWithParam(const ThreadWithParam&) = delete; + ThreadWithParam& operator=(const ThreadWithParam&) = delete; +}; + +// Implements thread-local storage on Windows systems. +// +// // Thread 1 +// ThreadLocal tl(100); // 100 is the default value for each thread. +// +// // Thread 2 +// tl.set(150); // Changes the value for thread 2 only. +// EXPECT_EQ(150, tl.get()); +// +// // Thread 1 +// EXPECT_EQ(100, tl.get()); // In thread 1, tl has the original value. +// tl.set(200); +// EXPECT_EQ(200, tl.get()); +// +// The template type argument T must have a public copy constructor. +// In addition, the default ThreadLocal constructor requires T to have +// a public default constructor. +// +// The users of a TheadLocal instance have to make sure that all but one +// threads (including the main one) using that instance have exited before +// destroying it. Otherwise, the per-thread objects managed for them by the +// ThreadLocal instance are not guaranteed to be destroyed on all platforms. +// +// Google Test only uses global ThreadLocal objects. That means they +// will die after main() has returned. Therefore, no per-thread +// object managed by Google Test will be leaked as long as all threads +// using Google Test have exited when main() returns. +template +class ThreadLocal : public ThreadLocalBase { + public: + ThreadLocal() : default_factory_(new DefaultValueHolderFactory()) {} + explicit ThreadLocal(const T& value) + : default_factory_(new InstanceValueHolderFactory(value)) {} + + ~ThreadLocal() override { ThreadLocalRegistry::OnThreadLocalDestroyed(this); } + + T* pointer() { return GetOrCreateValue(); } + const T* pointer() const { return GetOrCreateValue(); } + const T& get() const { return *pointer(); } + void set(const T& value) { *pointer() = value; } + + private: + // Holds a value of T. Can be deleted via its base class without the caller + // knowing the type of T. + class ValueHolder : public ThreadLocalValueHolderBase { + public: + ValueHolder() : value_() {} + explicit ValueHolder(const T& value) : value_(value) {} + + T* pointer() { return &value_; } + + private: + T value_; + ValueHolder(const ValueHolder&) = delete; + ValueHolder& operator=(const ValueHolder&) = delete; + }; + + T* GetOrCreateValue() const { + return static_cast( + ThreadLocalRegistry::GetValueOnCurrentThread(this)) + ->pointer(); + } + + ThreadLocalValueHolderBase* NewValueForCurrentThread() const override { + return default_factory_->MakeNewHolder(); + } + + class ValueHolderFactory { + public: + ValueHolderFactory() {} + virtual ~ValueHolderFactory() {} + virtual ValueHolder* MakeNewHolder() const = 0; + + private: + ValueHolderFactory(const ValueHolderFactory&) = delete; + ValueHolderFactory& operator=(const ValueHolderFactory&) = delete; + }; + + class DefaultValueHolderFactory : public ValueHolderFactory { + public: + DefaultValueHolderFactory() {} + ValueHolder* MakeNewHolder() const override { return new ValueHolder(); } + + private: + DefaultValueHolderFactory(const DefaultValueHolderFactory&) = delete; + DefaultValueHolderFactory& operator=(const DefaultValueHolderFactory&) = + delete; + }; + + class InstanceValueHolderFactory : public ValueHolderFactory { + public: + explicit InstanceValueHolderFactory(const T& value) : value_(value) {} + ValueHolder* MakeNewHolder() const override { + return new ValueHolder(value_); + } + + private: + const T value_; // The value for each thread. + + InstanceValueHolderFactory(const InstanceValueHolderFactory&) = delete; + InstanceValueHolderFactory& operator=(const InstanceValueHolderFactory&) = + delete; + }; + + std::unique_ptr default_factory_; + + ThreadLocal(const ThreadLocal&) = delete; + ThreadLocal& operator=(const ThreadLocal&) = delete; +}; + +#elif GTEST_HAS_PTHREAD + +// MutexBase and Mutex implement mutex on pthreads-based platforms. +class MutexBase { + public: + // Acquires this mutex. + void Lock() { + GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_lock(&mutex_)); + owner_ = pthread_self(); + has_owner_ = true; + } + + // Releases this mutex. + void Unlock() { + // Since the lock is being released the owner_ field should no longer be + // considered valid. We don't protect writing to has_owner_ here, as it's + // the caller's responsibility to ensure that the current thread holds the + // mutex when this is called. + has_owner_ = false; + GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_unlock(&mutex_)); + } + + // Does nothing if the current thread holds the mutex. Otherwise, crashes + // with high probability. + void AssertHeld() const { + GTEST_CHECK_(has_owner_ && pthread_equal(owner_, pthread_self())) + << "The current thread is not holding the mutex @" << this; + } + + // A static mutex may be used before main() is entered. It may even + // be used before the dynamic initialization stage. Therefore we + // must be able to initialize a static mutex object at link time. + // This means MutexBase has to be a POD and its member variables + // have to be public. + public: + pthread_mutex_t mutex_; // The underlying pthread mutex. + // has_owner_ indicates whether the owner_ field below contains a valid thread + // ID and is therefore safe to inspect (e.g., to use in pthread_equal()). All + // accesses to the owner_ field should be protected by a check of this field. + // An alternative might be to memset() owner_ to all zeros, but there's no + // guarantee that a zero'd pthread_t is necessarily invalid or even different + // from pthread_self(). + bool has_owner_; + pthread_t owner_; // The thread holding the mutex. +}; + +// Forward-declares a static mutex. +#define GTEST_DECLARE_STATIC_MUTEX_(mutex) \ + extern ::testing::internal::MutexBase mutex + +// Defines and statically (i.e. at link time) initializes a static mutex. +// The initialization list here does not explicitly initialize each field, +// instead relying on default initialization for the unspecified fields. In +// particular, the owner_ field (a pthread_t) is not explicitly initialized. +// This allows initialization to work whether pthread_t is a scalar or struct. +// The flag -Wmissing-field-initializers must not be specified for this to work. +#define GTEST_DEFINE_STATIC_MUTEX_(mutex) \ + ::testing::internal::MutexBase mutex = {PTHREAD_MUTEX_INITIALIZER, false, 0} + +// The Mutex class can only be used for mutexes created at runtime. It +// shares its API with MutexBase otherwise. +class Mutex : public MutexBase { + public: + Mutex() { + GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_init(&mutex_, nullptr)); + has_owner_ = false; + } + ~Mutex() { GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_destroy(&mutex_)); } + + private: + Mutex(const Mutex&) = delete; + Mutex& operator=(const Mutex&) = delete; +}; + +// We cannot name this class MutexLock because the ctor declaration would +// conflict with a macro named MutexLock, which is defined on some +// platforms. That macro is used as a defensive measure to prevent against +// inadvertent misuses of MutexLock like "MutexLock(&mu)" rather than +// "MutexLock l(&mu)". Hence the typedef trick below. +class GTestMutexLock { + public: + explicit GTestMutexLock(MutexBase* mutex) : mutex_(mutex) { mutex_->Lock(); } + + ~GTestMutexLock() { mutex_->Unlock(); } + + private: + MutexBase* const mutex_; + + GTestMutexLock(const GTestMutexLock&) = delete; + GTestMutexLock& operator=(const GTestMutexLock&) = delete; +}; + +typedef GTestMutexLock MutexLock; + +// Helpers for ThreadLocal. + +// pthread_key_create() requires DeleteThreadLocalValue() to have +// C-linkage. Therefore it cannot be templatized to access +// ThreadLocal. Hence the need for class +// ThreadLocalValueHolderBase. +class GTEST_API_ ThreadLocalValueHolderBase { + public: + virtual ~ThreadLocalValueHolderBase() = default; +}; + +// Called by pthread to delete thread-local data stored by +// pthread_setspecific(). +extern "C" inline void DeleteThreadLocalValue(void* value_holder) { + delete static_cast(value_holder); +} + +// Implements thread-local storage on pthreads-based systems. +template +class GTEST_API_ ThreadLocal { + public: + ThreadLocal() + : key_(CreateKey()), default_factory_(new DefaultValueHolderFactory()) {} + explicit ThreadLocal(const T& value) + : key_(CreateKey()), + default_factory_(new InstanceValueHolderFactory(value)) {} + + ~ThreadLocal() { + // Destroys the managed object for the current thread, if any. + DeleteThreadLocalValue(pthread_getspecific(key_)); + + // Releases resources associated with the key. This will *not* + // delete managed objects for other threads. + GTEST_CHECK_POSIX_SUCCESS_(pthread_key_delete(key_)); + } + + T* pointer() { return GetOrCreateValue(); } + const T* pointer() const { return GetOrCreateValue(); } + const T& get() const { return *pointer(); } + void set(const T& value) { *pointer() = value; } + + private: + // Holds a value of type T. + class ValueHolder : public ThreadLocalValueHolderBase { + public: + ValueHolder() : value_() {} + explicit ValueHolder(const T& value) : value_(value) {} + + T* pointer() { return &value_; } + + private: + T value_; + ValueHolder(const ValueHolder&) = delete; + ValueHolder& operator=(const ValueHolder&) = delete; + }; + + static pthread_key_t CreateKey() { + pthread_key_t key; + // When a thread exits, DeleteThreadLocalValue() will be called on + // the object managed for that thread. + GTEST_CHECK_POSIX_SUCCESS_( + pthread_key_create(&key, &DeleteThreadLocalValue)); + return key; + } + + T* GetOrCreateValue() const { + ThreadLocalValueHolderBase* const holder = + static_cast(pthread_getspecific(key_)); + if (holder != nullptr) { + return CheckedDowncastToActualType(holder)->pointer(); + } + + ValueHolder* const new_holder = default_factory_->MakeNewHolder(); + ThreadLocalValueHolderBase* const holder_base = new_holder; + GTEST_CHECK_POSIX_SUCCESS_(pthread_setspecific(key_, holder_base)); + return new_holder->pointer(); + } + + class ValueHolderFactory { + public: + ValueHolderFactory() = default; + virtual ~ValueHolderFactory() = default; + virtual ValueHolder* MakeNewHolder() const = 0; + + private: + ValueHolderFactory(const ValueHolderFactory&) = delete; + ValueHolderFactory& operator=(const ValueHolderFactory&) = delete; + }; + + class DefaultValueHolderFactory : public ValueHolderFactory { + public: + DefaultValueHolderFactory() = default; + ValueHolder* MakeNewHolder() const override { return new ValueHolder(); } + + private: + DefaultValueHolderFactory(const DefaultValueHolderFactory&) = delete; + DefaultValueHolderFactory& operator=(const DefaultValueHolderFactory&) = + delete; + }; + + class InstanceValueHolderFactory : public ValueHolderFactory { + public: + explicit InstanceValueHolderFactory(const T& value) : value_(value) {} + ValueHolder* MakeNewHolder() const override { + return new ValueHolder(value_); + } + + private: + const T value_; // The value for each thread. + + InstanceValueHolderFactory(const InstanceValueHolderFactory&) = delete; + InstanceValueHolderFactory& operator=(const InstanceValueHolderFactory&) = + delete; + }; + + // A key pthreads uses for looking up per-thread values. + const pthread_key_t key_; + std::unique_ptr default_factory_; + + ThreadLocal(const ThreadLocal&) = delete; + ThreadLocal& operator=(const ThreadLocal&) = delete; +}; + +#endif // GTEST_HAS_MUTEX_AND_THREAD_LOCAL_ + +#else // GTEST_IS_THREADSAFE + +// A dummy implementation of synchronization primitives (mutex, lock, +// and thread-local variable). Necessary for compiling Google Test where +// mutex is not supported - using Google Test in multiple threads is not +// supported on such platforms. + +class Mutex { + public: + Mutex() {} + void Lock() {} + void Unlock() {} + void AssertHeld() const {} +}; + +#define GTEST_DECLARE_STATIC_MUTEX_(mutex) \ + extern ::testing::internal::Mutex mutex + +#define GTEST_DEFINE_STATIC_MUTEX_(mutex) ::testing::internal::Mutex mutex + +// We cannot name this class MutexLock because the ctor declaration would +// conflict with a macro named MutexLock, which is defined on some +// platforms. That macro is used as a defensive measure to prevent against +// inadvertent misuses of MutexLock like "MutexLock(&mu)" rather than +// "MutexLock l(&mu)". Hence the typedef trick below. +class GTestMutexLock { + public: + explicit GTestMutexLock(Mutex*) {} // NOLINT +}; + +typedef GTestMutexLock MutexLock; + +template +class GTEST_API_ ThreadLocal { + public: + ThreadLocal() : value_() {} + explicit ThreadLocal(const T& value) : value_(value) {} + T* pointer() { return &value_; } + const T* pointer() const { return &value_; } + const T& get() const { return value_; } + void set(const T& value) { value_ = value; } + + private: + T value_; +}; + +#endif // GTEST_IS_THREADSAFE + +// Returns the number of threads running in the process, or 0 to indicate that +// we cannot detect it. +GTEST_API_ size_t GetThreadCount(); + +#ifdef GTEST_OS_WINDOWS +#define GTEST_PATH_SEP_ "\\" +#define GTEST_HAS_ALT_PATH_SEP_ 1 +#else +#define GTEST_PATH_SEP_ "/" +#define GTEST_HAS_ALT_PATH_SEP_ 0 +#endif // GTEST_OS_WINDOWS + +// Utilities for char. + +// isspace(int ch) and friends accept an unsigned char or EOF. char +// may be signed, depending on the compiler (or compiler flags). +// Therefore we need to cast a char to unsigned char before calling +// isspace(), etc. + +inline bool IsAlpha(char ch) { + return isalpha(static_cast(ch)) != 0; +} +inline bool IsAlNum(char ch) { + return isalnum(static_cast(ch)) != 0; +} +inline bool IsDigit(char ch) { + return isdigit(static_cast(ch)) != 0; +} +inline bool IsLower(char ch) { + return islower(static_cast(ch)) != 0; +} +inline bool IsSpace(char ch) { + return isspace(static_cast(ch)) != 0; +} +inline bool IsUpper(char ch) { + return isupper(static_cast(ch)) != 0; +} +inline bool IsXDigit(char ch) { + return isxdigit(static_cast(ch)) != 0; +} +#ifdef __cpp_lib_char8_t +inline bool IsXDigit(char8_t ch) { + return isxdigit(static_cast(ch)) != 0; +} +#endif +inline bool IsXDigit(char16_t ch) { + const unsigned char low_byte = static_cast(ch); + return ch == low_byte && isxdigit(low_byte) != 0; +} +inline bool IsXDigit(char32_t ch) { + const unsigned char low_byte = static_cast(ch); + return ch == low_byte && isxdigit(low_byte) != 0; +} +inline bool IsXDigit(wchar_t ch) { + const unsigned char low_byte = static_cast(ch); + return ch == low_byte && isxdigit(low_byte) != 0; +} + +inline char ToLower(char ch) { + return static_cast(tolower(static_cast(ch))); +} +inline char ToUpper(char ch) { + return static_cast(toupper(static_cast(ch))); +} + +inline std::string StripTrailingSpaces(std::string str) { + std::string::iterator it = str.end(); + while (it != str.begin() && IsSpace(*--it)) it = str.erase(it); + return str; +} + +// The testing::internal::posix namespace holds wrappers for common +// POSIX functions. These wrappers hide the differences between +// Windows/MSVC and POSIX systems. Since some compilers define these +// standard functions as macros, the wrapper cannot have the same name +// as the wrapped function. + +namespace posix { + +// File system porting. +// Note: Not every I/O-related function is related to file systems, so don't +// just disable all of them here. For example, fileno() and isatty(), etc. must +// always be available in order to detect if a pipe points to a terminal. +#ifdef GTEST_OS_WINDOWS + +typedef struct _stat StatStruct; + +#ifdef GTEST_OS_WINDOWS_MOBILE +inline int FileNo(FILE* file) { return reinterpret_cast(_fileno(file)); } +// Stat(), RmDir(), and IsDir() are not needed on Windows CE at this +// time and thus not defined there. +#else +inline int FileNo(FILE* file) { return _fileno(file); } +#if GTEST_HAS_FILE_SYSTEM +inline int Stat(const char* path, StatStruct* buf) { return _stat(path, buf); } +inline int RmDir(const char* dir) { return _rmdir(dir); } +inline bool IsDir(const StatStruct& st) { return (_S_IFDIR & st.st_mode) != 0; } +#endif +#endif // GTEST_OS_WINDOWS_MOBILE + +#elif defined(GTEST_OS_ESP8266) +typedef struct stat StatStruct; + +inline int FileNo(FILE* file) { return fileno(file); } +#if GTEST_HAS_FILE_SYSTEM +inline int Stat(const char* path, StatStruct* buf) { + // stat function not implemented on ESP8266 + return 0; +} +inline int RmDir(const char* dir) { return rmdir(dir); } +inline bool IsDir(const StatStruct& st) { return S_ISDIR(st.st_mode); } +#endif + +#else + +typedef struct stat StatStruct; + +inline int FileNo(FILE* file) { return fileno(file); } +#if GTEST_HAS_FILE_SYSTEM +inline int Stat(const char* path, StatStruct* buf) { return stat(path, buf); } +#ifdef GTEST_OS_QURT +// QuRT doesn't support any directory functions, including rmdir +inline int RmDir(const char*) { return 0; } +#else +inline int RmDir(const char* dir) { return rmdir(dir); } +#endif +inline bool IsDir(const StatStruct& st) { return S_ISDIR(st.st_mode); } +#endif + +#endif // GTEST_OS_WINDOWS + +// Other functions with a different name on Windows. + +#ifdef GTEST_OS_WINDOWS + +#ifdef __BORLANDC__ +inline int DoIsATTY(int fd) { return isatty(fd); } +inline int StrCaseCmp(const char* s1, const char* s2) { + return stricmp(s1, s2); +} +#else // !__BORLANDC__ +#if defined(GTEST_OS_WINDOWS_MOBILE) || defined(GTEST_OS_ZOS) || \ + defined(GTEST_OS_IOS) || defined(GTEST_OS_WINDOWS_PHONE) || \ + defined(GTEST_OS_WINDOWS_RT) || defined(ESP_PLATFORM) +inline int DoIsATTY(int /* fd */) { return 0; } +#else +inline int DoIsATTY(int fd) { return _isatty(fd); } +#endif // GTEST_OS_WINDOWS_MOBILE +inline int StrCaseCmp(const char* s1, const char* s2) { + return _stricmp(s1, s2); +} +#endif // __BORLANDC__ + +#else + +inline int DoIsATTY(int fd) { return isatty(fd); } +inline int StrCaseCmp(const char* s1, const char* s2) { + return strcasecmp(s1, s2); +} + +#endif // GTEST_OS_WINDOWS + +inline int IsATTY(int fd) { + // DoIsATTY might change errno (for example ENOTTY in case you redirect stdout + // to a file on Linux), which is unexpected, so save the previous value, and + // restore it after the call. + int savedErrno = errno; + int isAttyValue = DoIsATTY(fd); + errno = savedErrno; + + return isAttyValue; +} + +// Functions deprecated by MSVC 8.0. + +GTEST_DISABLE_MSC_DEPRECATED_PUSH_() + +// ChDir(), FReopen(), FDOpen(), Read(), Write(), Close(), and +// StrError() aren't needed on Windows CE at this time and thus not +// defined there. +#if GTEST_HAS_FILE_SYSTEM +#if !defined(GTEST_OS_WINDOWS_MOBILE) && !defined(GTEST_OS_WINDOWS_PHONE) && \ + !defined(GTEST_OS_WINDOWS_RT) && !defined(GTEST_OS_WINDOWS_GAMES) && \ + !defined(GTEST_OS_ESP8266) && !defined(GTEST_OS_XTENSA) && \ + !defined(GTEST_OS_QURT) +inline int ChDir(const char* dir) { return chdir(dir); } +#endif +inline FILE* FOpen(const char* path, const char* mode) { +#if defined(GTEST_OS_WINDOWS) && !defined(GTEST_OS_WINDOWS_MINGW) + struct wchar_codecvt : public std::codecvt {}; + std::wstring_convert converter; + std::wstring wide_path = converter.from_bytes(path); + std::wstring wide_mode = converter.from_bytes(mode); + return _wfopen(wide_path.c_str(), wide_mode.c_str()); +#else // GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MINGW + return fopen(path, mode); +#endif // GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MINGW +} +#if !defined(GTEST_OS_WINDOWS_MOBILE) && !defined(GTEST_OS_QURT) +inline FILE* FReopen(const char* path, const char* mode, FILE* stream) { + return freopen(path, mode, stream); +} +inline FILE* FDOpen(int fd, const char* mode) { return fdopen(fd, mode); } +#endif // !GTEST_OS_WINDOWS_MOBILE && !GTEST_OS_QURT +inline int FClose(FILE* fp) { return fclose(fp); } +#if !defined(GTEST_OS_WINDOWS_MOBILE) && !defined(GTEST_OS_QURT) +inline int Read(int fd, void* buf, unsigned int count) { + return static_cast(read(fd, buf, count)); +} +inline int Write(int fd, const void* buf, unsigned int count) { + return static_cast(write(fd, buf, count)); +} +inline int Close(int fd) { return close(fd); } +#endif // !GTEST_OS_WINDOWS_MOBILE && !GTEST_OS_QURT +#endif // GTEST_HAS_FILE_SYSTEM + +#if !defined(GTEST_OS_WINDOWS_MOBILE) && !defined(GTEST_OS_QURT) +inline const char* StrError(int errnum) { return strerror(errnum); } +#endif // !GTEST_OS_WINDOWS_MOBILE && !GTEST_OS_QURT + +inline const char* GetEnv(const char* name) { +#if defined(GTEST_OS_WINDOWS_MOBILE) || defined(GTEST_OS_WINDOWS_PHONE) || \ + defined(GTEST_OS_ESP8266) || defined(GTEST_OS_XTENSA) || \ + defined(GTEST_OS_QURT) + // We are on an embedded platform, which has no environment variables. + static_cast(name); // To prevent 'unused argument' warning. + return nullptr; +#elif defined(__BORLANDC__) || defined(__SunOS_5_8) || defined(__SunOS_5_9) + // Environment variables which we programmatically clear will be set to the + // empty string rather than unset (NULL). Handle that case. + const char* const env = getenv(name); + return (env != nullptr && env[0] != '\0') ? env : nullptr; +#else + return getenv(name); +#endif +} + +GTEST_DISABLE_MSC_DEPRECATED_POP_() + +#ifdef GTEST_OS_WINDOWS_MOBILE +// Windows CE has no C library. The abort() function is used in +// several places in Google Test. This implementation provides a reasonable +// imitation of standard behaviour. +[[noreturn]] void Abort(); +#else +[[noreturn]] inline void Abort() { abort(); } +#endif // GTEST_OS_WINDOWS_MOBILE + +} // namespace posix + +// MSVC "deprecates" snprintf and issues warnings wherever it is used. In +// order to avoid these warnings, we need to use _snprintf or _snprintf_s on +// MSVC-based platforms. We map the GTEST_SNPRINTF_ macro to the appropriate +// function in order to achieve that. We use macro definition here because +// snprintf is a variadic function. +#if defined(_MSC_VER) && !defined(GTEST_OS_WINDOWS_MOBILE) +// MSVC 2005 and above support variadic macros. +#define GTEST_SNPRINTF_(buffer, size, format, ...) \ + _snprintf_s(buffer, size, size, format, __VA_ARGS__) +#elif defined(_MSC_VER) +// Windows CE does not define _snprintf_s +#define GTEST_SNPRINTF_ _snprintf +#else +#define GTEST_SNPRINTF_ snprintf +#endif + +// The biggest signed integer type the compiler supports. +// +// long long is guaranteed to be at least 64-bits in C++11. +using BiggestInt = long long; // NOLINT + +// The maximum number a BiggestInt can represent. +constexpr BiggestInt kMaxBiggestInt = (std::numeric_limits::max)(); + +// This template class serves as a compile-time function from size to +// type. It maps a size in bytes to a primitive type with that +// size. e.g. +// +// TypeWithSize<4>::UInt +// +// is typedef-ed to be unsigned int (unsigned integer made up of 4 +// bytes). +// +// Such functionality should belong to STL, but I cannot find it +// there. +// +// Google Test uses this class in the implementation of floating-point +// comparison. +// +// For now it only handles UInt (unsigned int) as that's all Google Test +// needs. Other types can be easily added in the future if need +// arises. +template +class TypeWithSize { + public: + // This prevents the user from using TypeWithSize with incorrect + // values of N. + using UInt = void; +}; + +// The specialization for size 4. +template <> +class TypeWithSize<4> { + public: + using Int = std::int32_t; + using UInt = std::uint32_t; +}; + +// The specialization for size 8. +template <> +class TypeWithSize<8> { + public: + using Int = std::int64_t; + using UInt = std::uint64_t; +}; + +// Integer types of known sizes. +using TimeInMillis = int64_t; // Represents time in milliseconds. + +// Utilities for command line flags and environment variables. + +// Macro for referencing flags. +#if !defined(GTEST_FLAG) +#define GTEST_FLAG_NAME_(name) gtest_##name +#define GTEST_FLAG(name) FLAGS_gtest_##name +#endif // !defined(GTEST_FLAG) + +// Pick a command line flags implementation. +#ifdef GTEST_INTERNAL_HAS_ABSL_FLAGS + +// Macros for defining flags. +#define GTEST_DEFINE_bool_(name, default_val, doc) \ + ABSL_FLAG(bool, GTEST_FLAG_NAME_(name), default_val, doc) +#define GTEST_DEFINE_int32_(name, default_val, doc) \ + ABSL_FLAG(int32_t, GTEST_FLAG_NAME_(name), default_val, doc) +#define GTEST_DEFINE_string_(name, default_val, doc) \ + ABSL_FLAG(std::string, GTEST_FLAG_NAME_(name), default_val, doc) + +// Macros for declaring flags. +#define GTEST_DECLARE_bool_(name) \ + ABSL_DECLARE_FLAG(bool, GTEST_FLAG_NAME_(name)) +#define GTEST_DECLARE_int32_(name) \ + ABSL_DECLARE_FLAG(int32_t, GTEST_FLAG_NAME_(name)) +#define GTEST_DECLARE_string_(name) \ + ABSL_DECLARE_FLAG(std::string, GTEST_FLAG_NAME_(name)) + +#define GTEST_FLAG_SAVER_ ::absl::FlagSaver + +#define GTEST_FLAG_GET(name) ::absl::GetFlag(GTEST_FLAG(name)) +#define GTEST_FLAG_SET(name, value) \ + (void)(::absl::SetFlag(>EST_FLAG(name), value)) +#define GTEST_USE_OWN_FLAGFILE_FLAG_ 0 + +#undef GTEST_INTERNAL_HAS_ABSL_FLAGS +#else // ndef GTEST_INTERNAL_HAS_ABSL_FLAGS + +// Macros for defining flags. +#define GTEST_DEFINE_bool_(name, default_val, doc) \ + namespace testing { \ + GTEST_API_ bool GTEST_FLAG(name) = (default_val); \ + } \ + static_assert(true, "no-op to require trailing semicolon") +#define GTEST_DEFINE_int32_(name, default_val, doc) \ + namespace testing { \ + GTEST_API_ std::int32_t GTEST_FLAG(name) = (default_val); \ + } \ + static_assert(true, "no-op to require trailing semicolon") +#define GTEST_DEFINE_string_(name, default_val, doc) \ + namespace testing { \ + GTEST_API_ ::std::string GTEST_FLAG(name) = (default_val); \ + } \ + static_assert(true, "no-op to require trailing semicolon") + +// Macros for declaring flags. +#define GTEST_DECLARE_bool_(name) \ + namespace testing { \ + GTEST_API_ extern bool GTEST_FLAG(name); \ + } \ + static_assert(true, "no-op to require trailing semicolon") +#define GTEST_DECLARE_int32_(name) \ + namespace testing { \ + GTEST_API_ extern std::int32_t GTEST_FLAG(name); \ + } \ + static_assert(true, "no-op to require trailing semicolon") +#define GTEST_DECLARE_string_(name) \ + namespace testing { \ + GTEST_API_ extern ::std::string GTEST_FLAG(name); \ + } \ + static_assert(true, "no-op to require trailing semicolon") + +#define GTEST_FLAG_SAVER_ ::testing::internal::GTestFlagSaver + +#define GTEST_FLAG_GET(name) ::testing::GTEST_FLAG(name) +#define GTEST_FLAG_SET(name, value) (void)(::testing::GTEST_FLAG(name) = value) +#define GTEST_USE_OWN_FLAGFILE_FLAG_ 1 + +#endif // GTEST_INTERNAL_HAS_ABSL_FLAGS + +// Thread annotations +#if !defined(GTEST_EXCLUSIVE_LOCK_REQUIRED_) +#define GTEST_EXCLUSIVE_LOCK_REQUIRED_(locks) +#define GTEST_LOCK_EXCLUDED_(locks) +#endif // !defined(GTEST_EXCLUSIVE_LOCK_REQUIRED_) + +// Parses 'str' for a 32-bit signed integer. If successful, writes the result +// to *value and returns true; otherwise leaves *value unchanged and returns +// false. +GTEST_API_ bool ParseInt32(const Message& src_text, const char* str, + int32_t* value); + +// Parses a bool/int32_t/string from the environment variable +// corresponding to the given Google Test flag. +bool BoolFromGTestEnv(const char* flag, bool default_val); +GTEST_API_ int32_t Int32FromGTestEnv(const char* flag, int32_t default_val); +std::string OutputFlagAlsoCheckEnvVar(); +const char* StringFromGTestEnv(const char* flag, const char* default_val); + +} // namespace internal +} // namespace testing + +#ifdef GTEST_HAS_ABSL +// Always use absl::any for UniversalPrinter<> specializations if googletest +// is built with absl support. +#define GTEST_INTERNAL_HAS_ANY 1 +#include "absl/types/any.h" +namespace testing { +namespace internal { +using Any = ::absl::any; +} // namespace internal +} // namespace testing +#else +#if defined(__cpp_lib_any) || (GTEST_INTERNAL_HAS_INCLUDE() && \ + GTEST_INTERNAL_CPLUSPLUS_LANG >= 201703L && \ + (!defined(_MSC_VER) || GTEST_HAS_RTTI)) +// Otherwise for C++17 and higher use std::any for UniversalPrinter<> +// specializations. +#define GTEST_INTERNAL_HAS_ANY 1 +#include +namespace testing { +namespace internal { +using Any = ::std::any; +} // namespace internal +} // namespace testing +// The case where absl is configured NOT to alias std::any is not +// supported. +#endif // __cpp_lib_any +#endif // GTEST_HAS_ABSL + +#ifndef GTEST_INTERNAL_HAS_ANY +#define GTEST_INTERNAL_HAS_ANY 0 +#endif + +#ifdef GTEST_HAS_ABSL +// Always use absl::optional for UniversalPrinter<> specializations if +// googletest is built with absl support. +#define GTEST_INTERNAL_HAS_OPTIONAL 1 +#include "absl/types/optional.h" +namespace testing { +namespace internal { +template +using Optional = ::absl::optional; +inline ::absl::nullopt_t Nullopt() { return ::absl::nullopt; } +} // namespace internal +} // namespace testing +#else +#if defined(__cpp_lib_optional) || (GTEST_INTERNAL_HAS_INCLUDE() && \ + GTEST_INTERNAL_CPLUSPLUS_LANG >= 201703L) +// Otherwise for C++17 and higher use std::optional for UniversalPrinter<> +// specializations. +#define GTEST_INTERNAL_HAS_OPTIONAL 1 +#include +namespace testing { +namespace internal { +template +using Optional = ::std::optional; +inline ::std::nullopt_t Nullopt() { return ::std::nullopt; } +} // namespace internal +} // namespace testing +// The case where absl is configured NOT to alias std::optional is not +// supported. +#endif // __cpp_lib_optional +#endif // GTEST_HAS_ABSL + +#ifndef GTEST_INTERNAL_HAS_OPTIONAL +#define GTEST_INTERNAL_HAS_OPTIONAL 0 +#endif + +#if defined(__cpp_lib_span) || (GTEST_INTERNAL_HAS_INCLUDE() && \ + GTEST_INTERNAL_CPLUSPLUS_LANG >= 202002L) +#define GTEST_INTERNAL_HAS_STD_SPAN 1 +#endif // __cpp_lib_span + +#ifndef GTEST_INTERNAL_HAS_STD_SPAN +#define GTEST_INTERNAL_HAS_STD_SPAN 0 +#endif + +#ifdef GTEST_HAS_ABSL +// Always use absl::string_view for Matcher<> specializations if googletest +// is built with absl support. +#define GTEST_INTERNAL_HAS_STRING_VIEW 1 +#include "absl/strings/string_view.h" +namespace testing { +namespace internal { +using StringView = ::absl::string_view; +} // namespace internal +} // namespace testing +#else +#if defined(__cpp_lib_string_view) || \ + (GTEST_INTERNAL_HAS_INCLUDE() && \ + GTEST_INTERNAL_CPLUSPLUS_LANG >= 201703L) +// Otherwise for C++17 and higher use std::string_view for Matcher<> +// specializations. +#define GTEST_INTERNAL_HAS_STRING_VIEW 1 +#include +namespace testing { +namespace internal { +using StringView = ::std::string_view; +} // namespace internal +} // namespace testing +// The case where absl is configured NOT to alias std::string_view is not +// supported. +#endif // __cpp_lib_string_view +#endif // GTEST_HAS_ABSL + +#ifndef GTEST_INTERNAL_HAS_STRING_VIEW +#define GTEST_INTERNAL_HAS_STRING_VIEW 0 +#endif + +#ifdef GTEST_HAS_ABSL +// Always use absl::variant for UniversalPrinter<> specializations if googletest +// is built with absl support. +#define GTEST_INTERNAL_HAS_VARIANT 1 +#include "absl/types/variant.h" +namespace testing { +namespace internal { +template +using Variant = ::absl::variant; +} // namespace internal +} // namespace testing +#else +#if defined(__cpp_lib_variant) || (GTEST_INTERNAL_HAS_INCLUDE() && \ + GTEST_INTERNAL_CPLUSPLUS_LANG >= 201703L) +// Otherwise for C++17 and higher use std::variant for UniversalPrinter<> +// specializations. +#define GTEST_INTERNAL_HAS_VARIANT 1 +#include +namespace testing { +namespace internal { +template +using Variant = ::std::variant; +} // namespace internal +} // namespace testing +// The case where absl is configured NOT to alias std::variant is not supported. +#endif // __cpp_lib_variant +#endif // GTEST_HAS_ABSL + +#ifndef GTEST_INTERNAL_HAS_VARIANT +#define GTEST_INTERNAL_HAS_VARIANT 0 +#endif + +#if (defined(__cpp_lib_three_way_comparison) || \ + (GTEST_INTERNAL_HAS_INCLUDE() && \ + GTEST_INTERNAL_CPLUSPLUS_LANG >= 201907L)) +#define GTEST_INTERNAL_HAS_COMPARE_LIB 1 +#else +#define GTEST_INTERNAL_HAS_COMPARE_LIB 0 +#endif + +#endif // GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_H_ diff --git a/googletest/include/gtest/internal/gtest-string.h b/googletest/include/gtest/internal/gtest-string.h new file mode 100644 index 00000000..7c05b583 --- /dev/null +++ b/googletest/include/gtest/internal/gtest-string.h @@ -0,0 +1,178 @@ +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// The Google C++ Testing and Mocking Framework (Google Test) +// +// This header file declares the String class and functions used internally by +// Google Test. They are subject to change without notice. They should not used +// by code external to Google Test. +// +// This header file is #included by gtest-internal.h. +// It should not be #included by other files. + +// IWYU pragma: private, include "gtest/gtest.h" +// IWYU pragma: friend gtest/.* +// IWYU pragma: friend gmock/.* + +#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_STRING_H_ +#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_STRING_H_ + +#ifdef __BORLANDC__ +// string.h is not guaranteed to provide strcpy on C++ Builder. +#include +#endif + +#include + +#include +#include +#include + +#include "gtest/internal/gtest-port.h" + +namespace testing { +namespace internal { + +// String - an abstract class holding static string utilities. +class GTEST_API_ String { + public: + // Static utility methods + + // Clones a 0-terminated C string, allocating memory using new. The + // caller is responsible for deleting the return value using + // delete[]. Returns the cloned string, or NULL if the input is + // NULL. + // + // This is different from strdup() in string.h, which allocates + // memory using malloc(). + static const char* CloneCString(const char* c_str); + +#ifdef GTEST_OS_WINDOWS_MOBILE + // Windows CE does not have the 'ANSI' versions of Win32 APIs. To be + // able to pass strings to Win32 APIs on CE we need to convert them + // to 'Unicode', UTF-16. + + // Creates a UTF-16 wide string from the given ANSI string, allocating + // memory using new. The caller is responsible for deleting the return + // value using delete[]. Returns the wide string, or NULL if the + // input is NULL. + // + // The wide string is created using the ANSI codepage (CP_ACP) to + // match the behaviour of the ANSI versions of Win32 calls and the + // C runtime. + static LPCWSTR AnsiToUtf16(const char* c_str); + + // Creates an ANSI string from the given wide string, allocating + // memory using new. The caller is responsible for deleting the return + // value using delete[]. Returns the ANSI string, or NULL if the + // input is NULL. + // + // The returned string is created using the ANSI codepage (CP_ACP) to + // match the behaviour of the ANSI versions of Win32 calls and the + // C runtime. + static const char* Utf16ToAnsi(LPCWSTR utf16_str); +#endif + + // Compares two C strings. Returns true if and only if they have the same + // content. + // + // Unlike strcmp(), this function can handle NULL argument(s). A + // NULL C string is considered different to any non-NULL C string, + // including the empty string. + static bool CStringEquals(const char* lhs, const char* rhs); + + // Converts a wide C string to a String using the UTF-8 encoding. + // NULL will be converted to "(null)". If an error occurred during + // the conversion, "(failed to convert from wide string)" is + // returned. + static std::string ShowWideCString(const wchar_t* wide_c_str); + + // Compares two wide C strings. Returns true if and only if they have the + // same content. + // + // Unlike wcscmp(), this function can handle NULL argument(s). A + // NULL C string is considered different to any non-NULL C string, + // including the empty string. + static bool WideCStringEquals(const wchar_t* lhs, const wchar_t* rhs); + + // Compares two C strings, ignoring case. Returns true if and only if + // they have the same content. + // + // Unlike strcasecmp(), this function can handle NULL argument(s). + // A NULL C string is considered different to any non-NULL C string, + // including the empty string. + static bool CaseInsensitiveCStringEquals(const char* lhs, const char* rhs); + + // Compares two wide C strings, ignoring case. Returns true if and only if + // they have the same content. + // + // Unlike wcscasecmp(), this function can handle NULL argument(s). + // A NULL C string is considered different to any non-NULL wide C string, + // including the empty string. + // NB: The implementations on different platforms slightly differ. + // On windows, this method uses _wcsicmp which compares according to LC_CTYPE + // environment variable. On GNU platform this method uses wcscasecmp + // which compares according to LC_CTYPE category of the current locale. + // On MacOS X, it uses towlower, which also uses LC_CTYPE category of the + // current locale. + static bool CaseInsensitiveWideCStringEquals(const wchar_t* lhs, + const wchar_t* rhs); + + // Returns true if and only if the given string ends with the given suffix, + // ignoring case. Any string is considered to end with an empty suffix. + static bool EndsWithCaseInsensitive(const std::string& str, + const std::string& suffix); + + // Formats an int value as "%02d". + static std::string FormatIntWidth2(int value); // "%02d" for width == 2 + + // Formats an int value to given width with leading zeros. + static std::string FormatIntWidthN(int value, int width); + + // Formats an int value as "%X". + static std::string FormatHexInt(int value); + + // Formats an int value as "%X". + static std::string FormatHexUInt32(uint32_t value); + + // Formats a byte as "%02X". + static std::string FormatByte(unsigned char value); + + private: + String(); // Not meant to be instantiated. +}; // class String + +// Gets the content of the stringstream's buffer as an std::string. Each '\0' +// character in the buffer is replaced with "\\0". +GTEST_API_ std::string StringStreamToString(::std::stringstream* stream); + +} // namespace internal +} // namespace testing + +#endif // GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_STRING_H_ diff --git a/googletest/include/gtest/internal/gtest-type-util.h b/googletest/include/gtest/internal/gtest-type-util.h new file mode 100644 index 00000000..78da0531 --- /dev/null +++ b/googletest/include/gtest/internal/gtest-type-util.h @@ -0,0 +1,220 @@ +// Copyright 2008 Google Inc. +// All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Type utilities needed for implementing typed and type-parameterized +// tests. + +// IWYU pragma: private, include "gtest/gtest.h" +// IWYU pragma: friend gtest/.* +// IWYU pragma: friend gmock/.* + +#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_ +#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_ + +#include +#include +#include + +#include "gtest/internal/gtest-port.h" + +// #ifdef __GNUC__ is too general here. It is possible to use gcc without using +// libstdc++ (which is where cxxabi.h comes from). +#if GTEST_HAS_CXXABI_H_ +#include +#elif defined(__HP_aCC) +#include +#endif // GTEST_HASH_CXXABI_H_ + +namespace testing { +namespace internal { + +// Canonicalizes a given name with respect to the Standard C++ Library. +// This handles removing the inline namespace within `std` that is +// used by various standard libraries (e.g., `std::__1`). Names outside +// of namespace std are returned unmodified. +inline std::string CanonicalizeForStdLibVersioning(std::string s) { + static const char prefix[] = "std::__"; + if (s.compare(0, strlen(prefix), prefix) == 0) { + std::string::size_type end = s.find("::", strlen(prefix)); + if (end != s.npos) { + // Erase everything between the initial `std` and the second `::`. + s.erase(strlen("std"), end - strlen("std")); + } + } + + // Strip redundant spaces in typename to match MSVC + // For example, std::pair -> std::pair + static const char to_search[] = ", "; + const char replace_char = ','; + size_t pos = 0; + while (true) { + // Get the next occurrence from the current position + pos = s.find(to_search, pos); + if (pos == std::string::npos) { + break; + } + // Replace this occurrence of substring + s.replace(pos, strlen(to_search), 1, replace_char); + ++pos; + } + return s; +} + +#if GTEST_HAS_RTTI +// GetTypeName(const std::type_info&) returns a human-readable name of type T. +inline std::string GetTypeName(const std::type_info& type) { + const char* const name = type.name(); +#if GTEST_HAS_CXXABI_H_ || defined(__HP_aCC) + int status = 0; + // gcc's implementation of typeid(T).name() mangles the type name, + // so we have to demangle it. +#if GTEST_HAS_CXXABI_H_ + using abi::__cxa_demangle; +#endif // GTEST_HAS_CXXABI_H_ + char* const readable_name = __cxa_demangle(name, nullptr, nullptr, &status); + const std::string name_str(status == 0 ? readable_name : name); + free(readable_name); + return CanonicalizeForStdLibVersioning(name_str); +#elif defined(_MSC_VER) + // Strip struct and class due to differences between + // MSVC and other compilers. std::pair is printed as + // "struct std::pair" when using MSVC vs "std::pair" with + // other compilers. + std::string s = name; + // Only strip the leading "struct " and "class ", so uses rfind == 0 to + // ensure that + if (s.rfind("struct ", 0) == 0) { + s = s.substr(strlen("struct ")); + } else if (s.rfind("class ", 0) == 0) { + s = s.substr(strlen("class ")); + } + return s; +#else + return name; +#endif // GTEST_HAS_CXXABI_H_ || __HP_aCC +} +#endif // GTEST_HAS_RTTI + +// GetTypeName() returns a human-readable name of type T if and only if +// RTTI is enabled, otherwise it returns a dummy type name. +// NB: This function is also used in Google Mock, so don't move it inside of +// the typed-test-only section below. +template +std::string GetTypeName() { +#if GTEST_HAS_RTTI + return GetTypeName(typeid(T)); +#else + return ""; +#endif // GTEST_HAS_RTTI +} + +// A unique type indicating an empty node +struct None {}; + +#define GTEST_TEMPLATE_ \ + template \ + class + +// The template "selector" struct TemplateSel is used to +// represent Tmpl, which must be a class template with one type +// parameter, as a type. TemplateSel::Bind::type is defined +// as the type Tmpl. This allows us to actually instantiate the +// template "selected" by TemplateSel. +// +// This trick is necessary for simulating typedef for class templates, +// which C++ doesn't support directly. +template +struct TemplateSel { + template + struct Bind { + typedef Tmpl type; + }; +}; + +#define GTEST_BIND_(TmplSel, T) TmplSel::template Bind::type + +template +struct Templates { + using Head = TemplateSel; + using Tail = Templates; +}; + +template +struct Templates { + using Head = TemplateSel; + using Tail = None; +}; + +// Tuple-like type lists +template +struct Types { + using Head = Head_; + using Tail = Types; +}; + +template +struct Types { + using Head = Head_; + using Tail = None; +}; + +// Helper metafunctions to tell apart a single type from types +// generated by ::testing::Types +template +struct ProxyTypeList { + using type = Types; +}; + +template +struct is_proxy_type_list : std::false_type {}; + +template +struct is_proxy_type_list> : std::true_type {}; + +// Generator which conditionally creates type lists. +// It recognizes if a requested type list should be created +// and prevents creating a new type list nested within another one. +template +struct GenerateTypeList { + private: + using proxy = typename std::conditional::value, T, + ProxyTypeList>::type; + + public: + using type = typename proxy::type; +}; + +} // namespace internal + +template +using Types = internal::ProxyTypeList; + +} // namespace testing + +#endif // GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_ diff --git a/googletest/src/gtest-all.cc b/googletest/src/gtest-all.cc new file mode 100644 index 00000000..2a70ed88 --- /dev/null +++ b/googletest/src/gtest-all.cc @@ -0,0 +1,49 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// +// Google C++ Testing and Mocking Framework (Google Test) +// +// Sometimes it's desirable to build Google Test by compiling a single file. +// This file serves this purpose. + +// This line ensures that gtest.h can be compiled on its own, even +// when it's fused. +#include "gtest/gtest.h" + +// The following lines pull in the real gtest *.cc files. +#include "src/gtest-assertion-result.cc" +#include "src/gtest-death-test.cc" +#include "src/gtest-filepath.cc" +#include "src/gtest-matchers.cc" +#include "src/gtest-port.cc" +#include "src/gtest-printers.cc" +#include "src/gtest-test-part.cc" +#include "src/gtest-typed-test.cc" +#include "src/gtest.cc" diff --git a/googletest/src/gtest-assertion-result.cc b/googletest/src/gtest-assertion-result.cc new file mode 100644 index 00000000..39989216 --- /dev/null +++ b/googletest/src/gtest-assertion-result.cc @@ -0,0 +1,77 @@ +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// The Google C++ Testing and Mocking Framework (Google Test) +// +// This file defines the AssertionResult type. + +#include "gtest/gtest-assertion-result.h" + +#include +#include + +#include "gtest/gtest-message.h" + +namespace testing { + +// AssertionResult constructors. +// Used in EXPECT_TRUE/FALSE(assertion_result). +AssertionResult::AssertionResult(const AssertionResult& other) + : success_(other.success_), + message_(other.message_ != nullptr + ? new ::std::string(*other.message_) + : static_cast< ::std::string*>(nullptr)) {} + +// Swaps two AssertionResults. +void AssertionResult::swap(AssertionResult& other) { + using std::swap; + swap(success_, other.success_); + swap(message_, other.message_); +} + +// Returns the assertion's negation. Used with EXPECT/ASSERT_FALSE. +AssertionResult AssertionResult::operator!() const { + AssertionResult negation(!success_); + if (message_ != nullptr) negation << *message_; + return negation; +} + +// Makes a successful assertion result. +AssertionResult AssertionSuccess() { return AssertionResult(true); } + +// Makes a failed assertion result. +AssertionResult AssertionFailure() { return AssertionResult(false); } + +// Makes a failed assertion result with the given failure message. +// Deprecated; use AssertionFailure() << message. +AssertionResult AssertionFailure(const Message& message) { + return AssertionFailure() << message; +} + +} // namespace testing diff --git a/googletest/src/gtest-death-test.cc b/googletest/src/gtest-death-test.cc new file mode 100644 index 00000000..15472f1a --- /dev/null +++ b/googletest/src/gtest-death-test.cc @@ -0,0 +1,1587 @@ +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// +// This file implements death tests. + +#include "gtest/gtest-death-test.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include "gtest/internal/custom/gtest.h" +#include "gtest/internal/gtest-port.h" + +#ifdef GTEST_HAS_DEATH_TEST + +#ifdef GTEST_OS_MAC +#include +#endif // GTEST_OS_MAC + +#include +#include +#include + +#ifdef GTEST_OS_LINUX +#include +#endif // GTEST_OS_LINUX + +#include + +#ifdef GTEST_OS_WINDOWS +#include +#else +#include +#include +#endif // GTEST_OS_WINDOWS + +#ifdef GTEST_OS_QNX +#include +#endif // GTEST_OS_QNX + +#ifdef GTEST_OS_FUCHSIA +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif // GTEST_OS_FUCHSIA + +#endif // GTEST_HAS_DEATH_TEST + +#include "gtest/gtest-message.h" +#include "gtest/internal/gtest-string.h" +#include "src/gtest-internal-inl.h" + +namespace testing { + +// Constants. + +// The default death test style. +// +// This is defined in internal/gtest-port.h as "fast", but can be overridden by +// a definition in internal/custom/gtest-port.h. The recommended value, which is +// used internally at Google, is "threadsafe". +static const char kDefaultDeathTestStyle[] = GTEST_DEFAULT_DEATH_TEST_STYLE; + +} // namespace testing + +GTEST_DEFINE_string_( + death_test_style, + testing::internal::StringFromGTestEnv("death_test_style", + testing::kDefaultDeathTestStyle), + "Indicates how to run a death test in a forked child process: " + "\"threadsafe\" (child process re-executes the test binary " + "from the beginning, running only the specific death test) or " + "\"fast\" (child process runs the death test immediately " + "after forking)."); + +GTEST_DEFINE_bool_( + death_test_use_fork, + testing::internal::BoolFromGTestEnv("death_test_use_fork", false), + "Instructs to use fork()/_Exit() instead of clone() in death tests. " + "Ignored and always uses fork() on POSIX systems where clone() is not " + "implemented. Useful when running under valgrind or similar tools if " + "those do not support clone(). Valgrind 3.3.1 will just fail if " + "it sees an unsupported combination of clone() flags. " + "It is not recommended to use this flag w/o valgrind though it will " + "work in 99% of the cases. Once valgrind is fixed, this flag will " + "most likely be removed."); + +GTEST_DEFINE_string_( + internal_run_death_test, "", + "Indicates the file, line number, temporal index of " + "the single death test to run, and a file descriptor to " + "which a success code may be sent, all separated by " + "the '|' characters. This flag is specified if and only if the " + "current process is a sub-process launched for running a thread-safe " + "death test. FOR INTERNAL USE ONLY."); + +namespace testing { + +#ifdef GTEST_HAS_DEATH_TEST + +namespace internal { + +// Valid only for fast death tests. Indicates the code is running in the +// child process of a fast style death test. +#if !defined(GTEST_OS_WINDOWS) && !defined(GTEST_OS_FUCHSIA) +static bool g_in_fast_death_test_child = false; +#endif + +// Returns a Boolean value indicating whether the caller is currently +// executing in the context of the death test child process. Tools such as +// Valgrind heap checkers may need this to modify their behavior in death +// tests. IMPORTANT: This is an internal utility. Using it may break the +// implementation of death tests. User code MUST NOT use it. +bool InDeathTestChild() { +#if defined(GTEST_OS_WINDOWS) || defined(GTEST_OS_FUCHSIA) + + // On Windows and Fuchsia, death tests are thread-safe regardless of the value + // of the death_test_style flag. + return !GTEST_FLAG_GET(internal_run_death_test).empty(); + +#else + + if (GTEST_FLAG_GET(death_test_style) == "threadsafe") + return !GTEST_FLAG_GET(internal_run_death_test).empty(); + else + return g_in_fast_death_test_child; +#endif +} + +} // namespace internal + +// ExitedWithCode constructor. +ExitedWithCode::ExitedWithCode(int exit_code) : exit_code_(exit_code) {} + +// ExitedWithCode function-call operator. +bool ExitedWithCode::operator()(int exit_status) const { +#if defined(GTEST_OS_WINDOWS) || defined(GTEST_OS_FUCHSIA) + + return exit_status == exit_code_; + +#else + + return WIFEXITED(exit_status) && WEXITSTATUS(exit_status) == exit_code_; + +#endif // GTEST_OS_WINDOWS || GTEST_OS_FUCHSIA +} + +#if !defined(GTEST_OS_WINDOWS) && !defined(GTEST_OS_FUCHSIA) +// KilledBySignal constructor. +KilledBySignal::KilledBySignal(int signum) : signum_(signum) {} + +// KilledBySignal function-call operator. +bool KilledBySignal::operator()(int exit_status) const { +#if defined(GTEST_KILLED_BY_SIGNAL_OVERRIDE_) + { + bool result; + if (GTEST_KILLED_BY_SIGNAL_OVERRIDE_(signum_, exit_status, &result)) { + return result; + } + } +#endif // defined(GTEST_KILLED_BY_SIGNAL_OVERRIDE_) + return WIFSIGNALED(exit_status) && WTERMSIG(exit_status) == signum_; +} +#endif // !GTEST_OS_WINDOWS && !GTEST_OS_FUCHSIA + +namespace internal { + +// Utilities needed for death tests. + +// Generates a textual description of a given exit code, in the format +// specified by wait(2). +static std::string ExitSummary(int exit_code) { + Message m; + +#if defined(GTEST_OS_WINDOWS) || defined(GTEST_OS_FUCHSIA) + + m << "Exited with exit status " << exit_code; + +#else + + if (WIFEXITED(exit_code)) { + m << "Exited with exit status " << WEXITSTATUS(exit_code); + } else if (WIFSIGNALED(exit_code)) { + m << "Terminated by signal " << WTERMSIG(exit_code); + } +#ifdef WCOREDUMP + if (WCOREDUMP(exit_code)) { + m << " (core dumped)"; + } +#endif +#endif // GTEST_OS_WINDOWS || GTEST_OS_FUCHSIA + + return m.GetString(); +} + +// Returns true if exit_status describes a process that was terminated +// by a signal, or exited normally with a nonzero exit code. +bool ExitedUnsuccessfully(int exit_status) { + return !ExitedWithCode(0)(exit_status); +} + +#if !defined(GTEST_OS_WINDOWS) && !defined(GTEST_OS_FUCHSIA) +// Generates a textual failure message when a death test finds more than +// one thread running, or cannot determine the number of threads, prior +// to executing the given statement. It is the responsibility of the +// caller not to pass a thread_count of 1. +static std::string DeathTestThreadWarning(size_t thread_count) { + Message msg; + msg << "Death tests use fork(), which is unsafe particularly" + << " in a threaded context. For this test, " << GTEST_NAME_ << " "; + if (thread_count == 0) { + msg << "couldn't detect the number of threads."; + } else { + msg << "detected " << thread_count << " threads."; + } + msg << " See " + "https://github.com/google/googletest/blob/main/docs/" + "advanced.md#death-tests-and-threads" + << " for more explanation and suggested solutions, especially if" + << " this is the last message you see before your test times out."; + return msg.GetString(); +} +#endif // !GTEST_OS_WINDOWS && !GTEST_OS_FUCHSIA + +// Flag characters for reporting a death test that did not die. +static const char kDeathTestLived = 'L'; +static const char kDeathTestReturned = 'R'; +static const char kDeathTestThrew = 'T'; +static const char kDeathTestInternalError = 'I'; + +#ifdef GTEST_OS_FUCHSIA + +// File descriptor used for the pipe in the child process. +static const int kFuchsiaReadPipeFd = 3; + +#endif + +// An enumeration describing all of the possible ways that a death test can +// conclude. DIED means that the process died while executing the test +// code; LIVED means that process lived beyond the end of the test code; +// RETURNED means that the test statement attempted to execute a return +// statement, which is not allowed; THREW means that the test statement +// returned control by throwing an exception. IN_PROGRESS means the test +// has not yet concluded. +enum DeathTestOutcome { IN_PROGRESS, DIED, LIVED, RETURNED, THREW }; + +// Routine for aborting the program which is safe to call from an +// exec-style death test child process, in which case the error +// message is propagated back to the parent process. Otherwise, the +// message is simply printed to stderr. In either case, the program +// then exits with status 1. +[[noreturn]] static void DeathTestAbort(const std::string& message) { + // On a POSIX system, this function may be called from a threadsafe-style + // death test child process, which operates on a very small stack. Use + // the heap for any additional non-minuscule memory requirements. + const InternalRunDeathTestFlag* const flag = + GetUnitTestImpl()->internal_run_death_test_flag(); + if (flag != nullptr) { + FILE* parent = posix::FDOpen(flag->write_fd(), "w"); + fputc(kDeathTestInternalError, parent); + fprintf(parent, "%s", message.c_str()); + fflush(parent); + _Exit(1); + } else { + fprintf(stderr, "%s", message.c_str()); + fflush(stderr); + posix::Abort(); + } +} + +// A replacement for CHECK that calls DeathTestAbort if the assertion +// fails. +#define GTEST_DEATH_TEST_CHECK_(expression) \ + do { \ + if (!::testing::internal::IsTrue(expression)) { \ + DeathTestAbort(::std::string("CHECK failed: File ") + __FILE__ + \ + ", line " + \ + ::testing::internal::StreamableToString(__LINE__) + \ + ": " + #expression); \ + } \ + } while (::testing::internal::AlwaysFalse()) + +// This macro is similar to GTEST_DEATH_TEST_CHECK_, but it is meant for +// evaluating any system call that fulfills two conditions: it must return +// -1 on failure, and set errno to EINTR when it is interrupted and +// should be tried again. The macro expands to a loop that repeatedly +// evaluates the expression as long as it evaluates to -1 and sets +// errno to EINTR. If the expression evaluates to -1 but errno is +// something other than EINTR, DeathTestAbort is called. +#define GTEST_DEATH_TEST_CHECK_SYSCALL_(expression) \ + do { \ + int gtest_retval; \ + do { \ + gtest_retval = (expression); \ + } while (gtest_retval == -1 && errno == EINTR); \ + if (gtest_retval == -1) { \ + DeathTestAbort(::std::string("CHECK failed: File ") + __FILE__ + \ + ", line " + \ + ::testing::internal::StreamableToString(__LINE__) + \ + ": " + #expression + " != -1"); \ + } \ + } while (::testing::internal::AlwaysFalse()) + +// Returns the message describing the last system error in errno. +std::string GetLastErrnoDescription() { + return errno == 0 ? "" : posix::StrError(errno); +} + +// This is called from a death test parent process to read a failure +// message from the death test child process and log it with the FATAL +// severity. On Windows, the message is read from a pipe handle. On other +// platforms, it is read from a file descriptor. +static void FailFromInternalError(int fd) { + Message error; + char buffer[256]; + int num_read; + + do { + while ((num_read = posix::Read(fd, buffer, 255)) > 0) { + buffer[num_read] = '\0'; + error << buffer; + } + } while (num_read == -1 && errno == EINTR); + + if (num_read == 0) { + GTEST_LOG_(FATAL) << error.GetString(); + } else { + const int last_error = errno; + GTEST_LOG_(FATAL) << "Error while reading death test internal: " + << GetLastErrnoDescription() << " [" << last_error << "]"; + } +} + +// Death test constructor. Increments the running death test count +// for the current test. +DeathTest::DeathTest() { + TestInfo* const info = GetUnitTestImpl()->current_test_info(); + if (info == nullptr) { + DeathTestAbort( + "Cannot run a death test outside of a TEST or " + "TEST_F construct"); + } +} + +// Creates and returns a death test by dispatching to the current +// death test factory. +bool DeathTest::Create(const char* statement, + Matcher matcher, const char* file, + int line, DeathTest** test) { + return GetUnitTestImpl()->death_test_factory()->Create( + statement, std::move(matcher), file, line, test); +} + +const char* DeathTest::LastMessage() { + return last_death_test_message_.c_str(); +} + +void DeathTest::set_last_death_test_message(const std::string& message) { + last_death_test_message_ = message; +} + +std::string DeathTest::last_death_test_message_; + +// Provides cross platform implementation for some death functionality. +class DeathTestImpl : public DeathTest { + protected: + DeathTestImpl(const char* a_statement, Matcher matcher) + : statement_(a_statement), + matcher_(std::move(matcher)), + spawned_(false), + status_(-1), + outcome_(IN_PROGRESS), + read_fd_(-1), + write_fd_(-1) {} + + // read_fd_ is expected to be closed and cleared by a derived class. + ~DeathTestImpl() override { GTEST_DEATH_TEST_CHECK_(read_fd_ == -1); } + + void Abort(AbortReason reason) override; + bool Passed(bool status_ok) override; + + const char* statement() const { return statement_; } + bool spawned() const { return spawned_; } + void set_spawned(bool is_spawned) { spawned_ = is_spawned; } + int status() const { return status_; } + void set_status(int a_status) { status_ = a_status; } + DeathTestOutcome outcome() const { return outcome_; } + void set_outcome(DeathTestOutcome an_outcome) { outcome_ = an_outcome; } + int read_fd() const { return read_fd_; } + void set_read_fd(int fd) { read_fd_ = fd; } + int write_fd() const { return write_fd_; } + void set_write_fd(int fd) { write_fd_ = fd; } + + // Called in the parent process only. Reads the result code of the death + // test child process via a pipe, interprets it to set the outcome_ + // member, and closes read_fd_. Outputs diagnostics and terminates in + // case of unexpected codes. + void ReadAndInterpretStatusByte(); + + // Returns stderr output from the child process. + virtual std::string GetErrorLogs(); + + private: + // The textual content of the code this object is testing. This class + // doesn't own this string and should not attempt to delete it. + const char* const statement_; + // A matcher that's expected to match the stderr output by the child process. + Matcher matcher_; + // True if the death test child process has been successfully spawned. + bool spawned_; + // The exit status of the child process. + int status_; + // How the death test concluded. + DeathTestOutcome outcome_; + // Descriptor to the read end of the pipe to the child process. It is + // always -1 in the child process. The child keeps its write end of the + // pipe in write_fd_. + int read_fd_; + // Descriptor to the child's write end of the pipe to the parent process. + // It is always -1 in the parent process. The parent keeps its end of the + // pipe in read_fd_. + int write_fd_; +}; + +// Called in the parent process only. Reads the result code of the death +// test child process via a pipe, interprets it to set the outcome_ +// member, and closes read_fd_. Outputs diagnostics and terminates in +// case of unexpected codes. +void DeathTestImpl::ReadAndInterpretStatusByte() { + char flag; + int bytes_read; + + // The read() here blocks until data is available (signifying the + // failure of the death test) or until the pipe is closed (signifying + // its success), so it's okay to call this in the parent before + // the child process has exited. + do { + bytes_read = posix::Read(read_fd(), &flag, 1); + } while (bytes_read == -1 && errno == EINTR); + + if (bytes_read == 0) { + set_outcome(DIED); + } else if (bytes_read == 1) { + switch (flag) { + case kDeathTestReturned: + set_outcome(RETURNED); + break; + case kDeathTestThrew: + set_outcome(THREW); + break; + case kDeathTestLived: + set_outcome(LIVED); + break; + case kDeathTestInternalError: + FailFromInternalError(read_fd()); // Does not return. + break; + default: + GTEST_LOG_(FATAL) << "Death test child process reported " + << "unexpected status byte (" + << static_cast(flag) << ")"; + } + } else { + GTEST_LOG_(FATAL) << "Read from death test child process failed: " + << GetLastErrnoDescription(); + } + GTEST_DEATH_TEST_CHECK_SYSCALL_(posix::Close(read_fd())); + set_read_fd(-1); +} + +std::string DeathTestImpl::GetErrorLogs() { return GetCapturedStderr(); } + +// Signals that the death test code which should have exited, didn't. +// Should be called only in a death test child process. +// Writes a status byte to the child's status file descriptor, then +// calls _Exit(1). +void DeathTestImpl::Abort(AbortReason reason) { + // The parent process considers the death test to be a failure if + // it finds any data in our pipe. So, here we write a single flag byte + // to the pipe, then exit. + const char status_ch = reason == TEST_DID_NOT_DIE ? kDeathTestLived + : reason == TEST_THREW_EXCEPTION ? kDeathTestThrew + : kDeathTestReturned; + + GTEST_DEATH_TEST_CHECK_SYSCALL_(posix::Write(write_fd(), &status_ch, 1)); + // We are leaking the descriptor here because on some platforms (i.e., + // when built as Windows DLL), destructors of global objects will still + // run after calling _Exit(). On such systems, write_fd_ will be + // indirectly closed from the destructor of UnitTestImpl, causing double + // close if it is also closed here. On debug configurations, double close + // may assert. As there are no in-process buffers to flush here, we are + // relying on the OS to close the descriptor after the process terminates + // when the destructors are not run. + _Exit(1); // Exits w/o any normal exit hooks (we were supposed to crash) +} + +// Returns an indented copy of stderr output for a death test. +// This makes distinguishing death test output lines from regular log lines +// much easier. +static ::std::string FormatDeathTestOutput(const ::std::string& output) { + ::std::string ret; + for (size_t at = 0;;) { + const size_t line_end = output.find('\n', at); + ret += "[ DEATH ] "; + if (line_end == ::std::string::npos) { + ret += output.substr(at); + break; + } + ret += output.substr(at, line_end + 1 - at); + at = line_end + 1; + } + return ret; +} + +// Assesses the success or failure of a death test, using both private +// members which have previously been set, and one argument: +// +// Private data members: +// outcome: An enumeration describing how the death test +// concluded: DIED, LIVED, THREW, or RETURNED. The death test +// fails in the latter three cases. +// status: The exit status of the child process. On *nix, it is in the +// in the format specified by wait(2). On Windows, this is the +// value supplied to the ExitProcess() API or a numeric code +// of the exception that terminated the program. +// matcher_: A matcher that's expected to match the stderr output by the child +// process. +// +// Argument: +// status_ok: true if exit_status is acceptable in the context of +// this particular death test, which fails if it is false +// +// Returns true if and only if all of the above conditions are met. Otherwise, +// the first failing condition, in the order given above, is the one that is +// reported. Also sets the last death test message string. +bool DeathTestImpl::Passed(bool status_ok) { + if (!spawned()) return false; + + const std::string error_message = GetErrorLogs(); + + bool success = false; + Message buffer; + + buffer << "Death test: " << statement() << "\n"; + switch (outcome()) { + case LIVED: + buffer << " Result: failed to die.\n" + << " Error msg:\n" + << FormatDeathTestOutput(error_message); + break; + case THREW: + buffer << " Result: threw an exception.\n" + << " Error msg:\n" + << FormatDeathTestOutput(error_message); + break; + case RETURNED: + buffer << " Result: illegal return in test statement.\n" + << " Error msg:\n" + << FormatDeathTestOutput(error_message); + break; + case DIED: + if (status_ok) { + if (matcher_.Matches(error_message)) { + success = true; + } else { + std::ostringstream stream; + matcher_.DescribeTo(&stream); + buffer << " Result: died but not with expected error.\n" + << " Expected: " << stream.str() << "\n" + << "Actual msg:\n" + << FormatDeathTestOutput(error_message); + } + } else { + buffer << " Result: died but not with expected exit code:\n" + << " " << ExitSummary(status()) << "\n" + << "Actual msg:\n" + << FormatDeathTestOutput(error_message); + } + break; + case IN_PROGRESS: + default: + GTEST_LOG_(FATAL) + << "DeathTest::Passed somehow called before conclusion of test"; + } + + DeathTest::set_last_death_test_message(buffer.GetString()); + return success; +} + +#ifndef GTEST_OS_WINDOWS +// Note: The return value points into args, so the return value's lifetime is +// bound to that of args. +static std::vector CreateArgvFromArgs(std::vector& args) { + std::vector result; + result.reserve(args.size() + 1); + for (auto& arg : args) { + result.push_back(&arg[0]); + } + result.push_back(nullptr); // Extra null terminator. + return result; +} +#endif + +#ifdef GTEST_OS_WINDOWS +// WindowsDeathTest implements death tests on Windows. Due to the +// specifics of starting new processes on Windows, death tests there are +// always threadsafe, and Google Test considers the +// --gtest_death_test_style=fast setting to be equivalent to +// --gtest_death_test_style=threadsafe there. +// +// A few implementation notes: Like the Linux version, the Windows +// implementation uses pipes for child-to-parent communication. But due to +// the specifics of pipes on Windows, some extra steps are required: +// +// 1. The parent creates a communication pipe and stores handles to both +// ends of it. +// 2. The parent starts the child and provides it with the information +// necessary to acquire the handle to the write end of the pipe. +// 3. The child acquires the write end of the pipe and signals the parent +// using a Windows event. +// 4. Now the parent can release the write end of the pipe on its side. If +// this is done before step 3, the object's reference count goes down to +// 0 and it is destroyed, preventing the child from acquiring it. The +// parent now has to release it, or read operations on the read end of +// the pipe will not return when the child terminates. +// 5. The parent reads child's output through the pipe (outcome code and +// any possible error messages) from the pipe, and its stderr and then +// determines whether to fail the test. +// +// Note: to distinguish Win32 API calls from the local method and function +// calls, the former are explicitly resolved in the global namespace. +// +class WindowsDeathTest : public DeathTestImpl { + public: + WindowsDeathTest(const char* a_statement, Matcher matcher, + const char* file, int line) + : DeathTestImpl(a_statement, std::move(matcher)), + file_(file), + line_(line) {} + + // All of these virtual functions are inherited from DeathTest. + virtual int Wait(); + virtual TestRole AssumeRole(); + + private: + // The name of the file in which the death test is located. + const char* const file_; + // The line number on which the death test is located. + const int line_; + // Handle to the write end of the pipe to the child process. + AutoHandle write_handle_; + // Child process handle. + AutoHandle child_handle_; + // Event the child process uses to signal the parent that it has + // acquired the handle to the write end of the pipe. After seeing this + // event the parent can release its own handles to make sure its + // ReadFile() calls return when the child terminates. + AutoHandle event_handle_; +}; + +// Waits for the child in a death test to exit, returning its exit +// status, or 0 if no child process exists. As a side effect, sets the +// outcome data member. +int WindowsDeathTest::Wait() { + if (!spawned()) return 0; + + // Wait until the child either signals that it has acquired the write end + // of the pipe or it dies. + const HANDLE wait_handles[2] = {child_handle_.Get(), event_handle_.Get()}; + switch (::WaitForMultipleObjects(2, wait_handles, + FALSE, // Waits for any of the handles. + INFINITE)) { + case WAIT_OBJECT_0: + case WAIT_OBJECT_0 + 1: + break; + default: + GTEST_DEATH_TEST_CHECK_(false); // Should not get here. + } + + // The child has acquired the write end of the pipe or exited. + // We release the handle on our side and continue. + write_handle_.Reset(); + event_handle_.Reset(); + + ReadAndInterpretStatusByte(); + + // Waits for the child process to exit if it haven't already. This + // returns immediately if the child has already exited, regardless of + // whether previous calls to WaitForMultipleObjects synchronized on this + // handle or not. + GTEST_DEATH_TEST_CHECK_(WAIT_OBJECT_0 == + ::WaitForSingleObject(child_handle_.Get(), INFINITE)); + DWORD status_code; + GTEST_DEATH_TEST_CHECK_( + ::GetExitCodeProcess(child_handle_.Get(), &status_code) != FALSE); + child_handle_.Reset(); + set_status(static_cast(status_code)); + return status(); +} + +// The AssumeRole process for a Windows death test. It creates a child +// process with the same executable as the current process to run the +// death test. The child process is given the --gtest_filter and +// --gtest_internal_run_death_test flags such that it knows to run the +// current death test only. +DeathTest::TestRole WindowsDeathTest::AssumeRole() { + const UnitTestImpl* const impl = GetUnitTestImpl(); + const InternalRunDeathTestFlag* const flag = + impl->internal_run_death_test_flag(); + const TestInfo* const info = impl->current_test_info(); + const int death_test_index = info->result()->death_test_count(); + + if (flag != nullptr) { + // ParseInternalRunDeathTestFlag() has performed all the necessary + // processing. + set_write_fd(flag->write_fd()); + return EXECUTE_TEST; + } + + // WindowsDeathTest uses an anonymous pipe to communicate results of + // a death test. + SECURITY_ATTRIBUTES handles_are_inheritable = {sizeof(SECURITY_ATTRIBUTES), + nullptr, TRUE}; + HANDLE read_handle, write_handle; + GTEST_DEATH_TEST_CHECK_(::CreatePipe(&read_handle, &write_handle, + &handles_are_inheritable, + 0) // Default buffer size. + != FALSE); + set_read_fd( + ::_open_osfhandle(reinterpret_cast(read_handle), O_RDONLY)); + write_handle_.Reset(write_handle); + event_handle_.Reset(::CreateEvent( + &handles_are_inheritable, + TRUE, // The event will automatically reset to non-signaled state. + FALSE, // The initial state is non-signalled. + nullptr)); // The even is unnamed. + GTEST_DEATH_TEST_CHECK_(event_handle_.Get() != nullptr); + const std::string filter_flag = std::string("--") + GTEST_FLAG_PREFIX_ + + "filter=" + info->test_suite_name() + "." + + info->name(); + const std::string internal_flag = + std::string("--") + GTEST_FLAG_PREFIX_ + + "internal_run_death_test=" + file_ + "|" + StreamableToString(line_) + + "|" + StreamableToString(death_test_index) + "|" + + StreamableToString(static_cast(::GetCurrentProcessId())) + + // size_t has the same width as pointers on both 32-bit and 64-bit + // Windows platforms. + // See https://msdn.microsoft.com/en-us/library/tcxf1dw6.aspx. + "|" + StreamableToString(reinterpret_cast(write_handle)) + "|" + + StreamableToString(reinterpret_cast(event_handle_.Get())); + + char executable_path[_MAX_PATH + 1]; // NOLINT + GTEST_DEATH_TEST_CHECK_(_MAX_PATH + 1 != ::GetModuleFileNameA(nullptr, + executable_path, + _MAX_PATH)); + + std::string command_line = std::string(::GetCommandLineA()) + " " + + filter_flag + " \"" + internal_flag + "\""; + + DeathTest::set_last_death_test_message(""); + + CaptureStderr(); + // Flush the log buffers since the log streams are shared with the child. + FlushInfoLog(); + + // The child process will share the standard handles with the parent. + STARTUPINFOA startup_info; + memset(&startup_info, 0, sizeof(STARTUPINFO)); + startup_info.dwFlags = STARTF_USESTDHANDLES; + startup_info.hStdInput = ::GetStdHandle(STD_INPUT_HANDLE); + startup_info.hStdOutput = ::GetStdHandle(STD_OUTPUT_HANDLE); + startup_info.hStdError = ::GetStdHandle(STD_ERROR_HANDLE); + + PROCESS_INFORMATION process_info; + GTEST_DEATH_TEST_CHECK_( + ::CreateProcessA( + executable_path, const_cast(command_line.c_str()), + nullptr, // Returned process handle is not inheritable. + nullptr, // Returned thread handle is not inheritable. + TRUE, // Child inherits all inheritable handles (for write_handle_). + 0x0, // Default creation flags. + nullptr, // Inherit the parent's environment. + UnitTest::GetInstance()->original_working_dir(), &startup_info, + &process_info) != FALSE); + child_handle_.Reset(process_info.hProcess); + ::CloseHandle(process_info.hThread); + set_spawned(true); + return OVERSEE_TEST; +} + +#elif defined(GTEST_OS_FUCHSIA) + +class FuchsiaDeathTest : public DeathTestImpl { + public: + FuchsiaDeathTest(const char* a_statement, Matcher matcher, + const char* file, int line) + : DeathTestImpl(a_statement, std::move(matcher)), + file_(file), + line_(line) {} + + // All of these virtual functions are inherited from DeathTest. + int Wait() override; + TestRole AssumeRole() override; + std::string GetErrorLogs() override; + + private: + // The name of the file in which the death test is located. + const char* const file_; + // The line number on which the death test is located. + const int line_; + // The stderr data captured by the child process. + std::string captured_stderr_; + + zx::process child_process_; + zx::channel exception_channel_; + zx::socket stderr_socket_; +}; + +// Waits for the child in a death test to exit, returning its exit +// status, or 0 if no child process exists. As a side effect, sets the +// outcome data member. +int FuchsiaDeathTest::Wait() { + const int kProcessKey = 0; + const int kSocketKey = 1; + const int kExceptionKey = 2; + + if (!spawned()) return 0; + + // Create a port to wait for socket/task/exception events. + zx_status_t status_zx; + zx::port port; + status_zx = zx::port::create(0, &port); + GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK); + + // Register to wait for the child process to terminate. + status_zx = + child_process_.wait_async(port, kProcessKey, ZX_PROCESS_TERMINATED, 0); + GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK); + + // Register to wait for the socket to be readable or closed. + status_zx = stderr_socket_.wait_async( + port, kSocketKey, ZX_SOCKET_READABLE | ZX_SOCKET_PEER_CLOSED, 0); + GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK); + + // Register to wait for an exception. + status_zx = exception_channel_.wait_async(port, kExceptionKey, + ZX_CHANNEL_READABLE, 0); + GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK); + + bool process_terminated = false; + bool socket_closed = false; + do { + zx_port_packet_t packet = {}; + status_zx = port.wait(zx::time::infinite(), &packet); + GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK); + + if (packet.key == kExceptionKey) { + // Process encountered an exception. Kill it directly rather than + // letting other handlers process the event. We will get a kProcessKey + // event when the process actually terminates. + status_zx = child_process_.kill(); + GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK); + } else if (packet.key == kProcessKey) { + // Process terminated. + GTEST_DEATH_TEST_CHECK_(ZX_PKT_IS_SIGNAL_ONE(packet.type)); + GTEST_DEATH_TEST_CHECK_(packet.signal.observed & ZX_PROCESS_TERMINATED); + process_terminated = true; + } else if (packet.key == kSocketKey) { + GTEST_DEATH_TEST_CHECK_(ZX_PKT_IS_SIGNAL_ONE(packet.type)); + if (packet.signal.observed & ZX_SOCKET_READABLE) { + // Read data from the socket. + constexpr size_t kBufferSize = 1024; + do { + size_t old_length = captured_stderr_.length(); + size_t bytes_read = 0; + captured_stderr_.resize(old_length + kBufferSize); + status_zx = + stderr_socket_.read(0, &captured_stderr_.front() + old_length, + kBufferSize, &bytes_read); + captured_stderr_.resize(old_length + bytes_read); + } while (status_zx == ZX_OK); + if (status_zx == ZX_ERR_PEER_CLOSED) { + socket_closed = true; + } else { + GTEST_DEATH_TEST_CHECK_(status_zx == ZX_ERR_SHOULD_WAIT); + status_zx = stderr_socket_.wait_async( + port, kSocketKey, ZX_SOCKET_READABLE | ZX_SOCKET_PEER_CLOSED, 0); + GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK); + } + } else { + GTEST_DEATH_TEST_CHECK_(packet.signal.observed & ZX_SOCKET_PEER_CLOSED); + socket_closed = true; + } + } + } while (!process_terminated && !socket_closed); + + ReadAndInterpretStatusByte(); + + zx_info_process_t buffer; + status_zx = child_process_.get_info(ZX_INFO_PROCESS, &buffer, sizeof(buffer), + nullptr, nullptr); + GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK); + + GTEST_DEATH_TEST_CHECK_(buffer.flags & ZX_INFO_PROCESS_FLAG_EXITED); + set_status(static_cast(buffer.return_code)); + return status(); +} + +// The AssumeRole process for a Fuchsia death test. It creates a child +// process with the same executable as the current process to run the +// death test. The child process is given the --gtest_filter and +// --gtest_internal_run_death_test flags such that it knows to run the +// current death test only. +DeathTest::TestRole FuchsiaDeathTest::AssumeRole() { + const UnitTestImpl* const impl = GetUnitTestImpl(); + const InternalRunDeathTestFlag* const flag = + impl->internal_run_death_test_flag(); + const TestInfo* const info = impl->current_test_info(); + const int death_test_index = info->result()->death_test_count(); + + if (flag != nullptr) { + // ParseInternalRunDeathTestFlag() has performed all the necessary + // processing. + set_write_fd(kFuchsiaReadPipeFd); + return EXECUTE_TEST; + } + + // Flush the log buffers since the log streams are shared with the child. + FlushInfoLog(); + + // Build the child process command line. + const std::string filter_flag = std::string("--") + GTEST_FLAG_PREFIX_ + + "filter=" + info->test_suite_name() + "." + + info->name(); + const std::string internal_flag = std::string("--") + GTEST_FLAG_PREFIX_ + + kInternalRunDeathTestFlag + "=" + file_ + + "|" + StreamableToString(line_) + "|" + + StreamableToString(death_test_index); + + std::vector args = GetInjectableArgvs(); + args.push_back(filter_flag); + args.push_back(internal_flag); + + // Build the pipe for communication with the child. + zx_status_t status; + zx_handle_t child_pipe_handle; + int child_pipe_fd; + status = fdio_pipe_half(&child_pipe_fd, &child_pipe_handle); + GTEST_DEATH_TEST_CHECK_(status == ZX_OK); + set_read_fd(child_pipe_fd); + + // Set the pipe handle for the child. + fdio_spawn_action_t spawn_actions[2] = {}; + fdio_spawn_action_t* add_handle_action = &spawn_actions[0]; + add_handle_action->action = FDIO_SPAWN_ACTION_ADD_HANDLE; + add_handle_action->h.id = PA_HND(PA_FD, kFuchsiaReadPipeFd); + add_handle_action->h.handle = child_pipe_handle; + + // Create a socket pair will be used to receive the child process' stderr. + zx::socket stderr_producer_socket; + status = zx::socket::create(0, &stderr_producer_socket, &stderr_socket_); + GTEST_DEATH_TEST_CHECK_(status >= 0); + int stderr_producer_fd = -1; + status = + fdio_fd_create(stderr_producer_socket.release(), &stderr_producer_fd); + GTEST_DEATH_TEST_CHECK_(status >= 0); + + // Make the stderr socket nonblocking. + GTEST_DEATH_TEST_CHECK_(fcntl(stderr_producer_fd, F_SETFL, 0) == 0); + + fdio_spawn_action_t* add_stderr_action = &spawn_actions[1]; + add_stderr_action->action = FDIO_SPAWN_ACTION_CLONE_FD; + add_stderr_action->fd.local_fd = stderr_producer_fd; + add_stderr_action->fd.target_fd = STDERR_FILENO; + + // Create a child job. + zx_handle_t child_job = ZX_HANDLE_INVALID; + status = zx_job_create(zx_job_default(), 0, &child_job); + GTEST_DEATH_TEST_CHECK_(status == ZX_OK); + zx_policy_basic_t policy; + policy.condition = ZX_POL_NEW_ANY; + policy.policy = ZX_POL_ACTION_ALLOW; + status = zx_job_set_policy(child_job, ZX_JOB_POL_RELATIVE, ZX_JOB_POL_BASIC, + &policy, 1); + GTEST_DEATH_TEST_CHECK_(status == ZX_OK); + + // Create an exception channel attached to the |child_job|, to allow + // us to suppress the system default exception handler from firing. + status = zx_task_create_exception_channel( + child_job, 0, exception_channel_.reset_and_get_address()); + GTEST_DEATH_TEST_CHECK_(status == ZX_OK); + + // Spawn the child process. + // Note: The test component must have `fuchsia.process.Launcher` declared + // in its manifest. (Fuchsia integration tests require creating a + // "Fuchsia Test Component" which contains a "Fuchsia Component Manifest") + // Launching processes is a privileged operation in Fuchsia, and the + // declaration indicates that the ability is required for the component. + std::vector argv = CreateArgvFromArgs(args); + status = fdio_spawn_etc(child_job, FDIO_SPAWN_CLONE_ALL, argv[0], argv.data(), + nullptr, 2, spawn_actions, + child_process_.reset_and_get_address(), nullptr); + GTEST_DEATH_TEST_CHECK_(status == ZX_OK); + + set_spawned(true); + return OVERSEE_TEST; +} + +std::string FuchsiaDeathTest::GetErrorLogs() { return captured_stderr_; } + +#else // We are neither on Windows, nor on Fuchsia. + +// ForkingDeathTest provides implementations for most of the abstract +// methods of the DeathTest interface. Only the AssumeRole method is +// left undefined. +class ForkingDeathTest : public DeathTestImpl { + public: + ForkingDeathTest(const char* statement, Matcher matcher); + + // All of these virtual functions are inherited from DeathTest. + int Wait() override; + + protected: + void set_child_pid(pid_t child_pid) { child_pid_ = child_pid; } + + private: + // PID of child process during death test; 0 in the child process itself. + pid_t child_pid_; +}; + +// Constructs a ForkingDeathTest. +ForkingDeathTest::ForkingDeathTest(const char* a_statement, + Matcher matcher) + : DeathTestImpl(a_statement, std::move(matcher)), child_pid_(-1) {} + +// Waits for the child in a death test to exit, returning its exit +// status, or 0 if no child process exists. As a side effect, sets the +// outcome data member. +int ForkingDeathTest::Wait() { + if (!spawned()) return 0; + + ReadAndInterpretStatusByte(); + + int status_value; + GTEST_DEATH_TEST_CHECK_SYSCALL_(waitpid(child_pid_, &status_value, 0)); + set_status(status_value); + return status_value; +} + +// A concrete death test class that forks, then immediately runs the test +// in the child process. +class NoExecDeathTest : public ForkingDeathTest { + public: + NoExecDeathTest(const char* a_statement, Matcher matcher) + : ForkingDeathTest(a_statement, std::move(matcher)) {} + TestRole AssumeRole() override; +}; + +// The AssumeRole process for a fork-and-run death test. It implements a +// straightforward fork, with a simple pipe to transmit the status byte. +DeathTest::TestRole NoExecDeathTest::AssumeRole() { + const size_t thread_count = GetThreadCount(); + if (thread_count != 1) { + GTEST_LOG_(WARNING) << DeathTestThreadWarning(thread_count); + } + + int pipe_fd[2]; + GTEST_DEATH_TEST_CHECK_(pipe(pipe_fd) != -1); + + DeathTest::set_last_death_test_message(""); + CaptureStderr(); + // When we fork the process below, the log file buffers are copied, but the + // file descriptors are shared. We flush all log files here so that closing + // the file descriptors in the child process doesn't throw off the + // synchronization between descriptors and buffers in the parent process. + // This is as close to the fork as possible to avoid a race condition in case + // there are multiple threads running before the death test, and another + // thread writes to the log file. + FlushInfoLog(); + + const pid_t child_pid = fork(); + GTEST_DEATH_TEST_CHECK_(child_pid != -1); + set_child_pid(child_pid); + if (child_pid == 0) { + GTEST_DEATH_TEST_CHECK_SYSCALL_(close(pipe_fd[0])); + set_write_fd(pipe_fd[1]); + // Redirects all logging to stderr in the child process to prevent + // concurrent writes to the log files. We capture stderr in the parent + // process and append the child process' output to a log. + LogToStderr(); + // Event forwarding to the listeners of event listener API mush be shut + // down in death test subprocesses. + GetUnitTestImpl()->listeners()->SuppressEventForwarding(true); + g_in_fast_death_test_child = true; + return EXECUTE_TEST; + } else { + GTEST_DEATH_TEST_CHECK_SYSCALL_(close(pipe_fd[1])); + set_read_fd(pipe_fd[0]); + set_spawned(true); + return OVERSEE_TEST; + } +} + +// A concrete death test class that forks and re-executes the main +// program from the beginning, with command-line flags set that cause +// only this specific death test to be run. +class ExecDeathTest : public ForkingDeathTest { + public: + ExecDeathTest(const char* a_statement, Matcher matcher, + const char* file, int line) + : ForkingDeathTest(a_statement, std::move(matcher)), + file_(file), + line_(line) {} + TestRole AssumeRole() override; + + private: + static ::std::vector GetArgvsForDeathTestChildProcess() { + ::std::vector args = GetInjectableArgvs(); +#if defined(GTEST_EXTRA_DEATH_TEST_COMMAND_LINE_ARGS_) + ::std::vector extra_args = + GTEST_EXTRA_DEATH_TEST_COMMAND_LINE_ARGS_(); + args.insert(args.end(), extra_args.begin(), extra_args.end()); +#endif // defined(GTEST_EXTRA_DEATH_TEST_COMMAND_LINE_ARGS_) + return args; + } + // The name of the file in which the death test is located. + const char* const file_; + // The line number on which the death test is located. + const int line_; +}; + +// A struct that encompasses the arguments to the child process of a +// threadsafe-style death test process. +struct ExecDeathTestArgs { + char* const* argv; // Command-line arguments for the child's call to exec + int close_fd; // File descriptor to close; the read end of a pipe +}; + +#ifdef GTEST_OS_QNX +extern "C" char** environ; +#else // GTEST_OS_QNX +// The main function for a threadsafe-style death test child process. +// This function is called in a clone()-ed process and thus must avoid +// any potentially unsafe operations like malloc or libc functions. +static int ExecDeathTestChildMain(void* child_arg) { + ExecDeathTestArgs* const args = static_cast(child_arg); + GTEST_DEATH_TEST_CHECK_SYSCALL_(close(args->close_fd)); + + // We need to execute the test program in the same environment where + // it was originally invoked. Therefore we change to the original + // working directory first. + const char* const original_dir = + UnitTest::GetInstance()->original_working_dir(); + // We can safely call chdir() as it's a direct system call. + if (chdir(original_dir) != 0) { + DeathTestAbort(std::string("chdir(\"") + original_dir + + "\") failed: " + GetLastErrnoDescription()); + return EXIT_FAILURE; + } + + // We can safely call execv() as it's almost a direct system call. We + // cannot use execvp() as it's a libc function and thus potentially + // unsafe. Since execv() doesn't search the PATH, the user must + // invoke the test program via a valid path that contains at least + // one path separator. + execv(args->argv[0], args->argv); + DeathTestAbort(std::string("execv(") + args->argv[0] + ", ...) in " + + original_dir + " failed: " + GetLastErrnoDescription()); + return EXIT_FAILURE; +} +#endif // GTEST_OS_QNX + +#if GTEST_HAS_CLONE +// Two utility routines that together determine the direction the stack +// grows. +// This could be accomplished more elegantly by a single recursive +// function, but we want to guard against the unlikely possibility of +// a smart compiler optimizing the recursion away. +// +// GTEST_NO_INLINE_ is required to prevent GCC 4.6 from inlining +// StackLowerThanAddress into StackGrowsDown, which then doesn't give +// correct answer. +static void StackLowerThanAddress(const void* ptr, + bool* result) GTEST_NO_INLINE_; +// Make sure sanitizers do not tamper with the stack here. +// Ideally, we want to use `__builtin_frame_address` instead of a local variable +// address with sanitizer disabled, but it does not work when the +// compiler optimizes the stack frame out, which happens on PowerPC targets. +// HWAddressSanitizer add a random tag to the MSB of the local variable address, +// making comparison result unpredictable. +GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_ +GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_ +static void StackLowerThanAddress(const void* ptr, bool* result) { + int dummy = 0; + *result = std::less()(&dummy, ptr); +} + +// Make sure AddressSanitizer does not tamper with the stack here. +GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_ +GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_ +static bool StackGrowsDown() { + int dummy = 0; + bool result; + StackLowerThanAddress(&dummy, &result); + return result; +} +#endif // GTEST_HAS_CLONE + +// Spawns a child process with the same executable as the current process in +// a thread-safe manner and instructs it to run the death test. The +// implementation uses fork(2) + exec. On systems where clone(2) is +// available, it is used instead, being slightly more thread-safe. On QNX, +// fork supports only single-threaded environments, so this function uses +// spawn(2) there instead. The function dies with an error message if +// anything goes wrong. +static pid_t ExecDeathTestSpawnChild(char* const* argv, int close_fd) { + ExecDeathTestArgs args = {argv, close_fd}; + pid_t child_pid = -1; + +#ifdef GTEST_OS_QNX + // Obtains the current directory and sets it to be closed in the child + // process. + const int cwd_fd = open(".", O_RDONLY); + GTEST_DEATH_TEST_CHECK_(cwd_fd != -1); + GTEST_DEATH_TEST_CHECK_SYSCALL_(fcntl(cwd_fd, F_SETFD, FD_CLOEXEC)); + // We need to execute the test program in the same environment where + // it was originally invoked. Therefore we change to the original + // working directory first. + const char* const original_dir = + UnitTest::GetInstance()->original_working_dir(); + // We can safely call chdir() as it's a direct system call. + if (chdir(original_dir) != 0) { + DeathTestAbort(std::string("chdir(\"") + original_dir + + "\") failed: " + GetLastErrnoDescription()); + return EXIT_FAILURE; + } + + int fd_flags; + // Set close_fd to be closed after spawn. + GTEST_DEATH_TEST_CHECK_SYSCALL_(fd_flags = fcntl(close_fd, F_GETFD)); + GTEST_DEATH_TEST_CHECK_SYSCALL_( + fcntl(close_fd, F_SETFD, fd_flags | FD_CLOEXEC)); + struct inheritance inherit = {0}; + // spawn is a system call. + child_pid = spawn(args.argv[0], 0, nullptr, &inherit, args.argv, environ); + // Restores the current working directory. + GTEST_DEATH_TEST_CHECK_(fchdir(cwd_fd) != -1); + GTEST_DEATH_TEST_CHECK_SYSCALL_(close(cwd_fd)); + +#else // GTEST_OS_QNX +#ifdef GTEST_OS_LINUX + // When a SIGPROF signal is received while fork() or clone() are executing, + // the process may hang. To avoid this, we ignore SIGPROF here and re-enable + // it after the call to fork()/clone() is complete. + struct sigaction saved_sigprof_action; + struct sigaction ignore_sigprof_action; + memset(&ignore_sigprof_action, 0, sizeof(ignore_sigprof_action)); + sigemptyset(&ignore_sigprof_action.sa_mask); + ignore_sigprof_action.sa_handler = SIG_IGN; + GTEST_DEATH_TEST_CHECK_SYSCALL_( + sigaction(SIGPROF, &ignore_sigprof_action, &saved_sigprof_action)); +#endif // GTEST_OS_LINUX + +#if GTEST_HAS_CLONE + const bool use_fork = GTEST_FLAG_GET(death_test_use_fork); + + if (!use_fork) { + static const bool stack_grows_down = StackGrowsDown(); + const auto stack_size = static_cast(getpagesize() * 2); + // MMAP_ANONYMOUS is not defined on Mac, so we use MAP_ANON instead. + void* const stack = mmap(nullptr, stack_size, PROT_READ | PROT_WRITE, + MAP_ANON | MAP_PRIVATE, -1, 0); + GTEST_DEATH_TEST_CHECK_(stack != MAP_FAILED); + + // Maximum stack alignment in bytes: For a downward-growing stack, this + // amount is subtracted from size of the stack space to get an address + // that is within the stack space and is aligned on all systems we care + // about. As far as I know there is no ABI with stack alignment greater + // than 64. We assume stack and stack_size already have alignment of + // kMaxStackAlignment. + const size_t kMaxStackAlignment = 64; + void* const stack_top = + static_cast(stack) + + (stack_grows_down ? stack_size - kMaxStackAlignment : 0); + GTEST_DEATH_TEST_CHECK_( + static_cast(stack_size) > kMaxStackAlignment && + reinterpret_cast(stack_top) % kMaxStackAlignment == 0); + + child_pid = clone(&ExecDeathTestChildMain, stack_top, SIGCHLD, &args); + + GTEST_DEATH_TEST_CHECK_(munmap(stack, stack_size) != -1); + } +#else + const bool use_fork = true; +#endif // GTEST_HAS_CLONE + + if (use_fork && (child_pid = fork()) == 0) { + _Exit(ExecDeathTestChildMain(&args)); + } +#endif // GTEST_OS_QNX +#ifdef GTEST_OS_LINUX + GTEST_DEATH_TEST_CHECK_SYSCALL_( + sigaction(SIGPROF, &saved_sigprof_action, nullptr)); +#endif // GTEST_OS_LINUX + + GTEST_DEATH_TEST_CHECK_(child_pid != -1); + return child_pid; +} + +// The AssumeRole process for a fork-and-exec death test. It re-executes the +// main program from the beginning, setting the --gtest_filter +// and --gtest_internal_run_death_test flags to cause only the current +// death test to be re-run. +DeathTest::TestRole ExecDeathTest::AssumeRole() { + const UnitTestImpl* const impl = GetUnitTestImpl(); + const InternalRunDeathTestFlag* const flag = + impl->internal_run_death_test_flag(); + const TestInfo* const info = impl->current_test_info(); + const int death_test_index = info->result()->death_test_count(); + + if (flag != nullptr) { + set_write_fd(flag->write_fd()); + return EXECUTE_TEST; + } + + int pipe_fd[2]; + GTEST_DEATH_TEST_CHECK_(pipe(pipe_fd) != -1); + // Clear the close-on-exec flag on the write end of the pipe, lest + // it be closed when the child process does an exec: + GTEST_DEATH_TEST_CHECK_(fcntl(pipe_fd[1], F_SETFD, 0) != -1); + + const std::string filter_flag = std::string("--") + GTEST_FLAG_PREFIX_ + + "filter=" + info->test_suite_name() + "." + + info->name(); + const std::string internal_flag = std::string("--") + GTEST_FLAG_PREFIX_ + + "internal_run_death_test=" + file_ + "|" + + StreamableToString(line_) + "|" + + StreamableToString(death_test_index) + "|" + + StreamableToString(pipe_fd[1]); + std::vector args = GetArgvsForDeathTestChildProcess(); + args.push_back(filter_flag); + args.push_back(internal_flag); + + DeathTest::set_last_death_test_message(""); + + CaptureStderr(); + // See the comment in NoExecDeathTest::AssumeRole for why the next line + // is necessary. + FlushInfoLog(); + + std::vector argv = CreateArgvFromArgs(args); + const pid_t child_pid = ExecDeathTestSpawnChild(argv.data(), pipe_fd[0]); + GTEST_DEATH_TEST_CHECK_SYSCALL_(close(pipe_fd[1])); + set_child_pid(child_pid); + set_read_fd(pipe_fd[0]); + set_spawned(true); + return OVERSEE_TEST; +} + +#endif // !GTEST_OS_WINDOWS + +// Creates a concrete DeathTest-derived class that depends on the +// --gtest_death_test_style flag, and sets the pointer pointed to +// by the "test" argument to its address. If the test should be +// skipped, sets that pointer to NULL. Returns true, unless the +// flag is set to an invalid value. +bool DefaultDeathTestFactory::Create(const char* statement, + Matcher matcher, + const char* file, int line, + DeathTest** test) { + UnitTestImpl* const impl = GetUnitTestImpl(); + const InternalRunDeathTestFlag* const flag = + impl->internal_run_death_test_flag(); + const int death_test_index = + impl->current_test_info()->increment_death_test_count(); + + if (flag != nullptr) { + if (death_test_index > flag->index()) { + DeathTest::set_last_death_test_message( + "Death test count (" + StreamableToString(death_test_index) + + ") somehow exceeded expected maximum (" + + StreamableToString(flag->index()) + ")"); + return false; + } + + if (!(flag->file() == file && flag->line() == line && + flag->index() == death_test_index)) { + *test = nullptr; + return true; + } + } + +#ifdef GTEST_OS_WINDOWS + + if (GTEST_FLAG_GET(death_test_style) == "threadsafe" || + GTEST_FLAG_GET(death_test_style) == "fast") { + *test = new WindowsDeathTest(statement, std::move(matcher), file, line); + } + +#elif defined(GTEST_OS_FUCHSIA) + + if (GTEST_FLAG_GET(death_test_style) == "threadsafe" || + GTEST_FLAG_GET(death_test_style) == "fast") { + *test = new FuchsiaDeathTest(statement, std::move(matcher), file, line); + } + +#else + + if (GTEST_FLAG_GET(death_test_style) == "threadsafe") { + *test = new ExecDeathTest(statement, std::move(matcher), file, line); + } else if (GTEST_FLAG_GET(death_test_style) == "fast") { + *test = new NoExecDeathTest(statement, std::move(matcher)); + } + +#endif // GTEST_OS_WINDOWS + + else { // NOLINT - this is more readable than unbalanced brackets inside #if. + DeathTest::set_last_death_test_message("Unknown death test style \"" + + GTEST_FLAG_GET(death_test_style) + + "\" encountered"); + return false; + } + + return true; +} + +#ifdef GTEST_OS_WINDOWS +// Recreates the pipe and event handles from the provided parameters, +// signals the event, and returns a file descriptor wrapped around the pipe +// handle. This function is called in the child process only. +static int GetStatusFileDescriptor(unsigned int parent_process_id, + size_t write_handle_as_size_t, + size_t event_handle_as_size_t) { + AutoHandle parent_process_handle(::OpenProcess(PROCESS_DUP_HANDLE, + FALSE, // Non-inheritable. + parent_process_id)); + if (parent_process_handle.Get() == INVALID_HANDLE_VALUE) { + DeathTestAbort("Unable to open parent process " + + StreamableToString(parent_process_id)); + } + + GTEST_CHECK_(sizeof(HANDLE) <= sizeof(size_t)); + + const HANDLE write_handle = reinterpret_cast(write_handle_as_size_t); + HANDLE dup_write_handle; + + // The newly initialized handle is accessible only in the parent + // process. To obtain one accessible within the child, we need to use + // DuplicateHandle. + if (!::DuplicateHandle(parent_process_handle.Get(), write_handle, + ::GetCurrentProcess(), &dup_write_handle, + 0x0, // Requested privileges ignored since + // DUPLICATE_SAME_ACCESS is used. + FALSE, // Request non-inheritable handler. + DUPLICATE_SAME_ACCESS)) { + DeathTestAbort("Unable to duplicate the pipe handle " + + StreamableToString(write_handle_as_size_t) + + " from the parent process " + + StreamableToString(parent_process_id)); + } + + const HANDLE event_handle = reinterpret_cast(event_handle_as_size_t); + HANDLE dup_event_handle; + + if (!::DuplicateHandle(parent_process_handle.Get(), event_handle, + ::GetCurrentProcess(), &dup_event_handle, 0x0, FALSE, + DUPLICATE_SAME_ACCESS)) { + DeathTestAbort("Unable to duplicate the event handle " + + StreamableToString(event_handle_as_size_t) + + " from the parent process " + + StreamableToString(parent_process_id)); + } + + const int write_fd = + ::_open_osfhandle(reinterpret_cast(dup_write_handle), O_APPEND); + if (write_fd == -1) { + DeathTestAbort("Unable to convert pipe handle " + + StreamableToString(write_handle_as_size_t) + + " to a file descriptor"); + } + + // Signals the parent that the write end of the pipe has been acquired + // so the parent can release its own write end. + ::SetEvent(dup_event_handle); + + return write_fd; +} +#endif // GTEST_OS_WINDOWS + +// Returns a newly created InternalRunDeathTestFlag object with fields +// initialized from the GTEST_FLAG(internal_run_death_test) flag if +// the flag is specified; otherwise returns NULL. +InternalRunDeathTestFlag* ParseInternalRunDeathTestFlag() { + if (GTEST_FLAG_GET(internal_run_death_test).empty()) return nullptr; + + // GTEST_HAS_DEATH_TEST implies that we have ::std::string, so we + // can use it here. + int line = -1; + int index = -1; + ::std::vector< ::std::string> fields; + SplitString(GTEST_FLAG_GET(internal_run_death_test), '|', &fields); + int write_fd = -1; + +#ifdef GTEST_OS_WINDOWS + + unsigned int parent_process_id = 0; + size_t write_handle_as_size_t = 0; + size_t event_handle_as_size_t = 0; + + if (fields.size() != 6 || !ParseNaturalNumber(fields[1], &line) || + !ParseNaturalNumber(fields[2], &index) || + !ParseNaturalNumber(fields[3], &parent_process_id) || + !ParseNaturalNumber(fields[4], &write_handle_as_size_t) || + !ParseNaturalNumber(fields[5], &event_handle_as_size_t)) { + DeathTestAbort("Bad --gtest_internal_run_death_test flag: " + + GTEST_FLAG_GET(internal_run_death_test)); + } + write_fd = GetStatusFileDescriptor(parent_process_id, write_handle_as_size_t, + event_handle_as_size_t); + +#elif defined(GTEST_OS_FUCHSIA) + + if (fields.size() != 3 || !ParseNaturalNumber(fields[1], &line) || + !ParseNaturalNumber(fields[2], &index)) { + DeathTestAbort("Bad --gtest_internal_run_death_test flag: " + + GTEST_FLAG_GET(internal_run_death_test)); + } + +#else + + if (fields.size() != 4 || !ParseNaturalNumber(fields[1], &line) || + !ParseNaturalNumber(fields[2], &index) || + !ParseNaturalNumber(fields[3], &write_fd)) { + DeathTestAbort("Bad --gtest_internal_run_death_test flag: " + + GTEST_FLAG_GET(internal_run_death_test)); + } + +#endif // GTEST_OS_WINDOWS + + return new InternalRunDeathTestFlag(fields[0], line, index, write_fd); +} + +} // namespace internal + +#endif // GTEST_HAS_DEATH_TEST + +} // namespace testing diff --git a/googletest/src/gtest-filepath.cc b/googletest/src/gtest-filepath.cc new file mode 100644 index 00000000..902d8c7f --- /dev/null +++ b/googletest/src/gtest-filepath.cc @@ -0,0 +1,414 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "gtest/internal/gtest-filepath.h" + +#include + +#include +#include + +#include "gtest/gtest-message.h" +#include "gtest/internal/gtest-port.h" + +#ifdef GTEST_OS_WINDOWS_MOBILE +#include +#elif defined(GTEST_OS_WINDOWS) +#include +#include +#else +#include + +#include // Some Linux distributions define PATH_MAX here. +#endif // GTEST_OS_WINDOWS_MOBILE + +#include "gtest/internal/gtest-string.h" + +#ifdef GTEST_OS_WINDOWS +#define GTEST_PATH_MAX_ _MAX_PATH +#elif defined(PATH_MAX) +#define GTEST_PATH_MAX_ PATH_MAX +#elif defined(_XOPEN_PATH_MAX) +#define GTEST_PATH_MAX_ _XOPEN_PATH_MAX +#else +#define GTEST_PATH_MAX_ _POSIX_PATH_MAX +#endif // GTEST_OS_WINDOWS + +#if GTEST_HAS_FILE_SYSTEM + +namespace testing { +namespace internal { + +#ifdef GTEST_OS_WINDOWS +// On Windows, '\\' is the standard path separator, but many tools and the +// Windows API also accept '/' as an alternate path separator. Unless otherwise +// noted, a file path can contain either kind of path separators, or a mixture +// of them. +const char kPathSeparator = '\\'; +const char kAlternatePathSeparator = '/'; +const char kAlternatePathSeparatorString[] = "/"; +#ifdef GTEST_OS_WINDOWS_MOBILE +// Windows CE doesn't have a current directory. You should not use +// the current directory in tests on Windows CE, but this at least +// provides a reasonable fallback. +const char kCurrentDirectoryString[] = "\\"; +// Windows CE doesn't define INVALID_FILE_ATTRIBUTES +const DWORD kInvalidFileAttributes = 0xffffffff; +#else +const char kCurrentDirectoryString[] = ".\\"; +#endif // GTEST_OS_WINDOWS_MOBILE +#else +const char kPathSeparator = '/'; +const char kCurrentDirectoryString[] = "./"; +#endif // GTEST_OS_WINDOWS + +// Returns whether the given character is a valid path separator. +static bool IsPathSeparator(char c) { +#if GTEST_HAS_ALT_PATH_SEP_ + return (c == kPathSeparator) || (c == kAlternatePathSeparator); +#else + return c == kPathSeparator; +#endif +} + +// Returns the current working directory, or "" if unsuccessful. +FilePath FilePath::GetCurrentDir() { +#if defined(GTEST_OS_WINDOWS_MOBILE) || defined(GTEST_OS_WINDOWS_PHONE) || \ + defined(GTEST_OS_WINDOWS_RT) || defined(GTEST_OS_ESP8266) || \ + defined(GTEST_OS_ESP32) || defined(GTEST_OS_XTENSA) || \ + defined(GTEST_OS_QURT) || defined(GTEST_OS_NXP_QN9090) || \ + defined(GTEST_OS_NRF52) + // These platforms do not have a current directory, so we just return + // something reasonable. + return FilePath(kCurrentDirectoryString); +#elif defined(GTEST_OS_WINDOWS) + char cwd[GTEST_PATH_MAX_ + 1] = {'\0'}; + return FilePath(_getcwd(cwd, sizeof(cwd)) == nullptr ? "" : cwd); +#else + char cwd[GTEST_PATH_MAX_ + 1] = {'\0'}; + char* result = getcwd(cwd, sizeof(cwd)); +#ifdef GTEST_OS_NACL + // getcwd will likely fail in NaCl due to the sandbox, so return something + // reasonable. The user may have provided a shim implementation for getcwd, + // however, so fallback only when failure is detected. + return FilePath(result == nullptr ? kCurrentDirectoryString : cwd); +#endif // GTEST_OS_NACL + return FilePath(result == nullptr ? "" : cwd); +#endif // GTEST_OS_WINDOWS_MOBILE +} + +// Returns a copy of the FilePath with the case-insensitive extension removed. +// Example: FilePath("dir/file.exe").RemoveExtension("EXE") returns +// FilePath("dir/file"). If a case-insensitive extension is not +// found, returns a copy of the original FilePath. +FilePath FilePath::RemoveExtension(const char* extension) const { + const std::string dot_extension = std::string(".") + extension; + if (String::EndsWithCaseInsensitive(pathname_, dot_extension)) { + return FilePath( + pathname_.substr(0, pathname_.length() - dot_extension.length())); + } + return *this; +} + +// Returns a pointer to the last occurrence of a valid path separator in +// the FilePath. On Windows, for example, both '/' and '\' are valid path +// separators. Returns NULL if no path separator was found. +const char* FilePath::FindLastPathSeparator() const { + const char* const last_sep = strrchr(c_str(), kPathSeparator); +#if GTEST_HAS_ALT_PATH_SEP_ + const char* const last_alt_sep = strrchr(c_str(), kAlternatePathSeparator); + // Comparing two pointers of which only one is NULL is undefined. + if (last_alt_sep != nullptr && + (last_sep == nullptr || last_alt_sep > last_sep)) { + return last_alt_sep; + } +#endif + return last_sep; +} + +size_t FilePath::CalculateRootLength() const { + const auto& path = pathname_; + auto s = path.begin(); + auto end = path.end(); +#ifdef GTEST_OS_WINDOWS + if (end - s >= 2 && s[1] == ':' && (end - s == 2 || IsPathSeparator(s[2])) && + (('A' <= s[0] && s[0] <= 'Z') || ('a' <= s[0] && s[0] <= 'z'))) { + // A typical absolute path like "C:\Windows" or "D:" + s += 2; + if (s != end) { + ++s; + } + } else if (end - s >= 3 && IsPathSeparator(*s) && IsPathSeparator(*(s + 1)) && + !IsPathSeparator(*(s + 2))) { + // Move past the "\\" prefix in a UNC path like "\\Server\Share\Folder" + s += 2; + // Skip 2 components and their following separators ("Server\" and "Share\") + for (int i = 0; i < 2; ++i) { + while (s != end) { + bool stop = IsPathSeparator(*s); + ++s; + if (stop) { + break; + } + } + } + } else if (s != end && IsPathSeparator(*s)) { + // A drive-rooted path like "\Windows" + ++s; + } +#else + if (s != end && IsPathSeparator(*s)) { + ++s; + } +#endif + return static_cast(s - path.begin()); +} + +// Returns a copy of the FilePath with the directory part removed. +// Example: FilePath("path/to/file").RemoveDirectoryName() returns +// FilePath("file"). If there is no directory part ("just_a_file"), it returns +// the FilePath unmodified. If there is no file part ("just_a_dir/") it +// returns an empty FilePath (""). +// On Windows platform, '\' is the path separator, otherwise it is '/'. +FilePath FilePath::RemoveDirectoryName() const { + const char* const last_sep = FindLastPathSeparator(); + return last_sep ? FilePath(last_sep + 1) : *this; +} + +// RemoveFileName returns the directory path with the filename removed. +// Example: FilePath("path/to/file").RemoveFileName() returns "path/to/". +// If the FilePath is "a_file" or "/a_file", RemoveFileName returns +// FilePath("./") or, on Windows, FilePath(".\\"). If the filepath does +// not have a file, like "just/a/dir/", it returns the FilePath unmodified. +// On Windows platform, '\' is the path separator, otherwise it is '/'. +FilePath FilePath::RemoveFileName() const { + const char* const last_sep = FindLastPathSeparator(); + std::string dir; + if (last_sep) { + dir = std::string(c_str(), static_cast(last_sep + 1 - c_str())); + } else { + dir = kCurrentDirectoryString; + } + return FilePath(dir); +} + +// Helper functions for naming files in a directory for xml output. + +// Given directory = "dir", base_name = "test", number = 0, +// extension = "xml", returns "dir/test.xml". If number is greater +// than zero (e.g., 12), returns "dir/test_12.xml". +// On Windows platform, uses \ as the separator rather than /. +FilePath FilePath::MakeFileName(const FilePath& directory, + const FilePath& base_name, int number, + const char* extension) { + std::string file; + if (number == 0) { + file = base_name.string() + "." + extension; + } else { + file = + base_name.string() + "_" + StreamableToString(number) + "." + extension; + } + return ConcatPaths(directory, FilePath(file)); +} + +// Given directory = "dir", relative_path = "test.xml", returns "dir/test.xml". +// On Windows, uses \ as the separator rather than /. +FilePath FilePath::ConcatPaths(const FilePath& directory, + const FilePath& relative_path) { + if (directory.IsEmpty()) return relative_path; + const FilePath dir(directory.RemoveTrailingPathSeparator()); + return FilePath(dir.string() + kPathSeparator + relative_path.string()); +} + +// Returns true if pathname describes something findable in the file-system, +// either a file, directory, or whatever. +bool FilePath::FileOrDirectoryExists() const { +#ifdef GTEST_OS_WINDOWS_MOBILE + LPCWSTR unicode = String::AnsiToUtf16(pathname_.c_str()); + const DWORD attributes = GetFileAttributes(unicode); + delete[] unicode; + return attributes != kInvalidFileAttributes; +#else + posix::StatStruct file_stat{}; + return posix::Stat(pathname_.c_str(), &file_stat) == 0; +#endif // GTEST_OS_WINDOWS_MOBILE +} + +// Returns true if pathname describes a directory in the file-system +// that exists. +bool FilePath::DirectoryExists() const { + bool result = false; +#ifdef GTEST_OS_WINDOWS + // Don't strip off trailing separator if path is a root directory on + // Windows (like "C:\\"). + const FilePath& path(IsRootDirectory() ? *this + : RemoveTrailingPathSeparator()); +#else + const FilePath& path(*this); +#endif + +#ifdef GTEST_OS_WINDOWS_MOBILE + LPCWSTR unicode = String::AnsiToUtf16(path.c_str()); + const DWORD attributes = GetFileAttributes(unicode); + delete[] unicode; + if ((attributes != kInvalidFileAttributes) && + (attributes & FILE_ATTRIBUTE_DIRECTORY)) { + result = true; + } +#else + posix::StatStruct file_stat{}; + result = + posix::Stat(path.c_str(), &file_stat) == 0 && posix::IsDir(file_stat); +#endif // GTEST_OS_WINDOWS_MOBILE + + return result; +} + +// Returns true if pathname describes a root directory. (Windows has one +// root directory per disk drive. UNC share roots are also included.) +bool FilePath::IsRootDirectory() const { + size_t root_length = CalculateRootLength(); + return root_length > 0 && root_length == pathname_.size() && + IsPathSeparator(pathname_[root_length - 1]); +} + +// Returns true if pathname describes an absolute path. +bool FilePath::IsAbsolutePath() const { return CalculateRootLength() > 0; } + +// Returns a pathname for a file that does not currently exist. The pathname +// will be directory/base_name.extension or +// directory/base_name_.extension if directory/base_name.extension +// already exists. The number will be incremented until a pathname is found +// that does not already exist. +// Examples: 'dir/foo_test.xml' or 'dir/foo_test_1.xml'. +// There could be a race condition if two or more processes are calling this +// function at the same time -- they could both pick the same filename. +FilePath FilePath::GenerateUniqueFileName(const FilePath& directory, + const FilePath& base_name, + const char* extension) { + FilePath full_pathname; + int number = 0; + do { + full_pathname.Set(MakeFileName(directory, base_name, number++, extension)); + } while (full_pathname.FileOrDirectoryExists()); + return full_pathname; +} + +// Returns true if FilePath ends with a path separator, which indicates that +// it is intended to represent a directory. Returns false otherwise. +// This does NOT check that a directory (or file) actually exists. +bool FilePath::IsDirectory() const { + return !pathname_.empty() && + IsPathSeparator(pathname_.c_str()[pathname_.length() - 1]); +} + +// Create directories so that path exists. Returns true if successful or if +// the directories already exist; returns false if unable to create directories +// for any reason. +bool FilePath::CreateDirectoriesRecursively() const { + if (!this->IsDirectory()) { + return false; + } + + if (pathname_.empty() || this->DirectoryExists()) { + return true; + } + + const FilePath parent(this->RemoveTrailingPathSeparator().RemoveFileName()); + return parent.CreateDirectoriesRecursively() && this->CreateFolder(); +} + +// Create the directory so that path exists. Returns true if successful or +// if the directory already exists; returns false if unable to create the +// directory for any reason, including if the parent directory does not +// exist. Not named "CreateDirectory" because that's a macro on Windows. +bool FilePath::CreateFolder() const { +#ifdef GTEST_OS_WINDOWS_MOBILE + FilePath removed_sep(this->RemoveTrailingPathSeparator()); + LPCWSTR unicode = String::AnsiToUtf16(removed_sep.c_str()); + int result = CreateDirectory(unicode, nullptr) ? 0 : -1; + delete[] unicode; +#elif defined(GTEST_OS_WINDOWS) + int result = _mkdir(pathname_.c_str()); +#elif defined(GTEST_OS_ESP8266) || defined(GTEST_OS_XTENSA) || \ + defined(GTEST_OS_QURT) || defined(GTEST_OS_NXP_QN9090) || \ + defined(GTEST_OS_NRF52) + // do nothing + int result = 0; +#else + int result = mkdir(pathname_.c_str(), 0777); +#endif // GTEST_OS_WINDOWS_MOBILE + + if (result == -1) { + return this->DirectoryExists(); // An error is OK if the directory exists. + } + return true; // No error. +} + +// If input name has a trailing separator character, remove it and return the +// name, otherwise return the name string unmodified. +// On Windows platform, uses \ as the separator, other platforms use /. +FilePath FilePath::RemoveTrailingPathSeparator() const { + return IsDirectory() ? FilePath(pathname_.substr(0, pathname_.length() - 1)) + : *this; +} + +// Removes any redundant separators that might be in the pathname. +// For example, "bar///foo" becomes "bar/foo". Does not eliminate other +// redundancies that might be in a pathname involving "." or "..". +// Note that "\\Host\Share" does not contain a redundancy on Windows! +void FilePath::Normalize() { + auto out = pathname_.begin(); + + auto i = pathname_.cbegin(); +#ifdef GTEST_OS_WINDOWS + // UNC paths are treated specially + if (pathname_.end() - i >= 3 && IsPathSeparator(*i) && + IsPathSeparator(*(i + 1)) && !IsPathSeparator(*(i + 2))) { + *(out++) = kPathSeparator; + *(out++) = kPathSeparator; + } +#endif + while (i != pathname_.end()) { + const char character = *i; + if (!IsPathSeparator(character)) { + *(out++) = character; + } else if (out == pathname_.begin() || *std::prev(out) != kPathSeparator) { + *(out++) = kPathSeparator; + } + ++i; + } + + pathname_.erase(out, pathname_.end()); +} + +} // namespace internal +} // namespace testing + +#endif // GTEST_HAS_FILE_SYSTEM diff --git a/googletest/src/gtest-internal-inl.h b/googletest/src/gtest-internal-inl.h new file mode 100644 index 00000000..6a39b93b --- /dev/null +++ b/googletest/src/gtest-internal-inl.h @@ -0,0 +1,1234 @@ +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Utility functions and classes used by the Google C++ testing framework.// +// This file contains purely Google Test's internal implementation. Please +// DO NOT #INCLUDE IT IN A USER PROGRAM. + +#ifndef GOOGLETEST_SRC_GTEST_INTERNAL_INL_H_ +#define GOOGLETEST_SRC_GTEST_INTERNAL_INL_H_ + +#ifndef _WIN32_WCE +#include +#endif // !_WIN32_WCE +#include +#include // For strtoll/_strtoul64/malloc/free. +#include // For memmove. + +#include +#include +#include +#include +#include +#include +#include + +#include "gtest/internal/gtest-port.h" + +#if GTEST_CAN_STREAM_RESULTS_ +#include // NOLINT +#include // NOLINT +#endif + +#ifdef GTEST_OS_WINDOWS +#include // NOLINT +#endif // GTEST_OS_WINDOWS + +#include "gtest/gtest-spi.h" +#include "gtest/gtest.h" + +GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ +/* class A needs to have dll-interface to be used by clients of class B */) + +// Declares the flags. +// +// We don't want the users to modify this flag in the code, but want +// Google Test's own unit tests to be able to access it. Therefore we +// declare it here as opposed to in gtest.h. +GTEST_DECLARE_bool_(death_test_use_fork); + +namespace testing { +namespace internal { + +// The value of GetTestTypeId() as seen from within the Google Test +// library. This is solely for testing GetTestTypeId(). +GTEST_API_ extern const TypeId kTestTypeIdInGoogleTest; + +// A valid random seed must be in [1, kMaxRandomSeed]. +const int kMaxRandomSeed = 99999; + +// g_help_flag is true if and only if the --help flag or an equivalent form +// is specified on the command line. +GTEST_API_ extern bool g_help_flag; + +// Returns the current time in milliseconds. +GTEST_API_ TimeInMillis GetTimeInMillis(); + +// Returns true if and only if Google Test should use colors in the output. +GTEST_API_ bool ShouldUseColor(bool stdout_is_tty); + +// Formats the given time in milliseconds as seconds. If the input is an exact N +// seconds, the output has a trailing decimal point (e.g., "N." instead of "N"). +GTEST_API_ std::string FormatTimeInMillisAsSeconds(TimeInMillis ms); + +// Converts the given time in milliseconds to a date string in the ISO 8601 +// format, without the timezone information. N.B.: due to the use the +// non-reentrant localtime() function, this function is not thread safe. Do +// not use it in any code that can be called from multiple threads. +GTEST_API_ std::string FormatEpochTimeInMillisAsIso8601(TimeInMillis ms); + +// Parses a string for an Int32 flag, in the form of "--flag=value". +// +// On success, stores the value of the flag in *value, and returns +// true. On failure, returns false without changing *value. +GTEST_API_ bool ParseFlag(const char* str, const char* flag, int32_t* value); + +// Returns a random seed in range [1, kMaxRandomSeed] based on the +// given --gtest_random_seed flag value. +inline int GetRandomSeedFromFlag(int32_t random_seed_flag) { + const unsigned int raw_seed = + (random_seed_flag == 0) ? static_cast(GetTimeInMillis()) + : static_cast(random_seed_flag); + + // Normalizes the actual seed to range [1, kMaxRandomSeed] such that + // it's easy to type. + const int normalized_seed = + static_cast((raw_seed - 1U) % + static_cast(kMaxRandomSeed)) + + 1; + return normalized_seed; +} + +// Returns the first valid random seed after 'seed'. The behavior is +// undefined if 'seed' is invalid. The seed after kMaxRandomSeed is +// considered to be 1. +inline int GetNextRandomSeed(int seed) { + GTEST_CHECK_(1 <= seed && seed <= kMaxRandomSeed) + << "Invalid random seed " << seed << " - must be in [1, " + << kMaxRandomSeed << "]."; + const int next_seed = seed + 1; + return (next_seed > kMaxRandomSeed) ? 1 : next_seed; +} + +// This class saves the values of all Google Test flags in its c'tor, and +// restores them in its d'tor. +class GTestFlagSaver { + public: + // The c'tor. + GTestFlagSaver() { + also_run_disabled_tests_ = GTEST_FLAG_GET(also_run_disabled_tests); + break_on_failure_ = GTEST_FLAG_GET(break_on_failure); + catch_exceptions_ = GTEST_FLAG_GET(catch_exceptions); + color_ = GTEST_FLAG_GET(color); + death_test_style_ = GTEST_FLAG_GET(death_test_style); + death_test_use_fork_ = GTEST_FLAG_GET(death_test_use_fork); + fail_fast_ = GTEST_FLAG_GET(fail_fast); + filter_ = GTEST_FLAG_GET(filter); + internal_run_death_test_ = GTEST_FLAG_GET(internal_run_death_test); + list_tests_ = GTEST_FLAG_GET(list_tests); + output_ = GTEST_FLAG_GET(output); + brief_ = GTEST_FLAG_GET(brief); + print_time_ = GTEST_FLAG_GET(print_time); + print_utf8_ = GTEST_FLAG_GET(print_utf8); + random_seed_ = GTEST_FLAG_GET(random_seed); + repeat_ = GTEST_FLAG_GET(repeat); + recreate_environments_when_repeating_ = + GTEST_FLAG_GET(recreate_environments_when_repeating); + shuffle_ = GTEST_FLAG_GET(shuffle); + stack_trace_depth_ = GTEST_FLAG_GET(stack_trace_depth); + stream_result_to_ = GTEST_FLAG_GET(stream_result_to); + throw_on_failure_ = GTEST_FLAG_GET(throw_on_failure); + } + + // The d'tor is not virtual. DO NOT INHERIT FROM THIS CLASS. + ~GTestFlagSaver() { + GTEST_FLAG_SET(also_run_disabled_tests, also_run_disabled_tests_); + GTEST_FLAG_SET(break_on_failure, break_on_failure_); + GTEST_FLAG_SET(catch_exceptions, catch_exceptions_); + GTEST_FLAG_SET(color, color_); + GTEST_FLAG_SET(death_test_style, death_test_style_); + GTEST_FLAG_SET(death_test_use_fork, death_test_use_fork_); + GTEST_FLAG_SET(filter, filter_); + GTEST_FLAG_SET(fail_fast, fail_fast_); + GTEST_FLAG_SET(internal_run_death_test, internal_run_death_test_); + GTEST_FLAG_SET(list_tests, list_tests_); + GTEST_FLAG_SET(output, output_); + GTEST_FLAG_SET(brief, brief_); + GTEST_FLAG_SET(print_time, print_time_); + GTEST_FLAG_SET(print_utf8, print_utf8_); + GTEST_FLAG_SET(random_seed, random_seed_); + GTEST_FLAG_SET(repeat, repeat_); + GTEST_FLAG_SET(recreate_environments_when_repeating, + recreate_environments_when_repeating_); + GTEST_FLAG_SET(shuffle, shuffle_); + GTEST_FLAG_SET(stack_trace_depth, stack_trace_depth_); + GTEST_FLAG_SET(stream_result_to, stream_result_to_); + GTEST_FLAG_SET(throw_on_failure, throw_on_failure_); + } + + private: + // Fields for saving the original values of flags. + bool also_run_disabled_tests_; + bool break_on_failure_; + bool catch_exceptions_; + std::string color_; + std::string death_test_style_; + bool death_test_use_fork_; + bool fail_fast_; + std::string filter_; + std::string internal_run_death_test_; + bool list_tests_; + std::string output_; + bool brief_; + bool print_time_; + bool print_utf8_; + int32_t random_seed_; + int32_t repeat_; + bool recreate_environments_when_repeating_; + bool shuffle_; + int32_t stack_trace_depth_; + std::string stream_result_to_; + bool throw_on_failure_; +}; + +// Converts a Unicode code point to a narrow string in UTF-8 encoding. +// code_point parameter is of type UInt32 because wchar_t may not be +// wide enough to contain a code point. +// If the code_point is not a valid Unicode code point +// (i.e. outside of Unicode range U+0 to U+10FFFF) it will be converted +// to "(Invalid Unicode 0xXXXXXXXX)". +GTEST_API_ std::string CodePointToUtf8(uint32_t code_point); + +// Converts a wide string to a narrow string in UTF-8 encoding. +// The wide string is assumed to have the following encoding: +// UTF-16 if sizeof(wchar_t) == 2 (on Windows, Cygwin) +// UTF-32 if sizeof(wchar_t) == 4 (on Linux) +// Parameter str points to a null-terminated wide string. +// Parameter num_chars may additionally limit the number +// of wchar_t characters processed. -1 is used when the entire string +// should be processed. +// If the string contains code points that are not valid Unicode code points +// (i.e. outside of Unicode range U+0 to U+10FFFF) they will be output +// as '(Invalid Unicode 0xXXXXXXXX)'. If the string is in UTF16 encoding +// and contains invalid UTF-16 surrogate pairs, values in those pairs +// will be encoded as individual Unicode characters from Basic Normal Plane. +GTEST_API_ std::string WideStringToUtf8(const wchar_t* str, int num_chars); + +// Reads the GTEST_SHARD_STATUS_FILE environment variable, and creates the file +// if the variable is present. If a file already exists at this location, this +// function will write over it. If the variable is present, but the file cannot +// be created, prints an error and exits. +void WriteToShardStatusFileIfNeeded(); + +// Checks whether sharding is enabled by examining the relevant +// environment variable values. If the variables are present, +// but inconsistent (e.g., shard_index >= total_shards), prints +// an error and exits. If in_subprocess_for_death_test, sharding is +// disabled because it must only be applied to the original test +// process. Otherwise, we could filter out death tests we intended to execute. +GTEST_API_ bool ShouldShard(const char* total_shards_str, + const char* shard_index_str, + bool in_subprocess_for_death_test); + +// Parses the environment variable var as a 32-bit integer. If it is unset, +// returns default_val. If it is not a 32-bit integer, prints an error and +// and aborts. +GTEST_API_ int32_t Int32FromEnvOrDie(const char* env_var, int32_t default_val); + +// Given the total number of shards, the shard index, and the test id, +// returns true if and only if the test should be run on this shard. The test id +// is some arbitrary but unique non-negative integer assigned to each test +// method. Assumes that 0 <= shard_index < total_shards. +GTEST_API_ bool ShouldRunTestOnShard(int total_shards, int shard_index, + int test_id); + +// STL container utilities. + +// Returns the number of elements in the given container that satisfy +// the given predicate. +template +inline int CountIf(const Container& c, Predicate predicate) { + // Implemented as an explicit loop since std::count_if() in libCstd on + // Solaris has a non-standard signature. + int count = 0; + for (auto it = c.begin(); it != c.end(); ++it) { + if (predicate(*it)) ++count; + } + return count; +} + +// Applies a function/functor to each element in the container. +template +void ForEach(const Container& c, Functor functor) { + std::for_each(c.begin(), c.end(), functor); +} + +// Returns the i-th element of the vector, or default_value if i is not +// in range [0, v.size()). +template +inline E GetElementOr(const std::vector& v, int i, E default_value) { + return (i < 0 || i >= static_cast(v.size())) ? default_value + : v[static_cast(i)]; +} + +// Performs an in-place shuffle of a range of the vector's elements. +// 'begin' and 'end' are element indices as an STL-style range; +// i.e. [begin, end) are shuffled, where 'end' == size() means to +// shuffle to the end of the vector. +template +void ShuffleRange(internal::Random* random, int begin, int end, + std::vector* v) { + const int size = static_cast(v->size()); + GTEST_CHECK_(0 <= begin && begin <= size) + << "Invalid shuffle range start " << begin << ": must be in range [0, " + << size << "]."; + GTEST_CHECK_(begin <= end && end <= size) + << "Invalid shuffle range finish " << end << ": must be in range [" + << begin << ", " << size << "]."; + + // Fisher-Yates shuffle, from + // https://en.wikipedia.org/wiki/Fisher-Yates_shuffle + for (int range_width = end - begin; range_width >= 2; range_width--) { + const int last_in_range = begin + range_width - 1; + const int selected = + begin + + static_cast(random->Generate(static_cast(range_width))); + std::swap((*v)[static_cast(selected)], + (*v)[static_cast(last_in_range)]); + } +} + +// Performs an in-place shuffle of the vector's elements. +template +inline void Shuffle(internal::Random* random, std::vector* v) { + ShuffleRange(random, 0, static_cast(v->size()), v); +} + +// A function for deleting an object. Handy for being used as a +// functor. +template +static void Delete(T* x) { + delete x; +} + +// A predicate that checks the key of a TestProperty against a known key. +// +// TestPropertyKeyIs is copyable. +class TestPropertyKeyIs { + public: + // Constructor. + // + // TestPropertyKeyIs has NO default constructor. + explicit TestPropertyKeyIs(const std::string& key) : key_(key) {} + + // Returns true if and only if the test name of test property matches on key_. + bool operator()(const TestProperty& test_property) const { + return test_property.key() == key_; + } + + private: + std::string key_; +}; + +// Class UnitTestOptions. +// +// This class contains functions for processing options the user +// specifies when running the tests. It has only static members. +// +// In most cases, the user can specify an option using either an +// environment variable or a command line flag. E.g. you can set the +// test filter using either GTEST_FILTER or --gtest_filter. If both +// the variable and the flag are present, the latter overrides the +// former. +class GTEST_API_ UnitTestOptions { + public: + // Functions for processing the gtest_output flag. + + // Returns the output format, or "" for normal printed output. + static std::string GetOutputFormat(); + + // Returns the absolute path of the requested output file, or the + // default (test_detail.xml in the original working directory) if + // none was explicitly specified. + static std::string GetAbsolutePathToOutputFile(); + + // Functions for processing the gtest_filter flag. + + // Returns true if and only if the user-specified filter matches the test + // suite name and the test name. + static bool FilterMatchesTest(const std::string& test_suite_name, + const std::string& test_name); + +#ifdef GTEST_OS_WINDOWS + // Function for supporting the gtest_catch_exception flag. + + // Returns EXCEPTION_EXECUTE_HANDLER if given SEH exception was handled, or + // EXCEPTION_CONTINUE_SEARCH otherwise. + // This function is useful as an __except condition. + static int GTestProcessSEH(DWORD seh_code, const char* location); +#endif // GTEST_OS_WINDOWS + + // Returns true if "name" matches the ':' separated list of glob-style + // filters in "filter". + static bool MatchesFilter(const std::string& name, const char* filter); +}; + +#if GTEST_HAS_FILE_SYSTEM +// Returns the current application's name, removing directory path if that +// is present. Used by UnitTestOptions::GetOutputFile. +GTEST_API_ FilePath GetCurrentExecutableName(); +#endif // GTEST_HAS_FILE_SYSTEM + +// The role interface for getting the OS stack trace as a string. +class OsStackTraceGetterInterface { + public: + OsStackTraceGetterInterface() = default; + virtual ~OsStackTraceGetterInterface() = default; + + // Returns the current OS stack trace as an std::string. Parameters: + // + // max_depth - the maximum number of stack frames to be included + // in the trace. + // skip_count - the number of top frames to be skipped; doesn't count + // against max_depth. + virtual std::string CurrentStackTrace(int max_depth, int skip_count) = 0; + + // UponLeavingGTest() should be called immediately before Google Test calls + // user code. It saves some information about the current stack that + // CurrentStackTrace() will use to find and hide Google Test stack frames. + virtual void UponLeavingGTest() = 0; + + // This string is inserted in place of stack frames that are part of + // Google Test's implementation. + static const char* const kElidedFramesMarker; + + private: + OsStackTraceGetterInterface(const OsStackTraceGetterInterface&) = delete; + OsStackTraceGetterInterface& operator=(const OsStackTraceGetterInterface&) = + delete; +}; + +// A working implementation of the OsStackTraceGetterInterface interface. +class OsStackTraceGetter : public OsStackTraceGetterInterface { + public: + OsStackTraceGetter() = default; + + std::string CurrentStackTrace(int max_depth, int skip_count) override; + void UponLeavingGTest() override; + + private: +#ifdef GTEST_HAS_ABSL + Mutex mutex_; // Protects all internal state. + + // We save the stack frame below the frame that calls user code. + // We do this because the address of the frame immediately below + // the user code changes between the call to UponLeavingGTest() + // and any calls to the stack trace code from within the user code. + void* caller_frame_ = nullptr; +#endif // GTEST_HAS_ABSL + + OsStackTraceGetter(const OsStackTraceGetter&) = delete; + OsStackTraceGetter& operator=(const OsStackTraceGetter&) = delete; +}; + +// Information about a Google Test trace point. +struct TraceInfo { + const char* file; + int line; + std::string message; +}; + +// This is the default global test part result reporter used in UnitTestImpl. +// This class should only be used by UnitTestImpl. +class DefaultGlobalTestPartResultReporter + : public TestPartResultReporterInterface { + public: + explicit DefaultGlobalTestPartResultReporter(UnitTestImpl* unit_test); + // Implements the TestPartResultReporterInterface. Reports the test part + // result in the current test. + void ReportTestPartResult(const TestPartResult& result) override; + + private: + UnitTestImpl* const unit_test_; + + DefaultGlobalTestPartResultReporter( + const DefaultGlobalTestPartResultReporter&) = delete; + DefaultGlobalTestPartResultReporter& operator=( + const DefaultGlobalTestPartResultReporter&) = delete; +}; + +// This is the default per thread test part result reporter used in +// UnitTestImpl. This class should only be used by UnitTestImpl. +class DefaultPerThreadTestPartResultReporter + : public TestPartResultReporterInterface { + public: + explicit DefaultPerThreadTestPartResultReporter(UnitTestImpl* unit_test); + // Implements the TestPartResultReporterInterface. The implementation just + // delegates to the current global test part result reporter of *unit_test_. + void ReportTestPartResult(const TestPartResult& result) override; + + private: + UnitTestImpl* const unit_test_; + + DefaultPerThreadTestPartResultReporter( + const DefaultPerThreadTestPartResultReporter&) = delete; + DefaultPerThreadTestPartResultReporter& operator=( + const DefaultPerThreadTestPartResultReporter&) = delete; +}; + +// The private implementation of the UnitTest class. We don't protect +// the methods under a mutex, as this class is not accessible by a +// user and the UnitTest class that delegates work to this class does +// proper locking. +class GTEST_API_ UnitTestImpl { + public: + explicit UnitTestImpl(UnitTest* parent); + virtual ~UnitTestImpl(); + + // There are two different ways to register your own TestPartResultReporter. + // You can register your own reporter to listen either only for test results + // from the current thread or for results from all threads. + // By default, each per-thread test result reporter just passes a new + // TestPartResult to the global test result reporter, which registers the + // test part result for the currently running test. + + // Returns the global test part result reporter. + TestPartResultReporterInterface* GetGlobalTestPartResultReporter(); + + // Sets the global test part result reporter. + void SetGlobalTestPartResultReporter( + TestPartResultReporterInterface* reporter); + + // Returns the test part result reporter for the current thread. + TestPartResultReporterInterface* GetTestPartResultReporterForCurrentThread(); + + // Sets the test part result reporter for the current thread. + void SetTestPartResultReporterForCurrentThread( + TestPartResultReporterInterface* reporter); + + // Gets the number of successful test suites. + int successful_test_suite_count() const; + + // Gets the number of failed test suites. + int failed_test_suite_count() const; + + // Gets the number of all test suites. + int total_test_suite_count() const; + + // Gets the number of all test suites that contain at least one test + // that should run. + int test_suite_to_run_count() const; + + // Gets the number of successful tests. + int successful_test_count() const; + + // Gets the number of skipped tests. + int skipped_test_count() const; + + // Gets the number of failed tests. + int failed_test_count() const; + + // Gets the number of disabled tests that will be reported in the XML report. + int reportable_disabled_test_count() const; + + // Gets the number of disabled tests. + int disabled_test_count() const; + + // Gets the number of tests to be printed in the XML report. + int reportable_test_count() const; + + // Gets the number of all tests. + int total_test_count() const; + + // Gets the number of tests that should run. + int test_to_run_count() const; + + // Gets the time of the test program start, in ms from the start of the + // UNIX epoch. + TimeInMillis start_timestamp() const { return start_timestamp_; } + + // Gets the elapsed time, in milliseconds. + TimeInMillis elapsed_time() const { return elapsed_time_; } + + // Returns true if and only if the unit test passed (i.e. all test suites + // passed). + bool Passed() const { return !Failed(); } + + // Returns true if and only if the unit test failed (i.e. some test suite + // failed or something outside of all tests failed). + bool Failed() const { + return failed_test_suite_count() > 0 || ad_hoc_test_result()->Failed(); + } + + // Gets the i-th test suite among all the test suites. i can range from 0 to + // total_test_suite_count() - 1. If i is not in that range, returns NULL. + const TestSuite* GetTestSuite(int i) const { + const int index = GetElementOr(test_suite_indices_, i, -1); + return index < 0 ? nullptr : test_suites_[static_cast(i)]; + } + + // Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + const TestCase* GetTestCase(int i) const { return GetTestSuite(i); } +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + + // Gets the i-th test suite among all the test suites. i can range from 0 to + // total_test_suite_count() - 1. If i is not in that range, returns NULL. + TestSuite* GetMutableSuiteCase(int i) { + const int index = GetElementOr(test_suite_indices_, i, -1); + return index < 0 ? nullptr : test_suites_[static_cast(index)]; + } + + // Provides access to the event listener list. + TestEventListeners* listeners() { return &listeners_; } + + // Returns the TestResult for the test that's currently running, or + // the TestResult for the ad hoc test if no test is running. + TestResult* current_test_result(); + + // Returns the TestResult for the ad hoc test. + const TestResult* ad_hoc_test_result() const { return &ad_hoc_test_result_; } + + // Sets the OS stack trace getter. + // + // Does nothing if the input and the current OS stack trace getter + // are the same; otherwise, deletes the old getter and makes the + // input the current getter. + void set_os_stack_trace_getter(OsStackTraceGetterInterface* getter); + + // Returns the current OS stack trace getter if it is not NULL; + // otherwise, creates an OsStackTraceGetter, makes it the current + // getter, and returns it. + OsStackTraceGetterInterface* os_stack_trace_getter(); + + // Returns the current OS stack trace as an std::string. + // + // The maximum number of stack frames to be included is specified by + // the gtest_stack_trace_depth flag. The skip_count parameter + // specifies the number of top frames to be skipped, which doesn't + // count against the number of frames to be included. + // + // For example, if Foo() calls Bar(), which in turn calls + // CurrentOsStackTraceExceptTop(1), Foo() will be included in the + // trace but Bar() and CurrentOsStackTraceExceptTop() won't. + std::string CurrentOsStackTraceExceptTop(int skip_count) + GTEST_NO_INLINE_ GTEST_NO_TAIL_CALL_; + + // Finds and returns a TestSuite with the given name. If one doesn't + // exist, creates one and returns it. + // + // Arguments: + // + // test_suite_name: name of the test suite + // type_param: the name of the test's type parameter, or NULL if + // this is not a typed or a type-parameterized test. + // set_up_tc: pointer to the function that sets up the test suite + // tear_down_tc: pointer to the function that tears down the test suite + TestSuite* GetTestSuite(const std::string& test_suite_name, + const char* type_param, + internal::SetUpTestSuiteFunc set_up_tc, + internal::TearDownTestSuiteFunc tear_down_tc); + +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + TestCase* GetTestCase(const std::string& test_case_name, + const char* type_param, + internal::SetUpTestSuiteFunc set_up_tc, + internal::TearDownTestSuiteFunc tear_down_tc) { + return GetTestSuite(test_case_name, type_param, set_up_tc, tear_down_tc); + } +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + + // Adds a TestInfo to the unit test. + // + // Arguments: + // + // set_up_tc: pointer to the function that sets up the test suite + // tear_down_tc: pointer to the function that tears down the test suite + // test_info: the TestInfo object + void AddTestInfo(internal::SetUpTestSuiteFunc set_up_tc, + internal::TearDownTestSuiteFunc tear_down_tc, + TestInfo* test_info) { +#if GTEST_HAS_FILE_SYSTEM + // In order to support thread-safe death tests, we need to + // remember the original working directory when the test program + // was first invoked. We cannot do this in RUN_ALL_TESTS(), as + // the user may have changed the current directory before calling + // RUN_ALL_TESTS(). Therefore we capture the current directory in + // AddTestInfo(), which is called to register a TEST or TEST_F + // before main() is reached. + if (original_working_dir_.IsEmpty()) { + original_working_dir_ = FilePath::GetCurrentDir(); + GTEST_CHECK_(!original_working_dir_.IsEmpty()) + << "Failed to get the current working directory."; + } +#endif // GTEST_HAS_FILE_SYSTEM + + GetTestSuite(test_info->test_suite_name_, test_info->type_param(), + set_up_tc, tear_down_tc) + ->AddTestInfo(test_info); + } + + // Returns ParameterizedTestSuiteRegistry object used to keep track of + // value-parameterized tests and instantiate and register them. + internal::ParameterizedTestSuiteRegistry& parameterized_test_registry() { + return parameterized_test_registry_; + } + + std::set* ignored_parameterized_test_suites() { + return &ignored_parameterized_test_suites_; + } + + // Returns TypeParameterizedTestSuiteRegistry object used to keep track of + // type-parameterized tests and instantiations of them. + internal::TypeParameterizedTestSuiteRegistry& + type_parameterized_test_registry() { + return type_parameterized_test_registry_; + } + + // Registers all parameterized tests defined using TEST_P and + // INSTANTIATE_TEST_SUITE_P, creating regular tests for each test/parameter + // combination. This method can be called more then once; it has guards + // protecting from registering the tests more then once. If + // value-parameterized tests are disabled, RegisterParameterizedTests is + // present but does nothing. + void RegisterParameterizedTests(); + + // Runs all tests in this UnitTest object, prints the result, and + // returns true if all tests are successful. If any exception is + // thrown during a test, this test is considered to be failed, but + // the rest of the tests will still be run. + bool RunAllTests(); + + // Clears the results of all tests, except the ad hoc tests. + void ClearNonAdHocTestResult() { + ForEach(test_suites_, TestSuite::ClearTestSuiteResult); + } + + // Clears the results of ad-hoc test assertions. + void ClearAdHocTestResult() { ad_hoc_test_result_.Clear(); } + + // Adds a TestProperty to the current TestResult object when invoked in a + // context of a test or a test suite, or to the global property set. If the + // result already contains a property with the same key, the value will be + // updated. + void RecordProperty(const TestProperty& test_property); + + enum ReactionToSharding { HONOR_SHARDING_PROTOCOL, IGNORE_SHARDING_PROTOCOL }; + + // Matches the full name of each test against the user-specified + // filter to decide whether the test should run, then records the + // result in each TestSuite and TestInfo object. + // If shard_tests == HONOR_SHARDING_PROTOCOL, further filters tests + // based on sharding variables in the environment. + // Returns the number of tests that should run. + int FilterTests(ReactionToSharding shard_tests); + + // Prints the names of the tests matching the user-specified filter flag. + void ListTestsMatchingFilter(); + + const TestSuite* current_test_suite() const { return current_test_suite_; } + TestInfo* current_test_info() { return current_test_info_; } + const TestInfo* current_test_info() const { return current_test_info_; } + + // Returns the vector of environments that need to be set-up/torn-down + // before/after the tests are run. + std::vector& environments() { return environments_; } + + // Getters for the per-thread Google Test trace stack. + std::vector& gtest_trace_stack() { + return *(gtest_trace_stack_.pointer()); + } + const std::vector& gtest_trace_stack() const { + return gtest_trace_stack_.get(); + } + +#ifdef GTEST_HAS_DEATH_TEST + void InitDeathTestSubprocessControlInfo() { + internal_run_death_test_flag_.reset(ParseInternalRunDeathTestFlag()); + } + // Returns a pointer to the parsed --gtest_internal_run_death_test + // flag, or NULL if that flag was not specified. + // This information is useful only in a death test child process. + // Must not be called before a call to InitGoogleTest. + const InternalRunDeathTestFlag* internal_run_death_test_flag() const { + return internal_run_death_test_flag_.get(); + } + + // Returns a pointer to the current death test factory. + internal::DeathTestFactory* death_test_factory() { + return death_test_factory_.get(); + } + + void SuppressTestEventsIfInSubprocess(); + + friend class ReplaceDeathTestFactory; +#endif // GTEST_HAS_DEATH_TEST + + // Initializes the event listener performing XML output as specified by + // UnitTestOptions. Must not be called before InitGoogleTest. + void ConfigureXmlOutput(); + +#if GTEST_CAN_STREAM_RESULTS_ + // Initializes the event listener for streaming test results to a socket. + // Must not be called before InitGoogleTest. + void ConfigureStreamingOutput(); +#endif + + // Performs initialization dependent upon flag values obtained in + // ParseGoogleTestFlagsOnly. Is called from InitGoogleTest after the call to + // ParseGoogleTestFlagsOnly. In case a user neglects to call InitGoogleTest + // this function is also called from RunAllTests. Since this function can be + // called more than once, it has to be idempotent. + void PostFlagParsingInit(); + + // Gets the random seed used at the start of the current test iteration. + int random_seed() const { return random_seed_; } + + // Gets the random number generator. + internal::Random* random() { return &random_; } + + // Shuffles all test suites, and the tests within each test suite, + // making sure that death tests are still run first. + void ShuffleTests(); + + // Restores the test suites and tests to their order before the first shuffle. + void UnshuffleTests(); + + // Returns the value of GTEST_FLAG(catch_exceptions) at the moment + // UnitTest::Run() starts. + bool catch_exceptions() const { return catch_exceptions_; } + + private: + // Returns true if a warning should be issued if no tests match the test + // filter flag. + bool ShouldWarnIfNoTestsMatchFilter() const; + + struct CompareTestSuitesByPointer { + bool operator()(const TestSuite* lhs, const TestSuite* rhs) const { + return lhs->name_ < rhs->name_; + } + }; + + friend class ::testing::UnitTest; + + // Used by UnitTest::Run() to capture the state of + // GTEST_FLAG(catch_exceptions) at the moment it starts. + void set_catch_exceptions(bool value) { catch_exceptions_ = value; } + + // Sets the TestSuite object for the test that's currently running. + void set_current_test_suite(TestSuite* a_current_test_suite) { + current_test_suite_ = a_current_test_suite; + } + + // Sets the TestInfo object for the test that's currently running. If + // current_test_info is NULL, the assertion results will be stored in + // ad_hoc_test_result_. + void set_current_test_info(TestInfo* a_current_test_info) { + current_test_info_ = a_current_test_info; + } + + // The UnitTest object that owns this implementation object. + UnitTest* const parent_; + +#if GTEST_HAS_FILE_SYSTEM + // The working directory when the first TEST() or TEST_F() was + // executed. + internal::FilePath original_working_dir_; +#endif // GTEST_HAS_FILE_SYSTEM + + // The default test part result reporters. + DefaultGlobalTestPartResultReporter default_global_test_part_result_reporter_; + DefaultPerThreadTestPartResultReporter + default_per_thread_test_part_result_reporter_; + + // Points to (but doesn't own) the global test part result reporter. + TestPartResultReporterInterface* global_test_part_result_reporter_; + + // Protects read and write access to global_test_part_result_reporter_. + internal::Mutex global_test_part_result_reporter_mutex_; + + // Points to (but doesn't own) the per-thread test part result reporter. + internal::ThreadLocal + per_thread_test_part_result_reporter_; + + // The vector of environments that need to be set-up/torn-down + // before/after the tests are run. + std::vector environments_; + + // The vector of TestSuites in their original order. It owns the + // elements in the vector. + std::vector test_suites_; + + // The set of TestSuites by name. + std::unordered_map test_suites_by_name_; + + // Provides a level of indirection for the test suite list to allow + // easy shuffling and restoring the test suite order. The i-th + // element of this vector is the index of the i-th test suite in the + // shuffled order. + std::vector test_suite_indices_; + + // ParameterizedTestRegistry object used to register value-parameterized + // tests. + internal::ParameterizedTestSuiteRegistry parameterized_test_registry_; + internal::TypeParameterizedTestSuiteRegistry + type_parameterized_test_registry_; + + // The set holding the name of parameterized + // test suites that may go uninstantiated. + std::set ignored_parameterized_test_suites_; + + // Indicates whether RegisterParameterizedTests() has been called already. + bool parameterized_tests_registered_; + + // Index of the last death test suite registered. Initially -1. + int last_death_test_suite_; + + // This points to the TestSuite for the currently running test. It + // changes as Google Test goes through one test suite after another. + // When no test is running, this is set to NULL and Google Test + // stores assertion results in ad_hoc_test_result_. Initially NULL. + TestSuite* current_test_suite_; + + // This points to the TestInfo for the currently running test. It + // changes as Google Test goes through one test after another. When + // no test is running, this is set to NULL and Google Test stores + // assertion results in ad_hoc_test_result_. Initially NULL. + TestInfo* current_test_info_; + + // Normally, a user only writes assertions inside a TEST or TEST_F, + // or inside a function called by a TEST or TEST_F. Since Google + // Test keeps track of which test is current running, it can + // associate such an assertion with the test it belongs to. + // + // If an assertion is encountered when no TEST or TEST_F is running, + // Google Test attributes the assertion result to an imaginary "ad hoc" + // test, and records the result in ad_hoc_test_result_. + TestResult ad_hoc_test_result_; + + // The list of event listeners that can be used to track events inside + // Google Test. + TestEventListeners listeners_; + + // The OS stack trace getter. Will be deleted when the UnitTest + // object is destructed. By default, an OsStackTraceGetter is used, + // but the user can set this field to use a custom getter if that is + // desired. + OsStackTraceGetterInterface* os_stack_trace_getter_; + + // True if and only if PostFlagParsingInit() has been called. + bool post_flag_parse_init_performed_; + + // The random number seed used at the beginning of the test run. + int random_seed_; + + // Our random number generator. + internal::Random random_; + + // The time of the test program start, in ms from the start of the + // UNIX epoch. + TimeInMillis start_timestamp_; + + // How long the test took to run, in milliseconds. + TimeInMillis elapsed_time_; + +#ifdef GTEST_HAS_DEATH_TEST + // The decomposed components of the gtest_internal_run_death_test flag, + // parsed when RUN_ALL_TESTS is called. + std::unique_ptr internal_run_death_test_flag_; + std::unique_ptr death_test_factory_; +#endif // GTEST_HAS_DEATH_TEST + + // A per-thread stack of traces created by the SCOPED_TRACE() macro. + internal::ThreadLocal > gtest_trace_stack_; + + // The value of GTEST_FLAG(catch_exceptions) at the moment RunAllTests() + // starts. + bool catch_exceptions_; + + UnitTestImpl(const UnitTestImpl&) = delete; + UnitTestImpl& operator=(const UnitTestImpl&) = delete; +}; // class UnitTestImpl + +// Convenience function for accessing the global UnitTest +// implementation object. +inline UnitTestImpl* GetUnitTestImpl() { + return UnitTest::GetInstance()->impl(); +} + +#ifdef GTEST_USES_SIMPLE_RE + +// Internal helper functions for implementing the simple regular +// expression matcher. +GTEST_API_ bool IsInSet(char ch, const char* str); +GTEST_API_ bool IsAsciiDigit(char ch); +GTEST_API_ bool IsAsciiPunct(char ch); +GTEST_API_ bool IsRepeat(char ch); +GTEST_API_ bool IsAsciiWhiteSpace(char ch); +GTEST_API_ bool IsAsciiWordChar(char ch); +GTEST_API_ bool IsValidEscape(char ch); +GTEST_API_ bool AtomMatchesChar(bool escaped, char pattern, char ch); +GTEST_API_ bool ValidateRegex(const char* regex); +GTEST_API_ bool MatchRegexAtHead(const char* regex, const char* str); +GTEST_API_ bool MatchRepetitionAndRegexAtHead(bool escaped, char ch, + char repeat, const char* regex, + const char* str); +GTEST_API_ bool MatchRegexAnywhere(const char* regex, const char* str); + +#endif // GTEST_USES_SIMPLE_RE + +// Parses the command line for Google Test flags, without initializing +// other parts of Google Test. +GTEST_API_ void ParseGoogleTestFlagsOnly(int* argc, char** argv); +GTEST_API_ void ParseGoogleTestFlagsOnly(int* argc, wchar_t** argv); + +#ifdef GTEST_HAS_DEATH_TEST + +// Returns the message describing the last system error, regardless of the +// platform. +GTEST_API_ std::string GetLastErrnoDescription(); + +// Attempts to parse a string into a positive integer pointed to by the +// number parameter. Returns true if that is possible. +// GTEST_HAS_DEATH_TEST implies that we have ::std::string, so we can use +// it here. +template +bool ParseNaturalNumber(const ::std::string& str, Integer* number) { + // Fail fast if the given string does not begin with a digit; + // this bypasses strtoXXX's "optional leading whitespace and plus + // or minus sign" semantics, which are undesirable here. + if (str.empty() || !IsDigit(str[0])) { + return false; + } + errno = 0; + + char* end; + // BiggestConvertible is the largest integer type that system-provided + // string-to-number conversion routines can return. + using BiggestConvertible = unsigned long long; // NOLINT + + const BiggestConvertible parsed = strtoull(str.c_str(), &end, 10); // NOLINT + const bool parse_success = *end == '\0' && errno == 0; + + GTEST_CHECK_(sizeof(Integer) <= sizeof(parsed)); + + const Integer result = static_cast(parsed); + if (parse_success && static_cast(result) == parsed) { + *number = result; + return true; + } + return false; +} +#endif // GTEST_HAS_DEATH_TEST + +// TestResult contains some private methods that should be hidden from +// Google Test user but are required for testing. This class allow our tests +// to access them. +// +// This class is supplied only for the purpose of testing Google Test's own +// constructs. Do not use it in user tests, either directly or indirectly. +class TestResultAccessor { + public: + static void RecordProperty(TestResult* test_result, + const std::string& xml_element, + const TestProperty& property) { + test_result->RecordProperty(xml_element, property); + } + + static void ClearTestPartResults(TestResult* test_result) { + test_result->ClearTestPartResults(); + } + + static const std::vector& test_part_results( + const TestResult& test_result) { + return test_result.test_part_results(); + } +}; + +#if GTEST_CAN_STREAM_RESULTS_ + +// Streams test results to the given port on the given host machine. +class StreamingListener : public EmptyTestEventListener { + public: + // Abstract base class for writing strings to a socket. + class AbstractSocketWriter { + public: + virtual ~AbstractSocketWriter() = default; + + // Sends a string to the socket. + virtual void Send(const std::string& message) = 0; + + // Closes the socket. + virtual void CloseConnection() {} + + // Sends a string and a newline to the socket. + void SendLn(const std::string& message) { Send(message + "\n"); } + }; + + // Concrete class for actually writing strings to a socket. + class SocketWriter : public AbstractSocketWriter { + public: + SocketWriter(const std::string& host, const std::string& port) + : sockfd_(-1), host_name_(host), port_num_(port) { + MakeConnection(); + } + + ~SocketWriter() override { + if (sockfd_ != -1) CloseConnection(); + } + + // Sends a string to the socket. + void Send(const std::string& message) override { + GTEST_CHECK_(sockfd_ != -1) + << "Send() can be called only when there is a connection."; + + const auto len = static_cast(message.length()); + if (write(sockfd_, message.c_str(), len) != static_cast(len)) { + GTEST_LOG_(WARNING) << "stream_result_to: failed to stream to " + << host_name_ << ":" << port_num_; + } + } + + private: + // Creates a client socket and connects to the server. + void MakeConnection(); + + // Closes the socket. + void CloseConnection() override { + GTEST_CHECK_(sockfd_ != -1) + << "CloseConnection() can be called only when there is a connection."; + + close(sockfd_); + sockfd_ = -1; + } + + int sockfd_; // socket file descriptor + const std::string host_name_; + const std::string port_num_; + + SocketWriter(const SocketWriter&) = delete; + SocketWriter& operator=(const SocketWriter&) = delete; + }; // class SocketWriter + + // Escapes '=', '&', '%', and '\n' characters in str as "%xx". + static std::string UrlEncode(const char* str); + + StreamingListener(const std::string& host, const std::string& port) + : socket_writer_(new SocketWriter(host, port)) { + Start(); + } + + explicit StreamingListener(AbstractSocketWriter* socket_writer) + : socket_writer_(socket_writer) { + Start(); + } + + void OnTestProgramStart(const UnitTest& /* unit_test */) override { + SendLn("event=TestProgramStart"); + } + + void OnTestProgramEnd(const UnitTest& unit_test) override { + // Note that Google Test current only report elapsed time for each + // test iteration, not for the entire test program. + SendLn("event=TestProgramEnd&passed=" + FormatBool(unit_test.Passed())); + + // Notify the streaming server to stop. + socket_writer_->CloseConnection(); + } + + void OnTestIterationStart(const UnitTest& /* unit_test */, + int iteration) override { + SendLn("event=TestIterationStart&iteration=" + + StreamableToString(iteration)); + } + + void OnTestIterationEnd(const UnitTest& unit_test, + int /* iteration */) override { + SendLn("event=TestIterationEnd&passed=" + FormatBool(unit_test.Passed()) + + "&elapsed_time=" + StreamableToString(unit_test.elapsed_time()) + + "ms"); + } + + // Note that "event=TestCaseStart" is a wire format and has to remain + // "case" for compatibility + void OnTestSuiteStart(const TestSuite& test_suite) override { + SendLn(std::string("event=TestCaseStart&name=") + test_suite.name()); + } + + // Note that "event=TestCaseEnd" is a wire format and has to remain + // "case" for compatibility + void OnTestSuiteEnd(const TestSuite& test_suite) override { + SendLn("event=TestCaseEnd&passed=" + FormatBool(test_suite.Passed()) + + "&elapsed_time=" + StreamableToString(test_suite.elapsed_time()) + + "ms"); + } + + void OnTestStart(const TestInfo& test_info) override { + SendLn(std::string("event=TestStart&name=") + test_info.name()); + } + + void OnTestEnd(const TestInfo& test_info) override { + SendLn("event=TestEnd&passed=" + + FormatBool((test_info.result())->Passed()) + "&elapsed_time=" + + StreamableToString((test_info.result())->elapsed_time()) + "ms"); + } + + void OnTestPartResult(const TestPartResult& test_part_result) override { + const char* file_name = test_part_result.file_name(); + if (file_name == nullptr) file_name = ""; + SendLn("event=TestPartResult&file=" + UrlEncode(file_name) + + "&line=" + StreamableToString(test_part_result.line_number()) + + "&message=" + UrlEncode(test_part_result.message())); + } + + private: + // Sends the given message and a newline to the socket. + void SendLn(const std::string& message) { socket_writer_->SendLn(message); } + + // Called at the start of streaming to notify the receiver what + // protocol we are using. + void Start() { SendLn("gtest_streaming_protocol_version=1.0"); } + + std::string FormatBool(bool value) { return value ? "1" : "0"; } + + const std::unique_ptr socket_writer_; + + StreamingListener(const StreamingListener&) = delete; + StreamingListener& operator=(const StreamingListener&) = delete; +}; // class StreamingListener + +#endif // GTEST_CAN_STREAM_RESULTS_ + +} // namespace internal +} // namespace testing + +GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 + +#endif // GOOGLETEST_SRC_GTEST_INTERNAL_INL_H_ diff --git a/googletest/src/gtest-matchers.cc b/googletest/src/gtest-matchers.cc new file mode 100644 index 00000000..7e3bcc0c --- /dev/null +++ b/googletest/src/gtest-matchers.cc @@ -0,0 +1,98 @@ +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// The Google C++ Testing and Mocking Framework (Google Test) +// +// This file implements just enough of the matcher interface to allow +// EXPECT_DEATH and friends to accept a matcher argument. + +#include "gtest/gtest-matchers.h" + +#include + +#include "gtest/internal/gtest-internal.h" +#include "gtest/internal/gtest-port.h" + +namespace testing { + +// Constructs a matcher that matches a const std::string& whose value is +// equal to s. +Matcher::Matcher(const std::string& s) { *this = Eq(s); } + +// Constructs a matcher that matches a const std::string& whose value is +// equal to s. +Matcher::Matcher(const char* s) { + *this = Eq(std::string(s)); +} + +// Constructs a matcher that matches a std::string whose value is equal to +// s. +Matcher::Matcher(const std::string& s) { *this = Eq(s); } + +// Constructs a matcher that matches a std::string whose value is equal to +// s. +Matcher::Matcher(const char* s) { *this = Eq(std::string(s)); } + +#if GTEST_INTERNAL_HAS_STRING_VIEW +// Constructs a matcher that matches a const StringView& whose value is +// equal to s. +Matcher::Matcher(const std::string& s) { + *this = Eq(s); +} + +// Constructs a matcher that matches a const StringView& whose value is +// equal to s. +Matcher::Matcher(const char* s) { + *this = Eq(std::string(s)); +} + +// Constructs a matcher that matches a const StringView& whose value is +// equal to s. +Matcher::Matcher(internal::StringView s) { + *this = Eq(std::string(s)); +} + +// Constructs a matcher that matches a StringView whose value is equal to +// s. +Matcher::Matcher(const std::string& s) { *this = Eq(s); } + +// Constructs a matcher that matches a StringView whose value is equal to +// s. +Matcher::Matcher(const char* s) { + *this = Eq(std::string(s)); +} + +// Constructs a matcher that matches a StringView whose value is equal to +// s. +Matcher::Matcher(internal::StringView s) { + *this = Eq(std::string(s)); +} +#endif // GTEST_INTERNAL_HAS_STRING_VIEW + +} // namespace testing diff --git a/googletest/src/gtest-port.cc b/googletest/src/gtest-port.cc new file mode 100644 index 00000000..1038ad7b --- /dev/null +++ b/googletest/src/gtest-port.cc @@ -0,0 +1,1434 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "gtest/internal/gtest-port.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifdef GTEST_OS_WINDOWS +#include +#include +#include + +#include // Used in ThreadLocal. +#ifdef _MSC_VER +#include +#endif // _MSC_VER +#else +#include +#endif // GTEST_OS_WINDOWS + +#ifdef GTEST_OS_MAC +#include +#include +#include +#endif // GTEST_OS_MAC + +#if defined(GTEST_OS_DRAGONFLY) || defined(GTEST_OS_FREEBSD) || \ + defined(GTEST_OS_GNU_KFREEBSD) || defined(GTEST_OS_NETBSD) || \ + defined(GTEST_OS_OPENBSD) +#include +#if defined(GTEST_OS_DRAGONFLY) || defined(GTEST_OS_FREEBSD) || \ + defined(GTEST_OS_GNU_KFREEBSD) +#include +#endif +#endif + +#ifdef GTEST_OS_QNX +#include +#include +#include +#endif // GTEST_OS_QNX + +#ifdef GTEST_OS_AIX +#include +#include +#endif // GTEST_OS_AIX + +#ifdef GTEST_OS_FUCHSIA +#include +#include +#endif // GTEST_OS_FUCHSIA + +#include "gtest/gtest-message.h" +#include "gtest/gtest-spi.h" +#include "gtest/internal/gtest-internal.h" +#include "gtest/internal/gtest-string.h" +#include "src/gtest-internal-inl.h" + +namespace testing { +namespace internal { + +#if defined(GTEST_OS_LINUX) || defined(GTEST_OS_GNU_HURD) + +namespace { +template +T ReadProcFileField(const std::string& filename, int field) { + std::string dummy; + std::ifstream file(filename.c_str()); + while (field-- > 0) { + file >> dummy; + } + T output = 0; + file >> output; + return output; +} +} // namespace + +// Returns the number of active threads, or 0 when there is an error. +size_t GetThreadCount() { + const std::string filename = + (Message() << "/proc/" << getpid() << "/stat").GetString(); + return ReadProcFileField(filename, 19); +} + +#elif defined(GTEST_OS_MAC) + +size_t GetThreadCount() { + const task_t task = mach_task_self(); + mach_msg_type_number_t thread_count; + thread_act_array_t thread_list; + const kern_return_t status = task_threads(task, &thread_list, &thread_count); + if (status == KERN_SUCCESS) { + // task_threads allocates resources in thread_list and we need to free them + // to avoid leaks. + vm_deallocate(task, reinterpret_cast(thread_list), + sizeof(thread_t) * thread_count); + return static_cast(thread_count); + } else { + return 0; + } +} + +#elif defined(GTEST_OS_DRAGONFLY) || defined(GTEST_OS_FREEBSD) || \ + defined(GTEST_OS_GNU_KFREEBSD) || defined(GTEST_OS_NETBSD) + +#ifdef GTEST_OS_NETBSD +#undef KERN_PROC +#define KERN_PROC KERN_PROC2 +#define kinfo_proc kinfo_proc2 +#endif + +#ifdef GTEST_OS_DRAGONFLY +#define KP_NLWP(kp) (kp.kp_nthreads) +#elif defined(GTEST_OS_FREEBSD) || defined(GTEST_OS_GNU_KFREEBSD) +#define KP_NLWP(kp) (kp.ki_numthreads) +#elif defined(GTEST_OS_NETBSD) +#define KP_NLWP(kp) (kp.p_nlwps) +#endif + +// Returns the number of threads running in the process, or 0 to indicate that +// we cannot detect it. +size_t GetThreadCount() { + int mib[] = { + CTL_KERN, + KERN_PROC, + KERN_PROC_PID, + getpid(), +#ifdef GTEST_OS_NETBSD + sizeof(struct kinfo_proc), + 1, +#endif + }; + u_int miblen = sizeof(mib) / sizeof(mib[0]); + struct kinfo_proc info; + size_t size = sizeof(info); + if (sysctl(mib, miblen, &info, &size, NULL, 0)) { + return 0; + } + return static_cast(KP_NLWP(info)); +} +#elif defined(GTEST_OS_OPENBSD) + +// Returns the number of threads running in the process, or 0 to indicate that +// we cannot detect it. +size_t GetThreadCount() { + int mib[] = { + CTL_KERN, + KERN_PROC, + KERN_PROC_PID | KERN_PROC_SHOW_THREADS, + getpid(), + sizeof(struct kinfo_proc), + 0, + }; + u_int miblen = sizeof(mib) / sizeof(mib[0]); + + // get number of structs + size_t size; + if (sysctl(mib, miblen, NULL, &size, NULL, 0)) { + return 0; + } + + mib[5] = static_cast(size / static_cast(mib[4])); + + // populate array of structs + std::vector info(mib[5]); + if (sysctl(mib, miblen, info.data(), &size, NULL, 0)) { + return 0; + } + + // exclude empty members + size_t nthreads = 0; + for (size_t i = 0; i < size / static_cast(mib[4]); i++) { + if (info[i].p_tid != -1) nthreads++; + } + return nthreads; +} + +#elif defined(GTEST_OS_QNX) + +// Returns the number of threads running in the process, or 0 to indicate that +// we cannot detect it. +size_t GetThreadCount() { + const int fd = open("/proc/self/as", O_RDONLY); + if (fd < 0) { + return 0; + } + procfs_info process_info; + const int status = + devctl(fd, DCMD_PROC_INFO, &process_info, sizeof(process_info), nullptr); + close(fd); + if (status == EOK) { + return static_cast(process_info.num_threads); + } else { + return 0; + } +} + +#elif defined(GTEST_OS_AIX) + +size_t GetThreadCount() { + struct procentry64 entry; + pid_t pid = getpid(); + int status = getprocs64(&entry, sizeof(entry), nullptr, 0, &pid, 1); + if (status == 1) { + return entry.pi_thcount; + } else { + return 0; + } +} + +#elif defined(GTEST_OS_FUCHSIA) + +size_t GetThreadCount() { + int dummy_buffer; + size_t avail; + zx_status_t status = + zx_object_get_info(zx_process_self(), ZX_INFO_PROCESS_THREADS, + &dummy_buffer, 0, nullptr, &avail); + if (status == ZX_OK) { + return avail; + } else { + return 0; + } +} + +#else + +size_t GetThreadCount() { + // There's no portable way to detect the number of threads, so we just + // return 0 to indicate that we cannot detect it. + return 0; +} + +#endif // GTEST_OS_LINUX + +#if defined(GTEST_IS_THREADSAFE) && defined(GTEST_OS_WINDOWS) + +AutoHandle::AutoHandle() : handle_(INVALID_HANDLE_VALUE) {} + +AutoHandle::AutoHandle(Handle handle) : handle_(handle) {} + +AutoHandle::~AutoHandle() { Reset(); } + +AutoHandle::Handle AutoHandle::Get() const { return handle_; } + +void AutoHandle::Reset() { Reset(INVALID_HANDLE_VALUE); } + +void AutoHandle::Reset(HANDLE handle) { + // Resetting with the same handle we already own is invalid. + if (handle_ != handle) { + if (IsCloseable()) { + ::CloseHandle(handle_); + } + handle_ = handle; + } else { + GTEST_CHECK_(!IsCloseable()) + << "Resetting a valid handle to itself is likely a programmer error " + "and thus not allowed."; + } +} + +bool AutoHandle::IsCloseable() const { + // Different Windows APIs may use either of these values to represent an + // invalid handle. + return handle_ != nullptr && handle_ != INVALID_HANDLE_VALUE; +} + +Mutex::Mutex() + : owner_thread_id_(0), + type_(kDynamic), + critical_section_init_phase_(0), + critical_section_(new CRITICAL_SECTION) { + ::InitializeCriticalSection(critical_section_); +} + +Mutex::~Mutex() { + // Static mutexes are leaked intentionally. It is not thread-safe to try + // to clean them up. + if (type_ == kDynamic) { + ::DeleteCriticalSection(critical_section_); + delete critical_section_; + critical_section_ = nullptr; + } +} + +void Mutex::Lock() { + ThreadSafeLazyInit(); + ::EnterCriticalSection(critical_section_); + owner_thread_id_ = ::GetCurrentThreadId(); +} + +void Mutex::Unlock() { + ThreadSafeLazyInit(); + // We don't protect writing to owner_thread_id_ here, as it's the + // caller's responsibility to ensure that the current thread holds the + // mutex when this is called. + owner_thread_id_ = 0; + ::LeaveCriticalSection(critical_section_); +} + +// Does nothing if the current thread holds the mutex. Otherwise, crashes +// with high probability. +void Mutex::AssertHeld() { + ThreadSafeLazyInit(); + GTEST_CHECK_(owner_thread_id_ == ::GetCurrentThreadId()) + << "The current thread is not holding the mutex @" << this; +} + +namespace { + +#ifdef _MSC_VER +// Use the RAII idiom to flag mem allocs that are intentionally never +// deallocated. The motivation is to silence the false positive mem leaks +// that are reported by the debug version of MS's CRT which can only detect +// if an alloc is missing a matching deallocation. +// Example: +// MemoryIsNotDeallocated memory_is_not_deallocated; +// critical_section_ = new CRITICAL_SECTION; +// +class MemoryIsNotDeallocated { + public: + MemoryIsNotDeallocated() : old_crtdbg_flag_(0) { + old_crtdbg_flag_ = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); + // Set heap allocation block type to _IGNORE_BLOCK so that MS debug CRT + // doesn't report mem leak if there's no matching deallocation. + (void)_CrtSetDbgFlag(old_crtdbg_flag_ & ~_CRTDBG_ALLOC_MEM_DF); + } + + ~MemoryIsNotDeallocated() { + // Restore the original _CRTDBG_ALLOC_MEM_DF flag + (void)_CrtSetDbgFlag(old_crtdbg_flag_); + } + + private: + int old_crtdbg_flag_; + + MemoryIsNotDeallocated(const MemoryIsNotDeallocated&) = delete; + MemoryIsNotDeallocated& operator=(const MemoryIsNotDeallocated&) = delete; +}; +#endif // _MSC_VER + +} // namespace + +// Initializes owner_thread_id_ and critical_section_ in static mutexes. +void Mutex::ThreadSafeLazyInit() { + // Dynamic mutexes are initialized in the constructor. + if (type_ == kStatic) { + switch ( + ::InterlockedCompareExchange(&critical_section_init_phase_, 1L, 0L)) { + case 0: + // If critical_section_init_phase_ was 0 before the exchange, we + // are the first to test it and need to perform the initialization. + owner_thread_id_ = 0; + { + // Use RAII to flag that following mem alloc is never deallocated. +#ifdef _MSC_VER + MemoryIsNotDeallocated memory_is_not_deallocated; +#endif // _MSC_VER + critical_section_ = new CRITICAL_SECTION; + } + ::InitializeCriticalSection(critical_section_); + // Updates the critical_section_init_phase_ to 2 to signal + // initialization complete. + GTEST_CHECK_(::InterlockedCompareExchange(&critical_section_init_phase_, + 2L, 1L) == 1L); + break; + case 1: + // Somebody else is already initializing the mutex; spin until they + // are done. + while (::InterlockedCompareExchange(&critical_section_init_phase_, 2L, + 2L) != 2L) { + // Possibly yields the rest of the thread's time slice to other + // threads. + ::Sleep(0); + } + break; + + case 2: + break; // The mutex is already initialized and ready for use. + + default: + GTEST_CHECK_(false) + << "Unexpected value of critical_section_init_phase_ " + << "while initializing a static mutex."; + } + } +} + +namespace { + +class ThreadWithParamSupport : public ThreadWithParamBase { + public: + static HANDLE CreateThread(Runnable* runnable, + Notification* thread_can_start) { + ThreadMainParam* param = new ThreadMainParam(runnable, thread_can_start); + DWORD thread_id; + HANDLE thread_handle = ::CreateThread( + nullptr, // Default security. + 0, // Default stack size. + &ThreadWithParamSupport::ThreadMain, + param, // Parameter to ThreadMainStatic + 0x0, // Default creation flags. + &thread_id); // Need a valid pointer for the call to work under Win98. + GTEST_CHECK_(thread_handle != nullptr) + << "CreateThread failed with error " << ::GetLastError() << "."; + if (thread_handle == nullptr) { + delete param; + } + return thread_handle; + } + + private: + struct ThreadMainParam { + ThreadMainParam(Runnable* runnable, Notification* thread_can_start) + : runnable_(runnable), thread_can_start_(thread_can_start) {} + std::unique_ptr runnable_; + // Does not own. + Notification* thread_can_start_; + }; + + static DWORD WINAPI ThreadMain(void* ptr) { + // Transfers ownership. + std::unique_ptr param(static_cast(ptr)); + if (param->thread_can_start_ != nullptr) + param->thread_can_start_->WaitForNotification(); + param->runnable_->Run(); + return 0; + } + + // Prohibit instantiation. + ThreadWithParamSupport(); + + ThreadWithParamSupport(const ThreadWithParamSupport&) = delete; + ThreadWithParamSupport& operator=(const ThreadWithParamSupport&) = delete; +}; + +} // namespace + +ThreadWithParamBase::ThreadWithParamBase(Runnable* runnable, + Notification* thread_can_start) + : thread_( + ThreadWithParamSupport::CreateThread(runnable, thread_can_start)) {} + +ThreadWithParamBase::~ThreadWithParamBase() { Join(); } + +void ThreadWithParamBase::Join() { + GTEST_CHECK_(::WaitForSingleObject(thread_.Get(), INFINITE) == WAIT_OBJECT_0) + << "Failed to join the thread with error " << ::GetLastError() << "."; +} + +// Maps a thread to a set of ThreadIdToThreadLocals that have values +// instantiated on that thread and notifies them when the thread exits. A +// ThreadLocal instance is expected to persist until all threads it has +// values on have terminated. +class ThreadLocalRegistryImpl { + public: + // Registers thread_local_instance as having value on the current thread. + // Returns a value that can be used to identify the thread from other threads. + static ThreadLocalValueHolderBase* GetValueOnCurrentThread( + const ThreadLocalBase* thread_local_instance) { +#ifdef _MSC_VER + MemoryIsNotDeallocated memory_is_not_deallocated; +#endif // _MSC_VER + DWORD current_thread = ::GetCurrentThreadId(); + MutexLock lock(&mutex_); + ThreadIdToThreadLocals* const thread_to_thread_locals = + GetThreadLocalsMapLocked(); + ThreadIdToThreadLocals::iterator thread_local_pos = + thread_to_thread_locals->find(current_thread); + if (thread_local_pos == thread_to_thread_locals->end()) { + thread_local_pos = + thread_to_thread_locals + ->insert(std::make_pair(current_thread, ThreadLocalValues())) + .first; + StartWatcherThreadFor(current_thread); + } + ThreadLocalValues& thread_local_values = thread_local_pos->second; + ThreadLocalValues::iterator value_pos = + thread_local_values.find(thread_local_instance); + if (value_pos == thread_local_values.end()) { + value_pos = + thread_local_values + .insert(std::make_pair( + thread_local_instance, + std::shared_ptr( + thread_local_instance->NewValueForCurrentThread()))) + .first; + } + return value_pos->second.get(); + } + + static void OnThreadLocalDestroyed( + const ThreadLocalBase* thread_local_instance) { + std::vector > value_holders; + // Clean up the ThreadLocalValues data structure while holding the lock, but + // defer the destruction of the ThreadLocalValueHolderBases. + { + MutexLock lock(&mutex_); + ThreadIdToThreadLocals* const thread_to_thread_locals = + GetThreadLocalsMapLocked(); + for (ThreadIdToThreadLocals::iterator it = + thread_to_thread_locals->begin(); + it != thread_to_thread_locals->end(); ++it) { + ThreadLocalValues& thread_local_values = it->second; + ThreadLocalValues::iterator value_pos = + thread_local_values.find(thread_local_instance); + if (value_pos != thread_local_values.end()) { + value_holders.push_back(value_pos->second); + thread_local_values.erase(value_pos); + // This 'if' can only be successful at most once, so theoretically we + // could break out of the loop here, but we don't bother doing so. + } + } + } + // Outside the lock, let the destructor for 'value_holders' deallocate the + // ThreadLocalValueHolderBases. + } + + static void OnThreadExit(DWORD thread_id) { + GTEST_CHECK_(thread_id != 0) << ::GetLastError(); + std::vector > value_holders; + // Clean up the ThreadIdToThreadLocals data structure while holding the + // lock, but defer the destruction of the ThreadLocalValueHolderBases. + { + MutexLock lock(&mutex_); + ThreadIdToThreadLocals* const thread_to_thread_locals = + GetThreadLocalsMapLocked(); + ThreadIdToThreadLocals::iterator thread_local_pos = + thread_to_thread_locals->find(thread_id); + if (thread_local_pos != thread_to_thread_locals->end()) { + ThreadLocalValues& thread_local_values = thread_local_pos->second; + for (ThreadLocalValues::iterator value_pos = + thread_local_values.begin(); + value_pos != thread_local_values.end(); ++value_pos) { + value_holders.push_back(value_pos->second); + } + thread_to_thread_locals->erase(thread_local_pos); + } + } + // Outside the lock, let the destructor for 'value_holders' deallocate the + // ThreadLocalValueHolderBases. + } + + private: + // In a particular thread, maps a ThreadLocal object to its value. + typedef std::map > + ThreadLocalValues; + // Stores all ThreadIdToThreadLocals having values in a thread, indexed by + // thread's ID. + typedef std::map ThreadIdToThreadLocals; + + struct WatcherThreadParams { + DWORD thread_id; + HANDLE handle; + Notification has_initialized; + }; + + static void StartWatcherThreadFor(DWORD thread_id) { + // The returned handle will be kept in thread_map and closed by + // watcher_thread in WatcherThreadFunc. + HANDLE thread = + ::OpenThread(SYNCHRONIZE | THREAD_QUERY_INFORMATION, FALSE, thread_id); + GTEST_CHECK_(thread != nullptr); + + WatcherThreadParams* watcher_thread_params = new WatcherThreadParams; + watcher_thread_params->thread_id = thread_id; + watcher_thread_params->handle = thread; + + // We need to pass a valid thread ID pointer into CreateThread for it + // to work correctly under Win98. + DWORD watcher_thread_id; + HANDLE watcher_thread = + ::CreateThread(nullptr, // Default security. + 0, // Default stack size + &ThreadLocalRegistryImpl::WatcherThreadFunc, + reinterpret_cast(watcher_thread_params), + CREATE_SUSPENDED, &watcher_thread_id); + GTEST_CHECK_(watcher_thread != nullptr) + << "CreateThread failed with error " << ::GetLastError() << "."; + // Give the watcher thread the same priority as ours to avoid being + // blocked by it. + ::SetThreadPriority(watcher_thread, + ::GetThreadPriority(::GetCurrentThread())); + ::ResumeThread(watcher_thread); + ::CloseHandle(watcher_thread); + + // Wait for the watcher thread to start to avoid race conditions. + // One specific race condition that can happen is that we have returned + // from main and have started to tear down, the newly spawned watcher + // thread may access already-freed variables, like global shared_ptrs. + watcher_thread_params->has_initialized.WaitForNotification(); + } + + // Monitors exit from a given thread and notifies those + // ThreadIdToThreadLocals about thread termination. + static DWORD WINAPI WatcherThreadFunc(LPVOID param) { + WatcherThreadParams* watcher_thread_params = + reinterpret_cast(param); + watcher_thread_params->has_initialized.Notify(); + GTEST_CHECK_(::WaitForSingleObject(watcher_thread_params->handle, + INFINITE) == WAIT_OBJECT_0); + OnThreadExit(watcher_thread_params->thread_id); + ::CloseHandle(watcher_thread_params->handle); + delete watcher_thread_params; + return 0; + } + + // Returns map of thread local instances. + static ThreadIdToThreadLocals* GetThreadLocalsMapLocked() { + mutex_.AssertHeld(); +#ifdef _MSC_VER + MemoryIsNotDeallocated memory_is_not_deallocated; +#endif // _MSC_VER + static ThreadIdToThreadLocals* map = new ThreadIdToThreadLocals(); + return map; + } + + // Protects access to GetThreadLocalsMapLocked() and its return value. + static Mutex mutex_; + // Protects access to GetThreadMapLocked() and its return value. + static Mutex thread_map_mutex_; +}; + +Mutex ThreadLocalRegistryImpl::mutex_(Mutex::kStaticMutex); // NOLINT +Mutex ThreadLocalRegistryImpl::thread_map_mutex_( + Mutex::kStaticMutex); // NOLINT + +ThreadLocalValueHolderBase* ThreadLocalRegistry::GetValueOnCurrentThread( + const ThreadLocalBase* thread_local_instance) { + return ThreadLocalRegistryImpl::GetValueOnCurrentThread( + thread_local_instance); +} + +void ThreadLocalRegistry::OnThreadLocalDestroyed( + const ThreadLocalBase* thread_local_instance) { + ThreadLocalRegistryImpl::OnThreadLocalDestroyed(thread_local_instance); +} + +#endif // GTEST_IS_THREADSAFE && GTEST_OS_WINDOWS + +#ifdef GTEST_USES_POSIX_RE + +// Implements RE. Currently only needed for death tests. + +RE::~RE() { + if (is_valid_) { + // regfree'ing an invalid regex might crash because the content + // of the regex is undefined. Since the regex's are essentially + // the same, one cannot be valid (or invalid) without the other + // being so too. + regfree(&partial_regex_); + regfree(&full_regex_); + } +} + +// Returns true if and only if regular expression re matches the entire str. +bool RE::FullMatch(const char* str, const RE& re) { + if (!re.is_valid_) return false; + + regmatch_t match; + return regexec(&re.full_regex_, str, 1, &match, 0) == 0; +} + +// Returns true if and only if regular expression re matches a substring of +// str (including str itself). +bool RE::PartialMatch(const char* str, const RE& re) { + if (!re.is_valid_) return false; + + regmatch_t match; + return regexec(&re.partial_regex_, str, 1, &match, 0) == 0; +} + +// Initializes an RE from its string representation. +void RE::Init(const char* regex) { + pattern_ = regex; + + // NetBSD (and Android, which takes its regex implemntation from NetBSD) does + // not include the GNU regex extensions (such as Perl style character classes + // like \w) in REG_EXTENDED. REG_EXTENDED is only specified to include the + // [[:alpha:]] style character classes. Enable REG_GNU wherever it is defined + // so users can use those extensions. +#if defined(REG_GNU) + constexpr int reg_flags = REG_EXTENDED | REG_GNU; +#else + constexpr int reg_flags = REG_EXTENDED; +#endif + + // Reserves enough bytes to hold the regular expression used for a + // full match. + const size_t full_regex_len = strlen(regex) + 10; + char* const full_pattern = new char[full_regex_len]; + + snprintf(full_pattern, full_regex_len, "^(%s)$", regex); + is_valid_ = regcomp(&full_regex_, full_pattern, reg_flags) == 0; + // We want to call regcomp(&partial_regex_, ...) even if the + // previous expression returns false. Otherwise partial_regex_ may + // not be properly initialized can may cause trouble when it's + // freed. + // + // Some implementation of POSIX regex (e.g. on at least some + // versions of Cygwin) doesn't accept the empty string as a valid + // regex. We change it to an equivalent form "()" to be safe. + if (is_valid_) { + const char* const partial_regex = (*regex == '\0') ? "()" : regex; + is_valid_ = regcomp(&partial_regex_, partial_regex, reg_flags) == 0; + } + EXPECT_TRUE(is_valid_) + << "Regular expression \"" << regex + << "\" is not a valid POSIX Extended regular expression."; + + delete[] full_pattern; +} + +#elif defined(GTEST_USES_SIMPLE_RE) + +// Returns true if and only if ch appears anywhere in str (excluding the +// terminating '\0' character). +bool IsInSet(char ch, const char* str) { + return ch != '\0' && strchr(str, ch) != nullptr; +} + +// Returns true if and only if ch belongs to the given classification. +// Unlike similar functions in , these aren't affected by the +// current locale. +bool IsAsciiDigit(char ch) { return '0' <= ch && ch <= '9'; } +bool IsAsciiPunct(char ch) { + return IsInSet(ch, "^-!\"#$%&'()*+,./:;<=>?@[\\]_`{|}~"); +} +bool IsRepeat(char ch) { return IsInSet(ch, "?*+"); } +bool IsAsciiWhiteSpace(char ch) { return IsInSet(ch, " \f\n\r\t\v"); } +bool IsAsciiWordChar(char ch) { + return ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || + ('0' <= ch && ch <= '9') || ch == '_'; +} + +// Returns true if and only if "\\c" is a supported escape sequence. +bool IsValidEscape(char c) { + return (IsAsciiPunct(c) || IsInSet(c, "dDfnrsStvwW")); +} + +// Returns true if and only if the given atom (specified by escaped and +// pattern) matches ch. The result is undefined if the atom is invalid. +bool AtomMatchesChar(bool escaped, char pattern_char, char ch) { + if (escaped) { // "\\p" where p is pattern_char. + switch (pattern_char) { + case 'd': + return IsAsciiDigit(ch); + case 'D': + return !IsAsciiDigit(ch); + case 'f': + return ch == '\f'; + case 'n': + return ch == '\n'; + case 'r': + return ch == '\r'; + case 's': + return IsAsciiWhiteSpace(ch); + case 'S': + return !IsAsciiWhiteSpace(ch); + case 't': + return ch == '\t'; + case 'v': + return ch == '\v'; + case 'w': + return IsAsciiWordChar(ch); + case 'W': + return !IsAsciiWordChar(ch); + } + return IsAsciiPunct(pattern_char) && pattern_char == ch; + } + + return (pattern_char == '.' && ch != '\n') || pattern_char == ch; +} + +// Helper function used by ValidateRegex() to format error messages. +static std::string FormatRegexSyntaxError(const char* regex, int index) { + return (Message() << "Syntax error at index " << index + << " in simple regular expression \"" << regex << "\": ") + .GetString(); +} + +// Generates non-fatal failures and returns false if regex is invalid; +// otherwise returns true. +bool ValidateRegex(const char* regex) { + if (regex == nullptr) { + ADD_FAILURE() << "NULL is not a valid simple regular expression."; + return false; + } + + bool is_valid = true; + + // True if and only if ?, *, or + can follow the previous atom. + bool prev_repeatable = false; + for (int i = 0; regex[i]; i++) { + if (regex[i] == '\\') { // An escape sequence + i++; + if (regex[i] == '\0') { + ADD_FAILURE() << FormatRegexSyntaxError(regex, i - 1) + << "'\\' cannot appear at the end."; + return false; + } + + if (!IsValidEscape(regex[i])) { + ADD_FAILURE() << FormatRegexSyntaxError(regex, i - 1) + << "invalid escape sequence \"\\" << regex[i] << "\"."; + is_valid = false; + } + prev_repeatable = true; + } else { // Not an escape sequence. + const char ch = regex[i]; + + if (ch == '^' && i > 0) { + ADD_FAILURE() << FormatRegexSyntaxError(regex, i) + << "'^' can only appear at the beginning."; + is_valid = false; + } else if (ch == '$' && regex[i + 1] != '\0') { + ADD_FAILURE() << FormatRegexSyntaxError(regex, i) + << "'$' can only appear at the end."; + is_valid = false; + } else if (IsInSet(ch, "()[]{}|")) { + ADD_FAILURE() << FormatRegexSyntaxError(regex, i) << "'" << ch + << "' is unsupported."; + is_valid = false; + } else if (IsRepeat(ch) && !prev_repeatable) { + ADD_FAILURE() << FormatRegexSyntaxError(regex, i) << "'" << ch + << "' can only follow a repeatable token."; + is_valid = false; + } + + prev_repeatable = !IsInSet(ch, "^$?*+"); + } + } + + return is_valid; +} + +// Matches a repeated regex atom followed by a valid simple regular +// expression. The regex atom is defined as c if escaped is false, +// or \c otherwise. repeat is the repetition meta character (?, *, +// or +). The behavior is undefined if str contains too many +// characters to be indexable by size_t, in which case the test will +// probably time out anyway. We are fine with this limitation as +// std::string has it too. +bool MatchRepetitionAndRegexAtHead(bool escaped, char c, char repeat, + const char* regex, const char* str) { + const size_t min_count = (repeat == '+') ? 1 : 0; + const size_t max_count = (repeat == '?') ? 1 : static_cast(-1) - 1; + // We cannot call numeric_limits::max() as it conflicts with the + // max() macro on Windows. + + for (size_t i = 0; i <= max_count; ++i) { + // We know that the atom matches each of the first i characters in str. + if (i >= min_count && MatchRegexAtHead(regex, str + i)) { + // We have enough matches at the head, and the tail matches too. + // Since we only care about *whether* the pattern matches str + // (as opposed to *how* it matches), there is no need to find a + // greedy match. + return true; + } + if (str[i] == '\0' || !AtomMatchesChar(escaped, c, str[i])) return false; + } + return false; +} + +// Returns true if and only if regex matches a prefix of str. regex must +// be a valid simple regular expression and not start with "^", or the +// result is undefined. +bool MatchRegexAtHead(const char* regex, const char* str) { + if (*regex == '\0') // An empty regex matches a prefix of anything. + return true; + + // "$" only matches the end of a string. Note that regex being + // valid guarantees that there's nothing after "$" in it. + if (*regex == '$') return *str == '\0'; + + // Is the first thing in regex an escape sequence? + const bool escaped = *regex == '\\'; + if (escaped) ++regex; + if (IsRepeat(regex[1])) { + // MatchRepetitionAndRegexAtHead() calls MatchRegexAtHead(), so + // here's an indirect recursion. It terminates as the regex gets + // shorter in each recursion. + return MatchRepetitionAndRegexAtHead(escaped, regex[0], regex[1], regex + 2, + str); + } else { + // regex isn't empty, isn't "$", and doesn't start with a + // repetition. We match the first atom of regex with the first + // character of str and recurse. + return (*str != '\0') && AtomMatchesChar(escaped, *regex, *str) && + MatchRegexAtHead(regex + 1, str + 1); + } +} + +// Returns true if and only if regex matches any substring of str. regex must +// be a valid simple regular expression, or the result is undefined. +// +// The algorithm is recursive, but the recursion depth doesn't exceed +// the regex length, so we won't need to worry about running out of +// stack space normally. In rare cases the time complexity can be +// exponential with respect to the regex length + the string length, +// but usually it's must faster (often close to linear). +bool MatchRegexAnywhere(const char* regex, const char* str) { + if (regex == nullptr || str == nullptr) return false; + + if (*regex == '^') return MatchRegexAtHead(regex + 1, str); + + // A successful match can be anywhere in str. + do { + if (MatchRegexAtHead(regex, str)) return true; + } while (*str++ != '\0'); + return false; +} + +// Implements the RE class. + +RE::~RE() = default; + +// Returns true if and only if regular expression re matches the entire str. +bool RE::FullMatch(const char* str, const RE& re) { + return re.is_valid_ && MatchRegexAnywhere(re.full_pattern_.c_str(), str); +} + +// Returns true if and only if regular expression re matches a substring of +// str (including str itself). +bool RE::PartialMatch(const char* str, const RE& re) { + return re.is_valid_ && MatchRegexAnywhere(re.pattern_.c_str(), str); +} + +// Initializes an RE from its string representation. +void RE::Init(const char* regex) { + full_pattern_.clear(); + pattern_.clear(); + + if (regex != nullptr) { + pattern_ = regex; + } + + is_valid_ = ValidateRegex(regex); + if (!is_valid_) { + // No need to calculate the full pattern when the regex is invalid. + return; + } + + // Reserves enough bytes to hold the regular expression used for a + // full match: we need space to prepend a '^' and append a '$'. + full_pattern_.reserve(pattern_.size() + 2); + + if (pattern_.empty() || pattern_.front() != '^') { + full_pattern_.push_back('^'); // Makes sure full_pattern_ starts with '^'. + } + + full_pattern_.append(pattern_); + + if (pattern_.empty() || pattern_.back() != '$') { + full_pattern_.push_back('$'); // Makes sure full_pattern_ ends with '$'. + } +} + +#endif // GTEST_USES_POSIX_RE + +const char kUnknownFile[] = "unknown file"; + +// Formats a source file path and a line number as they would appear +// in an error message from the compiler used to compile this code. +GTEST_API_ ::std::string FormatFileLocation(const char* file, int line) { + const std::string file_name(file == nullptr ? kUnknownFile : file); + + if (line < 0) { + return file_name + ":"; + } +#ifdef _MSC_VER + return file_name + "(" + StreamableToString(line) + "):"; +#else + return file_name + ":" + StreamableToString(line) + ":"; +#endif // _MSC_VER +} + +// Formats a file location for compiler-independent XML output. +// Although this function is not platform dependent, we put it next to +// FormatFileLocation in order to contrast the two functions. +// Note that FormatCompilerIndependentFileLocation() does NOT append colon +// to the file location it produces, unlike FormatFileLocation(). +GTEST_API_ ::std::string FormatCompilerIndependentFileLocation(const char* file, + int line) { + const std::string file_name(file == nullptr ? kUnknownFile : file); + + if (line < 0) + return file_name; + else + return file_name + ":" + StreamableToString(line); +} + +GTestLog::GTestLog(GTestLogSeverity severity, const char* file, int line) + : severity_(severity) { + const char* const marker = severity == GTEST_INFO ? "[ INFO ]" + : severity == GTEST_WARNING ? "[WARNING]" + : severity == GTEST_ERROR ? "[ ERROR ]" + : "[ FATAL ]"; + GetStream() << ::std::endl + << marker << " " << FormatFileLocation(file, line).c_str() + << ": "; +} + +// Flushes the buffers and, if severity is GTEST_FATAL, aborts the program. +GTestLog::~GTestLog() { + GetStream() << ::std::endl; + if (severity_ == GTEST_FATAL) { + fflush(stderr); + posix::Abort(); + } +} + +#if GTEST_HAS_STREAM_REDIRECTION + +// Disable Microsoft deprecation warnings for POSIX functions called from +// this class (creat, dup, dup2, and close) +GTEST_DISABLE_MSC_DEPRECATED_PUSH_() + +namespace { + +#if defined(GTEST_OS_LINUX_ANDROID) || defined(GTEST_OS_IOS) +bool EndsWithPathSeparator(const std::string& path) { + return !path.empty() && path.back() == GTEST_PATH_SEP_[0]; +} +#endif + +} // namespace + +// Object that captures an output stream (stdout/stderr). +class CapturedStream { + public: + // The ctor redirects the stream to a temporary file. + explicit CapturedStream(int fd) : fd_(fd), uncaptured_fd_(dup(fd)) { +#ifdef GTEST_OS_WINDOWS + char temp_dir_path[MAX_PATH + 1] = {'\0'}; // NOLINT + char temp_file_path[MAX_PATH + 1] = {'\0'}; // NOLINT + + ::GetTempPathA(sizeof(temp_dir_path), temp_dir_path); + const UINT success = ::GetTempFileNameA(temp_dir_path, "gtest_redir", + 0, // Generate unique file name. + temp_file_path); + GTEST_CHECK_(success != 0) + << "Unable to create a temporary file in " << temp_dir_path; + const int captured_fd = creat(temp_file_path, _S_IREAD | _S_IWRITE); + GTEST_CHECK_(captured_fd != -1) + << "Unable to open temporary file " << temp_file_path; + filename_ = temp_file_path; +#else + // There's no guarantee that a test has write access to the current + // directory, so we create the temporary file in a temporary directory. + std::string name_template; + +#ifdef GTEST_OS_LINUX_ANDROID + // Note: Android applications are expected to call the framework's + // Context.getExternalStorageDirectory() method through JNI to get + // the location of the world-writable SD Card directory. However, + // this requires a Context handle, which cannot be retrieved + // globally from native code. Doing so also precludes running the + // code as part of a regular standalone executable, which doesn't + // run in a Dalvik process (e.g. when running it through 'adb shell'). + // + // The location /data/local/tmp is directly accessible from native code. + // '/sdcard' and other variants cannot be relied on, as they are not + // guaranteed to be mounted, or may have a delay in mounting. + // + // However, prefer using the TMPDIR environment variable if set, as newer + // devices may have /data/local/tmp read-only. + name_template = TempDir(); + if (!EndsWithPathSeparator(name_template)) + name_template.push_back(GTEST_PATH_SEP_[0]); + +#elif defined(GTEST_OS_IOS) + char user_temp_dir[PATH_MAX + 1]; + + // Documented alternative to NSTemporaryDirectory() (for obtaining creating + // a temporary directory) at + // https://developer.apple.com/library/archive/documentation/Security/Conceptual/SecureCodingGuide/Articles/RaceConditions.html#//apple_ref/doc/uid/TP40002585-SW10 + // + // _CS_DARWIN_USER_TEMP_DIR (as well as _CS_DARWIN_USER_CACHE_DIR) is not + // documented in the confstr() man page at + // https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/confstr.3.html#//apple_ref/doc/man/3/confstr + // but are still available, according to the WebKit patches at + // https://trac.webkit.org/changeset/262004/webkit + // https://trac.webkit.org/changeset/263705/webkit + // + // The confstr() implementation falls back to getenv("TMPDIR"). See + // https://opensource.apple.com/source/Libc/Libc-1439.100.3/gen/confstr.c.auto.html + ::confstr(_CS_DARWIN_USER_TEMP_DIR, user_temp_dir, sizeof(user_temp_dir)); + + name_template = user_temp_dir; + if (!EndsWithPathSeparator(name_template)) + name_template.push_back(GTEST_PATH_SEP_[0]); +#else + name_template = "/tmp/"; +#endif + name_template.append("gtest_captured_stream.XXXXXX"); + + // mkstemp() modifies the string bytes in place, and does not go beyond the + // string's length. This results in well-defined behavior in C++17. + // + // The const_cast is needed below C++17. The constraints on std::string + // implementations in C++11 and above make assumption behind the const_cast + // fairly safe. + const int captured_fd = ::mkstemp(const_cast(name_template.data())); + if (captured_fd == -1) { + GTEST_LOG_(WARNING) + << "Failed to create tmp file " << name_template + << " for test; does the test have access to the /tmp directory?"; + } + filename_ = std::move(name_template); +#endif // GTEST_OS_WINDOWS + fflush(nullptr); + dup2(captured_fd, fd_); + close(captured_fd); + } + + ~CapturedStream() { remove(filename_.c_str()); } + + std::string GetCapturedString() { + if (uncaptured_fd_ != -1) { + // Restores the original stream. + fflush(nullptr); + dup2(uncaptured_fd_, fd_); + close(uncaptured_fd_); + uncaptured_fd_ = -1; + } + + FILE* const file = posix::FOpen(filename_.c_str(), "r"); + if (file == nullptr) { + GTEST_LOG_(FATAL) << "Failed to open tmp file " << filename_ + << " for capturing stream."; + } + const std::string content = ReadEntireFile(file); + posix::FClose(file); + return content; + } + + private: + const int fd_; // A stream to capture. + int uncaptured_fd_; + // Name of the temporary file holding the stderr output. + ::std::string filename_; + + CapturedStream(const CapturedStream&) = delete; + CapturedStream& operator=(const CapturedStream&) = delete; +}; + +GTEST_DISABLE_MSC_DEPRECATED_POP_() + +static CapturedStream* g_captured_stderr = nullptr; +static CapturedStream* g_captured_stdout = nullptr; + +// Starts capturing an output stream (stdout/stderr). +static void CaptureStream(int fd, const char* stream_name, + CapturedStream** stream) { + if (*stream != nullptr) { + GTEST_LOG_(FATAL) << "Only one " << stream_name + << " capturer can exist at a time."; + } + *stream = new CapturedStream(fd); +} + +// Stops capturing the output stream and returns the captured string. +static std::string GetCapturedStream(CapturedStream** captured_stream) { + const std::string content = (*captured_stream)->GetCapturedString(); + + delete *captured_stream; + *captured_stream = nullptr; + + return content; +} + +#if defined(_MSC_VER) || defined(__BORLANDC__) +// MSVC and C++Builder do not provide a definition of STDERR_FILENO. +const int kStdOutFileno = 1; +const int kStdErrFileno = 2; +#else +const int kStdOutFileno = STDOUT_FILENO; +const int kStdErrFileno = STDERR_FILENO; +#endif // defined(_MSC_VER) || defined(__BORLANDC__) + +// Starts capturing stdout. +void CaptureStdout() { + CaptureStream(kStdOutFileno, "stdout", &g_captured_stdout); +} + +// Starts capturing stderr. +void CaptureStderr() { + CaptureStream(kStdErrFileno, "stderr", &g_captured_stderr); +} + +// Stops capturing stdout and returns the captured string. +std::string GetCapturedStdout() { + return GetCapturedStream(&g_captured_stdout); +} + +// Stops capturing stderr and returns the captured string. +std::string GetCapturedStderr() { + return GetCapturedStream(&g_captured_stderr); +} + +#endif // GTEST_HAS_STREAM_REDIRECTION + +size_t GetFileSize(FILE* file) { + fseek(file, 0, SEEK_END); + return static_cast(ftell(file)); +} + +std::string ReadEntireFile(FILE* file) { + const size_t file_size = GetFileSize(file); + char* const buffer = new char[file_size]; + + size_t bytes_last_read = 0; // # of bytes read in the last fread() + size_t bytes_read = 0; // # of bytes read so far + + fseek(file, 0, SEEK_SET); + + // Keeps reading the file until we cannot read further or the + // pre-determined file size is reached. + do { + bytes_last_read = + fread(buffer + bytes_read, 1, file_size - bytes_read, file); + bytes_read += bytes_last_read; + } while (bytes_last_read > 0 && bytes_read < file_size); + + const std::string content(buffer, bytes_read); + delete[] buffer; + + return content; +} + +#ifdef GTEST_HAS_DEATH_TEST +static const std::vector* g_injected_test_argvs = + nullptr; // Owned. + +std::vector GetInjectableArgvs() { + if (g_injected_test_argvs != nullptr) { + return *g_injected_test_argvs; + } + return GetArgvs(); +} + +void SetInjectableArgvs(const std::vector* new_argvs) { + if (g_injected_test_argvs != new_argvs) delete g_injected_test_argvs; + g_injected_test_argvs = new_argvs; +} + +void SetInjectableArgvs(const std::vector& new_argvs) { + SetInjectableArgvs( + new std::vector(new_argvs.begin(), new_argvs.end())); +} + +void ClearInjectableArgvs() { + delete g_injected_test_argvs; + g_injected_test_argvs = nullptr; +} +#endif // GTEST_HAS_DEATH_TEST + +#ifdef GTEST_OS_WINDOWS_MOBILE +namespace posix { +void Abort() { + DebugBreak(); + TerminateProcess(GetCurrentProcess(), 1); +} +} // namespace posix +#endif // GTEST_OS_WINDOWS_MOBILE + +// Returns the name of the environment variable corresponding to the +// given flag. For example, FlagToEnvVar("foo") will return +// "GTEST_FOO" in the open-source version. +static std::string FlagToEnvVar(const char* flag) { + const std::string full_flag = + (Message() << GTEST_FLAG_PREFIX_ << flag).GetString(); + + Message env_var; + for (size_t i = 0; i != full_flag.length(); i++) { + env_var << ToUpper(full_flag.c_str()[i]); + } + + return env_var.GetString(); +} + +// Parses 'str' for a 32-bit signed integer. If successful, writes +// the result to *value and returns true; otherwise leaves *value +// unchanged and returns false. +bool ParseInt32(const Message& src_text, const char* str, int32_t* value) { + // Parses the environment variable as a decimal integer. + char* end = nullptr; + const long long_value = strtol(str, &end, 10); // NOLINT + + // Has strtol() consumed all characters in the string? + if (*end != '\0') { + // No - an invalid character was encountered. + Message msg; + msg << "WARNING: " << src_text + << " is expected to be a 32-bit integer, but actually" + << " has value \"" << str << "\".\n"; + printf("%s", msg.GetString().c_str()); + fflush(stdout); + return false; + } + + // Is the parsed value in the range of an int32_t? + const auto result = static_cast(long_value); + if (long_value == LONG_MAX || long_value == LONG_MIN || + // The parsed value overflows as a long. (strtol() returns + // LONG_MAX or LONG_MIN when the input overflows.) + result != long_value + // The parsed value overflows as an int32_t. + ) { + Message msg; + msg << "WARNING: " << src_text + << " is expected to be a 32-bit integer, but actually" << " has value " + << str << ", which overflows.\n"; + printf("%s", msg.GetString().c_str()); + fflush(stdout); + return false; + } + + *value = result; + return true; +} + +// Reads and returns the Boolean environment variable corresponding to +// the given flag; if it's not set, returns default_value. +// +// The value is considered true if and only if it's not "0". +bool BoolFromGTestEnv(const char* flag, bool default_value) { +#if defined(GTEST_GET_BOOL_FROM_ENV_) + return GTEST_GET_BOOL_FROM_ENV_(flag, default_value); +#else + const std::string env_var = FlagToEnvVar(flag); + const char* const string_value = posix::GetEnv(env_var.c_str()); + return string_value == nullptr ? default_value + : strcmp(string_value, "0") != 0; +#endif // defined(GTEST_GET_BOOL_FROM_ENV_) +} + +// Reads and returns a 32-bit integer stored in the environment +// variable corresponding to the given flag; if it isn't set or +// doesn't represent a valid 32-bit integer, returns default_value. +int32_t Int32FromGTestEnv(const char* flag, int32_t default_value) { +#if defined(GTEST_GET_INT32_FROM_ENV_) + return GTEST_GET_INT32_FROM_ENV_(flag, default_value); +#else + const std::string env_var = FlagToEnvVar(flag); + const char* const string_value = posix::GetEnv(env_var.c_str()); + if (string_value == nullptr) { + // The environment variable is not set. + return default_value; + } + + int32_t result = default_value; + if (!ParseInt32(Message() << "Environment variable " << env_var, string_value, + &result)) { + printf("The default value %s is used.\n", + (Message() << default_value).GetString().c_str()); + fflush(stdout); + return default_value; + } + + return result; +#endif // defined(GTEST_GET_INT32_FROM_ENV_) +} + +// As a special case for the 'output' flag, if GTEST_OUTPUT is not +// set, we look for XML_OUTPUT_FILE, which is set by the Bazel build +// system. The value of XML_OUTPUT_FILE is a filename without the +// "xml:" prefix of GTEST_OUTPUT. +// Note that this is meant to be called at the call site so it does +// not check that the flag is 'output' +// In essence this checks an env variable called XML_OUTPUT_FILE +// and if it is set we prepend "xml:" to its value, if it not set we return "" +std::string OutputFlagAlsoCheckEnvVar() { + std::string default_value_for_output_flag = ""; + const char* xml_output_file_env = posix::GetEnv("XML_OUTPUT_FILE"); + if (nullptr != xml_output_file_env) { + default_value_for_output_flag = std::string("xml:") + xml_output_file_env; + } + return default_value_for_output_flag; +} + +// Reads and returns the string environment variable corresponding to +// the given flag; if it's not set, returns default_value. +const char* StringFromGTestEnv(const char* flag, const char* default_value) { +#if defined(GTEST_GET_STRING_FROM_ENV_) + return GTEST_GET_STRING_FROM_ENV_(flag, default_value); +#else + const std::string env_var = FlagToEnvVar(flag); + const char* const value = posix::GetEnv(env_var.c_str()); + return value == nullptr ? default_value : value; +#endif // defined(GTEST_GET_STRING_FROM_ENV_) +} + +} // namespace internal +} // namespace testing diff --git a/googletest/src/gtest-printers.cc b/googletest/src/gtest-printers.cc new file mode 100644 index 00000000..e3acecba --- /dev/null +++ b/googletest/src/gtest-printers.cc @@ -0,0 +1,555 @@ +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Google Test - The Google C++ Testing and Mocking Framework +// +// This file implements a universal value printer that can print a +// value of any type T: +// +// void ::testing::internal::UniversalPrinter::Print(value, ostream_ptr); +// +// It uses the << operator when possible, and prints the bytes in the +// object otherwise. A user can override its behavior for a class +// type Foo by defining either operator<<(::std::ostream&, const Foo&) +// or void PrintTo(const Foo&, ::std::ostream*) in the namespace that +// defines Foo. + +#include "gtest/gtest-printers.h" + +#include + +#include +#include +#include +#include +#include +#include // NOLINT +#include +#include + +#include "gtest/internal/gtest-port.h" +#include "src/gtest-internal-inl.h" + +namespace testing { + +namespace { + +using ::std::ostream; + +// Prints a segment of bytes in the given object. +GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_ +GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_ +GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_ +GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_ +void PrintByteSegmentInObjectTo(const unsigned char* obj_bytes, size_t start, + size_t count, ostream* os) { + char text[5] = ""; + for (size_t i = 0; i != count; i++) { + const size_t j = start + i; + if (i != 0) { + // Organizes the bytes into groups of 2 for easy parsing by + // human. + if ((j % 2) == 0) + *os << ' '; + else + *os << '-'; + } + GTEST_SNPRINTF_(text, sizeof(text), "%02X", obj_bytes[j]); + *os << text; + } +} + +// Prints the bytes in the given value to the given ostream. +void PrintBytesInObjectToImpl(const unsigned char* obj_bytes, size_t count, + ostream* os) { + // Tells the user how big the object is. + *os << count << "-byte object <"; + + const size_t kThreshold = 132; + const size_t kChunkSize = 64; + // If the object size is bigger than kThreshold, we'll have to omit + // some details by printing only the first and the last kChunkSize + // bytes. + if (count < kThreshold) { + PrintByteSegmentInObjectTo(obj_bytes, 0, count, os); + } else { + PrintByteSegmentInObjectTo(obj_bytes, 0, kChunkSize, os); + *os << " ... "; + // Rounds up to 2-byte boundary. + const size_t resume_pos = (count - kChunkSize + 1) / 2 * 2; + PrintByteSegmentInObjectTo(obj_bytes, resume_pos, count - resume_pos, os); + } + *os << ">"; +} + +// Helpers for widening a character to char32_t. Since the standard does not +// specify if char / wchar_t is signed or unsigned, it is important to first +// convert it to the unsigned type of the same width before widening it to +// char32_t. +template +char32_t ToChar32(CharType in) { + return static_cast( + static_cast::type>(in)); +} + +} // namespace + +namespace internal { + +// Delegates to PrintBytesInObjectToImpl() to print the bytes in the +// given object. The delegation simplifies the implementation, which +// uses the << operator and thus is easier done outside of the +// ::testing::internal namespace, which contains a << operator that +// sometimes conflicts with the one in STL. +void PrintBytesInObjectTo(const unsigned char* obj_bytes, size_t count, + ostream* os) { + PrintBytesInObjectToImpl(obj_bytes, count, os); +} + +// Depending on the value of a char (or wchar_t), we print it in one +// of three formats: +// - as is if it's a printable ASCII (e.g. 'a', '2', ' '), +// - as a hexadecimal escape sequence (e.g. '\x7F'), or +// - as a special escape sequence (e.g. '\r', '\n'). +enum CharFormat { kAsIs, kHexEscape, kSpecialEscape }; + +// Returns true if c is a printable ASCII character. We test the +// value of c directly instead of calling isprint(), which is buggy on +// Windows Mobile. +inline bool IsPrintableAscii(char32_t c) { return 0x20 <= c && c <= 0x7E; } + +// Prints c (of type char, char8_t, char16_t, char32_t, or wchar_t) as a +// character literal without the quotes, escaping it when necessary; returns how +// c was formatted. +template +static CharFormat PrintAsCharLiteralTo(Char c, ostream* os) { + const char32_t u_c = ToChar32(c); + switch (u_c) { + case L'\0': + *os << "\\0"; + break; + case L'\'': + *os << "\\'"; + break; + case L'\\': + *os << "\\\\"; + break; + case L'\a': + *os << "\\a"; + break; + case L'\b': + *os << "\\b"; + break; + case L'\f': + *os << "\\f"; + break; + case L'\n': + *os << "\\n"; + break; + case L'\r': + *os << "\\r"; + break; + case L'\t': + *os << "\\t"; + break; + case L'\v': + *os << "\\v"; + break; + default: + if (IsPrintableAscii(u_c)) { + *os << static_cast(c); + return kAsIs; + } else { + ostream::fmtflags flags = os->flags(); + *os << "\\x" << std::hex << std::uppercase << static_cast(u_c); + os->flags(flags); + return kHexEscape; + } + } + return kSpecialEscape; +} + +// Prints a char32_t c as if it's part of a string literal, escaping it when +// necessary; returns how c was formatted. +static CharFormat PrintAsStringLiteralTo(char32_t c, ostream* os) { + switch (c) { + case L'\'': + *os << "'"; + return kAsIs; + case L'"': + *os << "\\\""; + return kSpecialEscape; + default: + return PrintAsCharLiteralTo(c, os); + } +} + +static const char* GetCharWidthPrefix(char) { return ""; } + +static const char* GetCharWidthPrefix(signed char) { return ""; } + +static const char* GetCharWidthPrefix(unsigned char) { return ""; } + +#ifdef __cpp_lib_char8_t +static const char* GetCharWidthPrefix(char8_t) { return "u8"; } +#endif + +static const char* GetCharWidthPrefix(char16_t) { return "u"; } + +static const char* GetCharWidthPrefix(char32_t) { return "U"; } + +static const char* GetCharWidthPrefix(wchar_t) { return "L"; } + +// Prints a char c as if it's part of a string literal, escaping it when +// necessary; returns how c was formatted. +static CharFormat PrintAsStringLiteralTo(char c, ostream* os) { + return PrintAsStringLiteralTo(ToChar32(c), os); +} + +#ifdef __cpp_lib_char8_t +static CharFormat PrintAsStringLiteralTo(char8_t c, ostream* os) { + return PrintAsStringLiteralTo(ToChar32(c), os); +} +#endif + +static CharFormat PrintAsStringLiteralTo(char16_t c, ostream* os) { + return PrintAsStringLiteralTo(ToChar32(c), os); +} + +static CharFormat PrintAsStringLiteralTo(wchar_t c, ostream* os) { + return PrintAsStringLiteralTo(ToChar32(c), os); +} + +// Prints a character c (of type char, char8_t, char16_t, char32_t, or wchar_t) +// and its code. '\0' is printed as "'\\0'", other unprintable characters are +// also properly escaped using the standard C++ escape sequence. +template +void PrintCharAndCodeTo(Char c, ostream* os) { + // First, print c as a literal in the most readable form we can find. + *os << GetCharWidthPrefix(c) << "'"; + const CharFormat format = PrintAsCharLiteralTo(c, os); + *os << "'"; + + // To aid user debugging, we also print c's code in decimal, unless + // it's 0 (in which case c was printed as '\\0', making the code + // obvious). + if (c == 0) return; + *os << " (" << static_cast(c); + + // For more convenience, we print c's code again in hexadecimal, + // unless c was already printed in the form '\x##' or the code is in + // [1, 9]. + if (format == kHexEscape || (1 <= c && c <= 9)) { + // Do nothing. + } else { + *os << ", 0x" << String::FormatHexInt(static_cast(c)); + } + *os << ")"; +} + +void PrintTo(unsigned char c, ::std::ostream* os) { PrintCharAndCodeTo(c, os); } +void PrintTo(signed char c, ::std::ostream* os) { PrintCharAndCodeTo(c, os); } + +// Prints a wchar_t as a symbol if it is printable or as its internal +// code otherwise and also as its code. L'\0' is printed as "L'\\0'". +void PrintTo(wchar_t wc, ostream* os) { PrintCharAndCodeTo(wc, os); } + +// TODO(dcheng): Consider making this delegate to PrintCharAndCodeTo() as well. +void PrintTo(char32_t c, ::std::ostream* os) { + *os << std::hex << "U+" << std::uppercase << std::setfill('0') << std::setw(4) + << static_cast(c); +} + +// gcc/clang __{u,}int128_t +#if defined(__SIZEOF_INT128__) +void PrintTo(__uint128_t v, ::std::ostream* os) { + if (v == 0) { + *os << "0"; + return; + } + + // Buffer large enough for ceil(log10(2^128))==39 and the null terminator + char buf[40]; + char* p = buf + sizeof(buf); + + // Some configurations have a __uint128_t, but no support for built in + // division. Do manual long division instead. + + uint64_t high = static_cast(v >> 64); + uint64_t low = static_cast(v); + + *--p = 0; + while (high != 0 || low != 0) { + uint64_t high_mod = high % 10; + high = high / 10; + // This is the long division algorithm specialized for a divisor of 10 and + // only two elements. + // Notable values: + // 2^64 / 10 == 1844674407370955161 + // 2^64 % 10 == 6 + const uint64_t carry = 6 * high_mod + low % 10; + low = low / 10 + high_mod * 1844674407370955161 + carry / 10; + + char digit = static_cast(carry % 10); + *--p = static_cast('0' + digit); + } + *os << p; +} +void PrintTo(__int128_t v, ::std::ostream* os) { + __uint128_t uv = static_cast<__uint128_t>(v); + if (v < 0) { + *os << "-"; + uv = -uv; + } + PrintTo(uv, os); +} +#endif // __SIZEOF_INT128__ + +// Prints the given array of characters to the ostream. CharType must be either +// char, char8_t, char16_t, char32_t, or wchar_t. +// The array starts at begin, the length is len, it may include '\0' characters +// and may not be NUL-terminated. +template +GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_ GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_ + GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_ + GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_ static CharFormat + PrintCharsAsStringTo(const CharType* begin, size_t len, ostream* os) { + const char* const quote_prefix = GetCharWidthPrefix(*begin); + *os << quote_prefix << "\""; + bool is_previous_hex = false; + CharFormat print_format = kAsIs; + for (size_t index = 0; index < len; ++index) { + const CharType cur = begin[index]; + if (is_previous_hex && IsXDigit(cur)) { + // Previous character is of '\x..' form and this character can be + // interpreted as another hexadecimal digit in its number. Break string to + // disambiguate. + *os << "\" " << quote_prefix << "\""; + } + is_previous_hex = PrintAsStringLiteralTo(cur, os) == kHexEscape; + // Remember if any characters required hex escaping. + if (is_previous_hex) { + print_format = kHexEscape; + } + } + *os << "\""; + return print_format; +} + +// Prints a (const) char/wchar_t array of 'len' elements, starting at address +// 'begin'. CharType must be either char or wchar_t. +template +GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_ GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_ + GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_ + GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_ static void + UniversalPrintCharArray(const CharType* begin, size_t len, + ostream* os) { + // The code + // const char kFoo[] = "foo"; + // generates an array of 4, not 3, elements, with the last one being '\0'. + // + // Therefore when printing a char array, we don't print the last element if + // it's '\0', such that the output matches the string literal as it's + // written in the source code. + if (len > 0 && begin[len - 1] == '\0') { + PrintCharsAsStringTo(begin, len - 1, os); + return; + } + + // If, however, the last element in the array is not '\0', e.g. + // const char kFoo[] = { 'f', 'o', 'o' }; + // we must print the entire array. We also print a message to indicate + // that the array is not NUL-terminated. + PrintCharsAsStringTo(begin, len, os); + *os << " (no terminating NUL)"; +} + +// Prints a (const) char array of 'len' elements, starting at address 'begin'. +void UniversalPrintArray(const char* begin, size_t len, ostream* os) { + UniversalPrintCharArray(begin, len, os); +} + +#ifdef __cpp_lib_char8_t +// Prints a (const) char8_t array of 'len' elements, starting at address +// 'begin'. +void UniversalPrintArray(const char8_t* begin, size_t len, ostream* os) { + UniversalPrintCharArray(begin, len, os); +} +#endif + +// Prints a (const) char16_t array of 'len' elements, starting at address +// 'begin'. +void UniversalPrintArray(const char16_t* begin, size_t len, ostream* os) { + UniversalPrintCharArray(begin, len, os); +} + +// Prints a (const) char32_t array of 'len' elements, starting at address +// 'begin'. +void UniversalPrintArray(const char32_t* begin, size_t len, ostream* os) { + UniversalPrintCharArray(begin, len, os); +} + +// Prints a (const) wchar_t array of 'len' elements, starting at address +// 'begin'. +void UniversalPrintArray(const wchar_t* begin, size_t len, ostream* os) { + UniversalPrintCharArray(begin, len, os); +} + +namespace { + +// Prints a null-terminated C-style string to the ostream. +template +void PrintCStringTo(const Char* s, ostream* os) { + if (s == nullptr) { + *os << "NULL"; + } else { + *os << ImplicitCast_(s) << " pointing to "; + PrintCharsAsStringTo(s, std::char_traits::length(s), os); + } +} + +} // anonymous namespace + +void PrintTo(const char* s, ostream* os) { PrintCStringTo(s, os); } + +#ifdef __cpp_lib_char8_t +void PrintTo(const char8_t* s, ostream* os) { PrintCStringTo(s, os); } +#endif + +void PrintTo(const char16_t* s, ostream* os) { PrintCStringTo(s, os); } + +void PrintTo(const char32_t* s, ostream* os) { PrintCStringTo(s, os); } + +// MSVC compiler can be configured to define whar_t as a typedef +// of unsigned short. Defining an overload for const wchar_t* in that case +// would cause pointers to unsigned shorts be printed as wide strings, +// possibly accessing more memory than intended and causing invalid +// memory accesses. MSVC defines _NATIVE_WCHAR_T_DEFINED symbol when +// wchar_t is implemented as a native type. +#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED) +// Prints the given wide C string to the ostream. +void PrintTo(const wchar_t* s, ostream* os) { PrintCStringTo(s, os); } +#endif // wchar_t is native + +namespace { + +bool ContainsUnprintableControlCodes(const char* str, size_t length) { + const unsigned char* s = reinterpret_cast(str); + + for (size_t i = 0; i < length; i++) { + unsigned char ch = *s++; + if (std::iscntrl(ch)) { + switch (ch) { + case '\t': + case '\n': + case '\r': + break; + default: + return true; + } + } + } + return false; +} + +bool IsUTF8TrailByte(unsigned char t) { return 0x80 <= t && t <= 0xbf; } + +bool IsValidUTF8(const char* str, size_t length) { + const unsigned char* s = reinterpret_cast(str); + + for (size_t i = 0; i < length;) { + unsigned char lead = s[i++]; + + if (lead <= 0x7f) { + continue; // single-byte character (ASCII) 0..7F + } + if (lead < 0xc2) { + return false; // trail byte or non-shortest form + } else if (lead <= 0xdf && (i + 1) <= length && IsUTF8TrailByte(s[i])) { + ++i; // 2-byte character + } else if (0xe0 <= lead && lead <= 0xef && (i + 2) <= length && + IsUTF8TrailByte(s[i]) && IsUTF8TrailByte(s[i + 1]) && + // check for non-shortest form and surrogate + (lead != 0xe0 || s[i] >= 0xa0) && + (lead != 0xed || s[i] < 0xa0)) { + i += 2; // 3-byte character + } else if (0xf0 <= lead && lead <= 0xf4 && (i + 3) <= length && + IsUTF8TrailByte(s[i]) && IsUTF8TrailByte(s[i + 1]) && + IsUTF8TrailByte(s[i + 2]) && + // check for non-shortest form + (lead != 0xf0 || s[i] >= 0x90) && + (lead != 0xf4 || s[i] < 0x90)) { + i += 3; // 4-byte character + } else { + return false; + } + } + return true; +} + +void ConditionalPrintAsText(const char* str, size_t length, ostream* os) { + if (!ContainsUnprintableControlCodes(str, length) && + IsValidUTF8(str, length)) { + *os << "\n As Text: \"" << str << "\""; + } +} + +} // anonymous namespace + +void PrintStringTo(const ::std::string& s, ostream* os) { + if (PrintCharsAsStringTo(s.data(), s.size(), os) == kHexEscape) { + if (GTEST_FLAG_GET(print_utf8)) { + ConditionalPrintAsText(s.data(), s.size(), os); + } + } +} + +#ifdef __cpp_lib_char8_t +void PrintU8StringTo(const ::std::u8string& s, ostream* os) { + PrintCharsAsStringTo(s.data(), s.size(), os); +} +#endif + +void PrintU16StringTo(const ::std::u16string& s, ostream* os) { + PrintCharsAsStringTo(s.data(), s.size(), os); +} + +void PrintU32StringTo(const ::std::u32string& s, ostream* os) { + PrintCharsAsStringTo(s.data(), s.size(), os); +} + +#if GTEST_HAS_STD_WSTRING +void PrintWideStringTo(const ::std::wstring& s, ostream* os) { + PrintCharsAsStringTo(s.data(), s.size(), os); +} +#endif // GTEST_HAS_STD_WSTRING + +} // namespace internal + +} // namespace testing diff --git a/googletest/src/gtest-test-part.cc b/googletest/src/gtest-test-part.cc new file mode 100644 index 00000000..6f8ddd7c --- /dev/null +++ b/googletest/src/gtest-test-part.cc @@ -0,0 +1,106 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// +// The Google C++ Testing and Mocking Framework (Google Test) + +#include "gtest/gtest-test-part.h" + +#include +#include + +#include "gtest/internal/gtest-port.h" +#include "src/gtest-internal-inl.h" + +namespace testing { + +// Gets the summary of the failure message by omitting the stack trace +// in it. +std::string TestPartResult::ExtractSummary(const char* message) { + const char* const stack_trace = strstr(message, internal::kStackTraceMarker); + return stack_trace == nullptr ? message : std::string(message, stack_trace); +} + +// Prints a TestPartResult object. +std::ostream& operator<<(std::ostream& os, const TestPartResult& result) { + return os << internal::FormatFileLocation(result.file_name(), + result.line_number()) + << " " + << (result.type() == TestPartResult::kSuccess ? "Success" + : result.type() == TestPartResult::kSkip ? "Skipped" + : result.type() == TestPartResult::kFatalFailure + ? "Fatal failure" + : "Non-fatal failure") + << ":\n" + << result.message() << std::endl; +} + +// Appends a TestPartResult to the array. +void TestPartResultArray::Append(const TestPartResult& result) { + array_.push_back(result); +} + +// Returns the TestPartResult at the given index (0-based). +const TestPartResult& TestPartResultArray::GetTestPartResult(int index) const { + if (index < 0 || index >= size()) { + printf("\nInvalid index (%d) into TestPartResultArray.\n", index); + internal::posix::Abort(); + } + + return array_[static_cast(index)]; +} + +// Returns the number of TestPartResult objects in the array. +int TestPartResultArray::size() const { + return static_cast(array_.size()); +} + +namespace internal { + +HasNewFatalFailureHelper::HasNewFatalFailureHelper() + : has_new_fatal_failure_(false), + original_reporter_( + GetUnitTestImpl()->GetTestPartResultReporterForCurrentThread()) { + GetUnitTestImpl()->SetTestPartResultReporterForCurrentThread(this); +} + +HasNewFatalFailureHelper::~HasNewFatalFailureHelper() { + GetUnitTestImpl()->SetTestPartResultReporterForCurrentThread( + original_reporter_); +} + +void HasNewFatalFailureHelper::ReportTestPartResult( + const TestPartResult& result) { + if (result.fatally_failed()) has_new_fatal_failure_ = true; + original_reporter_->ReportTestPartResult(result); +} + +} // namespace internal + +} // namespace testing diff --git a/googletest/src/gtest-typed-test.cc b/googletest/src/gtest-typed-test.cc new file mode 100644 index 00000000..b251c09d --- /dev/null +++ b/googletest/src/gtest-typed-test.cc @@ -0,0 +1,108 @@ +// Copyright 2008 Google Inc. +// All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "gtest/gtest-typed-test.h" + +#include +#include +#include + +#include "gtest/gtest.h" + +namespace testing { +namespace internal { + +// Skips to the first non-space char in str. Returns an empty string if str +// contains only whitespace characters. +static const char* SkipSpaces(const char* str) { + while (IsSpace(*str)) str++; + return str; +} + +static std::vector SplitIntoTestNames(const char* src) { + std::vector name_vec; + src = SkipSpaces(src); + for (; src != nullptr; src = SkipComma(src)) { + name_vec.push_back(StripTrailingSpaces(GetPrefixUntilComma(src))); + } + return name_vec; +} + +// Verifies that registered_tests match the test names in +// registered_tests_; returns registered_tests if successful, or +// aborts the program otherwise. +const char* TypedTestSuitePState::VerifyRegisteredTestNames( + const char* test_suite_name, const char* file, int line, + const char* registered_tests) { + RegisterTypeParameterizedTestSuite(test_suite_name, CodeLocation(file, line)); + + typedef RegisteredTestsMap::const_iterator RegisteredTestIter; + registered_ = true; + + std::vector name_vec = SplitIntoTestNames(registered_tests); + + Message errors; + + std::set tests; + for (std::vector::const_iterator name_it = name_vec.begin(); + name_it != name_vec.end(); ++name_it) { + const std::string& name = *name_it; + if (tests.count(name) != 0) { + errors << "Test " << name << " is listed more than once.\n"; + continue; + } + + if (registered_tests_.count(name) != 0) { + tests.insert(name); + } else { + errors << "No test named " << name + << " can be found in this test suite.\n"; + } + } + + for (RegisteredTestIter it = registered_tests_.begin(); + it != registered_tests_.end(); ++it) { + if (tests.count(it->first) == 0) { + errors << "You forgot to list test " << it->first << ".\n"; + } + } + + const std::string& errors_str = errors.GetString(); + if (!errors_str.empty()) { + fprintf(stderr, "%s %s", FormatFileLocation(file, line).c_str(), + errors_str.c_str()); + fflush(stderr); + posix::Abort(); + } + + return registered_tests; +} + +} // namespace internal +} // namespace testing diff --git a/googletest/src/gtest.cc b/googletest/src/gtest.cc new file mode 100644 index 00000000..09af1517 --- /dev/null +++ b/googletest/src/gtest.cc @@ -0,0 +1,7083 @@ +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// +// The Google C++ Testing and Mocking Framework (Google Test) + +#include "gtest/gtest.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include // NOLINT +#include +#include // NOLINT: raise(3) is used on some platforms +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // NOLINT +#include +#include +#include +#include +#include + +#include "gtest/gtest-assertion-result.h" +#include "gtest/gtest-spi.h" +#include "gtest/internal/custom/gtest.h" +#include "gtest/internal/gtest-port.h" + +#ifdef GTEST_OS_LINUX + +#include // NOLINT +#include // NOLINT +#include // NOLINT +// Declares vsnprintf(). This header is not available on Windows. +#include // NOLINT +#include // NOLINT +#include // NOLINT +#include // NOLINT + +#include + +#elif defined(GTEST_OS_ZOS) +#include // NOLINT + +// On z/OS we additionally need strings.h for strcasecmp. +#include // NOLINT + +#elif defined(GTEST_OS_WINDOWS_MOBILE) // We are on Windows CE. + +#include // NOLINT +#undef min + +#elif defined(GTEST_OS_WINDOWS) // We are on Windows proper. + +#include // NOLINT +#undef min + +#ifdef _MSC_VER +#include // NOLINT +#endif + +#include // NOLINT +#include // NOLINT +#include // NOLINT +#include // NOLINT + +#ifdef GTEST_OS_WINDOWS_MINGW +#include // NOLINT +#endif // GTEST_OS_WINDOWS_MINGW + +#else + +// cpplint thinks that the header is already included, so we want to +// silence it. +#include // NOLINT +#include // NOLINT + +#endif // GTEST_OS_LINUX + +#if GTEST_HAS_EXCEPTIONS +#include +#endif + +#if GTEST_CAN_STREAM_RESULTS_ +#include // NOLINT +#include // NOLINT +#include // NOLINT +#include // NOLINT +#endif + +#include "src/gtest-internal-inl.h" + +#ifdef GTEST_OS_WINDOWS +#define vsnprintf _vsnprintf +#endif // GTEST_OS_WINDOWS + +#ifdef GTEST_OS_MAC +#ifndef GTEST_OS_IOS +#include +#endif +#endif + +#ifdef GTEST_HAS_ABSL +#include "absl/container/flat_hash_set.h" +#include "absl/debugging/failure_signal_handler.h" +#include "absl/debugging/stacktrace.h" +#include "absl/debugging/symbolize.h" +#include "absl/flags/parse.h" +#include "absl/flags/usage.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_replace.h" +#include "absl/strings/string_view.h" +#include "absl/strings/strip.h" +#endif // GTEST_HAS_ABSL + +// Checks builtin compiler feature |x| while avoiding an extra layer of #ifdefs +// at the callsite. +#if defined(__has_builtin) +#define GTEST_HAS_BUILTIN(x) __has_builtin(x) +#else +#define GTEST_HAS_BUILTIN(x) 0 +#endif // defined(__has_builtin) + +#if defined(GTEST_HAS_ABSL) && !defined(GTEST_NO_ABSL_FLAGS) +#define GTEST_HAS_ABSL_FLAGS +#endif + +namespace testing { + +using internal::CountIf; +using internal::ForEach; +using internal::GetElementOr; +using internal::Shuffle; + +// Constants. + +// A test whose test suite name or test name matches this filter is +// disabled and not run. +static const char kDisableTestFilter[] = "DISABLED_*:*/DISABLED_*"; + +// A test suite whose name matches this filter is considered a death +// test suite and will be run before test suites whose name doesn't +// match this filter. +static const char kDeathTestSuiteFilter[] = "*DeathTest:*DeathTest/*"; + +// A test filter that matches everything. +static const char kUniversalFilter[] = "*"; + +// The default output format. +static const char kDefaultOutputFormat[] = "xml"; +// The default output file. +static const char kDefaultOutputFile[] = "test_detail"; + +// These environment variables are set by Bazel. +// https://bazel.build/reference/test-encyclopedia#initial-conditions +// +// The environment variable name for the test shard index. +static const char kTestShardIndex[] = "GTEST_SHARD_INDEX"; +// The environment variable name for the total number of test shards. +static const char kTestTotalShards[] = "GTEST_TOTAL_SHARDS"; +// The environment variable name for the test shard status file. +static const char kTestShardStatusFile[] = "GTEST_SHARD_STATUS_FILE"; +// The environment variable name for the test output warnings file. +static const char kTestWarningsOutputFile[] = "TEST_WARNINGS_OUTPUT_FILE"; + +namespace internal { + +// The text used in failure messages to indicate the start of the +// stack trace. +const char kStackTraceMarker[] = "\nStack trace:\n"; + +// g_help_flag is true if and only if the --help flag or an equivalent form +// is specified on the command line. +bool g_help_flag = false; + +#if GTEST_HAS_FILE_SYSTEM +// Utility function to Open File for Writing +static FILE* OpenFileForWriting(const std::string& output_file) { + FILE* fileout = nullptr; + FilePath output_file_path(output_file); + FilePath output_dir(output_file_path.RemoveFileName()); + + if (output_dir.CreateDirectoriesRecursively()) { + fileout = posix::FOpen(output_file.c_str(), "w"); + } + if (fileout == nullptr) { + GTEST_LOG_(FATAL) << "Unable to open file \"" << output_file << "\""; + } + return fileout; +} +#endif // GTEST_HAS_FILE_SYSTEM + +} // namespace internal + +// Bazel passes in the argument to '--test_filter' via the TESTBRIDGE_TEST_ONLY +// environment variable. +static const char* GetDefaultFilter() { + const char* const testbridge_test_only = + internal::posix::GetEnv("TESTBRIDGE_TEST_ONLY"); + if (testbridge_test_only != nullptr) { + return testbridge_test_only; + } + return kUniversalFilter; +} + +// Bazel passes in the argument to '--test_runner_fail_fast' via the +// TESTBRIDGE_TEST_RUNNER_FAIL_FAST environment variable. +static bool GetDefaultFailFast() { + const char* const testbridge_test_runner_fail_fast = + internal::posix::GetEnv("TESTBRIDGE_TEST_RUNNER_FAIL_FAST"); + if (testbridge_test_runner_fail_fast != nullptr) { + return strcmp(testbridge_test_runner_fail_fast, "1") == 0; + } + return false; +} + +} // namespace testing + +GTEST_DEFINE_bool_( + fail_fast, + testing::internal::BoolFromGTestEnv("fail_fast", + testing::GetDefaultFailFast()), + "True if and only if a test failure should stop further test execution."); + +GTEST_DEFINE_bool_( + fail_if_no_test_linked, + testing::internal::BoolFromGTestEnv("fail_if_no_test_linked", false), + "True if and only if the test should fail if no test case (including " + "disabled test cases) is linked."); + +GTEST_DEFINE_bool_( + also_run_disabled_tests, + testing::internal::BoolFromGTestEnv("also_run_disabled_tests", false), + "Run disabled tests too, in addition to the tests normally being run."); + +GTEST_DEFINE_bool_( + break_on_failure, + testing::internal::BoolFromGTestEnv("break_on_failure", false), + "True if and only if a failed assertion should be a debugger " + "break-point."); + +GTEST_DEFINE_bool_(catch_exceptions, + testing::internal::BoolFromGTestEnv("catch_exceptions", + true), + "True if and only if " GTEST_NAME_ + " should catch exceptions and treat them as test failures."); + +GTEST_DEFINE_string_( + color, testing::internal::StringFromGTestEnv("color", "auto"), + "Whether to use colors in the output. Valid values: yes, no, " + "and auto. 'auto' means to use colors if the output is " + "being sent to a terminal and the TERM environment variable " + "is set to a terminal type that supports colors."); + +GTEST_DEFINE_string_( + filter, + testing::internal::StringFromGTestEnv("filter", + testing::GetDefaultFilter()), + "A colon-separated list of glob (not regex) patterns " + "for filtering the tests to run, optionally followed by a " + "'-' and a : separated list of negative patterns (tests to " + "exclude). A test is run if it matches one of the positive " + "patterns and does not match any of the negative patterns."); + +GTEST_DEFINE_bool_( + install_failure_signal_handler, + testing::internal::BoolFromGTestEnv("install_failure_signal_handler", + false), + "If true and supported on the current platform, " GTEST_NAME_ + " should " + "install a signal handler that dumps debugging information when fatal " + "signals are raised."); + +GTEST_DEFINE_bool_(list_tests, false, "List all tests without running them."); + +// The net priority order after flag processing is thus: +// --gtest_output command line flag +// GTEST_OUTPUT environment variable +// XML_OUTPUT_FILE environment variable +// '' +GTEST_DEFINE_string_( + output, + testing::internal::StringFromGTestEnv( + "output", testing::internal::OutputFlagAlsoCheckEnvVar().c_str()), + "A format (defaults to \"xml\" but can be specified to be \"json\"), " + "optionally followed by a colon and an output file name or directory. " + "A directory is indicated by a trailing pathname separator. " + "Examples: \"xml:filename.xml\", \"xml::directoryname/\". " + "If a directory is specified, output files will be created " + "within that directory, with file-names based on the test " + "executable's name and, if necessary, made unique by adding " + "digits."); + +GTEST_DEFINE_bool_( + brief, testing::internal::BoolFromGTestEnv("brief", false), + "True if only test failures should be displayed in text output."); + +GTEST_DEFINE_bool_(print_time, + testing::internal::BoolFromGTestEnv("print_time", true), + "True if and only if " GTEST_NAME_ + " should display elapsed time in text output."); + +GTEST_DEFINE_bool_(print_utf8, + testing::internal::BoolFromGTestEnv("print_utf8", true), + "True if and only if " GTEST_NAME_ + " prints UTF8 characters as text."); + +GTEST_DEFINE_int32_( + random_seed, testing::internal::Int32FromGTestEnv("random_seed", 0), + "Random number seed to use when shuffling test orders. Must be in range " + "[1, 99999], or 0 to use a seed based on the current time."); + +GTEST_DEFINE_int32_( + repeat, testing::internal::Int32FromGTestEnv("repeat", 1), + "How many times to repeat each test. Specify a negative number " + "for repeating forever. Useful for shaking out flaky tests."); + +GTEST_DEFINE_bool_( + recreate_environments_when_repeating, + testing::internal::BoolFromGTestEnv("recreate_environments_when_repeating", + false), + "Controls whether global test environments are recreated for each repeat " + "of the tests. If set to false the global test environments are only set " + "up once, for the first iteration, and only torn down once, for the last. " + "Useful for shaking out flaky tests with stable, expensive test " + "environments. If --gtest_repeat is set to a negative number, meaning " + "there is no last run, the environments will always be recreated to avoid " + "leaks."); + +GTEST_DEFINE_bool_(show_internal_stack_frames, false, + "True if and only if " GTEST_NAME_ + " should include internal stack frames when " + "printing test failure stack traces."); + +GTEST_DEFINE_bool_(shuffle, + testing::internal::BoolFromGTestEnv("shuffle", false), + "True if and only if " GTEST_NAME_ + " should randomize tests' order on every run."); + +GTEST_DEFINE_int32_( + stack_trace_depth, + testing::internal::Int32FromGTestEnv("stack_trace_depth", + testing::kMaxStackTraceDepth), + "The maximum number of stack frames to print when an " + "assertion fails. The valid range is 0 through 100, inclusive."); + +GTEST_DEFINE_string_( + stream_result_to, + testing::internal::StringFromGTestEnv("stream_result_to", ""), + "This flag specifies the host name and the port number on which to stream " + "test results. Example: \"localhost:555\". The flag is effective only on " + "Linux and macOS."); + +GTEST_DEFINE_bool_( + throw_on_failure, + testing::internal::BoolFromGTestEnv("throw_on_failure", false), + "When this flag is specified, a failed assertion will throw an exception " + "if exceptions are enabled or exit the program with a non-zero code " + "otherwise. For use with an external test framework."); + +#if GTEST_USE_OWN_FLAGFILE_FLAG_ +GTEST_DEFINE_string_( + flagfile, testing::internal::StringFromGTestEnv("flagfile", ""), + "This flag specifies the flagfile to read command-line flags from."); +#endif // GTEST_USE_OWN_FLAGFILE_FLAG_ + +namespace testing { +namespace internal { + +const uint32_t Random::kMaxRange; + +// Generates a random number from [0, range), using a Linear +// Congruential Generator (LCG). Crashes if 'range' is 0 or greater +// than kMaxRange. +uint32_t Random::Generate(uint32_t range) { + // These constants are the same as are used in glibc's rand(3). + // Use wider types than necessary to prevent unsigned overflow diagnostics. + state_ = static_cast(1103515245ULL * state_ + 12345U) % kMaxRange; + + GTEST_CHECK_(range > 0) << "Cannot generate a number in the range [0, 0)."; + GTEST_CHECK_(range <= kMaxRange) + << "Generation of a number in [0, " << range << ") was requested, " + << "but this can only generate numbers in [0, " << kMaxRange << ")."; + + // Converting via modulus introduces a bit of downward bias, but + // it's simple, and a linear congruential generator isn't too good + // to begin with. + return state_ % range; +} + +// GTestIsInitialized() returns true if and only if the user has initialized +// Google Test. Useful for catching the user mistake of not initializing +// Google Test before calling RUN_ALL_TESTS(). +static bool GTestIsInitialized() { return !GetArgvs().empty(); } + +// Iterates over a vector of TestSuites, keeping a running sum of the +// results of calling a given int-returning method on each. +// Returns the sum. +static int SumOverTestSuiteList(const std::vector& case_list, + int (TestSuite::*method)() const) { + int sum = 0; + for (size_t i = 0; i < case_list.size(); i++) { + sum += (case_list[i]->*method)(); + } + return sum; +} + +// Returns true if and only if the test suite passed. +static bool TestSuitePassed(const TestSuite* test_suite) { + return test_suite->should_run() && test_suite->Passed(); +} + +// Returns true if and only if the test suite failed. +static bool TestSuiteFailed(const TestSuite* test_suite) { + return test_suite->should_run() && test_suite->Failed(); +} + +// Returns true if and only if test_suite contains at least one test that +// should run. +static bool ShouldRunTestSuite(const TestSuite* test_suite) { + return test_suite->should_run(); +} + +namespace { + +// Returns true if test part results of type `type` should include a stack +// trace. +bool ShouldEmitStackTraceForResultType(TestPartResult::Type type) { + // Suppress emission of the stack trace for SUCCEED() since it likely never + // requires investigation, and GTEST_SKIP() since skipping is an intentional + // act by the developer rather than a failure requiring investigation. + return type != TestPartResult::kSuccess && type != TestPartResult::kSkip; +} + +} // namespace + +// AssertHelper constructor. +AssertHelper::AssertHelper(TestPartResult::Type type, const char* file, + int line, const char* message) + : data_(new AssertHelperData(type, file, line, message)) {} + +AssertHelper::~AssertHelper() { delete data_; } + +// Message assignment, for assertion streaming support. +void AssertHelper::operator=(const Message& message) const { + UnitTest::GetInstance()->AddTestPartResult( + data_->type, data_->file, data_->line, + AppendUserMessage(data_->message, message), + ShouldEmitStackTraceForResultType(data_->type) + ? UnitTest::GetInstance()->impl()->CurrentOsStackTraceExceptTop(1) + : "" + // Skips the stack frame for this function itself. + ); // NOLINT +} + +namespace { + +// When TEST_P is found without a matching INSTANTIATE_TEST_SUITE_P +// to creates test cases for it, a synthetic test case is +// inserted to report ether an error or a log message. +// +// This configuration bit will likely be removed at some point. +constexpr bool kErrorOnUninstantiatedParameterizedTest = true; +constexpr bool kErrorOnUninstantiatedTypeParameterizedTest = true; + +// A test that fails at a given file/line location with a given message. +class FailureTest : public Test { + public: + explicit FailureTest(const CodeLocation& loc, std::string error_message, + bool as_error) + : loc_(loc), + error_message_(std::move(error_message)), + as_error_(as_error) {} + + void TestBody() override { + if (as_error_) { + AssertHelper(TestPartResult::kNonFatalFailure, loc_.file.c_str(), + loc_.line, "") = Message() << error_message_; + } else { + std::cout << error_message_ << std::endl; + } + } + + private: + const CodeLocation loc_; + const std::string error_message_; + const bool as_error_; +}; + +} // namespace + +std::set* GetIgnoredParameterizedTestSuites() { + return UnitTest::GetInstance()->impl()->ignored_parameterized_test_suites(); +} + +// Add a given test_suit to the list of them allow to go un-instantiated. +MarkAsIgnored::MarkAsIgnored(const char* test_suite) { + GetIgnoredParameterizedTestSuites()->insert(test_suite); +} + +// If this parameterized test suite has no instantiations (and that +// has not been marked as okay), emit a test case reporting that. +void InsertSyntheticTestCase(const std::string& name, CodeLocation location, + bool has_test_p) { + const auto& ignored = *GetIgnoredParameterizedTestSuites(); + if (ignored.find(name) != ignored.end()) return; + + const char kMissingInstantiation[] = // + " is defined via TEST_P, but never instantiated. None of the test " + "cases " + "will run. Either no INSTANTIATE_TEST_SUITE_P is provided or the only " + "ones provided expand to nothing." + "\n\n" + "Ideally, TEST_P definitions should only ever be included as part of " + "binaries that intend to use them. (As opposed to, for example, being " + "placed in a library that may be linked in to get other utilities.)"; + + const char kMissingTestCase[] = // + " is instantiated via INSTANTIATE_TEST_SUITE_P, but no tests are " + "defined via TEST_P . No test cases will run." + "\n\n" + "Ideally, INSTANTIATE_TEST_SUITE_P should only ever be invoked from " + "code that always depend on code that provides TEST_P. Failing to do " + "so is often an indication of dead code, e.g. the last TEST_P was " + "removed but the rest got left behind."; + + std::string message = + "Parameterized test suite " + name + + (has_test_p ? kMissingInstantiation : kMissingTestCase) + + "\n\n" + "To suppress this error for this test suite, insert the following line " + "(in a non-header) in the namespace it is defined in:" + "\n\n" + "GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(" + + name + ");"; + + std::string full_name = "UninstantiatedParameterizedTestSuite<" + name + ">"; + RegisterTest( // + "GoogleTestVerification", full_name.c_str(), + nullptr, // No type parameter. + nullptr, // No value parameter. + location.file.c_str(), location.line, [message, location] { + return new FailureTest(location, message, + kErrorOnUninstantiatedParameterizedTest); + }); +} + +void RegisterTypeParameterizedTestSuite(const char* test_suite_name, + CodeLocation code_location) { + GetUnitTestImpl()->type_parameterized_test_registry().RegisterTestSuite( + test_suite_name, std::move(code_location)); +} + +void RegisterTypeParameterizedTestSuiteInstantiation(const char* case_name) { + GetUnitTestImpl()->type_parameterized_test_registry().RegisterInstantiation( + case_name); +} + +void TypeParameterizedTestSuiteRegistry::RegisterTestSuite( + const char* test_suite_name, CodeLocation code_location) { + suites_.emplace(std::string(test_suite_name), + TypeParameterizedTestSuiteInfo(std::move(code_location))); +} + +void TypeParameterizedTestSuiteRegistry::RegisterInstantiation( + const char* test_suite_name) { + auto it = suites_.find(std::string(test_suite_name)); + if (it != suites_.end()) { + it->second.instantiated = true; + } else { + GTEST_LOG_(ERROR) << "Unknown type parameterized test suit '" + << test_suite_name << "'"; + } +} + +void TypeParameterizedTestSuiteRegistry::CheckForInstantiations() { + const auto& ignored = *GetIgnoredParameterizedTestSuites(); + for (const auto& testcase : suites_) { + if (testcase.second.instantiated) continue; + if (ignored.find(testcase.first) != ignored.end()) continue; + + std::string message = + "Type parameterized test suite " + testcase.first + + " is defined via REGISTER_TYPED_TEST_SUITE_P, but never instantiated " + "via INSTANTIATE_TYPED_TEST_SUITE_P. None of the test cases will run." + "\n\n" + "Ideally, TYPED_TEST_P definitions should only ever be included as " + "part of binaries that intend to use them. (As opposed to, for " + "example, being placed in a library that may be linked in to get " + "other " + "utilities.)" + "\n\n" + "To suppress this error for this test suite, insert the following " + "line " + "(in a non-header) in the namespace it is defined in:" + "\n\n" + "GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(" + + testcase.first + ");"; + + std::string full_name = + "UninstantiatedTypeParameterizedTestSuite<" + testcase.first + ">"; + RegisterTest( // + "GoogleTestVerification", full_name.c_str(), + nullptr, // No type parameter. + nullptr, // No value parameter. + testcase.second.code_location.file.c_str(), + testcase.second.code_location.line, [message, testcase] { + return new FailureTest(testcase.second.code_location, message, + kErrorOnUninstantiatedTypeParameterizedTest); + }); + } +} + +// A copy of all command line arguments. Set by InitGoogleTest(). +static ::std::vector g_argvs; + +::std::vector GetArgvs() { +#if defined(GTEST_CUSTOM_GET_ARGVS_) + // GTEST_CUSTOM_GET_ARGVS_() may return a container of std::string or + // ::string. This code converts it to the appropriate type. + const auto& custom = GTEST_CUSTOM_GET_ARGVS_(); + return ::std::vector(custom.begin(), custom.end()); +#else // defined(GTEST_CUSTOM_GET_ARGVS_) + return g_argvs; +#endif // defined(GTEST_CUSTOM_GET_ARGVS_) +} + +#if GTEST_HAS_FILE_SYSTEM +// Returns the current application's name, removing directory path if that +// is present. +FilePath GetCurrentExecutableName() { + FilePath result; + + auto args = GetArgvs(); + if (!args.empty()) { +#if defined(GTEST_OS_WINDOWS) || defined(GTEST_OS_OS2) + result.Set(FilePath(args[0]).RemoveExtension("exe")); +#else + result.Set(FilePath(args[0])); +#endif // GTEST_OS_WINDOWS + } + + return result.RemoveDirectoryName(); +} +#endif // GTEST_HAS_FILE_SYSTEM + +// Functions for processing the gtest_output flag. + +// Returns the output format, or "" for normal printed output. +std::string UnitTestOptions::GetOutputFormat() { + std::string s = GTEST_FLAG_GET(output); + const char* const gtest_output_flag = s.c_str(); + const char* const colon = strchr(gtest_output_flag, ':'); + return (colon == nullptr) + ? std::string(gtest_output_flag) + : std::string(gtest_output_flag, + static_cast(colon - gtest_output_flag)); +} + +#if GTEST_HAS_FILE_SYSTEM +// Returns the name of the requested output file, or the default if none +// was explicitly specified. +std::string UnitTestOptions::GetAbsolutePathToOutputFile() { + std::string s = GTEST_FLAG_GET(output); + const char* const gtest_output_flag = s.c_str(); + + std::string format = GetOutputFormat(); + if (format.empty()) format = std::string(kDefaultOutputFormat); + + const char* const colon = strchr(gtest_output_flag, ':'); + if (colon == nullptr) + return internal::FilePath::MakeFileName( + internal::FilePath( + UnitTest::GetInstance()->original_working_dir()), + internal::FilePath(kDefaultOutputFile), 0, format.c_str()) + .string(); + + internal::FilePath output_name(colon + 1); + if (!output_name.IsAbsolutePath()) + output_name = internal::FilePath::ConcatPaths( + internal::FilePath(UnitTest::GetInstance()->original_working_dir()), + internal::FilePath(colon + 1)); + + if (!output_name.IsDirectory()) return output_name.string(); + + internal::FilePath result(internal::FilePath::GenerateUniqueFileName( + output_name, internal::GetCurrentExecutableName(), + GetOutputFormat().c_str())); + return result.string(); +} +#endif // GTEST_HAS_FILE_SYSTEM + +// Returns true if and only if the wildcard pattern matches the string. Each +// pattern consists of regular characters, single-character wildcards (?), and +// multi-character wildcards (*). +// +// This function implements a linear-time string globbing algorithm based on +// https://research.swtch.com/glob. +static bool PatternMatchesString(const std::string& name_str, + const char* pattern, const char* pattern_end) { + const char* name = name_str.c_str(); + const char* const name_begin = name; + const char* const name_end = name + name_str.size(); + + const char* pattern_next = pattern; + const char* name_next = name; + + while (pattern < pattern_end || name < name_end) { + if (pattern < pattern_end) { + switch (*pattern) { + default: // Match an ordinary character. + if (name < name_end && *name == *pattern) { + ++pattern; + ++name; + continue; + } + break; + case '?': // Match any single character. + if (name < name_end) { + ++pattern; + ++name; + continue; + } + break; + case '*': + // Match zero or more characters. Start by skipping over the wildcard + // and matching zero characters from name. If that fails, restart and + // match one more character than the last attempt. + pattern_next = pattern; + name_next = name + 1; + ++pattern; + continue; + } + } + // Failed to match a character. Restart if possible. + if (name_begin < name_next && name_next <= name_end) { + pattern = pattern_next; + name = name_next; + continue; + } + return false; + } + return true; +} + +namespace { + +bool IsGlobPattern(const std::string& pattern) { + return std::any_of(pattern.begin(), pattern.end(), + [](const char c) { return c == '?' || c == '*'; }); +} + +class UnitTestFilter { + public: + UnitTestFilter() = default; + + // Constructs a filter from a string of patterns separated by `:`. + explicit UnitTestFilter(const std::string& filter) { + // By design "" filter matches "" string. + std::vector all_patterns; + SplitString(filter, ':', &all_patterns); + const auto exact_match_patterns_begin = std::partition( + all_patterns.begin(), all_patterns.end(), &IsGlobPattern); + + glob_patterns_.reserve(static_cast( + std::distance(all_patterns.begin(), exact_match_patterns_begin))); + std::move(all_patterns.begin(), exact_match_patterns_begin, + std::inserter(glob_patterns_, glob_patterns_.begin())); + std::move( + exact_match_patterns_begin, all_patterns.end(), + std::inserter(exact_match_patterns_, exact_match_patterns_.begin())); + } + + // Returns true if and only if name matches at least one of the patterns in + // the filter. + bool MatchesName(const std::string& name) const { + return exact_match_patterns_.find(name) != exact_match_patterns_.end() || + std::any_of(glob_patterns_.begin(), glob_patterns_.end(), + [&name](const std::string& pattern) { + return PatternMatchesString( + name, pattern.c_str(), + pattern.c_str() + pattern.size()); + }); + } + + private: + std::vector glob_patterns_; + std::unordered_set exact_match_patterns_; +}; + +class PositiveAndNegativeUnitTestFilter { + public: + // Constructs a positive and a negative filter from a string. The string + // contains a positive filter optionally followed by a '-' character and a + // negative filter. In case only a negative filter is provided the positive + // filter will be assumed "*". + // A filter is a list of patterns separated by ':'. + explicit PositiveAndNegativeUnitTestFilter(const std::string& filter) { + std::vector positive_and_negative_filters; + + // NOTE: `SplitString` always returns a non-empty container. + SplitString(filter, '-', &positive_and_negative_filters); + const auto& positive_filter = positive_and_negative_filters.front(); + + if (positive_and_negative_filters.size() > 1) { + positive_filter_ = UnitTestFilter( + positive_filter.empty() ? kUniversalFilter : positive_filter); + + // TODO(b/214626361): Fail on multiple '-' characters + // For the moment to preserve old behavior we concatenate the rest of the + // string parts with `-` as separator to generate the negative filter. + auto negative_filter_string = positive_and_negative_filters[1]; + for (std::size_t i = 2; i < positive_and_negative_filters.size(); i++) + negative_filter_string = + negative_filter_string + '-' + positive_and_negative_filters[i]; + negative_filter_ = UnitTestFilter(negative_filter_string); + } else { + // In case we don't have a negative filter and positive filter is "" + // we do not use kUniversalFilter by design as opposed to when we have a + // negative filter. + positive_filter_ = UnitTestFilter(positive_filter); + } + } + + // Returns true if and only if test name (this is generated by appending test + // suit name and test name via a '.' character) matches the positive filter + // and does not match the negative filter. + bool MatchesTest(const std::string& test_suite_name, + const std::string& test_name) const { + return MatchesName(test_suite_name + "." + test_name); + } + + // Returns true if and only if name matches the positive filter and does not + // match the negative filter. + bool MatchesName(const std::string& name) const { + return positive_filter_.MatchesName(name) && + !negative_filter_.MatchesName(name); + } + + private: + UnitTestFilter positive_filter_; + UnitTestFilter negative_filter_; +}; +} // namespace + +bool UnitTestOptions::MatchesFilter(const std::string& name_str, + const char* filter) { + return UnitTestFilter(filter).MatchesName(name_str); +} + +// Returns true if and only if the user-specified filter matches the test +// suite name and the test name. +bool UnitTestOptions::FilterMatchesTest(const std::string& test_suite_name, + const std::string& test_name) { + // Split --gtest_filter at '-', if there is one, to separate into + // positive filter and negative filter portions + return PositiveAndNegativeUnitTestFilter(GTEST_FLAG_GET(filter)) + .MatchesTest(test_suite_name, test_name); +} + +#if GTEST_HAS_SEH +static std::string FormatSehExceptionMessage(DWORD exception_code, + const char* location) { + Message message; + message << "SEH exception with code 0x" << std::setbase(16) << exception_code + << std::setbase(10) << " thrown in " << location << "."; + return message.GetString(); +} + +int UnitTestOptions::GTestProcessSEH(DWORD seh_code, const char* location) { + // Google Test should handle a SEH exception if: + // 1. the user wants it to, AND + // 2. this is not a breakpoint exception or stack overflow, AND + // 3. this is not a C++ exception (VC++ implements them via SEH, + // apparently). + // + // SEH exception code for C++ exceptions. + // (see https://support.microsoft.com/kb/185294 for more information). + const DWORD kCxxExceptionCode = 0xe06d7363; + + if (!GTEST_FLAG_GET(catch_exceptions) || seh_code == kCxxExceptionCode || + seh_code == EXCEPTION_BREAKPOINT || + seh_code == EXCEPTION_STACK_OVERFLOW) { + return EXCEPTION_CONTINUE_SEARCH; // Don't handle these exceptions + } + + internal::ReportFailureInUnknownLocation( + TestPartResult::kFatalFailure, + FormatSehExceptionMessage(seh_code, location) + + "\n" + "Stack trace:\n" + + ::testing::internal::GetCurrentOsStackTraceExceptTop(1)); + + return EXCEPTION_EXECUTE_HANDLER; +} +#endif // GTEST_HAS_SEH + +} // namespace internal + +// The c'tor sets this object as the test part result reporter used by +// Google Test. The 'result' parameter specifies where to report the +// results. Intercepts only failures from the current thread. +ScopedFakeTestPartResultReporter::ScopedFakeTestPartResultReporter( + TestPartResultArray* result) + : intercept_mode_(INTERCEPT_ONLY_CURRENT_THREAD), result_(result) { + Init(); +} + +// The c'tor sets this object as the test part result reporter used by +// Google Test. The 'result' parameter specifies where to report the +// results. +ScopedFakeTestPartResultReporter::ScopedFakeTestPartResultReporter( + InterceptMode intercept_mode, TestPartResultArray* result) + : intercept_mode_(intercept_mode), result_(result) { + Init(); +} + +void ScopedFakeTestPartResultReporter::Init() { + internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); + if (intercept_mode_ == INTERCEPT_ALL_THREADS) { + old_reporter_ = impl->GetGlobalTestPartResultReporter(); + impl->SetGlobalTestPartResultReporter(this); + } else { + old_reporter_ = impl->GetTestPartResultReporterForCurrentThread(); + impl->SetTestPartResultReporterForCurrentThread(this); + } +} + +// The d'tor restores the test part result reporter used by Google Test +// before. +ScopedFakeTestPartResultReporter::~ScopedFakeTestPartResultReporter() { + internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); + if (intercept_mode_ == INTERCEPT_ALL_THREADS) { + impl->SetGlobalTestPartResultReporter(old_reporter_); + } else { + impl->SetTestPartResultReporterForCurrentThread(old_reporter_); + } +} + +// Increments the test part result count and remembers the result. +// This method is from the TestPartResultReporterInterface interface. +void ScopedFakeTestPartResultReporter::ReportTestPartResult( + const TestPartResult& result) { + result_->Append(result); +} + +namespace internal { + +// Returns the type ID of ::testing::Test. We should always call this +// instead of GetTypeId< ::testing::Test>() to get the type ID of +// testing::Test. This is to work around a suspected linker bug when +// using Google Test as a framework on Mac OS X. The bug causes +// GetTypeId< ::testing::Test>() to return different values depending +// on whether the call is from the Google Test framework itself or +// from user test code. GetTestTypeId() is guaranteed to always +// return the same value, as it always calls GetTypeId<>() from the +// gtest.cc, which is within the Google Test framework. +TypeId GetTestTypeId() { return GetTypeId(); } + +// The value of GetTestTypeId() as seen from within the Google Test +// library. This is solely for testing GetTestTypeId(). +extern const TypeId kTestTypeIdInGoogleTest = GetTestTypeId(); + +// This predicate-formatter checks that 'results' contains a test part +// failure of the given type and that the failure message contains the +// given substring. +static AssertionResult HasOneFailure(const char* /* results_expr */, + const char* /* type_expr */, + const char* /* substr_expr */, + const TestPartResultArray& results, + TestPartResult::Type type, + const std::string& substr) { + const std::string expected(type == TestPartResult::kFatalFailure + ? "1 fatal failure" + : "1 non-fatal failure"); + Message msg; + if (results.size() != 1) { + msg << "Expected: " << expected << "\n" + << " Actual: " << results.size() << " failures"; + for (int i = 0; i < results.size(); i++) { + msg << "\n" << results.GetTestPartResult(i); + } + return AssertionFailure() << msg; + } + + const TestPartResult& r = results.GetTestPartResult(0); + if (r.type() != type) { + return AssertionFailure() << "Expected: " << expected << "\n" + << " Actual:\n" + << r; + } + + if (strstr(r.message(), substr.c_str()) == nullptr) { + return AssertionFailure() + << "Expected: " << expected << " containing \"" << substr << "\"\n" + << " Actual:\n" + << r; + } + + return AssertionSuccess(); +} + +// The constructor of SingleFailureChecker remembers where to look up +// test part results, what type of failure we expect, and what +// substring the failure message should contain. +SingleFailureChecker::SingleFailureChecker(const TestPartResultArray* results, + TestPartResult::Type type, + const std::string& substr) + : results_(results), type_(type), substr_(substr) {} + +// The destructor of SingleFailureChecker verifies that the given +// TestPartResultArray contains exactly one failure that has the given +// type and contains the given substring. If that's not the case, a +// non-fatal failure will be generated. +SingleFailureChecker::~SingleFailureChecker() { + EXPECT_PRED_FORMAT3(HasOneFailure, *results_, type_, substr_); +} + +DefaultGlobalTestPartResultReporter::DefaultGlobalTestPartResultReporter( + UnitTestImpl* unit_test) + : unit_test_(unit_test) {} + +void DefaultGlobalTestPartResultReporter::ReportTestPartResult( + const TestPartResult& result) { + unit_test_->current_test_result()->AddTestPartResult(result); + unit_test_->listeners()->repeater()->OnTestPartResult(result); +} + +DefaultPerThreadTestPartResultReporter::DefaultPerThreadTestPartResultReporter( + UnitTestImpl* unit_test) + : unit_test_(unit_test) {} + +void DefaultPerThreadTestPartResultReporter::ReportTestPartResult( + const TestPartResult& result) { + unit_test_->GetGlobalTestPartResultReporter()->ReportTestPartResult(result); +} + +// Returns the global test part result reporter. +TestPartResultReporterInterface* +UnitTestImpl::GetGlobalTestPartResultReporter() { + internal::MutexLock lock(&global_test_part_result_reporter_mutex_); + return global_test_part_result_reporter_; +} + +// Sets the global test part result reporter. +void UnitTestImpl::SetGlobalTestPartResultReporter( + TestPartResultReporterInterface* reporter) { + internal::MutexLock lock(&global_test_part_result_reporter_mutex_); + global_test_part_result_reporter_ = reporter; +} + +// Returns the test part result reporter for the current thread. +TestPartResultReporterInterface* +UnitTestImpl::GetTestPartResultReporterForCurrentThread() { + return per_thread_test_part_result_reporter_.get(); +} + +// Sets the test part result reporter for the current thread. +void UnitTestImpl::SetTestPartResultReporterForCurrentThread( + TestPartResultReporterInterface* reporter) { + per_thread_test_part_result_reporter_.set(reporter); +} + +// Gets the number of successful test suites. +int UnitTestImpl::successful_test_suite_count() const { + return CountIf(test_suites_, TestSuitePassed); +} + +// Gets the number of failed test suites. +int UnitTestImpl::failed_test_suite_count() const { + return CountIf(test_suites_, TestSuiteFailed); +} + +// Gets the number of all test suites. +int UnitTestImpl::total_test_suite_count() const { + return static_cast(test_suites_.size()); +} + +// Gets the number of all test suites that contain at least one test +// that should run. +int UnitTestImpl::test_suite_to_run_count() const { + return CountIf(test_suites_, ShouldRunTestSuite); +} + +// Gets the number of successful tests. +int UnitTestImpl::successful_test_count() const { + return SumOverTestSuiteList(test_suites_, &TestSuite::successful_test_count); +} + +// Gets the number of skipped tests. +int UnitTestImpl::skipped_test_count() const { + return SumOverTestSuiteList(test_suites_, &TestSuite::skipped_test_count); +} + +// Gets the number of failed tests. +int UnitTestImpl::failed_test_count() const { + return SumOverTestSuiteList(test_suites_, &TestSuite::failed_test_count); +} + +// Gets the number of disabled tests that will be reported in the XML report. +int UnitTestImpl::reportable_disabled_test_count() const { + return SumOverTestSuiteList(test_suites_, + &TestSuite::reportable_disabled_test_count); +} + +// Gets the number of disabled tests. +int UnitTestImpl::disabled_test_count() const { + return SumOverTestSuiteList(test_suites_, &TestSuite::disabled_test_count); +} + +// Gets the number of tests to be printed in the XML report. +int UnitTestImpl::reportable_test_count() const { + return SumOverTestSuiteList(test_suites_, &TestSuite::reportable_test_count); +} + +// Gets the number of all tests. +int UnitTestImpl::total_test_count() const { + return SumOverTestSuiteList(test_suites_, &TestSuite::total_test_count); +} + +// Gets the number of tests that should run. +int UnitTestImpl::test_to_run_count() const { + return SumOverTestSuiteList(test_suites_, &TestSuite::test_to_run_count); +} + +// Returns the current OS stack trace as an std::string. +// +// The maximum number of stack frames to be included is specified by +// the gtest_stack_trace_depth flag. The skip_count parameter +// specifies the number of top frames to be skipped, which doesn't +// count against the number of frames to be included. +// +// For example, if Foo() calls Bar(), which in turn calls +// CurrentOsStackTraceExceptTop(1), Foo() will be included in the +// trace but Bar() and CurrentOsStackTraceExceptTop() won't. +std::string UnitTestImpl::CurrentOsStackTraceExceptTop(int skip_count) { + return os_stack_trace_getter()->CurrentStackTrace( + static_cast(GTEST_FLAG_GET(stack_trace_depth)), skip_count + 1 + // Skips the user-specified number of frames plus this function + // itself. + ); // NOLINT +} + +// A helper class for measuring elapsed times. +class Timer { + public: + Timer() : start_(clock::now()) {} + + // Return time elapsed in milliseconds since the timer was created. + TimeInMillis Elapsed() { + return std::chrono::duration_cast(clock::now() - + start_) + .count(); + } + + private: + // Fall back to the system_clock when building with newlib on a system + // without a monotonic clock. +#if defined(_NEWLIB_VERSION) && !defined(CLOCK_MONOTONIC) + using clock = std::chrono::system_clock; +#else + using clock = std::chrono::steady_clock; +#endif + clock::time_point start_; +}; + +// Returns a timestamp as milliseconds since the epoch. Note this time may jump +// around subject to adjustments by the system, to measure elapsed time use +// Timer instead. +TimeInMillis GetTimeInMillis() { + return std::chrono::duration_cast( + std::chrono::system_clock::now() - + std::chrono::system_clock::from_time_t(0)) + .count(); +} + +// Utilities + +// class String. + +#ifdef GTEST_OS_WINDOWS_MOBILE +// Creates a UTF-16 wide string from the given ANSI string, allocating +// memory using new. The caller is responsible for deleting the return +// value using delete[]. Returns the wide string, or NULL if the +// input is NULL. +LPCWSTR String::AnsiToUtf16(const char* ansi) { + if (!ansi) return nullptr; + const int length = strlen(ansi); + const int unicode_length = + MultiByteToWideChar(CP_ACP, 0, ansi, length, nullptr, 0); + WCHAR* unicode = new WCHAR[unicode_length + 1]; + MultiByteToWideChar(CP_ACP, 0, ansi, length, unicode, unicode_length); + unicode[unicode_length] = 0; + return unicode; +} + +// Creates an ANSI string from the given wide string, allocating +// memory using new. The caller is responsible for deleting the return +// value using delete[]. Returns the ANSI string, or NULL if the +// input is NULL. +const char* String::Utf16ToAnsi(LPCWSTR utf16_str) { + if (!utf16_str) return nullptr; + const int ansi_length = WideCharToMultiByte(CP_ACP, 0, utf16_str, -1, nullptr, + 0, nullptr, nullptr); + char* ansi = new char[ansi_length + 1]; + WideCharToMultiByte(CP_ACP, 0, utf16_str, -1, ansi, ansi_length, nullptr, + nullptr); + ansi[ansi_length] = 0; + return ansi; +} + +#endif // GTEST_OS_WINDOWS_MOBILE + +// Compares two C strings. Returns true if and only if they have the same +// content. +// +// Unlike strcmp(), this function can handle NULL argument(s). A NULL +// C string is considered different to any non-NULL C string, +// including the empty string. +bool String::CStringEquals(const char* lhs, const char* rhs) { + if (lhs == nullptr) return rhs == nullptr; + + if (rhs == nullptr) return false; + + return strcmp(lhs, rhs) == 0; +} + +#if GTEST_HAS_STD_WSTRING + +// Converts an array of wide chars to a narrow string using the UTF-8 +// encoding, and streams the result to the given Message object. +static void StreamWideCharsToMessage(const wchar_t* wstr, size_t length, + Message* msg) { + for (size_t i = 0; i != length;) { // NOLINT + if (wstr[i] != L'\0') { + *msg << WideStringToUtf8(wstr + i, static_cast(length - i)); + while (i != length && wstr[i] != L'\0') i++; + } else { + *msg << '\0'; + i++; + } + } +} + +#endif // GTEST_HAS_STD_WSTRING + +void SplitString(const ::std::string& str, char delimiter, + ::std::vector< ::std::string>* dest) { + ::std::vector< ::std::string> parsed; + ::std::string::size_type pos = 0; + while (::testing::internal::AlwaysTrue()) { + const ::std::string::size_type colon = str.find(delimiter, pos); + if (colon == ::std::string::npos) { + parsed.push_back(str.substr(pos)); + break; + } else { + parsed.push_back(str.substr(pos, colon - pos)); + pos = colon + 1; + } + } + dest->swap(parsed); +} + +} // namespace internal + +// Constructs an empty Message. +// We allocate the stringstream separately because otherwise each use of +// ASSERT/EXPECT in a procedure adds over 200 bytes to the procedure's +// stack frame leading to huge stack frames in some cases; gcc does not reuse +// the stack space. +Message::Message() : ss_(new ::std::stringstream) { + // By default, we want there to be enough precision when printing + // a double to a Message. + *ss_ << std::setprecision(std::numeric_limits::digits10 + 2); +} + +// These two overloads allow streaming a wide C string to a Message +// using the UTF-8 encoding. +Message& Message::operator<<(const wchar_t* wide_c_str) { + return *this << internal::String::ShowWideCString(wide_c_str); +} +Message& Message::operator<<(wchar_t* wide_c_str) { + return *this << internal::String::ShowWideCString(wide_c_str); +} + +#if GTEST_HAS_STD_WSTRING +// Converts the given wide string to a narrow string using the UTF-8 +// encoding, and streams the result to this Message object. +Message& Message::operator<<(const ::std::wstring& wstr) { + internal::StreamWideCharsToMessage(wstr.c_str(), wstr.length(), this); + return *this; +} +#endif // GTEST_HAS_STD_WSTRING + +// Gets the text streamed to this object so far as an std::string. +// Each '\0' character in the buffer is replaced with "\\0". +std::string Message::GetString() const { + return internal::StringStreamToString(ss_.get()); +} + +namespace internal { + +namespace edit_distance { +std::vector CalculateOptimalEdits(const std::vector& left, + const std::vector& right) { + std::vector > costs( + left.size() + 1, std::vector(right.size() + 1)); + std::vector > best_move( + left.size() + 1, std::vector(right.size() + 1)); + + // Populate for empty right. + for (size_t l_i = 0; l_i < costs.size(); ++l_i) { + costs[l_i][0] = static_cast(l_i); + best_move[l_i][0] = kRemove; + } + // Populate for empty left. + for (size_t r_i = 1; r_i < costs[0].size(); ++r_i) { + costs[0][r_i] = static_cast(r_i); + best_move[0][r_i] = kAdd; + } + + for (size_t l_i = 0; l_i < left.size(); ++l_i) { + for (size_t r_i = 0; r_i < right.size(); ++r_i) { + if (left[l_i] == right[r_i]) { + // Found a match. Consume it. + costs[l_i + 1][r_i + 1] = costs[l_i][r_i]; + best_move[l_i + 1][r_i + 1] = kMatch; + continue; + } + + const double add = costs[l_i + 1][r_i]; + const double remove = costs[l_i][r_i + 1]; + const double replace = costs[l_i][r_i]; + if (add < remove && add < replace) { + costs[l_i + 1][r_i + 1] = add + 1; + best_move[l_i + 1][r_i + 1] = kAdd; + } else if (remove < add && remove < replace) { + costs[l_i + 1][r_i + 1] = remove + 1; + best_move[l_i + 1][r_i + 1] = kRemove; + } else { + // We make replace a little more expensive than add/remove to lower + // their priority. + costs[l_i + 1][r_i + 1] = replace + 1.00001; + best_move[l_i + 1][r_i + 1] = kReplace; + } + } + } + + // Reconstruct the best path. We do it in reverse order. + std::vector best_path; + for (size_t l_i = left.size(), r_i = right.size(); l_i > 0 || r_i > 0;) { + EditType move = best_move[l_i][r_i]; + best_path.push_back(move); + l_i -= move != kAdd; + r_i -= move != kRemove; + } + std::reverse(best_path.begin(), best_path.end()); + return best_path; +} + +namespace { + +// Helper class to convert string into ids with deduplication. +class InternalStrings { + public: + size_t GetId(const std::string& str) { + IdMap::iterator it = ids_.find(str); + if (it != ids_.end()) return it->second; + size_t id = ids_.size(); + return ids_[str] = id; + } + + private: + typedef std::map IdMap; + IdMap ids_; +}; + +} // namespace + +std::vector CalculateOptimalEdits( + const std::vector& left, + const std::vector& right) { + std::vector left_ids, right_ids; + { + InternalStrings intern_table; + for (size_t i = 0; i < left.size(); ++i) { + left_ids.push_back(intern_table.GetId(left[i])); + } + for (size_t i = 0; i < right.size(); ++i) { + right_ids.push_back(intern_table.GetId(right[i])); + } + } + return CalculateOptimalEdits(left_ids, right_ids); +} + +namespace { + +// Helper class that holds the state for one hunk and prints it out to the +// stream. +// It reorders adds/removes when possible to group all removes before all +// adds. It also adds the hunk header before printint into the stream. +class Hunk { + public: + Hunk(size_t left_start, size_t right_start) + : left_start_(left_start), + right_start_(right_start), + adds_(), + removes_(), + common_() {} + + void PushLine(char edit, const char* line) { + switch (edit) { + case ' ': + ++common_; + FlushEdits(); + hunk_.push_back(std::make_pair(' ', line)); + break; + case '-': + ++removes_; + hunk_removes_.push_back(std::make_pair('-', line)); + break; + case '+': + ++adds_; + hunk_adds_.push_back(std::make_pair('+', line)); + break; + } + } + + void PrintTo(std::ostream* os) { + PrintHeader(os); + FlushEdits(); + for (std::list >::const_iterator it = + hunk_.begin(); + it != hunk_.end(); ++it) { + *os << it->first << it->second << "\n"; + } + } + + bool has_edits() const { return adds_ || removes_; } + + private: + void FlushEdits() { + hunk_.splice(hunk_.end(), hunk_removes_); + hunk_.splice(hunk_.end(), hunk_adds_); + } + + // Print a unified diff header for one hunk. + // The format is + // "@@ -, +, @@" + // where the left/right parts are omitted if unnecessary. + void PrintHeader(std::ostream* ss) const { + *ss << "@@ "; + if (removes_) { + *ss << "-" << left_start_ << "," << (removes_ + common_); + } + if (removes_ && adds_) { + *ss << " "; + } + if (adds_) { + *ss << "+" << right_start_ << "," << (adds_ + common_); + } + *ss << " @@\n"; + } + + size_t left_start_, right_start_; + size_t adds_, removes_, common_; + std::list > hunk_, hunk_adds_, hunk_removes_; +}; + +} // namespace + +// Create a list of diff hunks in Unified diff format. +// Each hunk has a header generated by PrintHeader above plus a body with +// lines prefixed with ' ' for no change, '-' for deletion and '+' for +// addition. +// 'context' represents the desired unchanged prefix/suffix around the diff. +// If two hunks are close enough that their contexts overlap, then they are +// joined into one hunk. +std::string CreateUnifiedDiff(const std::vector& left, + const std::vector& right, + size_t context) { + const std::vector edits = CalculateOptimalEdits(left, right); + + size_t l_i = 0, r_i = 0, edit_i = 0; + std::stringstream ss; + while (edit_i < edits.size()) { + // Find first edit. + while (edit_i < edits.size() && edits[edit_i] == kMatch) { + ++l_i; + ++r_i; + ++edit_i; + } + + // Find the first line to include in the hunk. + const size_t prefix_context = std::min(l_i, context); + Hunk hunk(l_i - prefix_context + 1, r_i - prefix_context + 1); + for (size_t i = prefix_context; i > 0; --i) { + hunk.PushLine(' ', left[l_i - i].c_str()); + } + + // Iterate the edits until we found enough suffix for the hunk or the input + // is over. + size_t n_suffix = 0; + for (; edit_i < edits.size(); ++edit_i) { + if (n_suffix >= context) { + // Continue only if the next hunk is very close. + auto it = edits.begin() + static_cast(edit_i); + while (it != edits.end() && *it == kMatch) ++it; + if (it == edits.end() || + static_cast(it - edits.begin()) - edit_i >= context) { + // There is no next edit or it is too far away. + break; + } + } + + EditType edit = edits[edit_i]; + // Reset count when a non match is found. + n_suffix = edit == kMatch ? n_suffix + 1 : 0; + + if (edit == kMatch || edit == kRemove || edit == kReplace) { + hunk.PushLine(edit == kMatch ? ' ' : '-', left[l_i].c_str()); + } + if (edit == kAdd || edit == kReplace) { + hunk.PushLine('+', right[r_i].c_str()); + } + + // Advance indices, depending on edit type. + l_i += edit != kAdd; + r_i += edit != kRemove; + } + + if (!hunk.has_edits()) { + // We are done. We don't want this hunk. + break; + } + + hunk.PrintTo(&ss); + } + return ss.str(); +} + +} // namespace edit_distance + +namespace { + +// The string representation of the values received in EqFailure() are already +// escaped. Split them on escaped '\n' boundaries. Leave all other escaped +// characters the same. +std::vector SplitEscapedString(const std::string& str) { + std::vector lines; + size_t start = 0, end = str.size(); + if (end > 2 && str[0] == '"' && str[end - 1] == '"') { + ++start; + --end; + } + bool escaped = false; + for (size_t i = start; i + 1 < end; ++i) { + if (escaped) { + escaped = false; + if (str[i] == 'n') { + lines.push_back(str.substr(start, i - start - 1)); + start = i + 1; + } + } else { + escaped = str[i] == '\\'; + } + } + lines.push_back(str.substr(start, end - start)); + return lines; +} + +} // namespace + +// Constructs and returns the message for an equality assertion +// (e.g. ASSERT_EQ, EXPECT_STREQ, etc) failure. +// +// The first four parameters are the expressions used in the assertion +// and their values, as strings. For example, for ASSERT_EQ(foo, bar) +// where foo is 5 and bar is 6, we have: +// +// lhs_expression: "foo" +// rhs_expression: "bar" +// lhs_value: "5" +// rhs_value: "6" +// +// The ignoring_case parameter is true if and only if the assertion is a +// *_STRCASEEQ*. When it's true, the string "Ignoring case" will +// be inserted into the message. +AssertionResult EqFailure(const char* lhs_expression, + const char* rhs_expression, + const std::string& lhs_value, + const std::string& rhs_value, bool ignoring_case) { + Message msg; + msg << "Expected equality of these values:"; + msg << "\n " << lhs_expression; + if (lhs_value != lhs_expression) { + msg << "\n Which is: " << lhs_value; + } + msg << "\n " << rhs_expression; + if (rhs_value != rhs_expression) { + msg << "\n Which is: " << rhs_value; + } + + if (ignoring_case) { + msg << "\nIgnoring case"; + } + + if (!lhs_value.empty() && !rhs_value.empty()) { + const std::vector lhs_lines = SplitEscapedString(lhs_value); + const std::vector rhs_lines = SplitEscapedString(rhs_value); + if (lhs_lines.size() > 1 || rhs_lines.size() > 1) { + msg << "\nWith diff:\n" + << edit_distance::CreateUnifiedDiff(lhs_lines, rhs_lines); + } + } + + return AssertionFailure() << msg; +} + +// Constructs a failure message for Boolean assertions such as EXPECT_TRUE. +std::string GetBoolAssertionFailureMessage( + const AssertionResult& assertion_result, const char* expression_text, + const char* actual_predicate_value, const char* expected_predicate_value) { + const char* actual_message = assertion_result.message(); + Message msg; + msg << "Value of: " << expression_text + << "\n Actual: " << actual_predicate_value; + if (actual_message[0] != '\0') msg << " (" << actual_message << ")"; + msg << "\nExpected: " << expected_predicate_value; + return msg.GetString(); +} + +// Helper function for implementing ASSERT_NEAR. Treats infinity as a specific +// value, such that comparing infinity to infinity is equal, the distance +// between -infinity and +infinity is infinity, and infinity <= infinity is +// true. +AssertionResult DoubleNearPredFormat(const char* expr1, const char* expr2, + const char* abs_error_expr, double val1, + double val2, double abs_error) { + // We want to return success when the two values are infinity and at least + // one of the following is true: + // * The values are the same-signed infinity. + // * The error limit itself is infinity. + // This is done here so that we don't end up with a NaN when calculating the + // difference in values. + if (std::isinf(val1) && std::isinf(val2) && + (std::signbit(val1) == std::signbit(val2) || + (abs_error > 0.0 && std::isinf(abs_error)))) { + return AssertionSuccess(); + } + + const double diff = fabs(val1 - val2); + if (diff <= abs_error) return AssertionSuccess(); + + // Find the value which is closest to zero. + const double min_abs = std::min(fabs(val1), fabs(val2)); + // Find the distance to the next double from that value. + const double epsilon = + nextafter(min_abs, std::numeric_limits::infinity()) - min_abs; + // Detect the case where abs_error is so small that EXPECT_NEAR is + // effectively the same as EXPECT_EQUAL, and give an informative error + // message so that the situation can be more easily understood without + // requiring exotic floating-point knowledge. + // Don't do an epsilon check if abs_error is zero because that implies + // that an equality check was actually intended. + if (!(std::isnan)(val1) && !(std::isnan)(val2) && abs_error > 0 && + abs_error < epsilon) { + return AssertionFailure() + << "The difference between " << expr1 << " and " << expr2 << " is " + << diff << ", where\n" + << expr1 << " evaluates to " << val1 << ",\n" + << expr2 << " evaluates to " << val2 << ".\nThe abs_error parameter " + << abs_error_expr << " evaluates to " << abs_error + << " which is smaller than the minimum distance between doubles for " + "numbers of this magnitude which is " + << epsilon + << ", thus making this EXPECT_NEAR check equivalent to " + "EXPECT_EQUAL. Consider using EXPECT_DOUBLE_EQ instead."; + } + return AssertionFailure() + << "The difference between " << expr1 << " and " << expr2 << " is " + << diff << ", which exceeds " << abs_error_expr << ", where\n" + << expr1 << " evaluates to " << val1 << ",\n" + << expr2 << " evaluates to " << val2 << ", and\n" + << abs_error_expr << " evaluates to " << abs_error << "."; +} + +// Helper template for implementing FloatLE() and DoubleLE(). +template +AssertionResult FloatingPointLE(const char* expr1, const char* expr2, + RawType val1, RawType val2) { + // Returns success if val1 is less than val2, + if (val1 < val2) { + return AssertionSuccess(); + } + + // or if val1 is almost equal to val2. + const FloatingPoint lhs(val1), rhs(val2); + if (lhs.AlmostEquals(rhs)) { + return AssertionSuccess(); + } + + // Note that the above two checks will both fail if either val1 or + // val2 is NaN, as the IEEE floating-point standard requires that + // any predicate involving a NaN must return false. + + ::std::stringstream val1_ss; + val1_ss << std::setprecision(std::numeric_limits::digits10 + 2) + << val1; + + ::std::stringstream val2_ss; + val2_ss << std::setprecision(std::numeric_limits::digits10 + 2) + << val2; + + return AssertionFailure() + << "Expected: (" << expr1 << ") <= (" << expr2 << ")\n" + << " Actual: " << StringStreamToString(&val1_ss) << " vs " + << StringStreamToString(&val2_ss); +} + +} // namespace internal + +// Asserts that val1 is less than, or almost equal to, val2. Fails +// otherwise. In particular, it fails if either val1 or val2 is NaN. +AssertionResult FloatLE(const char* expr1, const char* expr2, float val1, + float val2) { + return internal::FloatingPointLE(expr1, expr2, val1, val2); +} + +// Asserts that val1 is less than, or almost equal to, val2. Fails +// otherwise. In particular, it fails if either val1 or val2 is NaN. +AssertionResult DoubleLE(const char* expr1, const char* expr2, double val1, + double val2) { + return internal::FloatingPointLE(expr1, expr2, val1, val2); +} + +namespace internal { + +// The helper function for {ASSERT|EXPECT}_STREQ. +AssertionResult CmpHelperSTREQ(const char* lhs_expression, + const char* rhs_expression, const char* lhs, + const char* rhs) { + if (String::CStringEquals(lhs, rhs)) { + return AssertionSuccess(); + } + + return EqFailure(lhs_expression, rhs_expression, PrintToString(lhs), + PrintToString(rhs), false); +} + +// The helper function for {ASSERT|EXPECT}_STRCASEEQ. +AssertionResult CmpHelperSTRCASEEQ(const char* lhs_expression, + const char* rhs_expression, const char* lhs, + const char* rhs) { + if (String::CaseInsensitiveCStringEquals(lhs, rhs)) { + return AssertionSuccess(); + } + + return EqFailure(lhs_expression, rhs_expression, PrintToString(lhs), + PrintToString(rhs), true); +} + +// The helper function for {ASSERT|EXPECT}_STRNE. +AssertionResult CmpHelperSTRNE(const char* s1_expression, + const char* s2_expression, const char* s1, + const char* s2) { + if (!String::CStringEquals(s1, s2)) { + return AssertionSuccess(); + } else { + return AssertionFailure() + << "Expected: (" << s1_expression << ") != (" << s2_expression + << "), actual: \"" << s1 << "\" vs \"" << s2 << "\""; + } +} + +// The helper function for {ASSERT|EXPECT}_STRCASENE. +AssertionResult CmpHelperSTRCASENE(const char* s1_expression, + const char* s2_expression, const char* s1, + const char* s2) { + if (!String::CaseInsensitiveCStringEquals(s1, s2)) { + return AssertionSuccess(); + } else { + return AssertionFailure() + << "Expected: (" << s1_expression << ") != (" << s2_expression + << ") (ignoring case), actual: \"" << s1 << "\" vs \"" << s2 << "\""; + } +} + +} // namespace internal + +namespace { + +// Helper functions for implementing IsSubString() and IsNotSubstring(). + +// This group of overloaded functions return true if and only if needle +// is a substring of haystack. NULL is considered a substring of +// itself only. + +bool IsSubstringPred(const char* needle, const char* haystack) { + if (needle == nullptr || haystack == nullptr) return needle == haystack; + + return strstr(haystack, needle) != nullptr; +} + +bool IsSubstringPred(const wchar_t* needle, const wchar_t* haystack) { + if (needle == nullptr || haystack == nullptr) return needle == haystack; + + return wcsstr(haystack, needle) != nullptr; +} + +// StringType here can be either ::std::string or ::std::wstring. +template +bool IsSubstringPred(const StringType& needle, const StringType& haystack) { + return haystack.find(needle) != StringType::npos; +} + +// This function implements either IsSubstring() or IsNotSubstring(), +// depending on the value of the expected_to_be_substring parameter. +// StringType here can be const char*, const wchar_t*, ::std::string, +// or ::std::wstring. +template +AssertionResult IsSubstringImpl(bool expected_to_be_substring, + const char* needle_expr, + const char* haystack_expr, + const StringType& needle, + const StringType& haystack) { + if (IsSubstringPred(needle, haystack) == expected_to_be_substring) + return AssertionSuccess(); + + const bool is_wide_string = sizeof(needle[0]) > 1; + const char* const begin_string_quote = is_wide_string ? "L\"" : "\""; + return AssertionFailure() + << "Value of: " << needle_expr << "\n" + << " Actual: " << begin_string_quote << needle << "\"\n" + << "Expected: " << (expected_to_be_substring ? "" : "not ") + << "a substring of " << haystack_expr << "\n" + << "Which is: " << begin_string_quote << haystack << "\""; +} + +} // namespace + +// IsSubstring() and IsNotSubstring() check whether needle is a +// substring of haystack (NULL is considered a substring of itself +// only), and return an appropriate error message when they fail. + +AssertionResult IsSubstring(const char* needle_expr, const char* haystack_expr, + const char* needle, const char* haystack) { + return IsSubstringImpl(true, needle_expr, haystack_expr, needle, haystack); +} + +AssertionResult IsSubstring(const char* needle_expr, const char* haystack_expr, + const wchar_t* needle, const wchar_t* haystack) { + return IsSubstringImpl(true, needle_expr, haystack_expr, needle, haystack); +} + +AssertionResult IsNotSubstring(const char* needle_expr, + const char* haystack_expr, const char* needle, + const char* haystack) { + return IsSubstringImpl(false, needle_expr, haystack_expr, needle, haystack); +} + +AssertionResult IsNotSubstring(const char* needle_expr, + const char* haystack_expr, const wchar_t* needle, + const wchar_t* haystack) { + return IsSubstringImpl(false, needle_expr, haystack_expr, needle, haystack); +} + +AssertionResult IsSubstring(const char* needle_expr, const char* haystack_expr, + const ::std::string& needle, + const ::std::string& haystack) { + return IsSubstringImpl(true, needle_expr, haystack_expr, needle, haystack); +} + +AssertionResult IsNotSubstring(const char* needle_expr, + const char* haystack_expr, + const ::std::string& needle, + const ::std::string& haystack) { + return IsSubstringImpl(false, needle_expr, haystack_expr, needle, haystack); +} + +#if GTEST_HAS_STD_WSTRING +AssertionResult IsSubstring(const char* needle_expr, const char* haystack_expr, + const ::std::wstring& needle, + const ::std::wstring& haystack) { + return IsSubstringImpl(true, needle_expr, haystack_expr, needle, haystack); +} + +AssertionResult IsNotSubstring(const char* needle_expr, + const char* haystack_expr, + const ::std::wstring& needle, + const ::std::wstring& haystack) { + return IsSubstringImpl(false, needle_expr, haystack_expr, needle, haystack); +} +#endif // GTEST_HAS_STD_WSTRING + +namespace internal { + +#ifdef GTEST_OS_WINDOWS + +namespace { + +// Helper function for IsHRESULT{SuccessFailure} predicates +AssertionResult HRESULTFailureHelper(const char* expr, const char* expected, + long hr) { // NOLINT +#if defined(GTEST_OS_WINDOWS_MOBILE) || defined(GTEST_OS_WINDOWS_TV_TITLE) + + // Windows CE doesn't support FormatMessage. + const char error_text[] = ""; + +#else + + // Looks up the human-readable system message for the HRESULT code + // and since we're not passing any params to FormatMessage, we don't + // want inserts expanded. + const DWORD kFlags = + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS; + const DWORD kBufSize = 4096; + // Gets the system's human readable message string for this HRESULT. + char error_text[kBufSize] = {'\0'}; + DWORD message_length = ::FormatMessageA(kFlags, + 0, // no source, we're asking system + static_cast(hr), // the error + 0, // no line width restrictions + error_text, // output buffer + kBufSize, // buf size + nullptr); // no arguments for inserts + // Trims tailing white space (FormatMessage leaves a trailing CR-LF) + for (; message_length && IsSpace(error_text[message_length - 1]); + --message_length) { + error_text[message_length - 1] = '\0'; + } + +#endif // GTEST_OS_WINDOWS_MOBILE + + const std::string error_hex("0x" + String::FormatHexInt(hr)); + return ::testing::AssertionFailure() + << "Expected: " << expr << " " << expected << ".\n" + << " Actual: " << error_hex << " " << error_text << "\n"; +} + +} // namespace + +AssertionResult IsHRESULTSuccess(const char* expr, long hr) { // NOLINT + if (SUCCEEDED(hr)) { + return AssertionSuccess(); + } + return HRESULTFailureHelper(expr, "succeeds", hr); +} + +AssertionResult IsHRESULTFailure(const char* expr, long hr) { // NOLINT + if (FAILED(hr)) { + return AssertionSuccess(); + } + return HRESULTFailureHelper(expr, "fails", hr); +} + +#endif // GTEST_OS_WINDOWS + +// Utility functions for encoding Unicode text (wide strings) in +// UTF-8. + +// A Unicode code-point can have up to 21 bits, and is encoded in UTF-8 +// like this: +// +// Code-point length Encoding +// 0 - 7 bits 0xxxxxxx +// 8 - 11 bits 110xxxxx 10xxxxxx +// 12 - 16 bits 1110xxxx 10xxxxxx 10xxxxxx +// 17 - 21 bits 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + +// The maximum code-point a one-byte UTF-8 sequence can represent. +constexpr uint32_t kMaxCodePoint1 = (static_cast(1) << 7) - 1; + +// The maximum code-point a two-byte UTF-8 sequence can represent. +constexpr uint32_t kMaxCodePoint2 = (static_cast(1) << (5 + 6)) - 1; + +// The maximum code-point a three-byte UTF-8 sequence can represent. +constexpr uint32_t kMaxCodePoint3 = + (static_cast(1) << (4 + 2 * 6)) - 1; + +// The maximum code-point a four-byte UTF-8 sequence can represent. +constexpr uint32_t kMaxCodePoint4 = + (static_cast(1) << (3 + 3 * 6)) - 1; + +// Chops off the n lowest bits from a bit pattern. Returns the n +// lowest bits. As a side effect, the original bit pattern will be +// shifted to the right by n bits. +inline uint32_t ChopLowBits(uint32_t* bits, int n) { + const uint32_t low_bits = *bits & ((static_cast(1) << n) - 1); + *bits >>= n; + return low_bits; +} + +// Converts a Unicode code point to a narrow string in UTF-8 encoding. +// code_point parameter is of type uint32_t because wchar_t may not be +// wide enough to contain a code point. +// If the code_point is not a valid Unicode code point +// (i.e. outside of Unicode range U+0 to U+10FFFF) it will be converted +// to "(Invalid Unicode 0xXXXXXXXX)". +std::string CodePointToUtf8(uint32_t code_point) { + if (code_point > kMaxCodePoint4) { + return "(Invalid Unicode 0x" + String::FormatHexUInt32(code_point) + ")"; + } + + char str[5]; // Big enough for the largest valid code point. + if (code_point <= kMaxCodePoint1) { + str[1] = '\0'; + str[0] = static_cast(code_point); // 0xxxxxxx + } else if (code_point <= kMaxCodePoint2) { + str[2] = '\0'; + str[1] = static_cast(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx + str[0] = static_cast(0xC0 | code_point); // 110xxxxx + } else if (code_point <= kMaxCodePoint3) { + str[3] = '\0'; + str[2] = static_cast(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx + str[1] = static_cast(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx + str[0] = static_cast(0xE0 | code_point); // 1110xxxx + } else { // code_point <= kMaxCodePoint4 + str[4] = '\0'; + str[3] = static_cast(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx + str[2] = static_cast(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx + str[1] = static_cast(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx + str[0] = static_cast(0xF0 | code_point); // 11110xxx + } + return str; +} + +// The following two functions only make sense if the system +// uses UTF-16 for wide string encoding. All supported systems +// with 16 bit wchar_t (Windows, Cygwin) do use UTF-16. + +// Determines if the arguments constitute UTF-16 surrogate pair +// and thus should be combined into a single Unicode code point +// using CreateCodePointFromUtf16SurrogatePair. +inline bool IsUtf16SurrogatePair(wchar_t first, wchar_t second) { + return sizeof(wchar_t) == 2 && (first & 0xFC00) == 0xD800 && + (second & 0xFC00) == 0xDC00; +} + +// Creates a Unicode code point from UTF16 surrogate pair. +inline uint32_t CreateCodePointFromUtf16SurrogatePair(wchar_t first, + wchar_t second) { + const auto first_u = static_cast(first); + const auto second_u = static_cast(second); + const uint32_t mask = (1 << 10) - 1; + return (sizeof(wchar_t) == 2) + ? (((first_u & mask) << 10) | (second_u & mask)) + 0x10000 + : + // This function should not be called when the condition is + // false, but we provide a sensible default in case it is. + first_u; +} + +// Converts a wide string to a narrow string in UTF-8 encoding. +// The wide string is assumed to have the following encoding: +// UTF-16 if sizeof(wchar_t) == 2 (on Windows, Cygwin) +// UTF-32 if sizeof(wchar_t) == 4 (on Linux) +// Parameter str points to a null-terminated wide string. +// Parameter num_chars may additionally limit the number +// of wchar_t characters processed. -1 is used when the entire string +// should be processed. +// If the string contains code points that are not valid Unicode code points +// (i.e. outside of Unicode range U+0 to U+10FFFF) they will be output +// as '(Invalid Unicode 0xXXXXXXXX)'. If the string is in UTF16 encoding +// and contains invalid UTF-16 surrogate pairs, values in those pairs +// will be encoded as individual Unicode characters from Basic Normal Plane. +std::string WideStringToUtf8(const wchar_t* str, int num_chars) { + if (num_chars == -1) num_chars = static_cast(wcslen(str)); + + ::std::stringstream stream; + for (int i = 0; i < num_chars; ++i) { + uint32_t unicode_code_point; + + if (str[i] == L'\0') { + break; + } else if (i + 1 < num_chars && IsUtf16SurrogatePair(str[i], str[i + 1])) { + unicode_code_point = + CreateCodePointFromUtf16SurrogatePair(str[i], str[i + 1]); + i++; + } else { + unicode_code_point = static_cast(str[i]); + } + + stream << CodePointToUtf8(unicode_code_point); + } + return StringStreamToString(&stream); +} + +// Converts a wide C string to an std::string using the UTF-8 encoding. +// NULL will be converted to "(null)". +std::string String::ShowWideCString(const wchar_t* wide_c_str) { + if (wide_c_str == nullptr) return "(null)"; + + return internal::WideStringToUtf8(wide_c_str, -1); +} + +// Compares two wide C strings. Returns true if and only if they have the +// same content. +// +// Unlike wcscmp(), this function can handle NULL argument(s). A NULL +// C string is considered different to any non-NULL C string, +// including the empty string. +bool String::WideCStringEquals(const wchar_t* lhs, const wchar_t* rhs) { + if (lhs == nullptr) return rhs == nullptr; + + if (rhs == nullptr) return false; + + return wcscmp(lhs, rhs) == 0; +} + +// Helper function for *_STREQ on wide strings. +AssertionResult CmpHelperSTREQ(const char* lhs_expression, + const char* rhs_expression, const wchar_t* lhs, + const wchar_t* rhs) { + if (String::WideCStringEquals(lhs, rhs)) { + return AssertionSuccess(); + } + + return EqFailure(lhs_expression, rhs_expression, PrintToString(lhs), + PrintToString(rhs), false); +} + +// Helper function for *_STRNE on wide strings. +AssertionResult CmpHelperSTRNE(const char* s1_expression, + const char* s2_expression, const wchar_t* s1, + const wchar_t* s2) { + if (!String::WideCStringEquals(s1, s2)) { + return AssertionSuccess(); + } + + return AssertionFailure() + << "Expected: (" << s1_expression << ") != (" << s2_expression + << "), actual: " << PrintToString(s1) << " vs " << PrintToString(s2); +} + +// Compares two C strings, ignoring case. Returns true if and only if they have +// the same content. +// +// Unlike strcasecmp(), this function can handle NULL argument(s). A +// NULL C string is considered different to any non-NULL C string, +// including the empty string. +bool String::CaseInsensitiveCStringEquals(const char* lhs, const char* rhs) { + if (lhs == nullptr) return rhs == nullptr; + if (rhs == nullptr) return false; + return posix::StrCaseCmp(lhs, rhs) == 0; +} + +// Compares two wide C strings, ignoring case. Returns true if and only if they +// have the same content. +// +// Unlike wcscasecmp(), this function can handle NULL argument(s). +// A NULL C string is considered different to any non-NULL wide C string, +// including the empty string. +// NB: The implementations on different platforms slightly differ. +// On windows, this method uses _wcsicmp which compares according to LC_CTYPE +// environment variable. On GNU platform this method uses wcscasecmp +// which compares according to LC_CTYPE category of the current locale. +// On MacOS X, it uses towlower, which also uses LC_CTYPE category of the +// current locale. +bool String::CaseInsensitiveWideCStringEquals(const wchar_t* lhs, + const wchar_t* rhs) { + if (lhs == nullptr) return rhs == nullptr; + + if (rhs == nullptr) return false; + +#ifdef GTEST_OS_WINDOWS + return _wcsicmp(lhs, rhs) == 0; +#elif defined(GTEST_OS_LINUX) && !defined(GTEST_OS_LINUX_ANDROID) + return wcscasecmp(lhs, rhs) == 0; +#else + // Android, Mac OS X and Cygwin don't define wcscasecmp. + // Other unknown OSes may not define it either. + wint_t left, right; + do { + left = towlower(static_cast(*lhs++)); + right = towlower(static_cast(*rhs++)); + } while (left && left == right); + return left == right; +#endif // OS selector +} + +// Returns true if and only if str ends with the given suffix, ignoring case. +// Any string is considered to end with an empty suffix. +bool String::EndsWithCaseInsensitive(const std::string& str, + const std::string& suffix) { + const size_t str_len = str.length(); + const size_t suffix_len = suffix.length(); + return (str_len >= suffix_len) && + CaseInsensitiveCStringEquals(str.c_str() + str_len - suffix_len, + suffix.c_str()); +} + +// Formats an int value as "%02d". +std::string String::FormatIntWidth2(int value) { + return FormatIntWidthN(value, 2); +} + +// Formats an int value to given width with leading zeros. +std::string String::FormatIntWidthN(int value, int width) { + std::stringstream ss; + ss << std::setfill('0') << std::setw(width) << value; + return ss.str(); +} + +// Formats an int value as "%X". +std::string String::FormatHexUInt32(uint32_t value) { + std::stringstream ss; + ss << std::hex << std::uppercase << value; + return ss.str(); +} + +// Formats an int value as "%X". +std::string String::FormatHexInt(int value) { + return FormatHexUInt32(static_cast(value)); +} + +// Formats a byte as "%02X". +std::string String::FormatByte(unsigned char value) { + std::stringstream ss; + ss << std::setfill('0') << std::setw(2) << std::hex << std::uppercase + << static_cast(value); + return ss.str(); +} + +// Converts the buffer in a stringstream to an std::string, converting NUL +// bytes to "\\0" along the way. +std::string StringStreamToString(::std::stringstream* ss) { + const ::std::string& str = ss->str(); + const char* const start = str.c_str(); + const char* const end = start + str.length(); + + std::string result; + result.reserve(static_cast(2 * (end - start))); + for (const char* ch = start; ch != end; ++ch) { + if (*ch == '\0') { + result += "\\0"; // Replaces NUL with "\\0"; + } else { + result += *ch; + } + } + + return result; +} + +// Appends the user-supplied message to the Google-Test-generated message. +std::string AppendUserMessage(const std::string& gtest_msg, + const Message& user_msg) { + // Appends the user message if it's non-empty. + const std::string user_msg_string = user_msg.GetString(); + if (user_msg_string.empty()) { + return gtest_msg; + } + if (gtest_msg.empty()) { + return user_msg_string; + } + return gtest_msg + "\n" + user_msg_string; +} + +} // namespace internal + +// class TestResult + +// Creates an empty TestResult. +TestResult::TestResult() + : death_test_count_(0), start_timestamp_(0), elapsed_time_(0) {} + +// D'tor. +TestResult::~TestResult() = default; + +// Returns the i-th test part result among all the results. i can +// range from 0 to total_part_count() - 1. If i is not in that range, +// aborts the program. +const TestPartResult& TestResult::GetTestPartResult(int i) const { + if (i < 0 || i >= total_part_count()) internal::posix::Abort(); + return test_part_results_.at(static_cast(i)); +} + +// Returns the i-th test property. i can range from 0 to +// test_property_count() - 1. If i is not in that range, aborts the +// program. +const TestProperty& TestResult::GetTestProperty(int i) const { + if (i < 0 || i >= test_property_count()) internal::posix::Abort(); + return test_properties_.at(static_cast(i)); +} + +// Clears the test part results. +void TestResult::ClearTestPartResults() { test_part_results_.clear(); } + +// Adds a test part result to the list. +void TestResult::AddTestPartResult(const TestPartResult& test_part_result) { + test_part_results_.push_back(test_part_result); +} + +// Adds a test property to the list. If a property with the same key as the +// supplied property is already represented, the value of this test_property +// replaces the old value for that key. +void TestResult::RecordProperty(const std::string& xml_element, + const TestProperty& test_property) { + if (!ValidateTestProperty(xml_element, test_property)) { + return; + } + internal::MutexLock lock(&test_properties_mutex_); + const std::vector::iterator property_with_matching_key = + std::find_if(test_properties_.begin(), test_properties_.end(), + internal::TestPropertyKeyIs(test_property.key())); + if (property_with_matching_key == test_properties_.end()) { + test_properties_.push_back(test_property); + return; + } + property_with_matching_key->SetValue(test_property.value()); +} + +// The list of reserved attributes used in the element of XML +// output. +static const char* const kReservedTestSuitesAttributes[] = { + "disabled", "errors", "failures", "name", + "random_seed", "tests", "time", "timestamp"}; + +// The list of reserved attributes used in the element of XML +// output. +static const char* const kReservedTestSuiteAttributes[] = { + "disabled", "errors", "failures", "name", + "tests", "time", "timestamp", "skipped"}; + +// The list of reserved attributes used in the element of XML output. +static const char* const kReservedTestCaseAttributes[] = { + "classname", "name", "status", "time", + "type_param", "value_param", "file", "line"}; + +// Use a slightly different set for allowed output to ensure existing tests can +// still RecordProperty("result") or RecordProperty("timestamp") +static const char* const kReservedOutputTestCaseAttributes[] = { + "classname", "name", "status", "time", "type_param", + "value_param", "file", "line", "result", "timestamp"}; + +template +std::vector ArrayAsVector(const char* const (&array)[kSize]) { + return std::vector(array, array + kSize); +} + +static std::vector GetReservedAttributesForElement( + const std::string& xml_element) { + if (xml_element == "testsuites") { + return ArrayAsVector(kReservedTestSuitesAttributes); + } else if (xml_element == "testsuite") { + return ArrayAsVector(kReservedTestSuiteAttributes); + } else if (xml_element == "testcase") { + return ArrayAsVector(kReservedTestCaseAttributes); + } else { + GTEST_CHECK_(false) << "Unrecognized xml_element provided: " << xml_element; + } + // This code is unreachable but some compilers may not realizes that. + return std::vector(); +} + +#if GTEST_HAS_FILE_SYSTEM +// TODO(jdesprez): Merge the two getReserved attributes once skip is improved +// This function is only used when file systems are enabled. +static std::vector GetReservedOutputAttributesForElement( + const std::string& xml_element) { + if (xml_element == "testsuites") { + return ArrayAsVector(kReservedTestSuitesAttributes); + } else if (xml_element == "testsuite") { + return ArrayAsVector(kReservedTestSuiteAttributes); + } else if (xml_element == "testcase") { + return ArrayAsVector(kReservedOutputTestCaseAttributes); + } else { + GTEST_CHECK_(false) << "Unrecognized xml_element provided: " << xml_element; + } + // This code is unreachable but some compilers may not realizes that. + return std::vector(); +} +#endif + +static std::string FormatWordList(const std::vector& words) { + Message word_list; + for (size_t i = 0; i < words.size(); ++i) { + if (i > 0 && words.size() > 2) { + word_list << ", "; + } + if (i == words.size() - 1) { + word_list << "and "; + } + word_list << "'" << words[i] << "'"; + } + return word_list.GetString(); +} + +static bool ValidateTestPropertyName( + const std::string& property_name, + const std::vector& reserved_names) { + if (std::find(reserved_names.begin(), reserved_names.end(), property_name) != + reserved_names.end()) { + ADD_FAILURE() << "Reserved key used in RecordProperty(): " << property_name + << " (" << FormatWordList(reserved_names) + << " are reserved by " << GTEST_NAME_ << ")"; + return false; + } + return true; +} + +// Adds a failure if the key is a reserved attribute of the element named +// xml_element. Returns true if the property is valid. +bool TestResult::ValidateTestProperty(const std::string& xml_element, + const TestProperty& test_property) { + return ValidateTestPropertyName(test_property.key(), + GetReservedAttributesForElement(xml_element)); +} + +// Clears the object. +void TestResult::Clear() { + test_part_results_.clear(); + test_properties_.clear(); + death_test_count_ = 0; + elapsed_time_ = 0; +} + +// Returns true off the test part was skipped. +static bool TestPartSkipped(const TestPartResult& result) { + return result.skipped(); +} + +// Returns true if and only if the test was skipped. +bool TestResult::Skipped() const { + return !Failed() && CountIf(test_part_results_, TestPartSkipped) > 0; +} + +// Returns true if and only if the test failed. +bool TestResult::Failed() const { + for (int i = 0; i < total_part_count(); ++i) { + if (GetTestPartResult(i).failed()) return true; + } + return false; +} + +// Returns true if and only if the test part fatally failed. +static bool TestPartFatallyFailed(const TestPartResult& result) { + return result.fatally_failed(); +} + +// Returns true if and only if the test fatally failed. +bool TestResult::HasFatalFailure() const { + return CountIf(test_part_results_, TestPartFatallyFailed) > 0; +} + +// Returns true if and only if the test part non-fatally failed. +static bool TestPartNonfatallyFailed(const TestPartResult& result) { + return result.nonfatally_failed(); +} + +// Returns true if and only if the test has a non-fatal failure. +bool TestResult::HasNonfatalFailure() const { + return CountIf(test_part_results_, TestPartNonfatallyFailed) > 0; +} + +// Gets the number of all test parts. This is the sum of the number +// of successful test parts and the number of failed test parts. +int TestResult::total_part_count() const { + return static_cast(test_part_results_.size()); +} + +// Returns the number of the test properties. +int TestResult::test_property_count() const { + return static_cast(test_properties_.size()); +} + +// class Test + +// Creates a Test object. + +// The c'tor saves the states of all flags. +Test::Test() : gtest_flag_saver_(new GTEST_FLAG_SAVER_) {} + +// The d'tor restores the states of all flags. The actual work is +// done by the d'tor of the gtest_flag_saver_ field, and thus not +// visible here. +Test::~Test() = default; + +// Sets up the test fixture. +// +// A sub-class may override this. +void Test::SetUp() {} + +// Tears down the test fixture. +// +// A sub-class may override this. +void Test::TearDown() {} + +// Allows user supplied key value pairs to be recorded for later output. +void Test::RecordProperty(const std::string& key, const std::string& value) { + UnitTest::GetInstance()->RecordProperty(key, value); +} + +namespace internal { + +void ReportFailureInUnknownLocation(TestPartResult::Type result_type, + const std::string& message) { + // This function is a friend of UnitTest and as such has access to + // AddTestPartResult. + UnitTest::GetInstance()->AddTestPartResult( + result_type, + nullptr, // No info about the source file where the exception occurred. + -1, // We have no info on which line caused the exception. + message, + ""); // No stack trace, either. +} + +} // namespace internal + +// Google Test requires all tests in the same test suite to use the same test +// fixture class. This function checks if the current test has the +// same fixture class as the first test in the current test suite. If +// yes, it returns true; otherwise it generates a Google Test failure and +// returns false. +bool Test::HasSameFixtureClass() { + internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); + const TestSuite* const test_suite = impl->current_test_suite(); + + // Info about the first test in the current test suite. + const TestInfo* const first_test_info = test_suite->test_info_list()[0]; + const internal::TypeId first_fixture_id = first_test_info->fixture_class_id_; + const char* const first_test_name = first_test_info->name(); + + // Info about the current test. + const TestInfo* const this_test_info = impl->current_test_info(); + const internal::TypeId this_fixture_id = this_test_info->fixture_class_id_; + const char* const this_test_name = this_test_info->name(); + + if (this_fixture_id != first_fixture_id) { + // Is the first test defined using TEST? + const bool first_is_TEST = first_fixture_id == internal::GetTestTypeId(); + // Is this test defined using TEST? + const bool this_is_TEST = this_fixture_id == internal::GetTestTypeId(); + + if (first_is_TEST || this_is_TEST) { + // Both TEST and TEST_F appear in same test suite, which is incorrect. + // Tell the user how to fix this. + + // Gets the name of the TEST and the name of the TEST_F. Note + // that first_is_TEST and this_is_TEST cannot both be true, as + // the fixture IDs are different for the two tests. + const char* const TEST_name = + first_is_TEST ? first_test_name : this_test_name; + const char* const TEST_F_name = + first_is_TEST ? this_test_name : first_test_name; + + ADD_FAILURE() + << "All tests in the same test suite must use the same test fixture\n" + << "class, so mixing TEST_F and TEST in the same test suite is\n" + << "illegal. In test suite " << this_test_info->test_suite_name() + << ",\n" + << "test " << TEST_F_name << " is defined using TEST_F but\n" + << "test " << TEST_name << " is defined using TEST. You probably\n" + << "want to change the TEST to TEST_F or move it to another test\n" + << "case."; + } else { + // Two fixture classes with the same name appear in two different + // namespaces, which is not allowed. Tell the user how to fix this. + ADD_FAILURE() + << "All tests in the same test suite must use the same test fixture\n" + << "class. However, in test suite " + << this_test_info->test_suite_name() << ",\n" + << "you defined test " << first_test_name << " and test " + << this_test_name << "\n" + << "using two different test fixture classes. This can happen if\n" + << "the two classes are from different namespaces or translation\n" + << "units and have the same name. You should probably rename one\n" + << "of the classes to put the tests into different test suites."; + } + return false; + } + + return true; +} + +namespace internal { + +#if GTEST_HAS_EXCEPTIONS + +// Adds an "exception thrown" fatal failure to the current test. +static std::string FormatCxxExceptionMessage(const char* description, + const char* location) { + Message message; + if (description != nullptr) { + message << "C++ exception with description \"" << description << "\""; + } else { + message << "Unknown C++ exception"; + } + message << " thrown in " << location << "."; + + return message.GetString(); +} + +static std::string PrintTestPartResultToString( + const TestPartResult& test_part_result); + +GoogleTestFailureException::GoogleTestFailureException( + const TestPartResult& failure) + : ::std::runtime_error(PrintTestPartResultToString(failure).c_str()) {} + +#endif // GTEST_HAS_EXCEPTIONS + +// We put these helper functions in the internal namespace as IBM's xlC +// compiler rejects the code if they were declared static. + +// Runs the given method and handles SEH exceptions it throws, when +// SEH is supported; returns the 0-value for type Result in case of an +// SEH exception. (Microsoft compilers cannot handle SEH and C++ +// exceptions in the same function. Therefore, we provide a separate +// wrapper function for handling SEH exceptions.) +template +Result HandleSehExceptionsInMethodIfSupported(T* object, Result (T::*method)(), + const char* location) { +#if GTEST_HAS_SEH + __try { + return (object->*method)(); + } __except (internal::UnitTestOptions::GTestProcessSEH( // NOLINT + GetExceptionCode(), location)) { + return static_cast(0); + } +#else + (void)location; + return (object->*method)(); +#endif // GTEST_HAS_SEH +} + +// Runs the given method and catches and reports C++ and/or SEH-style +// exceptions, if they are supported; returns the 0-value for type +// Result in case of an SEH exception. +template +Result HandleExceptionsInMethodIfSupported(T* object, Result (T::*method)(), + const char* location) { + // NOTE: The user code can affect the way in which Google Test handles + // exceptions by setting GTEST_FLAG(catch_exceptions), but only before + // RUN_ALL_TESTS() starts. It is technically possible to check the flag + // after the exception is caught and either report or re-throw the + // exception based on the flag's value: + // + // try { + // // Perform the test method. + // } catch (...) { + // if (GTEST_FLAG_GET(catch_exceptions)) + // // Report the exception as failure. + // else + // throw; // Re-throws the original exception. + // } + // + // However, the purpose of this flag is to allow the program to drop into + // the debugger when the exception is thrown. On most platforms, once the + // control enters the catch block, the exception origin information is + // lost and the debugger will stop the program at the point of the + // re-throw in this function -- instead of at the point of the original + // throw statement in the code under test. For this reason, we perform + // the check early, sacrificing the ability to affect Google Test's + // exception handling in the method where the exception is thrown. + if (internal::GetUnitTestImpl()->catch_exceptions()) { +#if GTEST_HAS_EXCEPTIONS + try { + return HandleSehExceptionsInMethodIfSupported(object, method, location); + } catch (const AssertionException&) { // NOLINT + // This failure was reported already. + } catch (const internal::GoogleTestFailureException&) { // NOLINT + // This exception type can only be thrown by a failed Google + // Test assertion with the intention of letting another testing + // framework catch it. Therefore we just re-throw it. + throw; + } catch (const std::exception& e) { // NOLINT + internal::ReportFailureInUnknownLocation( + TestPartResult::kFatalFailure, + FormatCxxExceptionMessage(e.what(), location)); + } catch (...) { // NOLINT + internal::ReportFailureInUnknownLocation( + TestPartResult::kFatalFailure, + FormatCxxExceptionMessage(nullptr, location)); + } + return static_cast(0); +#else + return HandleSehExceptionsInMethodIfSupported(object, method, location); +#endif // GTEST_HAS_EXCEPTIONS + } else { + return (object->*method)(); + } +} + +} // namespace internal + +// Runs the test and updates the test result. +void Test::Run() { + if (!HasSameFixtureClass()) return; + + internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); + impl->os_stack_trace_getter()->UponLeavingGTest(); + internal::HandleExceptionsInMethodIfSupported(this, &Test::SetUp, "SetUp()"); + // We will run the test only if SetUp() was successful and didn't call + // GTEST_SKIP(). + if (!HasFatalFailure() && !IsSkipped()) { + impl->os_stack_trace_getter()->UponLeavingGTest(); + internal::HandleExceptionsInMethodIfSupported(this, &Test::TestBody, + "the test body"); + } + + // However, we want to clean up as much as possible. Hence we will + // always call TearDown(), even if SetUp() or the test body has + // failed. + impl->os_stack_trace_getter()->UponLeavingGTest(); + internal::HandleExceptionsInMethodIfSupported(this, &Test::TearDown, + "TearDown()"); +} + +// Returns true if and only if the current test has a fatal failure. +bool Test::HasFatalFailure() { + return internal::GetUnitTestImpl()->current_test_result()->HasFatalFailure(); +} + +// Returns true if and only if the current test has a non-fatal failure. +bool Test::HasNonfatalFailure() { + return internal::GetUnitTestImpl() + ->current_test_result() + ->HasNonfatalFailure(); +} + +// Returns true if and only if the current test was skipped. +bool Test::IsSkipped() { + return internal::GetUnitTestImpl()->current_test_result()->Skipped(); +} + +// class TestInfo + +// Constructs a TestInfo object. It assumes ownership of the test factory +// object. +TestInfo::TestInfo(std::string a_test_suite_name, std::string a_name, + const char* a_type_param, const char* a_value_param, + internal::CodeLocation a_code_location, + internal::TypeId fixture_class_id, + internal::TestFactoryBase* factory) + : test_suite_name_(std::move(a_test_suite_name)), + name_(std::move(a_name)), + type_param_(a_type_param ? new std::string(a_type_param) : nullptr), + value_param_(a_value_param ? new std::string(a_value_param) : nullptr), + location_(std::move(a_code_location)), + fixture_class_id_(fixture_class_id), + should_run_(false), + is_disabled_(false), + matches_filter_(false), + is_in_another_shard_(false), + factory_(factory), + result_() {} + +// Destructs a TestInfo object. +TestInfo::~TestInfo() { delete factory_; } + +namespace internal { + +// Creates a new TestInfo object and registers it with Google Test; +// returns the created object. +// +// Arguments: +// +// test_suite_name: name of the test suite +// name: name of the test +// type_param: the name of the test's type parameter, or NULL if +// this is not a typed or a type-parameterized test. +// value_param: text representation of the test's value parameter, +// or NULL if this is not a value-parameterized test. +// code_location: code location where the test is defined +// fixture_class_id: ID of the test fixture class +// set_up_tc: pointer to the function that sets up the test suite +// tear_down_tc: pointer to the function that tears down the test suite +// factory: pointer to the factory that creates a test object. +// The newly created TestInfo instance will assume +// ownership of the factory object. +TestInfo* MakeAndRegisterTestInfo( + std::string test_suite_name, const char* name, const char* type_param, + const char* value_param, CodeLocation code_location, + TypeId fixture_class_id, SetUpTestSuiteFunc set_up_tc, + TearDownTestSuiteFunc tear_down_tc, TestFactoryBase* factory) { + TestInfo* const test_info = + new TestInfo(std::move(test_suite_name), name, type_param, value_param, + std::move(code_location), fixture_class_id, factory); + GetUnitTestImpl()->AddTestInfo(set_up_tc, tear_down_tc, test_info); + return test_info; +} + +void ReportInvalidTestSuiteType(const char* test_suite_name, + const CodeLocation& code_location) { + Message errors; + errors + << "Attempted redefinition of test suite " << test_suite_name << ".\n" + << "All tests in the same test suite must use the same test fixture\n" + << "class. However, in test suite " << test_suite_name << ", you tried\n" + << "to define a test using a fixture class different from the one\n" + << "used earlier. This can happen if the two fixture classes are\n" + << "from different namespaces and have the same name. You should\n" + << "probably rename one of the classes to put the tests into different\n" + << "test suites."; + + GTEST_LOG_(ERROR) << FormatFileLocation(code_location.file.c_str(), + code_location.line) + << " " << errors.GetString(); +} + +// This method expands all parameterized tests registered with macros TEST_P +// and INSTANTIATE_TEST_SUITE_P into regular tests and registers those. +// This will be done just once during the program runtime. +void UnitTestImpl::RegisterParameterizedTests() { + if (!parameterized_tests_registered_) { + parameterized_test_registry_.RegisterTests(); + type_parameterized_test_registry_.CheckForInstantiations(); + parameterized_tests_registered_ = true; + } +} + +} // namespace internal + +// Creates the test object, runs it, records its result, and then +// deletes it. +void TestInfo::Run() { + TestEventListener* repeater = UnitTest::GetInstance()->listeners().repeater(); + if (!should_run_) { + if (is_disabled_ && matches_filter_) repeater->OnTestDisabled(*this); + return; + } + + // Tells UnitTest where to store test result. + UnitTest::GetInstance()->set_current_test_info(this); + + // Notifies the unit test event listeners that a test is about to start. + repeater->OnTestStart(*this); + result_.set_start_timestamp(internal::GetTimeInMillis()); + internal::Timer timer; + UnitTest::GetInstance()->UponLeavingGTest(); + + // Creates the test object. + Test* const test = internal::HandleExceptionsInMethodIfSupported( + factory_, &internal::TestFactoryBase::CreateTest, + "the test fixture's constructor"); + + // Runs the test if the constructor didn't generate a fatal failure or invoke + // GTEST_SKIP(). + // Note that the object will not be null + if (!Test::HasFatalFailure() && !Test::IsSkipped()) { + // This doesn't throw as all user code that can throw are wrapped into + // exception handling code. + test->Run(); + } + + if (test != nullptr) { + // Deletes the test object. + UnitTest::GetInstance()->UponLeavingGTest(); + internal::HandleExceptionsInMethodIfSupported( + test, &Test::DeleteSelf_, "the test fixture's destructor"); + } + + result_.set_elapsed_time(timer.Elapsed()); + + // Notifies the unit test event listener that a test has just finished. + repeater->OnTestEnd(*this); + + // Tells UnitTest to stop associating assertion results to this + // test. + UnitTest::GetInstance()->set_current_test_info(nullptr); +} + +// Skip and records a skipped test result for this object. +void TestInfo::Skip() { + if (!should_run_) return; + + UnitTest::GetInstance()->set_current_test_info(this); + + TestEventListener* repeater = UnitTest::GetInstance()->listeners().repeater(); + + // Notifies the unit test event listeners that a test is about to start. + repeater->OnTestStart(*this); + + const TestPartResult test_part_result = + TestPartResult(TestPartResult::kSkip, this->file(), this->line(), ""); + internal::GetUnitTestImpl() + ->GetTestPartResultReporterForCurrentThread() + ->ReportTestPartResult(test_part_result); + + // Notifies the unit test event listener that a test has just finished. + repeater->OnTestEnd(*this); + UnitTest::GetInstance()->set_current_test_info(nullptr); +} + +// class TestSuite + +// Gets the number of successful tests in this test suite. +int TestSuite::successful_test_count() const { + return CountIf(test_info_list_, TestPassed); +} + +// Gets the number of successful tests in this test suite. +int TestSuite::skipped_test_count() const { + return CountIf(test_info_list_, TestSkipped); +} + +// Gets the number of failed tests in this test suite. +int TestSuite::failed_test_count() const { + return CountIf(test_info_list_, TestFailed); +} + +// Gets the number of disabled tests that will be reported in the XML report. +int TestSuite::reportable_disabled_test_count() const { + return CountIf(test_info_list_, TestReportableDisabled); +} + +// Gets the number of disabled tests in this test suite. +int TestSuite::disabled_test_count() const { + return CountIf(test_info_list_, TestDisabled); +} + +// Gets the number of tests to be printed in the XML report. +int TestSuite::reportable_test_count() const { + return CountIf(test_info_list_, TestReportable); +} + +// Get the number of tests in this test suite that should run. +int TestSuite::test_to_run_count() const { + return CountIf(test_info_list_, ShouldRunTest); +} + +// Gets the number of all tests. +int TestSuite::total_test_count() const { + return static_cast(test_info_list_.size()); +} + +// Creates a TestSuite with the given name. +// +// Arguments: +// +// a_name: name of the test suite +// a_type_param: the name of the test suite's type parameter, or NULL if +// this is not a typed or a type-parameterized test suite. +// set_up_tc: pointer to the function that sets up the test suite +// tear_down_tc: pointer to the function that tears down the test suite +TestSuite::TestSuite(const std::string& a_name, const char* a_type_param, + internal::SetUpTestSuiteFunc set_up_tc, + internal::TearDownTestSuiteFunc tear_down_tc) + : name_(a_name), + type_param_(a_type_param ? new std::string(a_type_param) : nullptr), + set_up_tc_(set_up_tc), + tear_down_tc_(tear_down_tc), + should_run_(false), + start_timestamp_(0), + elapsed_time_(0) {} + +// Destructor of TestSuite. +TestSuite::~TestSuite() { + // Deletes every Test in the collection. + ForEach(test_info_list_, internal::Delete); +} + +// Returns the i-th test among all the tests. i can range from 0 to +// total_test_count() - 1. If i is not in that range, returns NULL. +const TestInfo* TestSuite::GetTestInfo(int i) const { + const int index = GetElementOr(test_indices_, i, -1); + return index < 0 ? nullptr : test_info_list_[static_cast(index)]; +} + +// Returns the i-th test among all the tests. i can range from 0 to +// total_test_count() - 1. If i is not in that range, returns NULL. +TestInfo* TestSuite::GetMutableTestInfo(int i) { + const int index = GetElementOr(test_indices_, i, -1); + return index < 0 ? nullptr : test_info_list_[static_cast(index)]; +} + +// Adds a test to this test suite. Will delete the test upon +// destruction of the TestSuite object. +void TestSuite::AddTestInfo(TestInfo* test_info) { + test_info_list_.push_back(test_info); + test_indices_.push_back(static_cast(test_indices_.size())); +} + +// Runs every test in this TestSuite. +void TestSuite::Run() { + if (!should_run_) return; + + UnitTest::GetInstance()->set_current_test_suite(this); + + TestEventListener* repeater = UnitTest::GetInstance()->listeners().repeater(); + + // Ensure our tests are in a deterministic order. + // + // We do this by sorting lexicographically on (file, line number), providing + // an order matching what the user can see in the source code. + // + // In the common case the line number comparison shouldn't be necessary, + // because the registrations made by the TEST macro are executed in order + // within a translation unit. But this is not true of the manual registration + // API, and in more exotic scenarios a single file may be part of multiple + // translation units. + std::stable_sort(test_info_list_.begin(), test_info_list_.end(), + [](const TestInfo* const a, const TestInfo* const b) { + if (const int result = std::strcmp(a->file(), b->file())) { + return result < 0; + } + + return a->line() < b->line(); + }); + + // Call both legacy and the new API + repeater->OnTestSuiteStart(*this); +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + repeater->OnTestCaseStart(*this); +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + + UnitTest::GetInstance()->UponLeavingGTest(); + internal::HandleExceptionsInMethodIfSupported( + this, &TestSuite::RunSetUpTestSuite, "SetUpTestSuite()"); + + const bool skip_all = + ad_hoc_test_result().Failed() || ad_hoc_test_result().Skipped(); + + start_timestamp_ = internal::GetTimeInMillis(); + internal::Timer timer; + for (int i = 0; i < total_test_count(); i++) { + if (skip_all) { + GetMutableTestInfo(i)->Skip(); + } else { + GetMutableTestInfo(i)->Run(); + } + if (GTEST_FLAG_GET(fail_fast) && + GetMutableTestInfo(i)->result()->Failed()) { + for (int j = i + 1; j < total_test_count(); j++) { + GetMutableTestInfo(j)->Skip(); + } + break; + } + } + elapsed_time_ = timer.Elapsed(); + + UnitTest::GetInstance()->UponLeavingGTest(); + internal::HandleExceptionsInMethodIfSupported( + this, &TestSuite::RunTearDownTestSuite, "TearDownTestSuite()"); + + // Call both legacy and the new API + repeater->OnTestSuiteEnd(*this); +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + repeater->OnTestCaseEnd(*this); +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + + UnitTest::GetInstance()->set_current_test_suite(nullptr); +} + +// Skips all tests under this TestSuite. +void TestSuite::Skip() { + if (!should_run_) return; + + UnitTest::GetInstance()->set_current_test_suite(this); + + TestEventListener* repeater = UnitTest::GetInstance()->listeners().repeater(); + + // Call both legacy and the new API + repeater->OnTestSuiteStart(*this); +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + repeater->OnTestCaseStart(*this); +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + + for (int i = 0; i < total_test_count(); i++) { + GetMutableTestInfo(i)->Skip(); + } + + // Call both legacy and the new API + repeater->OnTestSuiteEnd(*this); + // Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + repeater->OnTestCaseEnd(*this); +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + + UnitTest::GetInstance()->set_current_test_suite(nullptr); +} + +// Clears the results of all tests in this test suite. +void TestSuite::ClearResult() { + ad_hoc_test_result_.Clear(); + ForEach(test_info_list_, TestInfo::ClearTestResult); +} + +// Shuffles the tests in this test suite. +void TestSuite::ShuffleTests(internal::Random* random) { + Shuffle(random, &test_indices_); +} + +// Restores the test order to before the first shuffle. +void TestSuite::UnshuffleTests() { + for (size_t i = 0; i < test_indices_.size(); i++) { + test_indices_[i] = static_cast(i); + } +} + +// Formats a countable noun. Depending on its quantity, either the +// singular form or the plural form is used. e.g. +// +// FormatCountableNoun(1, "formula", "formuli") returns "1 formula". +// FormatCountableNoun(5, "book", "books") returns "5 books". +static std::string FormatCountableNoun(int count, const char* singular_form, + const char* plural_form) { + return internal::StreamableToString(count) + " " + + (count == 1 ? singular_form : plural_form); +} + +// Formats the count of tests. +static std::string FormatTestCount(int test_count) { + return FormatCountableNoun(test_count, "test", "tests"); +} + +// Formats the count of test suites. +static std::string FormatTestSuiteCount(int test_suite_count) { + return FormatCountableNoun(test_suite_count, "test suite", "test suites"); +} + +// Converts a TestPartResult::Type enum to human-friendly string +// representation. Both kNonFatalFailure and kFatalFailure are translated +// to "Failure", as the user usually doesn't care about the difference +// between the two when viewing the test result. +static const char* TestPartResultTypeToString(TestPartResult::Type type) { + switch (type) { + case TestPartResult::kSkip: + return "Skipped\n"; + case TestPartResult::kSuccess: + return "Success"; + + case TestPartResult::kNonFatalFailure: + case TestPartResult::kFatalFailure: +#ifdef _MSC_VER + return "error: "; +#else + return "Failure\n"; +#endif + default: + return "Unknown result type"; + } +} + +namespace internal { +namespace { +enum class GTestColor { kDefault, kRed, kGreen, kYellow }; +} // namespace + +// Prints a TestPartResult to an std::string. +static std::string PrintTestPartResultToString( + const TestPartResult& test_part_result) { + return (Message() << internal::FormatFileLocation( + test_part_result.file_name(), + test_part_result.line_number()) + << " " + << TestPartResultTypeToString(test_part_result.type()) + << test_part_result.message()) + .GetString(); +} + +// Prints a TestPartResult. +static void PrintTestPartResult(const TestPartResult& test_part_result) { + const std::string& result = PrintTestPartResultToString(test_part_result); + printf("%s\n", result.c_str()); + fflush(stdout); + // If the test program runs in Visual Studio or a debugger, the + // following statements add the test part result message to the Output + // window such that the user can double-click on it to jump to the + // corresponding source code location; otherwise they do nothing. +#if defined(GTEST_OS_WINDOWS) && !defined(GTEST_OS_WINDOWS_MOBILE) + // We don't call OutputDebugString*() on Windows Mobile, as printing + // to stdout is done by OutputDebugString() there already - we don't + // want the same message printed twice. + ::OutputDebugStringA(result.c_str()); + ::OutputDebugStringA("\n"); +#endif +} + +// class PrettyUnitTestResultPrinter +#if defined(GTEST_OS_WINDOWS) && !defined(GTEST_OS_WINDOWS_MOBILE) && \ + !defined(GTEST_OS_WINDOWS_GAMES) && !defined(GTEST_OS_WINDOWS_PHONE) && \ + !defined(GTEST_OS_WINDOWS_RT) && !defined(GTEST_OS_WINDOWS_MINGW) + +// Returns the character attribute for the given color. +static WORD GetColorAttribute(GTestColor color) { + switch (color) { + case GTestColor::kRed: + return FOREGROUND_RED; + case GTestColor::kGreen: + return FOREGROUND_GREEN; + case GTestColor::kYellow: + return FOREGROUND_RED | FOREGROUND_GREEN; + default: + return 0; + } +} + +static int GetBitOffset(WORD color_mask) { + if (color_mask == 0) return 0; + + int bitOffset = 0; + while ((color_mask & 1) == 0) { + color_mask >>= 1; + ++bitOffset; + } + return bitOffset; +} + +static WORD GetNewColor(GTestColor color, WORD old_color_attrs) { + // Let's reuse the BG + static const WORD background_mask = BACKGROUND_BLUE | BACKGROUND_GREEN | + BACKGROUND_RED | BACKGROUND_INTENSITY; + static const WORD foreground_mask = FOREGROUND_BLUE | FOREGROUND_GREEN | + FOREGROUND_RED | FOREGROUND_INTENSITY; + const WORD existing_bg = old_color_attrs & background_mask; + + WORD new_color = + GetColorAttribute(color) | existing_bg | FOREGROUND_INTENSITY; + static const int bg_bitOffset = GetBitOffset(background_mask); + static const int fg_bitOffset = GetBitOffset(foreground_mask); + + if (((new_color & background_mask) >> bg_bitOffset) == + ((new_color & foreground_mask) >> fg_bitOffset)) { + new_color ^= FOREGROUND_INTENSITY; // invert intensity + } + return new_color; +} + +#else + +// Returns the ANSI color code for the given color. GTestColor::kDefault is +// an invalid input. +static const char* GetAnsiColorCode(GTestColor color) { + switch (color) { + case GTestColor::kRed: + return "1"; + case GTestColor::kGreen: + return "2"; + case GTestColor::kYellow: + return "3"; + default: + assert(false); + return "9"; + } +} + +#endif // GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE + +// Returns true if and only if Google Test should use colors in the output. +bool ShouldUseColor(bool stdout_is_tty) { + std::string c = GTEST_FLAG_GET(color); + const char* const gtest_color = c.c_str(); + + if (String::CaseInsensitiveCStringEquals(gtest_color, "auto")) { +#if defined(GTEST_OS_WINDOWS) && !defined(GTEST_OS_WINDOWS_MINGW) + // On Windows the TERM variable is usually not set, but the + // console there does support colors. + return stdout_is_tty; +#else + // On non-Windows platforms, we rely on the TERM variable. + const char* const term = posix::GetEnv("TERM"); + const bool term_supports_color = + term != nullptr && (String::CStringEquals(term, "xterm") || + String::CStringEquals(term, "xterm-color") || + String::CStringEquals(term, "xterm-kitty") || + String::CStringEquals(term, "alacritty") || + String::CStringEquals(term, "screen") || + String::CStringEquals(term, "tmux") || + String::CStringEquals(term, "rxvt-unicode") || + String::CStringEquals(term, "linux") || + String::CStringEquals(term, "cygwin") || + String::EndsWithCaseInsensitive(term, "-256color")); + return stdout_is_tty && term_supports_color; +#endif // GTEST_OS_WINDOWS + } + + return String::CaseInsensitiveCStringEquals(gtest_color, "yes") || + String::CaseInsensitiveCStringEquals(gtest_color, "true") || + String::CaseInsensitiveCStringEquals(gtest_color, "t") || + String::CStringEquals(gtest_color, "1"); + // We take "yes", "true", "t", and "1" as meaning "yes". If the + // value is neither one of these nor "auto", we treat it as "no" to + // be conservative. +} + +// Helpers for printing colored strings to stdout. Note that on Windows, we +// cannot simply emit special characters and have the terminal change colors. +// This routine must actually emit the characters rather than return a string +// that would be colored when printed, as can be done on Linux. + +GTEST_ATTRIBUTE_PRINTF_(2, 3) +static void ColoredPrintf(GTestColor color, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + + static const bool in_color_mode = + // We don't condition this on GTEST_HAS_FILE_SYSTEM because we still need + // to be able to detect terminal I/O regardless. + ShouldUseColor(posix::IsATTY(posix::FileNo(stdout)) != 0); + + const bool use_color = in_color_mode && (color != GTestColor::kDefault); + + if (!use_color) { + vprintf(fmt, args); + va_end(args); + return; + } + +#if defined(GTEST_OS_WINDOWS) && !defined(GTEST_OS_WINDOWS_MOBILE) && \ + !defined(GTEST_OS_WINDOWS_GAMES) && !defined(GTEST_OS_WINDOWS_PHONE) && \ + !defined(GTEST_OS_WINDOWS_RT) && !defined(GTEST_OS_WINDOWS_MINGW) + const HANDLE stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE); + + // Gets the current text color. + CONSOLE_SCREEN_BUFFER_INFO buffer_info; + GetConsoleScreenBufferInfo(stdout_handle, &buffer_info); + const WORD old_color_attrs = buffer_info.wAttributes; + const WORD new_color = GetNewColor(color, old_color_attrs); + + // We need to flush the stream buffers into the console before each + // SetConsoleTextAttribute call lest it affect the text that is already + // printed but has not yet reached the console. + fflush(stdout); + SetConsoleTextAttribute(stdout_handle, new_color); + + vprintf(fmt, args); + + fflush(stdout); + // Restores the text color. + SetConsoleTextAttribute(stdout_handle, old_color_attrs); +#else + printf("\033[0;3%sm", GetAnsiColorCode(color)); + vprintf(fmt, args); + printf("\033[m"); // Resets the terminal to default. +#endif // GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE + va_end(args); +} + +// Text printed in Google Test's text output and --gtest_list_tests +// output to label the type parameter and value parameter for a test. +static const char kTypeParamLabel[] = "TypeParam"; +static const char kValueParamLabel[] = "GetParam()"; + +static void PrintFullTestCommentIfPresent(const TestInfo& test_info) { + const char* const type_param = test_info.type_param(); + const char* const value_param = test_info.value_param(); + + if (type_param != nullptr || value_param != nullptr) { + printf(", where "); + if (type_param != nullptr) { + printf("%s = %s", kTypeParamLabel, type_param); + if (value_param != nullptr) printf(" and "); + } + if (value_param != nullptr) { + printf("%s = %s", kValueParamLabel, value_param); + } + } +} + +// This class implements the TestEventListener interface. +// +// Class PrettyUnitTestResultPrinter is copyable. +class PrettyUnitTestResultPrinter : public TestEventListener { + public: + PrettyUnitTestResultPrinter() = default; + static void PrintTestName(const char* test_suite, const char* test) { + printf("%s.%s", test_suite, test); + } + + // The following methods override what's in the TestEventListener class. + void OnTestProgramStart(const UnitTest& /*unit_test*/) override {} + void OnTestIterationStart(const UnitTest& unit_test, int iteration) override; + void OnEnvironmentsSetUpStart(const UnitTest& unit_test) override; + void OnEnvironmentsSetUpEnd(const UnitTest& /*unit_test*/) override {} +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + void OnTestCaseStart(const TestCase& test_case) override; +#else + void OnTestSuiteStart(const TestSuite& test_suite) override; +#endif // OnTestCaseStart + + void OnTestStart(const TestInfo& test_info) override; + void OnTestDisabled(const TestInfo& test_info) override; + + void OnTestPartResult(const TestPartResult& result) override; + void OnTestEnd(const TestInfo& test_info) override; +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + void OnTestCaseEnd(const TestCase& test_case) override; +#else + void OnTestSuiteEnd(const TestSuite& test_suite) override; +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + + void OnEnvironmentsTearDownStart(const UnitTest& unit_test) override; + void OnEnvironmentsTearDownEnd(const UnitTest& /*unit_test*/) override {} + void OnTestIterationEnd(const UnitTest& unit_test, int iteration) override; + void OnTestProgramEnd(const UnitTest& /*unit_test*/) override {} + + private: + static void PrintFailedTests(const UnitTest& unit_test); + static void PrintFailedTestSuites(const UnitTest& unit_test); + static void PrintSkippedTests(const UnitTest& unit_test); +}; + +// Fired before each iteration of tests starts. +void PrettyUnitTestResultPrinter::OnTestIterationStart( + const UnitTest& unit_test, int iteration) { + if (GTEST_FLAG_GET(repeat) != 1) + printf("\nRepeating all tests (iteration %d) . . .\n\n", iteration + 1); + + std::string f = GTEST_FLAG_GET(filter); + const char* const filter = f.c_str(); + + // Prints the filter if it's not *. This reminds the user that some + // tests may be skipped. + if (!String::CStringEquals(filter, kUniversalFilter)) { + ColoredPrintf(GTestColor::kYellow, "Note: %s filter = %s\n", GTEST_NAME_, + filter); + } + + if (internal::ShouldShard(kTestTotalShards, kTestShardIndex, false)) { + const int32_t shard_index = Int32FromEnvOrDie(kTestShardIndex, -1); + ColoredPrintf(GTestColor::kYellow, "Note: This is test shard %d of %s.\n", + static_cast(shard_index) + 1, + internal::posix::GetEnv(kTestTotalShards)); + } + + if (GTEST_FLAG_GET(shuffle)) { + ColoredPrintf(GTestColor::kYellow, + "Note: Randomizing tests' orders with a seed of %d .\n", + unit_test.random_seed()); + } + + ColoredPrintf(GTestColor::kGreen, "[==========] "); + printf("Running %s from %s.\n", + FormatTestCount(unit_test.test_to_run_count()).c_str(), + FormatTestSuiteCount(unit_test.test_suite_to_run_count()).c_str()); + fflush(stdout); +} + +void PrettyUnitTestResultPrinter::OnEnvironmentsSetUpStart( + const UnitTest& /*unit_test*/) { + ColoredPrintf(GTestColor::kGreen, "[----------] "); + printf("Global test environment set-up.\n"); + fflush(stdout); +} + +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ +void PrettyUnitTestResultPrinter::OnTestCaseStart(const TestCase& test_case) { + const std::string counts = + FormatCountableNoun(test_case.test_to_run_count(), "test", "tests"); + ColoredPrintf(GTestColor::kGreen, "[----------] "); + printf("%s from %s", counts.c_str(), test_case.name()); + if (test_case.type_param() == nullptr) { + printf("\n"); + } else { + printf(", where %s = %s\n", kTypeParamLabel, test_case.type_param()); + } + fflush(stdout); +} +#else +void PrettyUnitTestResultPrinter::OnTestSuiteStart( + const TestSuite& test_suite) { + const std::string counts = + FormatCountableNoun(test_suite.test_to_run_count(), "test", "tests"); + ColoredPrintf(GTestColor::kGreen, "[----------] "); + printf("%s from %s", counts.c_str(), test_suite.name()); + if (test_suite.type_param() == nullptr) { + printf("\n"); + } else { + printf(", where %s = %s\n", kTypeParamLabel, test_suite.type_param()); + } + fflush(stdout); +} +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + +void PrettyUnitTestResultPrinter::OnTestStart(const TestInfo& test_info) { + ColoredPrintf(GTestColor::kGreen, "[ RUN ] "); + PrintTestName(test_info.test_suite_name(), test_info.name()); + printf("\n"); + fflush(stdout); +} + +void PrettyUnitTestResultPrinter::OnTestDisabled(const TestInfo& test_info) { + ColoredPrintf(GTestColor::kYellow, "[ DISABLED ] "); + PrintTestName(test_info.test_suite_name(), test_info.name()); + printf("\n"); + fflush(stdout); +} + +// Called after an assertion failure. +void PrettyUnitTestResultPrinter::OnTestPartResult( + const TestPartResult& result) { + switch (result.type()) { + // If the test part succeeded, we don't need to do anything. + case TestPartResult::kSuccess: + return; + default: + // Print failure message from the assertion + // (e.g. expected this and got that). + PrintTestPartResult(result); + fflush(stdout); + } +} + +void PrettyUnitTestResultPrinter::OnTestEnd(const TestInfo& test_info) { + if (test_info.result()->Passed()) { + ColoredPrintf(GTestColor::kGreen, "[ OK ] "); + } else if (test_info.result()->Skipped()) { + ColoredPrintf(GTestColor::kGreen, "[ SKIPPED ] "); + } else { + ColoredPrintf(GTestColor::kRed, "[ FAILED ] "); + } + PrintTestName(test_info.test_suite_name(), test_info.name()); + if (test_info.result()->Failed()) PrintFullTestCommentIfPresent(test_info); + + if (GTEST_FLAG_GET(print_time)) { + printf(" (%s ms)\n", + internal::StreamableToString(test_info.result()->elapsed_time()) + .c_str()); + } else { + printf("\n"); + } + fflush(stdout); +} + +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ +void PrettyUnitTestResultPrinter::OnTestCaseEnd(const TestCase& test_case) { + if (!GTEST_FLAG_GET(print_time)) return; + + const std::string counts = + FormatCountableNoun(test_case.test_to_run_count(), "test", "tests"); + ColoredPrintf(GTestColor::kGreen, "[----------] "); + printf("%s from %s (%s ms total)\n\n", counts.c_str(), test_case.name(), + internal::StreamableToString(test_case.elapsed_time()).c_str()); + fflush(stdout); +} +#else +void PrettyUnitTestResultPrinter::OnTestSuiteEnd(const TestSuite& test_suite) { + if (!GTEST_FLAG_GET(print_time)) return; + + const std::string counts = + FormatCountableNoun(test_suite.test_to_run_count(), "test", "tests"); + ColoredPrintf(GTestColor::kGreen, "[----------] "); + printf("%s from %s (%s ms total)\n\n", counts.c_str(), test_suite.name(), + internal::StreamableToString(test_suite.elapsed_time()).c_str()); + fflush(stdout); +} +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + +void PrettyUnitTestResultPrinter::OnEnvironmentsTearDownStart( + const UnitTest& /*unit_test*/) { + ColoredPrintf(GTestColor::kGreen, "[----------] "); + printf("Global test environment tear-down\n"); + fflush(stdout); +} + +// Internal helper for printing the list of failed tests. +void PrettyUnitTestResultPrinter::PrintFailedTests(const UnitTest& unit_test) { + const int failed_test_count = unit_test.failed_test_count(); + ColoredPrintf(GTestColor::kRed, "[ FAILED ] "); + printf("%s, listed below:\n", FormatTestCount(failed_test_count).c_str()); + + for (int i = 0; i < unit_test.total_test_suite_count(); ++i) { + const TestSuite& test_suite = *unit_test.GetTestSuite(i); + if (!test_suite.should_run() || (test_suite.failed_test_count() == 0)) { + continue; + } + for (int j = 0; j < test_suite.total_test_count(); ++j) { + const TestInfo& test_info = *test_suite.GetTestInfo(j); + if (!test_info.should_run() || !test_info.result()->Failed()) { + continue; + } + ColoredPrintf(GTestColor::kRed, "[ FAILED ] "); + printf("%s.%s", test_suite.name(), test_info.name()); + PrintFullTestCommentIfPresent(test_info); + printf("\n"); + } + } + printf("\n%2d FAILED %s\n", failed_test_count, + failed_test_count == 1 ? "TEST" : "TESTS"); +} + +// Internal helper for printing the list of test suite failures not covered by +// PrintFailedTests. +void PrettyUnitTestResultPrinter::PrintFailedTestSuites( + const UnitTest& unit_test) { + int suite_failure_count = 0; + for (int i = 0; i < unit_test.total_test_suite_count(); ++i) { + const TestSuite& test_suite = *unit_test.GetTestSuite(i); + if (!test_suite.should_run()) { + continue; + } + if (test_suite.ad_hoc_test_result().Failed()) { + ColoredPrintf(GTestColor::kRed, "[ FAILED ] "); + printf("%s: SetUpTestSuite or TearDownTestSuite\n", test_suite.name()); + ++suite_failure_count; + } + } + if (suite_failure_count > 0) { + printf("\n%2d FAILED TEST %s\n", suite_failure_count, + suite_failure_count == 1 ? "SUITE" : "SUITES"); + } +} + +// Internal helper for printing the list of skipped tests. +void PrettyUnitTestResultPrinter::PrintSkippedTests(const UnitTest& unit_test) { + const int skipped_test_count = unit_test.skipped_test_count(); + if (skipped_test_count == 0) { + return; + } + + for (int i = 0; i < unit_test.total_test_suite_count(); ++i) { + const TestSuite& test_suite = *unit_test.GetTestSuite(i); + if (!test_suite.should_run() || (test_suite.skipped_test_count() == 0)) { + continue; + } + for (int j = 0; j < test_suite.total_test_count(); ++j) { + const TestInfo& test_info = *test_suite.GetTestInfo(j); + if (!test_info.should_run() || !test_info.result()->Skipped()) { + continue; + } + ColoredPrintf(GTestColor::kGreen, "[ SKIPPED ] "); + printf("%s.%s", test_suite.name(), test_info.name()); + printf("\n"); + } + } +} + +void PrettyUnitTestResultPrinter::OnTestIterationEnd(const UnitTest& unit_test, + int /*iteration*/) { + ColoredPrintf(GTestColor::kGreen, "[==========] "); + printf("%s from %s ran.", + FormatTestCount(unit_test.test_to_run_count()).c_str(), + FormatTestSuiteCount(unit_test.test_suite_to_run_count()).c_str()); + if (GTEST_FLAG_GET(print_time)) { + printf(" (%s ms total)", + internal::StreamableToString(unit_test.elapsed_time()).c_str()); + } + printf("\n"); + ColoredPrintf(GTestColor::kGreen, "[ PASSED ] "); + printf("%s.\n", FormatTestCount(unit_test.successful_test_count()).c_str()); + + const int skipped_test_count = unit_test.skipped_test_count(); + if (skipped_test_count > 0) { + ColoredPrintf(GTestColor::kGreen, "[ SKIPPED ] "); + printf("%s, listed below:\n", FormatTestCount(skipped_test_count).c_str()); + PrintSkippedTests(unit_test); + } + + if (!unit_test.Passed()) { + PrintFailedTests(unit_test); + PrintFailedTestSuites(unit_test); + } + + int num_disabled = unit_test.reportable_disabled_test_count(); + if (num_disabled && !GTEST_FLAG_GET(also_run_disabled_tests)) { + if (unit_test.Passed()) { + printf("\n"); // Add a spacer if no FAILURE banner is displayed. + } + ColoredPrintf(GTestColor::kYellow, " YOU HAVE %d DISABLED %s\n\n", + num_disabled, num_disabled == 1 ? "TEST" : "TESTS"); + } + // Ensure that Google Test output is printed before, e.g., heapchecker output. + fflush(stdout); +} + +// End PrettyUnitTestResultPrinter + +// This class implements the TestEventListener interface. +// +// Class BriefUnitTestResultPrinter is copyable. +class BriefUnitTestResultPrinter : public TestEventListener { + public: + BriefUnitTestResultPrinter() = default; + static void PrintTestName(const char* test_suite, const char* test) { + printf("%s.%s", test_suite, test); + } + + // The following methods override what's in the TestEventListener class. + void OnTestProgramStart(const UnitTest& /*unit_test*/) override {} + void OnTestIterationStart(const UnitTest& /*unit_test*/, + int /*iteration*/) override {} + void OnEnvironmentsSetUpStart(const UnitTest& /*unit_test*/) override {} + void OnEnvironmentsSetUpEnd(const UnitTest& /*unit_test*/) override {} +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + void OnTestCaseStart(const TestCase& /*test_case*/) override {} +#else + void OnTestSuiteStart(const TestSuite& /*test_suite*/) override {} +#endif // OnTestCaseStart + + void OnTestStart(const TestInfo& /*test_info*/) override {} + void OnTestDisabled(const TestInfo& /*test_info*/) override {} + + void OnTestPartResult(const TestPartResult& result) override; + void OnTestEnd(const TestInfo& test_info) override; +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + void OnTestCaseEnd(const TestCase& /*test_case*/) override {} +#else + void OnTestSuiteEnd(const TestSuite& /*test_suite*/) override {} +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + + void OnEnvironmentsTearDownStart(const UnitTest& /*unit_test*/) override {} + void OnEnvironmentsTearDownEnd(const UnitTest& /*unit_test*/) override {} + void OnTestIterationEnd(const UnitTest& unit_test, int iteration) override; + void OnTestProgramEnd(const UnitTest& /*unit_test*/) override {} +}; + +// Called after an assertion failure. +void BriefUnitTestResultPrinter::OnTestPartResult( + const TestPartResult& result) { + switch (result.type()) { + // If the test part succeeded, we don't need to do anything. + case TestPartResult::kSuccess: + return; + default: + // Print failure message from the assertion + // (e.g. expected this and got that). + PrintTestPartResult(result); + fflush(stdout); + } +} + +void BriefUnitTestResultPrinter::OnTestEnd(const TestInfo& test_info) { + if (test_info.result()->Failed()) { + ColoredPrintf(GTestColor::kRed, "[ FAILED ] "); + PrintTestName(test_info.test_suite_name(), test_info.name()); + PrintFullTestCommentIfPresent(test_info); + + if (GTEST_FLAG_GET(print_time)) { + printf(" (%s ms)\n", + internal::StreamableToString(test_info.result()->elapsed_time()) + .c_str()); + } else { + printf("\n"); + } + fflush(stdout); + } +} + +void BriefUnitTestResultPrinter::OnTestIterationEnd(const UnitTest& unit_test, + int /*iteration*/) { + ColoredPrintf(GTestColor::kGreen, "[==========] "); + printf("%s from %s ran.", + FormatTestCount(unit_test.test_to_run_count()).c_str(), + FormatTestSuiteCount(unit_test.test_suite_to_run_count()).c_str()); + if (GTEST_FLAG_GET(print_time)) { + printf(" (%s ms total)", + internal::StreamableToString(unit_test.elapsed_time()).c_str()); + } + printf("\n"); + ColoredPrintf(GTestColor::kGreen, "[ PASSED ] "); + printf("%s.\n", FormatTestCount(unit_test.successful_test_count()).c_str()); + + const int skipped_test_count = unit_test.skipped_test_count(); + if (skipped_test_count > 0) { + ColoredPrintf(GTestColor::kGreen, "[ SKIPPED ] "); + printf("%s.\n", FormatTestCount(skipped_test_count).c_str()); + } + + int num_disabled = unit_test.reportable_disabled_test_count(); + if (num_disabled && !GTEST_FLAG_GET(also_run_disabled_tests)) { + if (unit_test.Passed()) { + printf("\n"); // Add a spacer if no FAILURE banner is displayed. + } + ColoredPrintf(GTestColor::kYellow, " YOU HAVE %d DISABLED %s\n\n", + num_disabled, num_disabled == 1 ? "TEST" : "TESTS"); + } + // Ensure that Google Test output is printed before, e.g., heapchecker output. + fflush(stdout); +} + +// End BriefUnitTestResultPrinter + +// class TestEventRepeater +// +// This class forwards events to other event listeners. +class TestEventRepeater : public TestEventListener { + public: + TestEventRepeater() : forwarding_enabled_(true) {} + ~TestEventRepeater() override; + void Append(TestEventListener* listener); + TestEventListener* Release(TestEventListener* listener); + + // Controls whether events will be forwarded to listeners_. Set to false + // in death test child processes. + bool forwarding_enabled() const { return forwarding_enabled_; } + void set_forwarding_enabled(bool enable) { forwarding_enabled_ = enable; } + + void OnTestProgramStart(const UnitTest& parameter) override; + void OnTestIterationStart(const UnitTest& unit_test, int iteration) override; + void OnEnvironmentsSetUpStart(const UnitTest& parameter) override; + void OnEnvironmentsSetUpEnd(const UnitTest& parameter) override; +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + void OnTestCaseStart(const TestSuite& parameter) override; +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + void OnTestSuiteStart(const TestSuite& parameter) override; + void OnTestStart(const TestInfo& parameter) override; + void OnTestDisabled(const TestInfo& parameter) override; + void OnTestPartResult(const TestPartResult& parameter) override; + void OnTestEnd(const TestInfo& parameter) override; +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + void OnTestCaseEnd(const TestCase& parameter) override; +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + void OnTestSuiteEnd(const TestSuite& parameter) override; + void OnEnvironmentsTearDownStart(const UnitTest& parameter) override; + void OnEnvironmentsTearDownEnd(const UnitTest& parameter) override; + void OnTestIterationEnd(const UnitTest& unit_test, int iteration) override; + void OnTestProgramEnd(const UnitTest& parameter) override; + + private: + // Controls whether events will be forwarded to listeners_. Set to false + // in death test child processes. + bool forwarding_enabled_; + // The list of listeners that receive events. + std::vector listeners_; + + TestEventRepeater(const TestEventRepeater&) = delete; + TestEventRepeater& operator=(const TestEventRepeater&) = delete; +}; + +TestEventRepeater::~TestEventRepeater() { + ForEach(listeners_, Delete); +} + +void TestEventRepeater::Append(TestEventListener* listener) { + listeners_.push_back(listener); +} + +TestEventListener* TestEventRepeater::Release(TestEventListener* listener) { + for (size_t i = 0; i < listeners_.size(); ++i) { + if (listeners_[i] == listener) { + listeners_.erase(listeners_.begin() + static_cast(i)); + return listener; + } + } + + return nullptr; +} + +// Since most methods are very similar, use macros to reduce boilerplate. +// This defines a member that forwards the call to all listeners. +#define GTEST_REPEATER_METHOD_(Name, Type) \ + void TestEventRepeater::Name(const Type& parameter) { \ + if (forwarding_enabled_) { \ + for (size_t i = 0; i < listeners_.size(); i++) { \ + listeners_[i]->Name(parameter); \ + } \ + } \ + } +// This defines a member that forwards the call to all listeners in reverse +// order. +#define GTEST_REVERSE_REPEATER_METHOD_(Name, Type) \ + void TestEventRepeater::Name(const Type& parameter) { \ + if (forwarding_enabled_) { \ + for (size_t i = listeners_.size(); i != 0; i--) { \ + listeners_[i - 1]->Name(parameter); \ + } \ + } \ + } + +GTEST_REPEATER_METHOD_(OnTestProgramStart, UnitTest) +GTEST_REPEATER_METHOD_(OnEnvironmentsSetUpStart, UnitTest) +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ +GTEST_REPEATER_METHOD_(OnTestCaseStart, TestSuite) +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ +GTEST_REPEATER_METHOD_(OnTestSuiteStart, TestSuite) +GTEST_REPEATER_METHOD_(OnTestStart, TestInfo) +GTEST_REPEATER_METHOD_(OnTestDisabled, TestInfo) +GTEST_REPEATER_METHOD_(OnTestPartResult, TestPartResult) +GTEST_REPEATER_METHOD_(OnEnvironmentsTearDownStart, UnitTest) +GTEST_REVERSE_REPEATER_METHOD_(OnEnvironmentsSetUpEnd, UnitTest) +GTEST_REVERSE_REPEATER_METHOD_(OnEnvironmentsTearDownEnd, UnitTest) +GTEST_REVERSE_REPEATER_METHOD_(OnTestEnd, TestInfo) +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ +GTEST_REVERSE_REPEATER_METHOD_(OnTestCaseEnd, TestSuite) +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ +GTEST_REVERSE_REPEATER_METHOD_(OnTestSuiteEnd, TestSuite) +GTEST_REVERSE_REPEATER_METHOD_(OnTestProgramEnd, UnitTest) + +#undef GTEST_REPEATER_METHOD_ +#undef GTEST_REVERSE_REPEATER_METHOD_ + +void TestEventRepeater::OnTestIterationStart(const UnitTest& unit_test, + int iteration) { + if (forwarding_enabled_) { + for (size_t i = 0; i < listeners_.size(); i++) { + listeners_[i]->OnTestIterationStart(unit_test, iteration); + } + } +} + +void TestEventRepeater::OnTestIterationEnd(const UnitTest& unit_test, + int iteration) { + if (forwarding_enabled_) { + for (size_t i = listeners_.size(); i > 0; i--) { + listeners_[i - 1]->OnTestIterationEnd(unit_test, iteration); + } + } +} + +// End TestEventRepeater + +#if GTEST_HAS_FILE_SYSTEM +// This class generates an XML output file. +class XmlUnitTestResultPrinter : public EmptyTestEventListener { + public: + explicit XmlUnitTestResultPrinter(const char* output_file); + + void OnTestIterationEnd(const UnitTest& unit_test, int iteration) override; + void ListTestsMatchingFilter(const std::vector& test_suites); + + // Prints an XML summary of all unit tests. + static void PrintXmlTestsList(std::ostream* stream, + const std::vector& test_suites); + + private: + // Is c a whitespace character that is normalized to a space character + // when it appears in an XML attribute value? + static bool IsNormalizableWhitespace(unsigned char c) { + return c == '\t' || c == '\n' || c == '\r'; + } + + // May c appear in a well-formed XML document? + // https://www.w3.org/TR/REC-xml/#charsets + static bool IsValidXmlCharacter(unsigned char c) { + return IsNormalizableWhitespace(c) || c >= 0x20; + } + + // Returns an XML-escaped copy of the input string str. If + // is_attribute is true, the text is meant to appear as an attribute + // value, and normalizable whitespace is preserved by replacing it + // with character references. + static std::string EscapeXml(const std::string& str, bool is_attribute); + + // Returns the given string with all characters invalid in XML removed. + static std::string RemoveInvalidXmlCharacters(const std::string& str); + + // Convenience wrapper around EscapeXml when str is an attribute value. + static std::string EscapeXmlAttribute(const std::string& str) { + return EscapeXml(str, true); + } + + // Convenience wrapper around EscapeXml when str is not an attribute value. + static std::string EscapeXmlText(const char* str) { + return EscapeXml(str, false); + } + + // Verifies that the given attribute belongs to the given element and + // streams the attribute as XML. + static void OutputXmlAttribute(std::ostream* stream, + const std::string& element_name, + const std::string& name, + const std::string& value); + + // Streams an XML CDATA section, escaping invalid CDATA sequences as needed. + static void OutputXmlCDataSection(::std::ostream* stream, const char* data); + + // Streams a test suite XML stanza containing the given test result. + // + // Requires: result.Failed() + static void OutputXmlTestSuiteForTestResult(::std::ostream* stream, + const TestResult& result); + + // Streams a test case XML stanza containing the given test result. + // + // Requires: result.Failed() + static void OutputXmlTestCaseForTestResult(::std::ostream* stream, + const TestResult& result); + + // Streams an XML representation of a TestResult object. + static void OutputXmlTestResult(::std::ostream* stream, + const TestResult& result); + + // Streams an XML representation of a TestInfo object. + static void OutputXmlTestInfo(::std::ostream* stream, + const char* test_suite_name, + const TestInfo& test_info); + + // Prints an XML representation of a TestSuite object + static void PrintXmlTestSuite(::std::ostream* stream, + const TestSuite& test_suite); + + // Prints an XML summary of unit_test to output stream out. + static void PrintXmlUnitTest(::std::ostream* stream, + const UnitTest& unit_test); + + // Streams an XML representation of the test properties of a TestResult + // object. + static void OutputXmlTestProperties(std::ostream* stream, + const TestResult& result, + const std::string& indent); + + // The output file. + const std::string output_file_; + + XmlUnitTestResultPrinter(const XmlUnitTestResultPrinter&) = delete; + XmlUnitTestResultPrinter& operator=(const XmlUnitTestResultPrinter&) = delete; +}; + +// Creates a new XmlUnitTestResultPrinter. +XmlUnitTestResultPrinter::XmlUnitTestResultPrinter(const char* output_file) + : output_file_(output_file) { + if (output_file_.empty()) { + GTEST_LOG_(FATAL) << "XML output file may not be null"; + } +} + +// Called after the unit test ends. +void XmlUnitTestResultPrinter::OnTestIterationEnd(const UnitTest& unit_test, + int /*iteration*/) { + FILE* xmlout = OpenFileForWriting(output_file_); + std::stringstream stream; + PrintXmlUnitTest(&stream, unit_test); + fprintf(xmlout, "%s", StringStreamToString(&stream).c_str()); + fclose(xmlout); +} + +void XmlUnitTestResultPrinter::ListTestsMatchingFilter( + const std::vector& test_suites) { + FILE* xmlout = OpenFileForWriting(output_file_); + std::stringstream stream; + PrintXmlTestsList(&stream, test_suites); + fprintf(xmlout, "%s", StringStreamToString(&stream).c_str()); + fclose(xmlout); +} + +// Returns an XML-escaped copy of the input string str. If is_attribute +// is true, the text is meant to appear as an attribute value, and +// normalizable whitespace is preserved by replacing it with character +// references. +// +// Invalid XML characters in str, if any, are stripped from the output. +// It is expected that most, if not all, of the text processed by this +// module will consist of ordinary English text. +// If this module is ever modified to produce version 1.1 XML output, +// most invalid characters can be retained using character references. +std::string XmlUnitTestResultPrinter::EscapeXml(const std::string& str, + bool is_attribute) { + Message m; + + for (size_t i = 0; i < str.size(); ++i) { + const char ch = str[i]; + switch (ch) { + case '<': + m << "<"; + break; + case '>': + m << ">"; + break; + case '&': + m << "&"; + break; + case '\'': + if (is_attribute) + m << "'"; + else + m << '\''; + break; + case '"': + if (is_attribute) + m << """; + else + m << '"'; + break; + default: + if (IsValidXmlCharacter(static_cast(ch))) { + if (is_attribute && + IsNormalizableWhitespace(static_cast(ch))) + m << "&#x" << String::FormatByte(static_cast(ch)) + << ";"; + else + m << ch; + } + break; + } + } + + return m.GetString(); +} + +// Returns the given string with all characters invalid in XML removed. +// Currently invalid characters are dropped from the string. An +// alternative is to replace them with certain characters such as . or ?. +std::string XmlUnitTestResultPrinter::RemoveInvalidXmlCharacters( + const std::string& str) { + std::string output; + output.reserve(str.size()); + for (std::string::const_iterator it = str.begin(); it != str.end(); ++it) + if (IsValidXmlCharacter(static_cast(*it))) + output.push_back(*it); + + return output; +} + +// The following routines generate an XML representation of a UnitTest +// object. +// +// This is how Google Test concepts map to the DTD: +// +// <-- corresponds to a UnitTest object +// <-- corresponds to a TestSuite object +// <-- corresponds to a TestInfo object +// ... +// ... +// ... +// <-- individual assertion failures +// +// +// + +// Formats the given time in milliseconds as seconds. +std::string FormatTimeInMillisAsSeconds(TimeInMillis ms) { + ::std::stringstream ss; + // For the exact N seconds, makes sure output has a trailing decimal point. + // Sets precision so that we won't have many trailing zeros (e.g., 300 ms + // will be just 0.3, 410 ms 0.41, and so on) + ss << std::fixed + << std::setprecision( + ms % 1000 == 0 ? 0 : (ms % 100 == 0 ? 1 : (ms % 10 == 0 ? 2 : 3))) + << std::showpoint; + ss << (static_cast(ms) * 1e-3); + return ss.str(); +} + +static bool PortableLocaltime(time_t seconds, struct tm* out) { +#if defined(_MSC_VER) + return localtime_s(out, &seconds) == 0; +#elif defined(__MINGW32__) || defined(__MINGW64__) + // MINGW provides neither localtime_r nor localtime_s, but uses + // Windows' localtime(), which has a thread-local tm buffer. + struct tm* tm_ptr = localtime(&seconds); // NOLINT + if (tm_ptr == nullptr) return false; + *out = *tm_ptr; + return true; +#elif defined(__STDC_LIB_EXT1__) + // Uses localtime_s when available as localtime_r is only available from + // C23 standard. + return localtime_s(&seconds, out) != nullptr; +#else + return localtime_r(&seconds, out) != nullptr; +#endif +} + +// Converts the given epoch time in milliseconds to a date string in the ISO +// 8601 format, without the timezone information. +std::string FormatEpochTimeInMillisAsIso8601(TimeInMillis ms) { + struct tm time_struct; + if (!PortableLocaltime(static_cast(ms / 1000), &time_struct)) + return ""; + // YYYY-MM-DDThh:mm:ss.sss + return StreamableToString(time_struct.tm_year + 1900) + "-" + + String::FormatIntWidth2(time_struct.tm_mon + 1) + "-" + + String::FormatIntWidth2(time_struct.tm_mday) + "T" + + String::FormatIntWidth2(time_struct.tm_hour) + ":" + + String::FormatIntWidth2(time_struct.tm_min) + ":" + + String::FormatIntWidth2(time_struct.tm_sec) + "." + + String::FormatIntWidthN(static_cast(ms % 1000), 3); +} + +// Streams an XML CDATA section, escaping invalid CDATA sequences as needed. +void XmlUnitTestResultPrinter::OutputXmlCDataSection(::std::ostream* stream, + const char* data) { + const char* segment = data; + *stream << ""); + if (next_segment != nullptr) { + stream->write(segment, + static_cast(next_segment - segment)); + *stream << "]]>]]>"); + } else { + *stream << segment; + break; + } + } + *stream << "]]>"; +} + +void XmlUnitTestResultPrinter::OutputXmlAttribute( + std::ostream* stream, const std::string& element_name, + const std::string& name, const std::string& value) { + const std::vector& allowed_names = + GetReservedOutputAttributesForElement(element_name); + + GTEST_CHECK_(std::find(allowed_names.begin(), allowed_names.end(), name) != + allowed_names.end()) + << "Attribute " << name << " is not allowed for element <" << element_name + << ">."; + + *stream << " " << name << "=\"" << EscapeXmlAttribute(value) << "\""; +} + +// Streams a test suite XML stanza containing the given test result. +void XmlUnitTestResultPrinter::OutputXmlTestSuiteForTestResult( + ::std::ostream* stream, const TestResult& result) { + // Output the boilerplate for a minimal test suite with one test. + *stream << " "; + + OutputXmlTestCaseForTestResult(stream, result); + + // Complete the test suite. + *stream << " \n"; +} + +// Streams a test case XML stanza containing the given test result. +void XmlUnitTestResultPrinter::OutputXmlTestCaseForTestResult( + ::std::ostream* stream, const TestResult& result) { + // Output the boilerplate for a minimal test case with a single test. + *stream << " \n"; + return; + } + + OutputXmlAttribute(stream, kTestsuite, "status", + test_info.should_run() ? "run" : "notrun"); + OutputXmlAttribute(stream, kTestsuite, "result", + test_info.should_run() + ? (result.Skipped() ? "skipped" : "completed") + : "suppressed"); + OutputXmlAttribute(stream, kTestsuite, "time", + FormatTimeInMillisAsSeconds(result.elapsed_time())); + OutputXmlAttribute( + stream, kTestsuite, "timestamp", + FormatEpochTimeInMillisAsIso8601(result.start_timestamp())); + OutputXmlAttribute(stream, kTestsuite, "classname", test_suite_name); + + OutputXmlTestResult(stream, result); +} + +void XmlUnitTestResultPrinter::OutputXmlTestResult(::std::ostream* stream, + const TestResult& result) { + int failures = 0; + int skips = 0; + for (int i = 0; i < result.total_part_count(); ++i) { + const TestPartResult& part = result.GetTestPartResult(i); + if (part.failed()) { + if (++failures == 1 && skips == 0) { + *stream << ">\n"; + } + const std::string location = + internal::FormatCompilerIndependentFileLocation(part.file_name(), + part.line_number()); + const std::string summary = location + "\n" + part.summary(); + *stream << " "; + const std::string detail = location + "\n" + part.message(); + OutputXmlCDataSection(stream, RemoveInvalidXmlCharacters(detail).c_str()); + *stream << "\n"; + } else if (part.skipped()) { + if (++skips == 1 && failures == 0) { + *stream << ">\n"; + } + const std::string location = + internal::FormatCompilerIndependentFileLocation(part.file_name(), + part.line_number()); + const std::string summary = location + "\n" + part.summary(); + *stream << " "; + const std::string detail = location + "\n" + part.message(); + OutputXmlCDataSection(stream, RemoveInvalidXmlCharacters(detail).c_str()); + *stream << "\n"; + } + } + + if (failures == 0 && skips == 0 && result.test_property_count() == 0) { + *stream << " />\n"; + } else { + if (failures == 0 && skips == 0) { + *stream << ">\n"; + } + OutputXmlTestProperties(stream, result, /*indent=*/" "); + *stream << " \n"; + } +} + +// Prints an XML representation of a TestSuite object +void XmlUnitTestResultPrinter::PrintXmlTestSuite(std::ostream* stream, + const TestSuite& test_suite) { + const std::string kTestsuite = "testsuite"; + *stream << " <" << kTestsuite; + OutputXmlAttribute(stream, kTestsuite, "name", test_suite.name()); + OutputXmlAttribute(stream, kTestsuite, "tests", + StreamableToString(test_suite.reportable_test_count())); + if (!GTEST_FLAG_GET(list_tests)) { + OutputXmlAttribute(stream, kTestsuite, "failures", + StreamableToString(test_suite.failed_test_count())); + OutputXmlAttribute( + stream, kTestsuite, "disabled", + StreamableToString(test_suite.reportable_disabled_test_count())); + OutputXmlAttribute(stream, kTestsuite, "skipped", + StreamableToString(test_suite.skipped_test_count())); + + OutputXmlAttribute(stream, kTestsuite, "errors", "0"); + + OutputXmlAttribute(stream, kTestsuite, "time", + FormatTimeInMillisAsSeconds(test_suite.elapsed_time())); + OutputXmlAttribute( + stream, kTestsuite, "timestamp", + FormatEpochTimeInMillisAsIso8601(test_suite.start_timestamp())); + } + *stream << ">\n"; + OutputXmlTestProperties(stream, test_suite.ad_hoc_test_result(), + /*indent=*/" "); + for (int i = 0; i < test_suite.total_test_count(); ++i) { + if (test_suite.GetTestInfo(i)->is_reportable()) + OutputXmlTestInfo(stream, test_suite.name(), *test_suite.GetTestInfo(i)); + } + if (test_suite.ad_hoc_test_result().Failed()) { + OutputXmlTestCaseForTestResult(stream, test_suite.ad_hoc_test_result()); + } + + *stream << " \n"; +} + +// Prints an XML summary of unit_test to output stream out. +void XmlUnitTestResultPrinter::PrintXmlUnitTest(std::ostream* stream, + const UnitTest& unit_test) { + const std::string kTestsuites = "testsuites"; + + *stream << "\n"; + *stream << "<" << kTestsuites; + + OutputXmlAttribute(stream, kTestsuites, "tests", + StreamableToString(unit_test.reportable_test_count())); + OutputXmlAttribute(stream, kTestsuites, "failures", + StreamableToString(unit_test.failed_test_count())); + OutputXmlAttribute( + stream, kTestsuites, "disabled", + StreamableToString(unit_test.reportable_disabled_test_count())); + OutputXmlAttribute(stream, kTestsuites, "errors", "0"); + OutputXmlAttribute(stream, kTestsuites, "time", + FormatTimeInMillisAsSeconds(unit_test.elapsed_time())); + OutputXmlAttribute( + stream, kTestsuites, "timestamp", + FormatEpochTimeInMillisAsIso8601(unit_test.start_timestamp())); + + if (GTEST_FLAG_GET(shuffle)) { + OutputXmlAttribute(stream, kTestsuites, "random_seed", + StreamableToString(unit_test.random_seed())); + } + + OutputXmlAttribute(stream, kTestsuites, "name", "AllTests"); + *stream << ">\n"; + + OutputXmlTestProperties(stream, unit_test.ad_hoc_test_result(), + /*indent=*/" "); + for (int i = 0; i < unit_test.total_test_suite_count(); ++i) { + if (unit_test.GetTestSuite(i)->reportable_test_count() > 0) + PrintXmlTestSuite(stream, *unit_test.GetTestSuite(i)); + } + + // If there was a test failure outside of one of the test suites (like in a + // test environment) include that in the output. + if (unit_test.ad_hoc_test_result().Failed()) { + OutputXmlTestSuiteForTestResult(stream, unit_test.ad_hoc_test_result()); + } + + *stream << "\n"; +} + +void XmlUnitTestResultPrinter::PrintXmlTestsList( + std::ostream* stream, const std::vector& test_suites) { + const std::string kTestsuites = "testsuites"; + + *stream << "\n"; + *stream << "<" << kTestsuites; + + int total_tests = 0; + for (auto test_suite : test_suites) { + total_tests += test_suite->total_test_count(); + } + OutputXmlAttribute(stream, kTestsuites, "tests", + StreamableToString(total_tests)); + OutputXmlAttribute(stream, kTestsuites, "name", "AllTests"); + *stream << ">\n"; + + for (auto test_suite : test_suites) { + PrintXmlTestSuite(stream, *test_suite); + } + *stream << "\n"; +} + +void XmlUnitTestResultPrinter::OutputXmlTestProperties( + std::ostream* stream, const TestResult& result, const std::string& indent) { + const std::string kProperties = "properties"; + const std::string kProperty = "property"; + + if (result.test_property_count() <= 0) { + return; + } + + *stream << indent << "<" << kProperties << ">\n"; + for (int i = 0; i < result.test_property_count(); ++i) { + const TestProperty& property = result.GetTestProperty(i); + *stream << indent << " <" << kProperty; + *stream << " name=\"" << EscapeXmlAttribute(property.key()) << "\""; + *stream << " value=\"" << EscapeXmlAttribute(property.value()) << "\""; + *stream << "/>\n"; + } + *stream << indent << "\n"; +} + +// End XmlUnitTestResultPrinter +#endif // GTEST_HAS_FILE_SYSTEM + +#if GTEST_HAS_FILE_SYSTEM +// This class generates an JSON output file. +class JsonUnitTestResultPrinter : public EmptyTestEventListener { + public: + explicit JsonUnitTestResultPrinter(const char* output_file); + + void OnTestIterationEnd(const UnitTest& unit_test, int iteration) override; + + // Prints an JSON summary of all unit tests. + static void PrintJsonTestList(::std::ostream* stream, + const std::vector& test_suites); + + private: + // Returns an JSON-escaped copy of the input string str. + static std::string EscapeJson(const std::string& str); + + //// Verifies that the given attribute belongs to the given element and + //// streams the attribute as JSON. + static void OutputJsonKey(std::ostream* stream, + const std::string& element_name, + const std::string& name, const std::string& value, + const std::string& indent, bool comma = true); + static void OutputJsonKey(std::ostream* stream, + const std::string& element_name, + const std::string& name, int value, + const std::string& indent, bool comma = true); + + // Streams a test suite JSON stanza containing the given test result. + // + // Requires: result.Failed() + static void OutputJsonTestSuiteForTestResult(::std::ostream* stream, + const TestResult& result); + + // Streams a test case JSON stanza containing the given test result. + // + // Requires: result.Failed() + static void OutputJsonTestCaseForTestResult(::std::ostream* stream, + const TestResult& result); + + // Streams a JSON representation of a TestResult object. + static void OutputJsonTestResult(::std::ostream* stream, + const TestResult& result); + + // Streams a JSON representation of a TestInfo object. + static void OutputJsonTestInfo(::std::ostream* stream, + const char* test_suite_name, + const TestInfo& test_info); + + // Prints a JSON representation of a TestSuite object + static void PrintJsonTestSuite(::std::ostream* stream, + const TestSuite& test_suite); + + // Prints a JSON summary of unit_test to output stream out. + static void PrintJsonUnitTest(::std::ostream* stream, + const UnitTest& unit_test); + + // Produces a string representing the test properties in a result as + // a JSON dictionary. + static std::string TestPropertiesAsJson(const TestResult& result, + const std::string& indent); + + // The output file. + const std::string output_file_; + + JsonUnitTestResultPrinter(const JsonUnitTestResultPrinter&) = delete; + JsonUnitTestResultPrinter& operator=(const JsonUnitTestResultPrinter&) = + delete; +}; + +// Creates a new JsonUnitTestResultPrinter. +JsonUnitTestResultPrinter::JsonUnitTestResultPrinter(const char* output_file) + : output_file_(output_file) { + if (output_file_.empty()) { + GTEST_LOG_(FATAL) << "JSON output file may not be null"; + } +} + +void JsonUnitTestResultPrinter::OnTestIterationEnd(const UnitTest& unit_test, + int /*iteration*/) { + FILE* jsonout = OpenFileForWriting(output_file_); + std::stringstream stream; + PrintJsonUnitTest(&stream, unit_test); + fprintf(jsonout, "%s", StringStreamToString(&stream).c_str()); + fclose(jsonout); +} + +// Returns an JSON-escaped copy of the input string str. +std::string JsonUnitTestResultPrinter::EscapeJson(const std::string& str) { + Message m; + + for (size_t i = 0; i < str.size(); ++i) { + const char ch = str[i]; + switch (ch) { + case '\\': + case '"': + case '/': + m << '\\' << ch; + break; + case '\b': + m << "\\b"; + break; + case '\t': + m << "\\t"; + break; + case '\n': + m << "\\n"; + break; + case '\f': + m << "\\f"; + break; + case '\r': + m << "\\r"; + break; + default: + if (ch < ' ') { + m << "\\u00" << String::FormatByte(static_cast(ch)); + } else { + m << ch; + } + break; + } + } + + return m.GetString(); +} + +// The following routines generate an JSON representation of a UnitTest +// object. + +// Formats the given time in milliseconds as seconds. +static std::string FormatTimeInMillisAsDuration(TimeInMillis ms) { + ::std::stringstream ss; + ss << (static_cast(ms) * 1e-3) << "s"; + return ss.str(); +} + +// Converts the given epoch time in milliseconds to a date string in the +// RFC3339 format, without the timezone information. +static std::string FormatEpochTimeInMillisAsRFC3339(TimeInMillis ms) { + struct tm time_struct; + if (!PortableLocaltime(static_cast(ms / 1000), &time_struct)) + return ""; + // YYYY-MM-DDThh:mm:ss + return StreamableToString(time_struct.tm_year + 1900) + "-" + + String::FormatIntWidth2(time_struct.tm_mon + 1) + "-" + + String::FormatIntWidth2(time_struct.tm_mday) + "T" + + String::FormatIntWidth2(time_struct.tm_hour) + ":" + + String::FormatIntWidth2(time_struct.tm_min) + ":" + + String::FormatIntWidth2(time_struct.tm_sec) + "Z"; +} + +static inline std::string Indent(size_t width) { + return std::string(width, ' '); +} + +void JsonUnitTestResultPrinter::OutputJsonKey(std::ostream* stream, + const std::string& element_name, + const std::string& name, + const std::string& value, + const std::string& indent, + bool comma) { + const std::vector& allowed_names = + GetReservedOutputAttributesForElement(element_name); + + GTEST_CHECK_(std::find(allowed_names.begin(), allowed_names.end(), name) != + allowed_names.end()) + << "Key \"" << name << "\" is not allowed for value \"" << element_name + << "\"."; + + *stream << indent << "\"" << name << "\": \"" << EscapeJson(value) << "\""; + if (comma) *stream << ",\n"; +} + +void JsonUnitTestResultPrinter::OutputJsonKey( + std::ostream* stream, const std::string& element_name, + const std::string& name, int value, const std::string& indent, bool comma) { + const std::vector& allowed_names = + GetReservedOutputAttributesForElement(element_name); + + GTEST_CHECK_(std::find(allowed_names.begin(), allowed_names.end(), name) != + allowed_names.end()) + << "Key \"" << name << "\" is not allowed for value \"" << element_name + << "\"."; + + *stream << indent << "\"" << name << "\": " << StreamableToString(value); + if (comma) *stream << ",\n"; +} + +// Streams a test suite JSON stanza containing the given test result. +void JsonUnitTestResultPrinter::OutputJsonTestSuiteForTestResult( + ::std::ostream* stream, const TestResult& result) { + // Output the boilerplate for a new test suite. + *stream << Indent(4) << "{\n"; + OutputJsonKey(stream, "testsuite", "name", "NonTestSuiteFailure", Indent(6)); + OutputJsonKey(stream, "testsuite", "tests", 1, Indent(6)); + if (!GTEST_FLAG_GET(list_tests)) { + OutputJsonKey(stream, "testsuite", "failures", 1, Indent(6)); + OutputJsonKey(stream, "testsuite", "disabled", 0, Indent(6)); + OutputJsonKey(stream, "testsuite", "skipped", 0, Indent(6)); + OutputJsonKey(stream, "testsuite", "errors", 0, Indent(6)); + OutputJsonKey(stream, "testsuite", "time", + FormatTimeInMillisAsDuration(result.elapsed_time()), + Indent(6)); + OutputJsonKey(stream, "testsuite", "timestamp", + FormatEpochTimeInMillisAsRFC3339(result.start_timestamp()), + Indent(6)); + } + *stream << Indent(6) << "\"testsuite\": [\n"; + + OutputJsonTestCaseForTestResult(stream, result); + + // Finish the test suite. + *stream << "\n" << Indent(6) << "]\n" << Indent(4) << "}"; +} + +// Streams a test case JSON stanza containing the given test result. +void JsonUnitTestResultPrinter::OutputJsonTestCaseForTestResult( + ::std::ostream* stream, const TestResult& result) { + // Output the boilerplate for a new test case. + *stream << Indent(8) << "{\n"; + OutputJsonKey(stream, "testcase", "name", "", Indent(10)); + OutputJsonKey(stream, "testcase", "status", "RUN", Indent(10)); + OutputJsonKey(stream, "testcase", "result", "COMPLETED", Indent(10)); + OutputJsonKey(stream, "testcase", "timestamp", + FormatEpochTimeInMillisAsRFC3339(result.start_timestamp()), + Indent(10)); + OutputJsonKey(stream, "testcase", "time", + FormatTimeInMillisAsDuration(result.elapsed_time()), + Indent(10)); + OutputJsonKey(stream, "testcase", "classname", "", Indent(10), false); + *stream << TestPropertiesAsJson(result, Indent(10)); + + // Output the actual test result. + OutputJsonTestResult(stream, result); +} + +// Prints a JSON representation of a TestInfo object. +void JsonUnitTestResultPrinter::OutputJsonTestInfo(::std::ostream* stream, + const char* test_suite_name, + const TestInfo& test_info) { + const TestResult& result = *test_info.result(); + const std::string kTestsuite = "testcase"; + const std::string kIndent = Indent(10); + + *stream << Indent(8) << "{\n"; + OutputJsonKey(stream, kTestsuite, "name", test_info.name(), kIndent); + + if (test_info.value_param() != nullptr) { + OutputJsonKey(stream, kTestsuite, "value_param", test_info.value_param(), + kIndent); + } + if (test_info.type_param() != nullptr) { + OutputJsonKey(stream, kTestsuite, "type_param", test_info.type_param(), + kIndent); + } + + OutputJsonKey(stream, kTestsuite, "file", test_info.file(), kIndent); + OutputJsonKey(stream, kTestsuite, "line", test_info.line(), kIndent, false); + if (GTEST_FLAG_GET(list_tests)) { + *stream << "\n" << Indent(8) << "}"; + return; + } else { + *stream << ",\n"; + } + + OutputJsonKey(stream, kTestsuite, "status", + test_info.should_run() ? "RUN" : "NOTRUN", kIndent); + OutputJsonKey(stream, kTestsuite, "result", + test_info.should_run() + ? (result.Skipped() ? "SKIPPED" : "COMPLETED") + : "SUPPRESSED", + kIndent); + OutputJsonKey(stream, kTestsuite, "timestamp", + FormatEpochTimeInMillisAsRFC3339(result.start_timestamp()), + kIndent); + OutputJsonKey(stream, kTestsuite, "time", + FormatTimeInMillisAsDuration(result.elapsed_time()), kIndent); + OutputJsonKey(stream, kTestsuite, "classname", test_suite_name, kIndent, + false); + *stream << TestPropertiesAsJson(result, kIndent); + + OutputJsonTestResult(stream, result); +} + +void JsonUnitTestResultPrinter::OutputJsonTestResult(::std::ostream* stream, + const TestResult& result) { + const std::string kIndent = Indent(10); + + { + int failures = 0; + for (int i = 0; i < result.total_part_count(); ++i) { + const TestPartResult& part = result.GetTestPartResult(i); + if (part.failed()) { + *stream << ",\n"; + if (++failures == 1) { + *stream << kIndent << "\"" << "failures" << "\": [\n"; + } + const std::string location = + internal::FormatCompilerIndependentFileLocation(part.file_name(), + part.line_number()); + const std::string message = + EscapeJson(location + "\n" + part.message()); + *stream << kIndent << " {\n" + << kIndent << " \"failure\": \"" << message << "\",\n" + << kIndent << " \"type\": \"\"\n" + << kIndent << " }"; + } + } + + if (failures > 0) *stream << "\n" << kIndent << "]"; + } + + { + int skipped = 0; + for (int i = 0; i < result.total_part_count(); ++i) { + const TestPartResult& part = result.GetTestPartResult(i); + if (part.skipped()) { + *stream << ",\n"; + if (++skipped == 1) { + *stream << kIndent << "\"" << "skipped" << "\": [\n"; + } + const std::string location = + internal::FormatCompilerIndependentFileLocation(part.file_name(), + part.line_number()); + const std::string message = + EscapeJson(location + "\n" + part.message()); + *stream << kIndent << " {\n" + << kIndent << " \"message\": \"" << message << "\"\n" + << kIndent << " }"; + } + } + + if (skipped > 0) *stream << "\n" << kIndent << "]"; + } + + *stream << "\n" << Indent(8) << "}"; +} + +// Prints an JSON representation of a TestSuite object +void JsonUnitTestResultPrinter::PrintJsonTestSuite( + std::ostream* stream, const TestSuite& test_suite) { + const std::string kTestsuite = "testsuite"; + const std::string kIndent = Indent(6); + + *stream << Indent(4) << "{\n"; + OutputJsonKey(stream, kTestsuite, "name", test_suite.name(), kIndent); + OutputJsonKey(stream, kTestsuite, "tests", test_suite.reportable_test_count(), + kIndent); + if (!GTEST_FLAG_GET(list_tests)) { + OutputJsonKey(stream, kTestsuite, "failures", + test_suite.failed_test_count(), kIndent); + OutputJsonKey(stream, kTestsuite, "disabled", + test_suite.reportable_disabled_test_count(), kIndent); + OutputJsonKey(stream, kTestsuite, "errors", 0, kIndent); + OutputJsonKey( + stream, kTestsuite, "timestamp", + FormatEpochTimeInMillisAsRFC3339(test_suite.start_timestamp()), + kIndent); + OutputJsonKey(stream, kTestsuite, "time", + FormatTimeInMillisAsDuration(test_suite.elapsed_time()), + kIndent, false); + *stream << TestPropertiesAsJson(test_suite.ad_hoc_test_result(), kIndent) + << ",\n"; + } + + *stream << kIndent << "\"" << kTestsuite << "\": [\n"; + + bool comma = false; + for (int i = 0; i < test_suite.total_test_count(); ++i) { + if (test_suite.GetTestInfo(i)->is_reportable()) { + if (comma) { + *stream << ",\n"; + } else { + comma = true; + } + OutputJsonTestInfo(stream, test_suite.name(), *test_suite.GetTestInfo(i)); + } + } + + // If there was a failure in the test suite setup or teardown include that in + // the output. + if (test_suite.ad_hoc_test_result().Failed()) { + if (comma) { + *stream << ",\n"; + } + OutputJsonTestCaseForTestResult(stream, test_suite.ad_hoc_test_result()); + } + + *stream << "\n" << kIndent << "]\n" << Indent(4) << "}"; +} + +// Prints a JSON summary of unit_test to output stream out. +void JsonUnitTestResultPrinter::PrintJsonUnitTest(std::ostream* stream, + const UnitTest& unit_test) { + const std::string kTestsuites = "testsuites"; + const std::string kIndent = Indent(2); + *stream << "{\n"; + + OutputJsonKey(stream, kTestsuites, "tests", unit_test.reportable_test_count(), + kIndent); + OutputJsonKey(stream, kTestsuites, "failures", unit_test.failed_test_count(), + kIndent); + OutputJsonKey(stream, kTestsuites, "disabled", + unit_test.reportable_disabled_test_count(), kIndent); + OutputJsonKey(stream, kTestsuites, "errors", 0, kIndent); + if (GTEST_FLAG_GET(shuffle)) { + OutputJsonKey(stream, kTestsuites, "random_seed", unit_test.random_seed(), + kIndent); + } + OutputJsonKey(stream, kTestsuites, "timestamp", + FormatEpochTimeInMillisAsRFC3339(unit_test.start_timestamp()), + kIndent); + OutputJsonKey(stream, kTestsuites, "time", + FormatTimeInMillisAsDuration(unit_test.elapsed_time()), kIndent, + false); + + *stream << TestPropertiesAsJson(unit_test.ad_hoc_test_result(), kIndent) + << ",\n"; + + OutputJsonKey(stream, kTestsuites, "name", "AllTests", kIndent); + *stream << kIndent << "\"" << kTestsuites << "\": [\n"; + + bool comma = false; + for (int i = 0; i < unit_test.total_test_suite_count(); ++i) { + if (unit_test.GetTestSuite(i)->reportable_test_count() > 0) { + if (comma) { + *stream << ",\n"; + } else { + comma = true; + } + PrintJsonTestSuite(stream, *unit_test.GetTestSuite(i)); + } + } + + // If there was a test failure outside of one of the test suites (like in a + // test environment) include that in the output. + if (unit_test.ad_hoc_test_result().Failed()) { + if (comma) { + *stream << ",\n"; + } + OutputJsonTestSuiteForTestResult(stream, unit_test.ad_hoc_test_result()); + } + + *stream << "\n" + << kIndent << "]\n" + << "}\n"; +} + +void JsonUnitTestResultPrinter::PrintJsonTestList( + std::ostream* stream, const std::vector& test_suites) { + const std::string kTestsuites = "testsuites"; + const std::string kIndent = Indent(2); + *stream << "{\n"; + int total_tests = 0; + for (auto test_suite : test_suites) { + total_tests += test_suite->total_test_count(); + } + OutputJsonKey(stream, kTestsuites, "tests", total_tests, kIndent); + + OutputJsonKey(stream, kTestsuites, "name", "AllTests", kIndent); + *stream << kIndent << "\"" << kTestsuites << "\": [\n"; + + for (size_t i = 0; i < test_suites.size(); ++i) { + if (i != 0) { + *stream << ",\n"; + } + PrintJsonTestSuite(stream, *test_suites[i]); + } + + *stream << "\n" + << kIndent << "]\n" + << "}\n"; +} +// Produces a string representing the test properties in a result as +// a JSON dictionary. +std::string JsonUnitTestResultPrinter::TestPropertiesAsJson( + const TestResult& result, const std::string& indent) { + Message attributes; + for (int i = 0; i < result.test_property_count(); ++i) { + const TestProperty& property = result.GetTestProperty(i); + attributes << ",\n" + << indent << "\"" << property.key() << "\": " << "\"" + << EscapeJson(property.value()) << "\""; + } + return attributes.GetString(); +} + +// End JsonUnitTestResultPrinter +#endif // GTEST_HAS_FILE_SYSTEM + +#if GTEST_CAN_STREAM_RESULTS_ + +// Checks if str contains '=', '&', '%' or '\n' characters. If yes, +// replaces them by "%xx" where xx is their hexadecimal value. For +// example, replaces "=" with "%3D". This algorithm is O(strlen(str)) +// in both time and space -- important as the input str may contain an +// arbitrarily long test failure message and stack trace. +std::string StreamingListener::UrlEncode(const char* str) { + std::string result; + result.reserve(strlen(str) + 1); + for (char ch = *str; ch != '\0'; ch = *++str) { + switch (ch) { + case '%': + case '=': + case '&': + case '\n': + result.push_back('%'); + result.append(String::FormatByte(static_cast(ch))); + break; + default: + result.push_back(ch); + break; + } + } + return result; +} + +void StreamingListener::SocketWriter::MakeConnection() { + GTEST_CHECK_(sockfd_ == -1) + << "MakeConnection() can't be called when there is already a connection."; + + addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; // To allow both IPv4 and IPv6 addresses. + hints.ai_socktype = SOCK_STREAM; + addrinfo* servinfo = nullptr; + + // Use the getaddrinfo() to get a linked list of IP addresses for + // the given host name. + const int error_num = + getaddrinfo(host_name_.c_str(), port_num_.c_str(), &hints, &servinfo); + if (error_num != 0) { + GTEST_LOG_(WARNING) << "stream_result_to: getaddrinfo() failed: " + << gai_strerror(error_num); + } + + // Loop through all the results and connect to the first we can. + for (addrinfo* cur_addr = servinfo; sockfd_ == -1 && cur_addr != nullptr; + cur_addr = cur_addr->ai_next) { + sockfd_ = socket(cur_addr->ai_family, cur_addr->ai_socktype, + cur_addr->ai_protocol); + if (sockfd_ != -1) { + // Connect the client socket to the server socket. + if (connect(sockfd_, cur_addr->ai_addr, cur_addr->ai_addrlen) == -1) { + close(sockfd_); + sockfd_ = -1; + } + } + } + + freeaddrinfo(servinfo); // all done with this structure + + if (sockfd_ == -1) { + GTEST_LOG_(WARNING) << "stream_result_to: failed to connect to " + << host_name_ << ":" << port_num_; + } +} + +// End of class Streaming Listener +#endif // GTEST_CAN_STREAM_RESULTS__ + +// class OsStackTraceGetter + +const char* const OsStackTraceGetterInterface::kElidedFramesMarker = + "... " GTEST_NAME_ " internal frames ..."; + +std::string OsStackTraceGetter::CurrentStackTrace(int max_depth, int skip_count) + GTEST_LOCK_EXCLUDED_(mutex_) { +#ifdef GTEST_HAS_ABSL + std::string result; + + if (max_depth <= 0) { + return result; + } + + max_depth = std::min(max_depth, kMaxStackTraceDepth); + + std::vector raw_stack(max_depth); + // Skips the frames requested by the caller, plus this function. + const int raw_stack_size = + absl::GetStackTrace(&raw_stack[0], max_depth, skip_count + 1); + + void* caller_frame = nullptr; + { + MutexLock lock(&mutex_); + caller_frame = caller_frame_; + } + + for (int i = 0; i < raw_stack_size; ++i) { + if (raw_stack[i] == caller_frame && + !GTEST_FLAG_GET(show_internal_stack_frames)) { + // Add a marker to the trace and stop adding frames. + absl::StrAppend(&result, kElidedFramesMarker, "\n"); + break; + } + + char tmp[1024]; + const char* symbol = "(unknown)"; + if (absl::Symbolize(raw_stack[i], tmp, sizeof(tmp))) { + symbol = tmp; + } + + char line[1024]; + snprintf(line, sizeof(line), " %p: %s\n", raw_stack[i], symbol); + result += line; + } + + return result; + +#else // !GTEST_HAS_ABSL + static_cast(max_depth); + static_cast(skip_count); + return ""; +#endif // GTEST_HAS_ABSL +} + +void OsStackTraceGetter::UponLeavingGTest() GTEST_LOCK_EXCLUDED_(mutex_) { +#ifdef GTEST_HAS_ABSL + void* caller_frame = nullptr; + if (absl::GetStackTrace(&caller_frame, 1, 3) <= 0) { + caller_frame = nullptr; + } + + MutexLock lock(&mutex_); + caller_frame_ = caller_frame; +#endif // GTEST_HAS_ABSL +} + +#ifdef GTEST_HAS_DEATH_TEST +// A helper class that creates the premature-exit file in its +// constructor and deletes the file in its destructor. +class ScopedPrematureExitFile { + public: + explicit ScopedPrematureExitFile(const char* premature_exit_filepath) + : premature_exit_filepath_( + premature_exit_filepath ? premature_exit_filepath : "") { + // If a path to the premature-exit file is specified... + if (!premature_exit_filepath_.empty()) { + // create the file with a single "0" character in it. I/O + // errors are ignored as there's nothing better we can do and we + // don't want to fail the test because of this. + FILE* pfile = posix::FOpen(premature_exit_filepath_.c_str(), "w"); + fwrite("0", 1, 1, pfile); + fclose(pfile); + } + } + + ~ScopedPrematureExitFile() { +#ifndef GTEST_OS_ESP8266 + if (!premature_exit_filepath_.empty()) { + int retval = remove(premature_exit_filepath_.c_str()); + if (retval) { + GTEST_LOG_(ERROR) << "Failed to remove premature exit filepath \"" + << premature_exit_filepath_ << "\" with error " + << retval; + } + } +#endif + } + + private: + const std::string premature_exit_filepath_; + + ScopedPrematureExitFile(const ScopedPrematureExitFile&) = delete; + ScopedPrematureExitFile& operator=(const ScopedPrematureExitFile&) = delete; +}; +#endif // GTEST_HAS_DEATH_TEST + +} // namespace internal + +// class TestEventListeners + +TestEventListeners::TestEventListeners() + : repeater_(new internal::TestEventRepeater()), + default_result_printer_(nullptr), + default_xml_generator_(nullptr) {} + +TestEventListeners::~TestEventListeners() { delete repeater_; } + +// Returns the standard listener responsible for the default console +// output. Can be removed from the listeners list to shut down default +// console output. Note that removing this object from the listener list +// with Release transfers its ownership to the user. +void TestEventListeners::Append(TestEventListener* listener) { + repeater_->Append(listener); +} + +// Removes the given event listener from the list and returns it. It then +// becomes the caller's responsibility to delete the listener. Returns +// NULL if the listener is not found in the list. +TestEventListener* TestEventListeners::Release(TestEventListener* listener) { + if (listener == default_result_printer_) + default_result_printer_ = nullptr; + else if (listener == default_xml_generator_) + default_xml_generator_ = nullptr; + return repeater_->Release(listener); +} + +// Returns repeater that broadcasts the TestEventListener events to all +// subscribers. +TestEventListener* TestEventListeners::repeater() { return repeater_; } + +// Sets the default_result_printer attribute to the provided listener. +// The listener is also added to the listener list and previous +// default_result_printer is removed from it and deleted. The listener can +// also be NULL in which case it will not be added to the list. Does +// nothing if the previous and the current listener objects are the same. +void TestEventListeners::SetDefaultResultPrinter(TestEventListener* listener) { + if (default_result_printer_ != listener) { + // It is an error to pass this method a listener that is already in the + // list. + delete Release(default_result_printer_); + default_result_printer_ = listener; + if (listener != nullptr) Append(listener); + } +} + +// Sets the default_xml_generator attribute to the provided listener. The +// listener is also added to the listener list and previous +// default_xml_generator is removed from it and deleted. The listener can +// also be NULL in which case it will not be added to the list. Does +// nothing if the previous and the current listener objects are the same. +void TestEventListeners::SetDefaultXmlGenerator(TestEventListener* listener) { + if (default_xml_generator_ != listener) { + // It is an error to pass this method a listener that is already in the + // list. + delete Release(default_xml_generator_); + default_xml_generator_ = listener; + if (listener != nullptr) Append(listener); + } +} + +// Controls whether events will be forwarded by the repeater to the +// listeners in the list. +bool TestEventListeners::EventForwardingEnabled() const { + return repeater_->forwarding_enabled(); +} + +void TestEventListeners::SuppressEventForwarding(bool suppress) { + repeater_->set_forwarding_enabled(!suppress); +} + +// class UnitTest + +// Gets the singleton UnitTest object. The first time this method is +// called, a UnitTest object is constructed and returned. Consecutive +// calls will return the same object. +// +// We don't protect this under mutex_ as a user is not supposed to +// call this before main() starts, from which point on the return +// value will never change. +UnitTest* UnitTest::GetInstance() { + // CodeGear C++Builder insists on a public destructor for the + // default implementation. Use this implementation to keep good OO + // design with private destructor. + +#if defined(__BORLANDC__) + static UnitTest* const instance = new UnitTest; + return instance; +#else + static UnitTest instance; + return &instance; +#endif // defined(__BORLANDC__) +} + +// Gets the number of successful test suites. +int UnitTest::successful_test_suite_count() const { + return impl()->successful_test_suite_count(); +} + +// Gets the number of failed test suites. +int UnitTest::failed_test_suite_count() const { + return impl()->failed_test_suite_count(); +} + +// Gets the number of all test suites. +int UnitTest::total_test_suite_count() const { + return impl()->total_test_suite_count(); +} + +// Gets the number of all test suites that contain at least one test +// that should run. +int UnitTest::test_suite_to_run_count() const { + return impl()->test_suite_to_run_count(); +} + +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ +int UnitTest::successful_test_case_count() const { + return impl()->successful_test_suite_count(); +} +int UnitTest::failed_test_case_count() const { + return impl()->failed_test_suite_count(); +} +int UnitTest::total_test_case_count() const { + return impl()->total_test_suite_count(); +} +int UnitTest::test_case_to_run_count() const { + return impl()->test_suite_to_run_count(); +} +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + +// Gets the number of successful tests. +int UnitTest::successful_test_count() const { + return impl()->successful_test_count(); +} + +// Gets the number of skipped tests. +int UnitTest::skipped_test_count() const { + return impl()->skipped_test_count(); +} + +// Gets the number of failed tests. +int UnitTest::failed_test_count() const { return impl()->failed_test_count(); } + +// Gets the number of disabled tests that will be reported in the XML report. +int UnitTest::reportable_disabled_test_count() const { + return impl()->reportable_disabled_test_count(); +} + +// Gets the number of disabled tests. +int UnitTest::disabled_test_count() const { + return impl()->disabled_test_count(); +} + +// Gets the number of tests to be printed in the XML report. +int UnitTest::reportable_test_count() const { + return impl()->reportable_test_count(); +} + +// Gets the number of all tests. +int UnitTest::total_test_count() const { return impl()->total_test_count(); } + +// Gets the number of tests that should run. +int UnitTest::test_to_run_count() const { return impl()->test_to_run_count(); } + +// Gets the time of the test program start, in ms from the start of the +// UNIX epoch. +internal::TimeInMillis UnitTest::start_timestamp() const { + return impl()->start_timestamp(); +} + +// Gets the elapsed time, in milliseconds. +internal::TimeInMillis UnitTest::elapsed_time() const { + return impl()->elapsed_time(); +} + +// Returns true if and only if the unit test passed (i.e. all test suites +// passed). +bool UnitTest::Passed() const { return impl()->Passed(); } + +// Returns true if and only if the unit test failed (i.e. some test suite +// failed or something outside of all tests failed). +bool UnitTest::Failed() const { return impl()->Failed(); } + +// Gets the i-th test suite among all the test suites. i can range from 0 to +// total_test_suite_count() - 1. If i is not in that range, returns NULL. +const TestSuite* UnitTest::GetTestSuite(int i) const { + return impl()->GetTestSuite(i); +} + +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ +const TestCase* UnitTest::GetTestCase(int i) const { + return impl()->GetTestCase(i); +} +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + +// Returns the TestResult containing information on test failures and +// properties logged outside of individual test suites. +const TestResult& UnitTest::ad_hoc_test_result() const { + return *impl()->ad_hoc_test_result(); +} + +// Gets the i-th test suite among all the test suites. i can range from 0 to +// total_test_suite_count() - 1. If i is not in that range, returns NULL. +TestSuite* UnitTest::GetMutableTestSuite(int i) { + return impl()->GetMutableSuiteCase(i); +} + +void UnitTest::UponLeavingGTest() { + impl()->os_stack_trace_getter()->UponLeavingGTest(); +} + +// Sets the TestSuite object for the test that's currently running. +void UnitTest::set_current_test_suite(TestSuite* a_current_test_suite) { + internal::MutexLock lock(&mutex_); + impl_->set_current_test_suite(a_current_test_suite); +} + +// Sets the TestInfo object for the test that's currently running. +void UnitTest::set_current_test_info(TestInfo* a_current_test_info) { + internal::MutexLock lock(&mutex_); + impl_->set_current_test_info(a_current_test_info); +} + +// Returns the list of event listeners that can be used to track events +// inside Google Test. +TestEventListeners& UnitTest::listeners() { return *impl()->listeners(); } + +// Registers and returns a global test environment. When a test +// program is run, all global test environments will be set-up in the +// order they were registered. After all tests in the program have +// finished, all global test environments will be torn-down in the +// *reverse* order they were registered. +// +// The UnitTest object takes ownership of the given environment. +// +// We don't protect this under mutex_, as we only support calling it +// from the main thread. +Environment* UnitTest::AddEnvironment(Environment* env) { + if (env == nullptr) { + return nullptr; + } + + impl_->environments().push_back(env); + return env; +} + +// Adds a TestPartResult to the current TestResult object. All Google Test +// assertion macros (e.g. ASSERT_TRUE, EXPECT_EQ, etc) eventually call +// this to report their results. The user code should use the +// assertion macros instead of calling this directly. +void UnitTest::AddTestPartResult(TestPartResult::Type result_type, + const char* file_name, int line_number, + const std::string& message, + const std::string& os_stack_trace) + GTEST_LOCK_EXCLUDED_(mutex_) { + Message msg; + msg << message; + + internal::MutexLock lock(&mutex_); + if (!impl_->gtest_trace_stack().empty()) { + msg << "\n" << GTEST_NAME_ << " trace:"; + + for (size_t i = impl_->gtest_trace_stack().size(); i > 0; --i) { + const internal::TraceInfo& trace = impl_->gtest_trace_stack()[i - 1]; + msg << "\n" + << internal::FormatFileLocation(trace.file, trace.line) << " " + << trace.message; + } + } + + if (os_stack_trace.c_str() != nullptr && !os_stack_trace.empty()) { + msg << internal::kStackTraceMarker << os_stack_trace; + } else { + msg << "\n"; + } + + const TestPartResult result = TestPartResult( + result_type, file_name, line_number, msg.GetString().c_str()); + impl_->GetTestPartResultReporterForCurrentThread()->ReportTestPartResult( + result); + + if (result_type != TestPartResult::kSuccess && + result_type != TestPartResult::kSkip) { + // gtest_break_on_failure takes precedence over + // gtest_throw_on_failure. This allows a user to set the latter + // in the code (perhaps in order to use Google Test assertions + // with another testing framework) and specify the former on the + // command line for debugging. + if (GTEST_FLAG_GET(break_on_failure)) { +#if defined(GTEST_OS_WINDOWS) && !defined(GTEST_OS_WINDOWS_PHONE) && \ + !defined(GTEST_OS_WINDOWS_RT) + // Using DebugBreak on Windows allows gtest to still break into a debugger + // when a failure happens and both the --gtest_break_on_failure and + // the --gtest_catch_exceptions flags are specified. + DebugBreak(); +#elif (!defined(__native_client__)) && \ + ((defined(__clang__) || defined(__GNUC__)) && \ + (defined(__x86_64__) || defined(__i386__))) + // with clang/gcc we can achieve the same effect on x86 by invoking int3 + asm("int3"); +#elif GTEST_HAS_BUILTIN(__builtin_trap) + __builtin_trap(); +#elif defined(SIGTRAP) + raise(SIGTRAP); +#else + // Dereference nullptr through a volatile pointer to prevent the compiler + // from removing. We use this rather than abort() or __builtin_trap() for + // portability: some debuggers don't correctly trap abort(). + *static_cast(nullptr) = 1; +#endif // GTEST_OS_WINDOWS + } else if (GTEST_FLAG_GET(throw_on_failure)) { +#if GTEST_HAS_EXCEPTIONS + throw internal::GoogleTestFailureException(result); +#else + // We cannot call abort() as it generates a pop-up in debug mode + // that cannot be suppressed in VC 7.1 or below. + exit(1); +#endif + } + } +} + +// Adds a TestProperty to the current TestResult object when invoked from +// inside a test, to current TestSuite's ad_hoc_test_result_ when invoked +// from SetUpTestSuite or TearDownTestSuite, or to the global property set +// when invoked elsewhere. If the result already contains a property with +// the same key, the value will be updated. +void UnitTest::RecordProperty(const std::string& key, + const std::string& value) { + impl_->RecordProperty(TestProperty(key, value)); +} + +// Runs all tests in this UnitTest object and prints the result. +// Returns 0 if successful, or 1 otherwise. +// +// We don't protect this under mutex_, as we only support calling it +// from the main thread. +int UnitTest::Run() { +#ifdef GTEST_HAS_DEATH_TEST + const bool in_death_test_child_process = + !GTEST_FLAG_GET(internal_run_death_test).empty(); + + // Google Test implements this protocol for catching that a test + // program exits before returning control to Google Test: + // + // 1. Upon start, Google Test creates a file whose absolute path + // is specified by the environment variable + // TEST_PREMATURE_EXIT_FILE. + // 2. When Google Test has finished its work, it deletes the file. + // + // This allows a test runner to set TEST_PREMATURE_EXIT_FILE before + // running a Google-Test-based test program and check the existence + // of the file at the end of the test execution to see if it has + // exited prematurely. + + // If we are in the child process of a death test, don't + // create/delete the premature exit file, as doing so is unnecessary + // and will confuse the parent process. Otherwise, create/delete + // the file upon entering/leaving this function. If the program + // somehow exits before this function has a chance to return, the + // premature-exit file will be left undeleted, causing a test runner + // that understands the premature-exit-file protocol to report the + // test as having failed. + const internal::ScopedPrematureExitFile premature_exit_file( + in_death_test_child_process + ? nullptr + : internal::posix::GetEnv("TEST_PREMATURE_EXIT_FILE")); +#else + const bool in_death_test_child_process = false; +#endif // GTEST_HAS_DEATH_TEST + + // Captures the value of GTEST_FLAG(catch_exceptions). This value will be + // used for the duration of the program. + impl()->set_catch_exceptions(GTEST_FLAG_GET(catch_exceptions)); + +#ifdef GTEST_OS_WINDOWS + // Either the user wants Google Test to catch exceptions thrown by the + // tests or this is executing in the context of death test child + // process. In either case the user does not want to see pop-up dialogs + // about crashes - they are expected. + if (impl()->catch_exceptions() || in_death_test_child_process) { +#if !defined(GTEST_OS_WINDOWS_MOBILE) && !defined(GTEST_OS_WINDOWS_PHONE) && \ + !defined(GTEST_OS_WINDOWS_RT) && !defined(GTEST_OS_WINDOWS_GAMES) + // SetErrorMode doesn't exist on CE. + SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOALIGNMENTFAULTEXCEPT | + SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX); +#endif // !GTEST_OS_WINDOWS_MOBILE + +#if (defined(_MSC_VER) || defined(GTEST_OS_WINDOWS_MINGW)) && \ + !defined(GTEST_OS_WINDOWS_MOBILE) + // Death test children can be terminated with _abort(). On Windows, + // _abort() can show a dialog with a warning message. This forces the + // abort message to go to stderr instead. + _set_error_mode(_OUT_TO_STDERR); +#endif + +#if defined(_MSC_VER) && !defined(GTEST_OS_WINDOWS_MOBILE) + // In the debug version, Visual Studio pops up a separate dialog + // offering a choice to debug the aborted program. We need to suppress + // this dialog or it will pop up for every EXPECT/ASSERT_DEATH statement + // executed. Google Test will notify the user of any unexpected + // failure via stderr. + if (!GTEST_FLAG_GET(break_on_failure)) + _set_abort_behavior( + 0x0, // Clear the following flags: + _WRITE_ABORT_MSG | _CALL_REPORTFAULT); // pop-up window, core dump. + + // In debug mode, the Windows CRT can crash with an assertion over invalid + // input (e.g. passing an invalid file descriptor). The default handling + // for these assertions is to pop up a dialog and wait for user input. + // Instead ask the CRT to dump such assertions to stderr non-interactively. + if (!IsDebuggerPresent()) { + (void)_CrtSetReportMode(_CRT_ASSERT, + _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); + (void)_CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); + } +#endif + } +#else + (void)in_death_test_child_process; // Needed inside the #if block above +#endif // GTEST_OS_WINDOWS + + return internal::HandleExceptionsInMethodIfSupported( + impl(), &internal::UnitTestImpl::RunAllTests, + "auxiliary test code (environments or event listeners)") + ? 0 + : 1; +} + +#if GTEST_HAS_FILE_SYSTEM +// Returns the working directory when the first TEST() or TEST_F() was +// executed. +const char* UnitTest::original_working_dir() const { + return impl_->original_working_dir_.c_str(); +} +#endif // GTEST_HAS_FILE_SYSTEM + +// Returns the TestSuite object for the test that's currently running, +// or NULL if no test is running. +const TestSuite* UnitTest::current_test_suite() const + GTEST_LOCK_EXCLUDED_(mutex_) { + internal::MutexLock lock(&mutex_); + return impl_->current_test_suite(); +} + +// Legacy API is still available but deprecated +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ +const TestCase* UnitTest::current_test_case() const + GTEST_LOCK_EXCLUDED_(mutex_) { + internal::MutexLock lock(&mutex_); + return impl_->current_test_suite(); +} +#endif + +// Returns the TestInfo object for the test that's currently running, +// or NULL if no test is running. +const TestInfo* UnitTest::current_test_info() const + GTEST_LOCK_EXCLUDED_(mutex_) { + internal::MutexLock lock(&mutex_); + return impl_->current_test_info(); +} + +// Returns the random seed used at the start of the current test run. +int UnitTest::random_seed() const { return impl_->random_seed(); } + +// Returns ParameterizedTestSuiteRegistry object used to keep track of +// value-parameterized tests and instantiate and register them. +internal::ParameterizedTestSuiteRegistry& +UnitTest::parameterized_test_registry() GTEST_LOCK_EXCLUDED_(mutex_) { + return impl_->parameterized_test_registry(); +} + +// Creates an empty UnitTest. +UnitTest::UnitTest() { impl_ = new internal::UnitTestImpl(this); } + +// Destructor of UnitTest. +UnitTest::~UnitTest() { delete impl_; } + +// Pushes a trace defined by SCOPED_TRACE() on to the per-thread +// Google Test trace stack. +void UnitTest::PushGTestTrace(const internal::TraceInfo& trace) + GTEST_LOCK_EXCLUDED_(mutex_) { + internal::MutexLock lock(&mutex_); + impl_->gtest_trace_stack().push_back(trace); +} + +// Pops a trace from the per-thread Google Test trace stack. +void UnitTest::PopGTestTrace() GTEST_LOCK_EXCLUDED_(mutex_) { + internal::MutexLock lock(&mutex_); + impl_->gtest_trace_stack().pop_back(); +} + +namespace internal { + +UnitTestImpl::UnitTestImpl(UnitTest* parent) + : parent_(parent), + GTEST_DISABLE_MSC_WARNINGS_PUSH_(4355 /* using this in initializer */) + default_global_test_part_result_reporter_(this), + default_per_thread_test_part_result_reporter_(this), + GTEST_DISABLE_MSC_WARNINGS_POP_() global_test_part_result_reporter_( + &default_global_test_part_result_reporter_), + per_thread_test_part_result_reporter_( + &default_per_thread_test_part_result_reporter_), + parameterized_test_registry_(), + parameterized_tests_registered_(false), + last_death_test_suite_(-1), + current_test_suite_(nullptr), + current_test_info_(nullptr), + ad_hoc_test_result_(), + os_stack_trace_getter_(nullptr), + post_flag_parse_init_performed_(false), + random_seed_(0), // Will be overridden by the flag before first use. + random_(0), // Will be reseeded before first use. + start_timestamp_(0), + elapsed_time_(0), +#ifdef GTEST_HAS_DEATH_TEST + death_test_factory_(new DefaultDeathTestFactory), +#endif + // Will be overridden by the flag before first use. + catch_exceptions_(false) { + listeners()->SetDefaultResultPrinter(new PrettyUnitTestResultPrinter); +} + +UnitTestImpl::~UnitTestImpl() { + // Deletes every TestSuite. + ForEach(test_suites_, internal::Delete); + + // Deletes every Environment. + ForEach(environments_, internal::Delete); + + delete os_stack_trace_getter_; +} + +// Adds a TestProperty to the current TestResult object when invoked in a +// context of a test, to current test suite's ad_hoc_test_result when invoke +// from SetUpTestSuite/TearDownTestSuite, or to the global property set +// otherwise. If the result already contains a property with the same key, +// the value will be updated. +void UnitTestImpl::RecordProperty(const TestProperty& test_property) { + std::string xml_element; + TestResult* test_result; // TestResult appropriate for property recording. + + if (current_test_info_ != nullptr) { + xml_element = "testcase"; + test_result = &(current_test_info_->result_); + } else if (current_test_suite_ != nullptr) { + xml_element = "testsuite"; + test_result = &(current_test_suite_->ad_hoc_test_result_); + } else { + xml_element = "testsuites"; + test_result = &ad_hoc_test_result_; + } + test_result->RecordProperty(xml_element, test_property); +} + +#ifdef GTEST_HAS_DEATH_TEST +// Disables event forwarding if the control is currently in a death test +// subprocess. Must not be called before InitGoogleTest. +void UnitTestImpl::SuppressTestEventsIfInSubprocess() { + if (internal_run_death_test_flag_ != nullptr) + listeners()->SuppressEventForwarding(true); +} +#endif // GTEST_HAS_DEATH_TEST + +// Initializes event listeners performing XML output as specified by +// UnitTestOptions. Must not be called before InitGoogleTest. +void UnitTestImpl::ConfigureXmlOutput() { + const std::string& output_format = UnitTestOptions::GetOutputFormat(); +#if GTEST_HAS_FILE_SYSTEM + if (output_format == "xml") { + listeners()->SetDefaultXmlGenerator(new XmlUnitTestResultPrinter( + UnitTestOptions::GetAbsolutePathToOutputFile().c_str())); + } else if (output_format == "json") { + listeners()->SetDefaultXmlGenerator(new JsonUnitTestResultPrinter( + UnitTestOptions::GetAbsolutePathToOutputFile().c_str())); + } else if (!output_format.empty()) { + GTEST_LOG_(WARNING) << "WARNING: unrecognized output format \"" + << output_format << "\" ignored."; + } +#else + if (!output_format.empty()) { + GTEST_LOG_(ERROR) << "ERROR: alternative output formats require " + << "GTEST_HAS_FILE_SYSTEM to be enabled"; + } +#endif // GTEST_HAS_FILE_SYSTEM +} + +#if GTEST_CAN_STREAM_RESULTS_ +// Initializes event listeners for streaming test results in string form. +// Must not be called before InitGoogleTest. +void UnitTestImpl::ConfigureStreamingOutput() { + const std::string& target = GTEST_FLAG_GET(stream_result_to); + if (!target.empty()) { + const size_t pos = target.find(':'); + if (pos != std::string::npos) { + listeners()->Append( + new StreamingListener(target.substr(0, pos), target.substr(pos + 1))); + } else { + GTEST_LOG_(WARNING) << "unrecognized streaming target \"" << target + << "\" ignored."; + } + } +} +#endif // GTEST_CAN_STREAM_RESULTS_ + +// Performs initialization dependent upon flag values obtained in +// ParseGoogleTestFlagsOnly. Is called from InitGoogleTest after the call to +// ParseGoogleTestFlagsOnly. In case a user neglects to call InitGoogleTest +// this function is also called from RunAllTests. Since this function can be +// called more than once, it has to be idempotent. +void UnitTestImpl::PostFlagParsingInit() { + // Ensures that this function does not execute more than once. + if (!post_flag_parse_init_performed_) { + post_flag_parse_init_performed_ = true; + +#if defined(GTEST_CUSTOM_TEST_EVENT_LISTENER_) + // Register to send notifications about key process state changes. + listeners()->Append(new GTEST_CUSTOM_TEST_EVENT_LISTENER_()); +#endif // defined(GTEST_CUSTOM_TEST_EVENT_LISTENER_) + +#ifdef GTEST_HAS_DEATH_TEST + InitDeathTestSubprocessControlInfo(); + SuppressTestEventsIfInSubprocess(); +#endif // GTEST_HAS_DEATH_TEST + + // Registers parameterized tests. This makes parameterized tests + // available to the UnitTest reflection API without running + // RUN_ALL_TESTS. + RegisterParameterizedTests(); + + // Configures listeners for XML output. This makes it possible for users + // to shut down the default XML output before invoking RUN_ALL_TESTS. + ConfigureXmlOutput(); + + if (GTEST_FLAG_GET(brief)) { + listeners()->SetDefaultResultPrinter(new BriefUnitTestResultPrinter); + } + +#if GTEST_CAN_STREAM_RESULTS_ + // Configures listeners for streaming test results to the specified server. + ConfigureStreamingOutput(); +#endif // GTEST_CAN_STREAM_RESULTS_ + +#ifdef GTEST_HAS_ABSL + if (GTEST_FLAG_GET(install_failure_signal_handler)) { + absl::FailureSignalHandlerOptions options; + absl::InstallFailureSignalHandler(options); + } +#endif // GTEST_HAS_ABSL + } +} + +// Finds and returns a TestSuite with the given name. If one doesn't +// exist, creates one and returns it. It's the CALLER'S +// RESPONSIBILITY to ensure that this function is only called WHEN THE +// TESTS ARE NOT SHUFFLED. +// +// Arguments: +// +// test_suite_name: name of the test suite +// type_param: the name of the test suite's type parameter, or NULL if +// this is not a typed or a type-parameterized test suite. +// set_up_tc: pointer to the function that sets up the test suite +// tear_down_tc: pointer to the function that tears down the test suite +TestSuite* UnitTestImpl::GetTestSuite( + const std::string& test_suite_name, const char* type_param, + internal::SetUpTestSuiteFunc set_up_tc, + internal::TearDownTestSuiteFunc tear_down_tc) { + // During initialization, all TestInfos for a given suite are added in + // sequence. To optimize this case, see if the most recently added suite is + // the one being requested now. + if (!test_suites_.empty() && + (*test_suites_.rbegin())->name_ == test_suite_name) { + return *test_suites_.rbegin(); + } + + // Fall back to searching the collection. + auto item_it = test_suites_by_name_.find(test_suite_name); + if (item_it != test_suites_by_name_.end()) { + return item_it->second; + } + + // Not found. Create a new instance. + auto* const new_test_suite = + new TestSuite(test_suite_name, type_param, set_up_tc, tear_down_tc); + test_suites_by_name_.emplace(test_suite_name, new_test_suite); + + const UnitTestFilter death_test_suite_filter(kDeathTestSuiteFilter); + // Is this a death test suite? + if (death_test_suite_filter.MatchesName(test_suite_name)) { + // Yes. Inserts the test suite after the last death test suite + // defined so far. This only works when the test suites haven't + // been shuffled. Otherwise we may end up running a death test + // after a non-death test. + ++last_death_test_suite_; + test_suites_.insert(test_suites_.begin() + last_death_test_suite_, + new_test_suite); + } else { + // No. Appends to the end of the list. + test_suites_.push_back(new_test_suite); + } + + test_suite_indices_.push_back(static_cast(test_suite_indices_.size())); + return new_test_suite; +} + +// Helpers for setting up / tearing down the given environment. They +// are for use in the ForEach() function. +static void SetUpEnvironment(Environment* env) { env->SetUp(); } +static void TearDownEnvironment(Environment* env) { env->TearDown(); } + +// If the environment variable TEST_WARNINGS_OUTPUT_FILE was provided, appends +// `str` to the file, creating the file if necessary. +#if GTEST_HAS_FILE_SYSTEM +static void AppendToTestWarningsOutputFile(const std::string& str) { + const char* const filename = posix::GetEnv(kTestWarningsOutputFile); + if (filename == nullptr) { + return; + } + auto* const file = posix::FOpen(filename, "a"); + if (file == nullptr) { + return; + } + GTEST_CHECK_(fwrite(str.data(), 1, str.size(), file) == str.size()); + GTEST_CHECK_(posix::FClose(file) == 0); +} +#endif // GTEST_HAS_FILE_SYSTEM + +// Runs all tests in this UnitTest object, prints the result, and +// returns true if all tests are successful. If any exception is +// thrown during a test, the test is considered to be failed, but the +// rest of the tests will still be run. +// +// When parameterized tests are enabled, it expands and registers +// parameterized tests first in RegisterParameterizedTests(). +// All other functions called from RunAllTests() may safely assume that +// parameterized tests are ready to be counted and run. +bool UnitTestImpl::RunAllTests() { + // True if and only if Google Test is initialized before RUN_ALL_TESTS() is + // called. + const bool gtest_is_initialized_before_run_all_tests = GTestIsInitialized(); + + // Do not run any test if the --help flag was specified. + if (g_help_flag) return true; + + // Repeats the call to the post-flag parsing initialization in case the + // user didn't call InitGoogleTest. + PostFlagParsingInit(); + + // Handle the case where the program has no tests linked. + // Sometimes this is a programmer mistake, but sometimes it is intended. + if (total_test_count() == 0) { + constexpr char kNoTestLinkedMessage[] = + "This test program does NOT link in any test case."; + constexpr char kNoTestLinkedFatal[] = + "This is INVALID. Please make sure to link in at least one test case."; + constexpr char kNoTestLinkedWarning[] = + "Please make sure this is intended."; + const bool fail_if_no_test_linked = GTEST_FLAG_GET(fail_if_no_test_linked); + ColoredPrintf( + GTestColor::kRed, "%s %s\n", kNoTestLinkedMessage, + fail_if_no_test_linked ? kNoTestLinkedFatal : kNoTestLinkedWarning); + if (fail_if_no_test_linked) { + return false; + } +#if GTEST_HAS_FILE_SYSTEM + AppendToTestWarningsOutputFile(std::string(kNoTestLinkedMessage) + ' ' + + kNoTestLinkedWarning + '\n'); +#endif // GTEST_HAS_FILE_SYSTEM + } + +#if GTEST_HAS_FILE_SYSTEM + // Even if sharding is not on, test runners may want to use the + // GTEST_SHARD_STATUS_FILE to query whether the test supports the sharding + // protocol. + internal::WriteToShardStatusFileIfNeeded(); +#endif // GTEST_HAS_FILE_SYSTEM + + // True if and only if we are in a subprocess for running a thread-safe-style + // death test. + bool in_subprocess_for_death_test = false; + +#ifdef GTEST_HAS_DEATH_TEST + in_subprocess_for_death_test = (internal_run_death_test_flag_ != nullptr); +#if defined(GTEST_EXTRA_DEATH_TEST_CHILD_SETUP_) + if (in_subprocess_for_death_test) { + GTEST_EXTRA_DEATH_TEST_CHILD_SETUP_(); + } +#endif // defined(GTEST_EXTRA_DEATH_TEST_CHILD_SETUP_) +#endif // GTEST_HAS_DEATH_TEST + + const bool should_shard = ShouldShard(kTestTotalShards, kTestShardIndex, + in_subprocess_for_death_test); + + // Compares the full test names with the filter to decide which + // tests to run. + const bool has_tests_to_run = + FilterTests(should_shard ? HONOR_SHARDING_PROTOCOL + : IGNORE_SHARDING_PROTOCOL) > 0; + + // Lists the tests and exits if the --gtest_list_tests flag was specified. + if (GTEST_FLAG_GET(list_tests)) { + // This must be called *after* FilterTests() has been called. + ListTestsMatchingFilter(); + return true; + } + + random_seed_ = GetRandomSeedFromFlag(GTEST_FLAG_GET(random_seed)); + + // True if and only if at least one test has failed. + bool failed = false; + + TestEventListener* repeater = listeners()->repeater(); + + start_timestamp_ = GetTimeInMillis(); + repeater->OnTestProgramStart(*parent_); + + // How many times to repeat the tests? We don't want to repeat them + // when we are inside the subprocess of a death test. + const int repeat = in_subprocess_for_death_test ? 1 : GTEST_FLAG_GET(repeat); + + // Repeats forever if the repeat count is negative. + const bool gtest_repeat_forever = repeat < 0; + + // Should test environments be set up and torn down for each repeat, or only + // set up on the first and torn down on the last iteration? If there is no + // "last" iteration because the tests will repeat forever, always recreate the + // environments to avoid leaks in case one of the environments is using + // resources that are external to this process. Without this check there would + // be no way to clean up those external resources automatically. + const bool recreate_environments_when_repeating = + GTEST_FLAG_GET(recreate_environments_when_repeating) || + gtest_repeat_forever; + + for (int i = 0; gtest_repeat_forever || i != repeat; i++) { + // We want to preserve failures generated by ad-hoc test + // assertions executed before RUN_ALL_TESTS(). + ClearNonAdHocTestResult(); + + Timer timer; + + // Shuffles test suites and tests if requested. + if (has_tests_to_run && GTEST_FLAG_GET(shuffle)) { + random()->Reseed(static_cast(random_seed_)); + // This should be done before calling OnTestIterationStart(), + // such that a test event listener can see the actual test order + // in the event. + ShuffleTests(); + } + + // Tells the unit test event listeners that the tests are about to start. + repeater->OnTestIterationStart(*parent_, i); + + // Runs each test suite if there is at least one test to run. + if (has_tests_to_run) { + // Sets up all environments beforehand. If test environments aren't + // recreated for each iteration, only do so on the first iteration. + if (i == 0 || recreate_environments_when_repeating) { + repeater->OnEnvironmentsSetUpStart(*parent_); + ForEach(environments_, SetUpEnvironment); + repeater->OnEnvironmentsSetUpEnd(*parent_); + } + + // Runs the tests only if there was no fatal failure or skip triggered + // during global set-up. + if (Test::IsSkipped()) { + // Emit diagnostics when global set-up calls skip, as it will not be + // emitted by default. + TestResult& test_result = + *internal::GetUnitTestImpl()->current_test_result(); + for (int j = 0; j < test_result.total_part_count(); ++j) { + const TestPartResult& test_part_result = + test_result.GetTestPartResult(j); + if (test_part_result.type() == TestPartResult::kSkip) { + const std::string& result = test_part_result.message(); + printf("%s\n", result.c_str()); + } + } + fflush(stdout); + } else if (!Test::HasFatalFailure()) { + for (int test_index = 0; test_index < total_test_suite_count(); + test_index++) { + GetMutableSuiteCase(test_index)->Run(); + if (GTEST_FLAG_GET(fail_fast) && + GetMutableSuiteCase(test_index)->Failed()) { + for (int j = test_index + 1; j < total_test_suite_count(); j++) { + GetMutableSuiteCase(j)->Skip(); + } + break; + } + } + } else if (Test::HasFatalFailure()) { + // If there was a fatal failure during the global setup then we know we + // aren't going to run any tests. Explicitly mark all of the tests as + // skipped to make this obvious in the output. + for (int test_index = 0; test_index < total_test_suite_count(); + test_index++) { + GetMutableSuiteCase(test_index)->Skip(); + } + } + + // Tears down all environments in reverse order afterwards. If test + // environments aren't recreated for each iteration, only do so on the + // last iteration. + if (i == repeat - 1 || recreate_environments_when_repeating) { + repeater->OnEnvironmentsTearDownStart(*parent_); + std::for_each(environments_.rbegin(), environments_.rend(), + TearDownEnvironment); + repeater->OnEnvironmentsTearDownEnd(*parent_); + } + } + + elapsed_time_ = timer.Elapsed(); + + // Tells the unit test event listener that the tests have just finished. + repeater->OnTestIterationEnd(*parent_, i); + + // Gets the result and clears it. + if (!Passed()) { + failed = true; + } + + // Restores the original test order after the iteration. This + // allows the user to quickly repro a failure that happens in the + // N-th iteration without repeating the first (N - 1) iterations. + // This is not enclosed in "if (GTEST_FLAG(shuffle)) { ... }", in + // case the user somehow changes the value of the flag somewhere + // (it's always safe to unshuffle the tests). + UnshuffleTests(); + + if (GTEST_FLAG_GET(shuffle)) { + // Picks a new random seed for each iteration. + random_seed_ = GetNextRandomSeed(random_seed_); + } + } + + repeater->OnTestProgramEnd(*parent_); + // Destroy environments in normal code, not in static teardown. + bool delete_environment_on_teardown = true; + if (delete_environment_on_teardown) { + ForEach(environments_, internal::Delete); + environments_.clear(); + } + + // Try to warn the user if no tests matched the test filter. + if (ShouldWarnIfNoTestsMatchFilter()) { + const std::string filter_warning = + std::string("filter \"") + GTEST_FLAG_GET(filter) + + "\" did not match any test; no tests were run\n"; + ColoredPrintf(GTestColor::kRed, "WARNING: %s", filter_warning.c_str()); +#if GTEST_HAS_FILE_SYSTEM + AppendToTestWarningsOutputFile(filter_warning); +#endif // GTEST_HAS_FILE_SYSTEM + } + + if (!gtest_is_initialized_before_run_all_tests) { + ColoredPrintf( + GTestColor::kRed, + "\nIMPORTANT NOTICE - DO NOT IGNORE:\n" + "This test program did NOT call " GTEST_INIT_GOOGLE_TEST_NAME_ + "() before calling RUN_ALL_TESTS(). This is INVALID. Soon " GTEST_NAME_ + " will start to enforce the valid usage. " + "Please fix it ASAP, or IT WILL START TO FAIL.\n"); // NOLINT + } + + return !failed; +} + +#if GTEST_HAS_FILE_SYSTEM +// Reads the GTEST_SHARD_STATUS_FILE environment variable, and creates the file +// if the variable is present. If a file already exists at this location, this +// function will write over it. If the variable is present, but the file cannot +// be created, prints an error and exits. +void WriteToShardStatusFileIfNeeded() { + const char* const test_shard_file = posix::GetEnv(kTestShardStatusFile); + if (test_shard_file != nullptr) { + FILE* const file = posix::FOpen(test_shard_file, "w"); + if (file == nullptr) { + ColoredPrintf(GTestColor::kRed, + "Could not write to the test shard status file \"%s\" " + "specified by the %s environment variable.\n", + test_shard_file, kTestShardStatusFile); + fflush(stdout); + exit(EXIT_FAILURE); + } + fclose(file); + } +} +#endif // GTEST_HAS_FILE_SYSTEM + +// Checks whether sharding is enabled by examining the relevant +// environment variable values. If the variables are present, +// but inconsistent (i.e., shard_index >= total_shards), prints +// an error and exits. If in_subprocess_for_death_test, sharding is +// disabled because it must only be applied to the original test +// process. Otherwise, we could filter out death tests we intended to execute. +bool ShouldShard(const char* total_shards_env, const char* shard_index_env, + bool in_subprocess_for_death_test) { + if (in_subprocess_for_death_test) { + return false; + } + + const int32_t total_shards = Int32FromEnvOrDie(total_shards_env, -1); + const int32_t shard_index = Int32FromEnvOrDie(shard_index_env, -1); + + if (total_shards == -1 && shard_index == -1) { + return false; + } else if (total_shards == -1 && shard_index != -1) { + const Message msg = Message() << "Invalid environment variables: you have " + << kTestShardIndex << " = " << shard_index + << ", but have left " << kTestTotalShards + << " unset.\n"; + ColoredPrintf(GTestColor::kRed, "%s", msg.GetString().c_str()); + fflush(stdout); + exit(EXIT_FAILURE); + } else if (total_shards != -1 && shard_index == -1) { + const Message msg = Message() + << "Invalid environment variables: you have " + << kTestTotalShards << " = " << total_shards + << ", but have left " << kTestShardIndex << " unset.\n"; + ColoredPrintf(GTestColor::kRed, "%s", msg.GetString().c_str()); + fflush(stdout); + exit(EXIT_FAILURE); + } else if (shard_index < 0 || shard_index >= total_shards) { + const Message msg = + Message() << "Invalid environment variables: we require 0 <= " + << kTestShardIndex << " < " << kTestTotalShards + << ", but you have " << kTestShardIndex << "=" << shard_index + << ", " << kTestTotalShards << "=" << total_shards << ".\n"; + ColoredPrintf(GTestColor::kRed, "%s", msg.GetString().c_str()); + fflush(stdout); + exit(EXIT_FAILURE); + } + + return total_shards > 1; +} + +// Parses the environment variable var as an Int32. If it is unset, +// returns default_val. If it is not an Int32, prints an error +// and aborts. +int32_t Int32FromEnvOrDie(const char* var, int32_t default_val) { + const char* str_val = posix::GetEnv(var); + if (str_val == nullptr) { + return default_val; + } + + int32_t result; + if (!ParseInt32(Message() << "The value of environment variable " << var, + str_val, &result)) { + exit(EXIT_FAILURE); + } + return result; +} + +// Given the total number of shards, the shard index, and the test id, +// returns true if and only if the test should be run on this shard. The test id +// is some arbitrary but unique non-negative integer assigned to each test +// method. Assumes that 0 <= shard_index < total_shards. +bool ShouldRunTestOnShard(int total_shards, int shard_index, int test_id) { + return (test_id % total_shards) == shard_index; +} + +// Compares the name of each test with the user-specified filter to +// decide whether the test should be run, then records the result in +// each TestSuite and TestInfo object. +// If shard_tests == true, further filters tests based on sharding +// variables in the environment - see +// https://github.com/google/googletest/blob/main/docs/advanced.md +// . Returns the number of tests that should run. +int UnitTestImpl::FilterTests(ReactionToSharding shard_tests) { + const int32_t total_shards = shard_tests == HONOR_SHARDING_PROTOCOL + ? Int32FromEnvOrDie(kTestTotalShards, -1) + : -1; + const int32_t shard_index = shard_tests == HONOR_SHARDING_PROTOCOL + ? Int32FromEnvOrDie(kTestShardIndex, -1) + : -1; + + const PositiveAndNegativeUnitTestFilter gtest_flag_filter( + GTEST_FLAG_GET(filter)); + const UnitTestFilter disable_test_filter(kDisableTestFilter); + // num_runnable_tests are the number of tests that will + // run across all shards (i.e., match filter and are not disabled). + // num_selected_tests are the number of tests to be run on + // this shard. + int num_runnable_tests = 0; + int num_selected_tests = 0; + for (auto* test_suite : test_suites_) { + const std::string& test_suite_name = test_suite->name_; + test_suite->set_should_run(false); + + for (TestInfo* test_info : test_suite->test_info_list()) { + const std::string& test_name = test_info->name_; + // A test is disabled if test suite name or test name matches + // kDisableTestFilter. + const bool is_disabled = + disable_test_filter.MatchesName(test_suite_name) || + disable_test_filter.MatchesName(test_name); + test_info->is_disabled_ = is_disabled; + + const bool matches_filter = + gtest_flag_filter.MatchesTest(test_suite_name, test_name); + test_info->matches_filter_ = matches_filter; + + const bool is_runnable = + (GTEST_FLAG_GET(also_run_disabled_tests) || !is_disabled) && + matches_filter; + + const bool is_in_another_shard = + shard_tests != IGNORE_SHARDING_PROTOCOL && + !ShouldRunTestOnShard(total_shards, shard_index, num_runnable_tests); + test_info->is_in_another_shard_ = is_in_another_shard; + const bool is_selected = is_runnable && !is_in_another_shard; + + num_runnable_tests += is_runnable; + num_selected_tests += is_selected; + + test_info->should_run_ = is_selected; + test_suite->set_should_run(test_suite->should_run() || is_selected); + } + } + return num_selected_tests; +} + +// Returns true if a warning should be issued if no tests match the test filter +// flag. We can't simply count the number of tests that ran because, for +// instance, test sharding and death tests might mean no tests are expected to +// run in this process, but will run in another process. +bool UnitTestImpl::ShouldWarnIfNoTestsMatchFilter() const { + if (total_test_count() == 0) { + // No tests were linked in to program. + // This case is handled by a different warning. + return false; + } + const PositiveAndNegativeUnitTestFilter gtest_flag_filter( + GTEST_FLAG_GET(filter)); + for (auto* test_suite : test_suites_) { + const std::string& test_suite_name = test_suite->name_; + for (TestInfo* test_info : test_suite->test_info_list()) { + const std::string& test_name = test_info->name_; + if (gtest_flag_filter.MatchesTest(test_suite_name, test_name)) { + return false; + } + } + } + return true; +} + +// Prints the given C-string on a single line by replacing all '\n' +// characters with string "\\n". If the output takes more than +// max_length characters, only prints the first max_length characters +// and "...". +static void PrintOnOneLine(const char* str, int max_length) { + if (str != nullptr) { + for (int i = 0; *str != '\0'; ++str) { + if (i >= max_length) { + printf("..."); + break; + } + if (*str == '\n') { + printf("\\n"); + i += 2; + } else { + printf("%c", *str); + ++i; + } + } + } +} + +// Prints the names of the tests matching the user-specified filter flag. +void UnitTestImpl::ListTestsMatchingFilter() { + // Print at most this many characters for each type/value parameter. + const int kMaxParamLength = 250; + + for (auto* test_suite : test_suites_) { + bool printed_test_suite_name = false; + + for (size_t j = 0; j < test_suite->test_info_list().size(); j++) { + const TestInfo* const test_info = test_suite->test_info_list()[j]; + if (test_info->matches_filter_) { + if (!printed_test_suite_name) { + printed_test_suite_name = true; + printf("%s.", test_suite->name()); + if (test_suite->type_param() != nullptr) { + printf(" # %s = ", kTypeParamLabel); + // We print the type parameter on a single line to make + // the output easy to parse by a program. + PrintOnOneLine(test_suite->type_param(), kMaxParamLength); + } + printf("\n"); + } + printf(" %s", test_info->name()); + if (test_info->value_param() != nullptr) { + printf(" # %s = ", kValueParamLabel); + // We print the value parameter on a single line to make the + // output easy to parse by a program. + PrintOnOneLine(test_info->value_param(), kMaxParamLength); + } + printf("\n"); + } + } + } + fflush(stdout); +#if GTEST_HAS_FILE_SYSTEM + const std::string& output_format = UnitTestOptions::GetOutputFormat(); + if (output_format == "xml" || output_format == "json") { + FILE* fileout = + OpenFileForWriting(UnitTestOptions::GetAbsolutePathToOutputFile()); + std::stringstream stream; + if (output_format == "xml") { + XmlUnitTestResultPrinter( + UnitTestOptions::GetAbsolutePathToOutputFile().c_str()) + .PrintXmlTestsList(&stream, test_suites_); + } else if (output_format == "json") { + JsonUnitTestResultPrinter( + UnitTestOptions::GetAbsolutePathToOutputFile().c_str()) + .PrintJsonTestList(&stream, test_suites_); + } + fprintf(fileout, "%s", StringStreamToString(&stream).c_str()); + fclose(fileout); + } +#endif // GTEST_HAS_FILE_SYSTEM +} + +// Sets the OS stack trace getter. +// +// Does nothing if the input and the current OS stack trace getter are +// the same; otherwise, deletes the old getter and makes the input the +// current getter. +void UnitTestImpl::set_os_stack_trace_getter( + OsStackTraceGetterInterface* getter) { + if (os_stack_trace_getter_ != getter) { + delete os_stack_trace_getter_; + os_stack_trace_getter_ = getter; + } +} + +// Returns the current OS stack trace getter if it is not NULL; +// otherwise, creates an OsStackTraceGetter, makes it the current +// getter, and returns it. +OsStackTraceGetterInterface* UnitTestImpl::os_stack_trace_getter() { + if (os_stack_trace_getter_ == nullptr) { +#ifdef GTEST_OS_STACK_TRACE_GETTER_ + os_stack_trace_getter_ = new GTEST_OS_STACK_TRACE_GETTER_; +#else + os_stack_trace_getter_ = new OsStackTraceGetter; +#endif // GTEST_OS_STACK_TRACE_GETTER_ + } + + return os_stack_trace_getter_; +} + +// Returns the most specific TestResult currently running. +TestResult* UnitTestImpl::current_test_result() { + if (current_test_info_ != nullptr) { + return ¤t_test_info_->result_; + } + if (current_test_suite_ != nullptr) { + return ¤t_test_suite_->ad_hoc_test_result_; + } + return &ad_hoc_test_result_; +} + +// Shuffles all test suites, and the tests within each test suite, +// making sure that death tests are still run first. +void UnitTestImpl::ShuffleTests() { + // Shuffles the death test suites. + ShuffleRange(random(), 0, last_death_test_suite_ + 1, &test_suite_indices_); + + // Shuffles the non-death test suites. + ShuffleRange(random(), last_death_test_suite_ + 1, + static_cast(test_suites_.size()), &test_suite_indices_); + + // Shuffles the tests inside each test suite. + for (auto& test_suite : test_suites_) { + test_suite->ShuffleTests(random()); + } +} + +// Restores the test suites and tests to their order before the first shuffle. +void UnitTestImpl::UnshuffleTests() { + for (size_t i = 0; i < test_suites_.size(); i++) { + // Unshuffles the tests in each test suite. + test_suites_[i]->UnshuffleTests(); + // Resets the index of each test suite. + test_suite_indices_[i] = static_cast(i); + } +} + +// Returns the current OS stack trace as an std::string. +// +// The maximum number of stack frames to be included is specified by +// the gtest_stack_trace_depth flag. The skip_count parameter +// specifies the number of top frames to be skipped, which doesn't +// count against the number of frames to be included. +// +// For example, if Foo() calls Bar(), which in turn calls +// GetCurrentOsStackTraceExceptTop(..., 1), Foo() will be included in +// the trace but Bar() and GetCurrentOsStackTraceExceptTop() won't. +GTEST_NO_INLINE_ GTEST_NO_TAIL_CALL_ std::string +GetCurrentOsStackTraceExceptTop(int skip_count) { + // We pass skip_count + 1 to skip this wrapper function in addition + // to what the user really wants to skip. + return GetUnitTestImpl()->CurrentOsStackTraceExceptTop(skip_count + 1); +} + +// Used by the GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_ macro to +// suppress unreachable code warnings. +namespace { +class ClassUniqueToAlwaysTrue {}; +} // namespace + +bool IsTrue(bool condition) { return condition; } + +bool AlwaysTrue() { +#if GTEST_HAS_EXCEPTIONS + // This condition is always false so AlwaysTrue() never actually throws, + // but it makes the compiler think that it may throw. + if (IsTrue(false)) throw ClassUniqueToAlwaysTrue(); +#endif // GTEST_HAS_EXCEPTIONS + return true; +} + +// If *pstr starts with the given prefix, modifies *pstr to be right +// past the prefix and returns true; otherwise leaves *pstr unchanged +// and returns false. None of pstr, *pstr, and prefix can be NULL. +bool SkipPrefix(const char* prefix, const char** pstr) { + const size_t prefix_len = strlen(prefix); + if (strncmp(*pstr, prefix, prefix_len) == 0) { + *pstr += prefix_len; + return true; + } + return false; +} + +// Parses a string as a command line flag. The string should have +// the format "--flag=value". When def_optional is true, the "=value" +// part can be omitted. +// +// Returns the value of the flag, or NULL if the parsing failed. +static const char* ParseFlagValue(const char* str, const char* flag_name, + bool def_optional) { + // str and flag must not be NULL. + if (str == nullptr || flag_name == nullptr) return nullptr; + + // The flag must start with "--" followed by GTEST_FLAG_PREFIX_. + const std::string flag_str = + std::string("--") + GTEST_FLAG_PREFIX_ + flag_name; + const size_t flag_len = flag_str.length(); + if (strncmp(str, flag_str.c_str(), flag_len) != 0) return nullptr; + + // Skips the flag name. + const char* flag_end = str + flag_len; + + // When def_optional is true, it's OK to not have a "=value" part. + if (def_optional && (flag_end[0] == '\0')) { + return flag_end; + } + + // If def_optional is true and there are more characters after the + // flag name, or if def_optional is false, there must be a '=' after + // the flag name. + if (flag_end[0] != '=') return nullptr; + + // Returns the string after "=". + return flag_end + 1; +} + +// Parses a string for a bool flag, in the form of either +// "--flag=value" or "--flag". +// +// In the former case, the value is taken as true as long as it does +// not start with '0', 'f', or 'F'. +// +// In the latter case, the value is taken as true. +// +// On success, stores the value of the flag in *value, and returns +// true. On failure, returns false without changing *value. +static bool ParseFlag(const char* str, const char* flag_name, bool* value) { + // Gets the value of the flag as a string. + const char* const value_str = ParseFlagValue(str, flag_name, true); + + // Aborts if the parsing failed. + if (value_str == nullptr) return false; + + // Converts the string value to a bool. + *value = !(*value_str == '0' || *value_str == 'f' || *value_str == 'F'); + return true; +} + +// Parses a string for an int32_t flag, in the form of "--flag=value". +// +// On success, stores the value of the flag in *value, and returns +// true. On failure, returns false without changing *value. +bool ParseFlag(const char* str, const char* flag_name, int32_t* value) { + // Gets the value of the flag as a string. + const char* const value_str = ParseFlagValue(str, flag_name, false); + + // Aborts if the parsing failed. + if (value_str == nullptr) return false; + + // Sets *value to the value of the flag. + return ParseInt32(Message() << "The value of flag --" << flag_name, value_str, + value); +} + +// Parses a string for a string flag, in the form of "--flag=value". +// +// On success, stores the value of the flag in *value, and returns +// true. On failure, returns false without changing *value. +template +static bool ParseFlag(const char* str, const char* flag_name, String* value) { + // Gets the value of the flag as a string. + const char* const value_str = ParseFlagValue(str, flag_name, false); + + // Aborts if the parsing failed. + if (value_str == nullptr) return false; + + // Sets *value to the value of the flag. + *value = value_str; + return true; +} + +// Determines whether a string has a prefix that Google Test uses for its +// flags, i.e., starts with GTEST_FLAG_PREFIX_ or GTEST_FLAG_PREFIX_DASH_. +// If Google Test detects that a command line flag has its prefix but is not +// recognized, it will print its help message. Flags starting with +// GTEST_INTERNAL_PREFIX_ followed by "internal_" are considered Google Test +// internal flags and do not trigger the help message. +static bool HasGoogleTestFlagPrefix(const char* str) { + return (SkipPrefix("--", &str) || SkipPrefix("-", &str) || + SkipPrefix("/", &str)) && + !SkipPrefix(GTEST_FLAG_PREFIX_ "internal_", &str) && + (SkipPrefix(GTEST_FLAG_PREFIX_, &str) || + SkipPrefix(GTEST_FLAG_PREFIX_DASH_, &str)); +} + +// Prints a string containing code-encoded text. The following escape +// sequences can be used in the string to control the text color: +// +// @@ prints a single '@' character. +// @R changes the color to red. +// @G changes the color to green. +// @Y changes the color to yellow. +// @D changes to the default terminal text color. +// +static void PrintColorEncoded(const char* str) { + GTestColor color = GTestColor::kDefault; // The current color. + + // Conceptually, we split the string into segments divided by escape + // sequences. Then we print one segment at a time. At the end of + // each iteration, the str pointer advances to the beginning of the + // next segment. + for (;;) { + const char* p = strchr(str, '@'); + if (p == nullptr) { + ColoredPrintf(color, "%s", str); + return; + } + + ColoredPrintf(color, "%s", std::string(str, p).c_str()); + + const char ch = p[1]; + str = p + 2; + if (ch == '@') { + ColoredPrintf(color, "@"); + } else if (ch == 'D') { + color = GTestColor::kDefault; + } else if (ch == 'R') { + color = GTestColor::kRed; + } else if (ch == 'G') { + color = GTestColor::kGreen; + } else if (ch == 'Y') { + color = GTestColor::kYellow; + } else { + --str; + } + } +} + +static const char kColorEncodedHelpMessage[] = + "This program contains tests written using " GTEST_NAME_ + ". You can use the\n" + "following command line flags to control its behavior:\n" + "\n" + "Test Selection:\n" + " @G--" GTEST_FLAG_PREFIX_ + "list_tests@D\n" + " List the names of all tests instead of running them. The name of\n" + " TEST(Foo, Bar) is \"Foo.Bar\".\n" + " @G--" GTEST_FLAG_PREFIX_ + "filter=@YPOSITIVE_PATTERNS" + "[@G-@YNEGATIVE_PATTERNS]@D\n" + " Run only the tests whose name matches one of the positive patterns " + "but\n" + " none of the negative patterns. '?' matches any single character; " + "'*'\n" + " matches any substring; ':' separates two patterns.\n" + " @G--" GTEST_FLAG_PREFIX_ + "also_run_disabled_tests@D\n" + " Run all disabled tests too.\n" + "\n" + "Test Execution:\n" + " @G--" GTEST_FLAG_PREFIX_ + "repeat=@Y[COUNT]@D\n" + " Run the tests repeatedly; use a negative count to repeat forever.\n" + " @G--" GTEST_FLAG_PREFIX_ + "shuffle@D\n" + " Randomize tests' orders on every iteration.\n" + " @G--" GTEST_FLAG_PREFIX_ + "random_seed=@Y[NUMBER]@D\n" + " Random number seed to use for shuffling test orders (between 1 and\n" + " 99999, or 0 to use a seed based on the current time).\n" + " @G--" GTEST_FLAG_PREFIX_ + "recreate_environments_when_repeating@D\n" + " Sets up and tears down the global test environment on each repeat\n" + " of the test.\n" + "\n" + "Test Output:\n" + " @G--" GTEST_FLAG_PREFIX_ + "color=@Y(@Gyes@Y|@Gno@Y|@Gauto@Y)@D\n" + " Enable/disable colored output. The default is @Gauto@D.\n" + " @G--" GTEST_FLAG_PREFIX_ + "brief=1@D\n" + " Only print test failures.\n" + " @G--" GTEST_FLAG_PREFIX_ + "print_time=0@D\n" + " Don't print the elapsed time of each test.\n" + " @G--" GTEST_FLAG_PREFIX_ + "output=@Y(@Gjson@Y|@Gxml@Y)[@G:@YDIRECTORY_PATH@G" GTEST_PATH_SEP_ + "@Y|@G:@YFILE_PATH]@D\n" + " Generate a JSON or XML report in the given directory or with the " + "given\n" + " file name. @YFILE_PATH@D defaults to @Gtest_detail.xml@D.\n" +#if GTEST_CAN_STREAM_RESULTS_ + " @G--" GTEST_FLAG_PREFIX_ + "stream_result_to=@YHOST@G:@YPORT@D\n" + " Stream test results to the given server.\n" +#endif // GTEST_CAN_STREAM_RESULTS_ + "\n" + "Assertion Behavior:\n" +#if defined(GTEST_HAS_DEATH_TEST) && !defined(GTEST_OS_WINDOWS) + " @G--" GTEST_FLAG_PREFIX_ + "death_test_style=@Y(@Gfast@Y|@Gthreadsafe@Y)@D\n" + " Set the default death test style.\n" +#endif // GTEST_HAS_DEATH_TEST && !GTEST_OS_WINDOWS + " @G--" GTEST_FLAG_PREFIX_ + "break_on_failure@D\n" + " Turn assertion failures into debugger break-points.\n" + " @G--" GTEST_FLAG_PREFIX_ + "throw_on_failure@D\n" + " Turn assertion failures into C++ exceptions for use by an external\n" + " test framework.\n" + " @G--" GTEST_FLAG_PREFIX_ + "catch_exceptions=0@D\n" + " Do not report exceptions as test failures. Instead, allow them\n" + " to crash the program or throw a pop-up (on Windows).\n" + "\n" + "Except for @G--" GTEST_FLAG_PREFIX_ + "list_tests@D, you can alternatively set " + "the corresponding\n" + "environment variable of a flag (all letters in upper-case). For example, " + "to\n" + "disable colored text output, you can either specify " + "@G--" GTEST_FLAG_PREFIX_ + "color=no@D or set\n" + "the @G" GTEST_FLAG_PREFIX_UPPER_ + "COLOR@D environment variable to @Gno@D.\n" + "\n" + "For more information, please read the " GTEST_NAME_ + " documentation at\n" + "@G" GTEST_PROJECT_URL_ "@D. If you find a bug in " GTEST_NAME_ + "\n" + "(not one in your own code or tests), please report it to\n" + "@G<" GTEST_DEV_EMAIL_ ">@D.\n"; + +static bool ParseGoogleTestFlag(const char* const arg) { +#define GTEST_INTERNAL_PARSE_FLAG(flag_name) \ + do { \ + auto value = GTEST_FLAG_GET(flag_name); \ + if (ParseFlag(arg, #flag_name, &value)) { \ + GTEST_FLAG_SET(flag_name, value); \ + return true; \ + } \ + } while (false) + + GTEST_INTERNAL_PARSE_FLAG(also_run_disabled_tests); + GTEST_INTERNAL_PARSE_FLAG(break_on_failure); + GTEST_INTERNAL_PARSE_FLAG(catch_exceptions); + GTEST_INTERNAL_PARSE_FLAG(color); + GTEST_INTERNAL_PARSE_FLAG(death_test_style); + GTEST_INTERNAL_PARSE_FLAG(death_test_use_fork); + GTEST_INTERNAL_PARSE_FLAG(fail_fast); + GTEST_INTERNAL_PARSE_FLAG(fail_if_no_test_linked); + GTEST_INTERNAL_PARSE_FLAG(filter); + GTEST_INTERNAL_PARSE_FLAG(internal_run_death_test); + GTEST_INTERNAL_PARSE_FLAG(list_tests); + GTEST_INTERNAL_PARSE_FLAG(output); + GTEST_INTERNAL_PARSE_FLAG(brief); + GTEST_INTERNAL_PARSE_FLAG(print_time); + GTEST_INTERNAL_PARSE_FLAG(print_utf8); + GTEST_INTERNAL_PARSE_FLAG(random_seed); + GTEST_INTERNAL_PARSE_FLAG(repeat); + GTEST_INTERNAL_PARSE_FLAG(recreate_environments_when_repeating); + GTEST_INTERNAL_PARSE_FLAG(shuffle); + GTEST_INTERNAL_PARSE_FLAG(stack_trace_depth); + GTEST_INTERNAL_PARSE_FLAG(stream_result_to); + GTEST_INTERNAL_PARSE_FLAG(throw_on_failure); + return false; +} + +#if GTEST_USE_OWN_FLAGFILE_FLAG_ && GTEST_HAS_FILE_SYSTEM +static void LoadFlagsFromFile(const std::string& path) { + FILE* flagfile = posix::FOpen(path.c_str(), "r"); + if (!flagfile) { + GTEST_LOG_(FATAL) << "Unable to open file \"" << GTEST_FLAG_GET(flagfile) + << "\""; + } + std::string contents(ReadEntireFile(flagfile)); + posix::FClose(flagfile); + std::vector lines; + SplitString(contents, '\n', &lines); + for (size_t i = 0; i < lines.size(); ++i) { + if (lines[i].empty()) continue; + if (!ParseGoogleTestFlag(lines[i].c_str())) g_help_flag = true; + } +} +#endif // GTEST_USE_OWN_FLAGFILE_FLAG_ && GTEST_HAS_FILE_SYSTEM + +// Parses the command line for Google Test flags, without initializing +// other parts of Google Test. The type parameter CharType can be +// instantiated to either char or wchar_t. +template +void ParseGoogleTestFlagsOnlyImpl(int* argc, CharType** argv) { + std::string flagfile_value; + for (int i = 1; i < *argc; i++) { + const std::string arg_string = StreamableToString(argv[i]); + const char* const arg = arg_string.c_str(); + + using internal::ParseFlag; + + bool remove_flag = false; + if (ParseGoogleTestFlag(arg)) { + remove_flag = true; +#if GTEST_USE_OWN_FLAGFILE_FLAG_ && GTEST_HAS_FILE_SYSTEM + } else if (ParseFlag(arg, "flagfile", &flagfile_value)) { + GTEST_FLAG_SET(flagfile, flagfile_value); + LoadFlagsFromFile(flagfile_value); + remove_flag = true; +#endif // GTEST_USE_OWN_FLAGFILE_FLAG_ && GTEST_HAS_FILE_SYSTEM + } else if (arg_string == "--help" || HasGoogleTestFlagPrefix(arg)) { + // Both help flag and unrecognized Google Test flags (excluding + // internal ones) trigger help display. + g_help_flag = true; + } + + if (remove_flag) { + // Shift the remainder of the argv list left by one. + for (int j = i + 1; j < *argc; ++j) { + argv[j - 1] = argv[j]; + } + + // Decrements the argument count. + (*argc)--; + + // Terminate the array with nullptr. + argv[*argc] = nullptr; + + // We also need to decrement the iterator as we just removed + // an element. + i--; + } + } + + if (g_help_flag) { + // We print the help here instead of in RUN_ALL_TESTS(), as the + // latter may not be called at all if the user is using Google + // Test with another testing framework. + PrintColorEncoded(kColorEncodedHelpMessage); + } +} + +// Parses the command line for Google Test flags, without initializing +// other parts of Google Test. This function updates argc and argv by removing +// flags that are known to GoogleTest (including other user flags defined using +// ABSL_FLAG if GoogleTest is built with GTEST_USE_ABSL). Other arguments +// remain in place. Unrecognized flags are not reported and do not cause the +// program to exit. +void ParseGoogleTestFlagsOnly(int* argc, char** argv) { +#ifdef GTEST_HAS_ABSL_FLAGS + if (*argc <= 0) return; + + std::vector positional_args; + std::vector unrecognized_flags; + absl::ParseAbseilFlagsOnly(*argc, argv, positional_args, unrecognized_flags); + absl::flat_hash_set unrecognized; + for (const auto& flag : unrecognized_flags) { + unrecognized.insert(flag.flag_name); + } + absl::flat_hash_set positional; + for (const auto& arg : positional_args) { + positional.insert(arg); + } + + int out_pos = 1; + int in_pos = 1; + for (; in_pos < *argc; ++in_pos) { + char* arg = argv[in_pos]; + absl::string_view arg_str(arg); + if (absl::ConsumePrefix(&arg_str, "--")) { + // Flag-like argument. If the flag was unrecognized, keep it. + // If it was a GoogleTest flag, remove it. + if (unrecognized.contains(arg_str)) { + argv[out_pos++] = argv[in_pos]; + continue; + } + } + + if (arg_str.empty()) { + ++in_pos; + break; // '--' indicates that the rest of the arguments are positional + } + + // Probably a positional argument. If it is in fact positional, keep it. + // If it was a value for the flag argument, remove it. + if (positional.contains(arg)) { + argv[out_pos++] = arg; + } + } + + // The rest are positional args for sure. + while (in_pos < *argc) { + argv[out_pos++] = argv[in_pos++]; + } + + *argc = out_pos; + argv[out_pos] = nullptr; +#else + ParseGoogleTestFlagsOnlyImpl(argc, argv); +#endif + + // Fix the value of *_NSGetArgc() on macOS, but if and only if + // *_NSGetArgv() == argv + // Only applicable to char** version of argv +#ifdef GTEST_OS_MAC +#ifndef GTEST_OS_IOS + if (*_NSGetArgv() == argv) { + *_NSGetArgc() = *argc; + } +#endif +#endif +} +void ParseGoogleTestFlagsOnly(int* argc, wchar_t** argv) { + ParseGoogleTestFlagsOnlyImpl(argc, argv); +} + +// The internal implementation of InitGoogleTest(). +// +// The type parameter CharType can be instantiated to either char or +// wchar_t. +template +void InitGoogleTestImpl(int* argc, CharType** argv) { + // We don't want to run the initialization code twice. + if (GTestIsInitialized()) return; + + if (*argc <= 0) return; + + g_argvs.clear(); + for (int i = 0; i != *argc; i++) { + g_argvs.push_back(StreamableToString(argv[i])); + } + +#ifdef GTEST_HAS_ABSL + absl::InitializeSymbolizer(g_argvs[0].c_str()); + +#ifdef GTEST_HAS_ABSL_FLAGS + // When using the Abseil Flags library, set the program usage message to the + // help message, but remove the color-encoding from the message first. + absl::SetProgramUsageMessage(absl::StrReplaceAll( + kColorEncodedHelpMessage, + {{"@D", ""}, {"@R", ""}, {"@G", ""}, {"@Y", ""}, {"@@", "@"}})); +#endif // GTEST_HAS_ABSL_FLAGS +#endif // GTEST_HAS_ABSL + + ParseGoogleTestFlagsOnly(argc, argv); + GetUnitTestImpl()->PostFlagParsingInit(); +} + +} // namespace internal + +// Initializes Google Test. This must be called before calling +// RUN_ALL_TESTS(). In particular, it parses a command line for the +// flags that Google Test recognizes. Whenever a Google Test flag is +// seen, it is removed from argv, and *argc is decremented. +// +// No value is returned. Instead, the Google Test flag variables are +// updated. +// +// Calling the function for the second time has no user-visible effect. +void InitGoogleTest(int* argc, char** argv) { +#if defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_) + GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_(argc, argv); +#else // defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_) + internal::InitGoogleTestImpl(argc, argv); +#endif // defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_) +} + +// This overloaded version can be used in Windows programs compiled in +// UNICODE mode. +void InitGoogleTest(int* argc, wchar_t** argv) { +#if defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_) + GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_(argc, argv); +#else // defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_) + internal::InitGoogleTestImpl(argc, argv); +#endif // defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_) +} + +// This overloaded version can be used on Arduino/embedded platforms where +// there is no argc/argv. +void InitGoogleTest() { + // Since Arduino doesn't have a command line, fake out the argc/argv arguments + int argc = 1; + const auto arg0 = "dummy"; + char* argv0 = const_cast(arg0); + char** argv = &argv0; + +#if defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_) + GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_(&argc, argv); +#else // defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_) + internal::InitGoogleTestImpl(&argc, argv); +#endif // defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_) +} + +#if !defined(GTEST_CUSTOM_TEMPDIR_FUNCTION_) || \ + !defined(GTEST_CUSTOM_SRCDIR_FUNCTION_) +// Returns the value of the first environment variable that is set and contains +// a non-empty string. If there are none, returns the "fallback" string. Adds +// the director-separator character as a suffix if not provided in the +// environment variable value. +static std::string GetDirFromEnv( + std::initializer_list environment_variables, + const char* fallback, char separator) { + for (const char* variable_name : environment_variables) { + const char* value = internal::posix::GetEnv(variable_name); + if (value != nullptr && value[0] != '\0') { + if (value[strlen(value) - 1] != separator) { + return std::string(value).append(1, separator); + } + return value; + } + } + return fallback; +} +#endif + +std::string TempDir() { +#if defined(GTEST_CUSTOM_TEMPDIR_FUNCTION_) + return GTEST_CUSTOM_TEMPDIR_FUNCTION_(); +#elif defined(GTEST_OS_WINDOWS) || defined(GTEST_OS_WINDOWS_MOBILE) + return GetDirFromEnv({"TEST_TMPDIR", "TEMP"}, "\\temp\\", '\\'); +#elif defined(GTEST_OS_LINUX_ANDROID) + return GetDirFromEnv({"TEST_TMPDIR", "TMPDIR"}, "/data/local/tmp/", '/'); +#else + return GetDirFromEnv({"TEST_TMPDIR", "TMPDIR"}, "/tmp/", '/'); +#endif +} + +#if GTEST_HAS_FILE_SYSTEM && !defined(GTEST_CUSTOM_SRCDIR_FUNCTION_) +// Returns the directory path (including terminating separator) of the current +// executable as derived from argv[0]. +static std::string GetCurrentExecutableDirectory() { + internal::FilePath argv_0(internal::GetArgvs()[0]); + return argv_0.RemoveFileName().string(); +} +#endif + +#if GTEST_HAS_FILE_SYSTEM +std::string SrcDir() { +#if defined(GTEST_CUSTOM_SRCDIR_FUNCTION_) + return GTEST_CUSTOM_SRCDIR_FUNCTION_(); +#elif defined(GTEST_OS_WINDOWS) || defined(GTEST_OS_WINDOWS_MOBILE) + return GetDirFromEnv({"TEST_SRCDIR"}, GetCurrentExecutableDirectory().c_str(), + '\\'); +#elif defined(GTEST_OS_LINUX_ANDROID) + return GetDirFromEnv({"TEST_SRCDIR"}, GetCurrentExecutableDirectory().c_str(), + '/'); +#else + return GetDirFromEnv({"TEST_SRCDIR"}, GetCurrentExecutableDirectory().c_str(), + '/'); +#endif +} +#endif + +// Class ScopedTrace + +// Pushes the given source file location and message onto a per-thread +// trace stack maintained by Google Test. +void ScopedTrace::PushTrace(const char* file, int line, std::string message) { + internal::TraceInfo trace; + trace.file = file; + trace.line = line; + trace.message.swap(message); + + UnitTest::GetInstance()->PushGTestTrace(trace); +} + +// Pops the info pushed by the c'tor. +ScopedTrace::~ScopedTrace() GTEST_LOCK_EXCLUDED_(&UnitTest::mutex_) { + UnitTest::GetInstance()->PopGTestTrace(); +} + +} // namespace testing diff --git a/googletest/src/gtest_main.cc b/googletest/src/gtest_main.cc new file mode 100644 index 00000000..8141caf4 --- /dev/null +++ b/googletest/src/gtest_main.cc @@ -0,0 +1,66 @@ +// Copyright 2006, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +#include "gtest/gtest.h" + +#if defined(GTEST_OS_ESP8266) || defined(GTEST_OS_ESP32) || \ + (defined(GTEST_OS_NRF52) && defined(ARDUINO)) +// Arduino-like platforms: program entry points are setup/loop instead of main. + +#ifdef GTEST_OS_ESP8266 +extern "C" { +#endif + +void setup() { testing::InitGoogleTest(); } + +void loop() { RUN_ALL_TESTS(); } + +#ifdef GTEST_OS_ESP8266 +} +#endif + +#elif defined(GTEST_OS_QURT) +// QuRT: program entry point is main, but argc/argv are unusable. + +GTEST_API_ int main() { + printf("Running main() from %s\n", __FILE__); + testing::InitGoogleTest(); + return RUN_ALL_TESTS(); +} +#else +// Normal platforms: program entry point is main, argc/argv are initialized. + +GTEST_API_ int main(int argc, char **argv) { + printf("Running main() from %s\n", __FILE__); + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} +#endif From f9dd09f6562dea100bb0167f170036ee341408c1 Mon Sep 17 00:00:00 2001 From: Caitlin Ross Date: Thu, 2 Jul 2026 20:13:33 -0500 Subject: [PATCH 5/5] testing: add a simple unit test for now to show how to use googletest --- tests/CMakeLists.txt | 12 ++++++++++++ tests/codes-unit-smoke.cxx | 9 +++++++++ 2 files changed, 21 insertions(+) create mode 100644 tests/codes-unit-smoke.cxx diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 70ab98e5..87b8430f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -2,6 +2,18 @@ enable_testing() configure_file(run-test.sh.in run-test.sh) +# --------------------------------------------------------------------------- +# Unit-test smoke test. The GoogleTest framework (GTest::gtest / gtest_main) is +# built by the thirdparty dispatcher when BUILD_TESTING is on — see +# thirdparty/googletest/. This smoke test proves the vendored framework builds, +# links, runs, and reports through CTest. Real unit-test consumers land in +# a future PR. Unit tests run serially (no MPI), distinct from the mpiexec-launched +# model tests below. +add_executable(codes-unit-smoke codes-unit-smoke.cxx) +target_link_libraries(codes-unit-smoke PRIVATE GTest::gtest_main) +add_test(NAME codes-unit-smoke COMMAND codes-unit-smoke) +# --------------------------------------------------------------------------- + # codes_add_equivalence_test — register an equivalence/determinism test. # # Runs a model binary two or more times and asserts a marker line (default diff --git a/tests/codes-unit-smoke.cxx b/tests/codes-unit-smoke.cxx new file mode 100644 index 00000000..d809f3e0 --- /dev/null +++ b/tests/codes-unit-smoke.cxx @@ -0,0 +1,9 @@ +// Smoke test for the vendored GoogleTest wiring: proves the +// framework builds, links, runs, and reports a result through CTest. It asserts +// nothing about CODES itself — real unit tests (starting with the YAML config +// compiler) arrive soon. Runs serially; no MPI. +#include + +TEST(Smoke, ArithmeticSanity) { + EXPECT_EQ(1 + 1, 2); +}