diff --git a/.clang-format b/.clang-format index ed150af9b..b3bb8d132 100644 --- a/.clang-format +++ b/.clang-format @@ -106,6 +106,8 @@ IncludeCategories: Priority: 4 - Regex: '^"engines\/.*\.h"' Priority: 4 + - Regex: '^"checkpoint\/.*\.h"' + Priority: 4 - Regex: '^"output\/.*\.h"' Priority: 4 - Regex: '^"archetypes\/.*\.h"' diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 04bc34050..cd7119789 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -16,8 +16,9 @@ jobs: exclude: - device: amd-gpu precision: double - # my AMD GPUs doesn't support fp64 atomics : ( + # my AMD GPU doesn't support fp64 atomics : ( runs-on: [self-hosted, "${{ matrix.device }}"] + if: contains(github.event.head_commit.message, 'totest') steps: - name: Checkout uses: actions/checkout@v3.3.0 diff --git a/.gitignore b/.gitignore index 20bfe33a3..53d09b648 100644 --- a/.gitignore +++ b/.gitignore @@ -54,3 +54,5 @@ Testing/ .schema.json *_old/ action-token +*.vim +ignore-* diff --git a/.gitmodules b/.gitmodules index bb2c39c87..e06c332fe 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "extern/toml11"] - path = extern/toml11 - url = https://github.com/ToruNiina/toml11.git [submodule "extern/plog"] path = extern/plog url = https://github.com/SergiusTheBest/plog.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 2260323a3..62319559b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,10 +82,8 @@ include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/dependencies.cmake) find_or_fetch_dependency(Kokkos FALSE) find_or_fetch_dependency(plog TRUE) -find_or_fetch_dependency(toml11 TRUE) set(DEPENDENCIES Kokkos::kokkos) include_directories(${plog_SRC}/include) -include_directories(${toml11_SRC}) # -------------------------------- Main code ------------------------------- # set_precision(${precision}) diff --git a/README.md b/README.md index 41a5fe47e..d6f4597f5 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ Our [detailed documentation](https://entity-toolkit.github.io/) includes everyth ## Core developers (alphabetical) +👀 __Yangyang Cai__ {[@StaticObserver](https://github.com/StaticObserver): GRPIC} + 💁‍♂️ __Alexander Chernoglazov__ {[@SChernoglazov](https://github.com/SChernoglazov): PIC} 🍵 __Benjamin Crinquand__ {[@bcrinquand](https://github.com/bcrinquand): GRPIC, cubed-sphere} diff --git a/TASKLIST.md b/TASKLIST.md index 82f44d0ea..069a7deb2 100644 --- a/TASKLIST.md +++ b/TASKLIST.md @@ -1,33 +1,3 @@ -# v0.8 - -- [x] thick layer boundary for the monopole -- [x] test with filters -- [x] add diagnostics for nans in fields and particles -- [x] add gravitationally bound atmosphere -- [x] rewrite UniformInjector with global random pool -- [x] add particle deletion routine -- [x] make more user-friendly and understandable boundary conditions -- [x] refine output -- [x] add different moments (momX, momY, momZ, meanGamma) -- [x] add charge -- [x] add per species densities - -# v0.9 - -- [x] add current deposit/filtering for GR -- [x] add moments for GR -- [x] add Maxwellian for GR - -# v1.0.0 - -- [x] particle output -- [x] BUG in MPI particles/currents - -# v1.1.0 - -- [ ] custom boundary conditions for particles and fields -- [ ] transfer GR from v0.9 - ### Performance improvements to try - [ ] removing temporary variables in interpolation diff --git a/cmake/report.cmake b/cmake/report.cmake index fe914baa8..6733dbcd4 100644 --- a/cmake/report.cmake +++ b/cmake/report.cmake @@ -276,10 +276,11 @@ endif() message(" ${PRECISION_REPORT}") message(" ${OUTPUT_REPORT}") -message("${DASHED_LINE_SYMBOL} -Compile configurations") +message("${DASHED_LINE_SYMBOL}\nCompile configurations") -message(" ${ARCH_REPORT}") +if(NOT "${ARCH_REPORT}" STREQUAL "") + message(" ${ARCH_REPORT}") +endif() message(" ${CUDA_REPORT}") message(" ${HIP_REPORT}") message(" ${OPENMP_REPORT}") diff --git a/cmake/tests.cmake b/cmake/tests.cmake index b53626723..f1342f679 100644 --- a/cmake/tests.cmake +++ b/cmake/tests.cmake @@ -10,12 +10,14 @@ add_subdirectory(${SRC_DIR}/archetypes ${CMAKE_CURRENT_BINARY_DIR}/archetypes) add_subdirectory(${SRC_DIR}/framework ${CMAKE_CURRENT_BINARY_DIR}/framework) if (${output}) add_subdirectory(${SRC_DIR}/output ${CMAKE_CURRENT_BINARY_DIR}/output) + add_subdirectory(${SRC_DIR}/checkpoint ${CMAKE_CURRENT_BINARY_DIR}/checkpoint) endif() if (${mpi}) # tests with mpi if (${output}) add_subdirectory(${SRC_DIR}/output/tests ${CMAKE_CURRENT_BINARY_DIR}/output/tests) + add_subdirectory(${SRC_DIR}/framework/tests ${CMAKE_CURRENT_BINARY_DIR}/checkpoint/tests) add_subdirectory(${SRC_DIR}/framework/tests ${CMAKE_CURRENT_BINARY_DIR}/framework/tests) endif() else() @@ -27,5 +29,6 @@ else() add_subdirectory(${SRC_DIR}/framework/tests ${CMAKE_CURRENT_BINARY_DIR}/framework/tests) if (${output}) add_subdirectory(${SRC_DIR}/output/tests ${CMAKE_CURRENT_BINARY_DIR}/output/tests) + add_subdirectory(${SRC_DIR}/checkpoint/tests ${CMAKE_CURRENT_BINARY_DIR}/checkpoint/tests) endif() endif() diff --git a/dev/runners/Dockerfile.runner.cuda b/dev/runners/Dockerfile.runner.cuda index 4ff132990..6e0b0755c 100644 --- a/dev/runners/Dockerfile.runner.cuda +++ b/dev/runners/Dockerfile.runner.cuda @@ -59,14 +59,17 @@ ARG HOME=/home/$USER WORKDIR $HOME # gh runner +ARG RUNNER_VERSION=2.317.0 RUN mkdir actions-runner WORKDIR $HOME/actions-runner -RUN --mount=type=secret,id=ghtoken \ - curl -o actions-runner-linux-x64-2.317.0.tar.gz \ - -L https://github.com/actions/runner/releases/download/v2.317.0/actions-runner-linux-x64-2.317.0.tar.gz && \ - tar xzf ./actions-runner-linux-x64-2.317.0.tar.gz && \ - sudo ./bin/installdependencies.sh && \ - ./config.sh --url https://github.com/entity-toolkit/entity --token "$(sudo cat /run/secrets/ghtoken)" --labels nvidia-gpu +RUN curl -o actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz \ + -L https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz && \ + tar xzf ./actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz && \ + sudo ./bin/installdependencies.sh -ENTRYPOINT ["./run.sh"] +ADD start.sh start.sh +RUN sudo chown $USER:$USER start.sh && \ + sudo chmod +x start.sh + +ENTRYPOINT ["./start.sh"] diff --git a/extern/Kokkos b/extern/Kokkos index eb11070f6..5fc08a9a7 160000 --- a/extern/Kokkos +++ b/extern/Kokkos @@ -1 +1 @@ -Subproject commit eb11070f67565b2e660659f5207f0363bdf3b882 +Subproject commit 5fc08a9a7da14d8530f8c7035d008ef63ddb4e5c diff --git a/extern/adios2 b/extern/adios2 index b8761e2af..e524dce1b 160000 --- a/extern/adios2 +++ b/extern/adios2 @@ -1 +1 @@ -Subproject commit b8761e2afab2cd05b89d09b2ee4da1cd7a834225 +Subproject commit e524dce1b72ccf75422cea6342ee2d64a6a87964 diff --git a/extern/toml11 b/extern/toml11 deleted file mode 160000 index 12c0f379f..000000000 --- a/extern/toml11 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 12c0f379f2e865b4ce984758d5ae004f9de07d69 diff --git a/input.example.toml b/input.example.toml index 88589495c..06225024a 100644 --- a/input.example.toml +++ b/input.example.toml @@ -232,7 +232,7 @@ # @default: 0.9 e_ovr_b_max = "" # Maximum Larmor radius allowed for GCA particles (in physical units): - # @type: float: > 0 + # @type: float # @default: 0.0 # @note: When `larmor_max` == 0, the limit is disabled larmor_max = "" @@ -253,7 +253,7 @@ # @default: false use_weights = "" # Timesteps between particle re-sorting: - # @type: unsigned int: >= 0 + # @type: unsigned int # @default: 100 # @note: When MPI is enable, particles are sorted every step. # @note: When `sort_interval` == 0, the sorting is disabled. @@ -293,7 +293,7 @@ # @valid: "Boris", "Vay", "Boris,GCA", "Vay,GCA", "Photon", "None" pusher = "" # Number of additional (payload) variables for each particle of the given species: - # @type: unsigned short: >= 0 + # @type: unsigned short # @default: 0 n_payloads = "" # Radiation reaction to use for the species: @@ -316,7 +316,7 @@ # @default: 1 interval = "" # Physical (code) time interval between all outputs (overriden by specific output intervals below): - # @type: float: > 0 + # @type: float # @default: -1.0 (disabled) # @note: When `interval_time` < 0, the output is controlled by `interval`, otherwise by `interval_time` interval_time = "" @@ -345,15 +345,15 @@ # @default: 1 stride = "" # Smoothing window for the output of moments (e.g., "Rho", "Charge", "T", etc.): - # @type: unsigned short: >= 0 + # @type: unsigned short # @default: 0 mom_smooth = "" # Number of timesteps between field outputs (overrides `output.interval`): - # @type: unsigned int: > 0 + # @type: unsigned int # @default: 0 (use `output.interval`) interval = "" # Physical (code) time interval between field outputs (overrides `output.interval_time`): - # @type: float: > 0 + # @type: float # @default: -1.0 (use `output.interval_time`) # @note: When `interval_time` < 0, the output is controlled by `interval`, otherwise by `interval_time` interval_time = "" @@ -376,7 +376,7 @@ # @default: 0 (use `output.interval`) interval = "" # Physical (code) time interval between field outputs (overrides `output.interval_time`): - # @type: float: > 0 + # @type: float # @default: -1.0 (use `output.interval_time`) # @note: When `interval_time` < 0, the output is controlled by `interval`, otherwise by `interval_time` interval_time = "" @@ -407,7 +407,7 @@ # @default: 0 (use `output.interval`) interval = "" # Physical (code) time interval between spectra outputs (overrides `output.interval_time`): - # @type: float: > 0 + # @type: float # @default: -1.0 (use `output.interval_time`) # @note: When `interval_time` < 0, the output is controlled by `interval`, otherwise by `interval_time` interval_time = "" @@ -422,6 +422,37 @@ # @default: false ghosts = "" +[checkpoint] + # Number of timesteps between checkpoints: + # @type: unsigned int: > 0 + # @default: 1000 + interval = "" + # Physical (code) time interval between checkpoints: + # @type: float: > 0 + # @default: -1.0 (disabled) + # @note: When `interval_time` < 0, the output is controlled by `interval`, otherwise by `interval_time` + interval_time = "" + # Number of checkpoints to keep: + # @type: int + # @default: 2 + # @note: 0 = disable checkpointing + # @note: -1 = keep all checkpoints + keep = "" + + # @inferred: + # - is_resuming + # @brief: Whether the simulation is resuming from a checkpoint + # @type: bool + # @from: command-line flag + # - start_step + # @brief: Timestep of the checkpoint used to resume + # @type: unsigned int + # @from: automatically determined during restart + # - start_time + # @brief: Time of the checkpoint used to resume + # @type: float + # @from: automatically determined during restart + [diagnostics] # Number of timesteps between diagnostic logs: # @type: int: > 0 diff --git a/legacy/src/framework/utils/particle_injectors.hpp b/legacy/src/framework/utils/particle_injectors.hpp index 21e3dad72..c275f170a 100644 --- a/legacy/src/framework/utils/particle_injectors.hpp +++ b/legacy/src/framework/utils/particle_injectors.hpp @@ -165,7 +165,7 @@ namespace ntt { * @brief Volumetrically uniform particle injector parallelized over particles. * @tparam D dimension. * @tparam S simulation engine. - * @tparam EnDist energy distribution [default = ColdDist]. + * @tparam EnDist energy distribution [default = Cold]. * * @param params simulation parameters. * @param mblock meshblock. @@ -174,7 +174,7 @@ namespace ntt { * @param region region to inject particles as a list of coordinates [optional]. * @param time current time [optional]. */ - template class EnDist = ColdDist> + template class EnDist = Cold> inline void InjectUniform(const SimulationParams& params, Meshblock& mblock, const std::vector& species, @@ -613,8 +613,8 @@ namespace ntt { * @brief Particle injector parallelized by cells in a volume. * @tparam D dimension. * @tparam S simulation engine. - * @tparam EnDist energy distribution [default = ColdDist]. - * @tparam SpDist spatial distribution [default = UniformDist]. + * @tparam EnDist energy distribution [default = Cold]. + * @tparam SpDist spatial distribution [default = Uniform]. * @tparam InjCrit injection criterion [default = NoCriterion]. * * @param params simulation parameters. @@ -626,8 +626,8 @@ namespace ntt { */ template class EnDist = ColdDist, - template class SpDist = UniformDist, + template class EnDist = Cold, + template class SpDist = Uniform, template class InjCrit = NoCriterion> inline void InjectInVolume(const SimulationParams& params, Meshblock& mblock, @@ -928,7 +928,7 @@ namespace ntt { * @brief ... up to certain number density. * @tparam D dimension. * @tparam S simulation engine. - * @tparam EnDist energy distribution [default = ColdDist]. + * @tparam EnDist energy distribution [default = Cold]. * @tparam InjCrit injection criterion [default = NoCriterion]. * * @param params simulation parameters. @@ -940,7 +940,7 @@ namespace ntt { */ template class EnDist = ColdDist, + template class EnDist = Cold, template class InjCrit = NoCriterion> inline void InjectNonUniform(const SimulationParams& params, Meshblock& mblock, diff --git a/setups/srpic/turbulence/pgen.hpp b/setups/srpic/turbulence/pgen.hpp index bd8b0ce41..bbd61cc3a 100644 --- a/setups/srpic/turbulence/pgen.hpp +++ b/setups/srpic/turbulence/pgen.hpp @@ -185,7 +185,7 @@ namespace user { const auto injector = arch::UniformInjector( energy_dist, { 1, 2 }); - const real_t ndens = 1.0; + const real_t ndens = 0.9; arch::InjectUniform(params, local_domain, injector, @@ -193,22 +193,17 @@ namespace user { } { - // const auto energy_dist = arch::Maxwellian(local_domain.mesh.metric, - // local_domain.random_pool, - // temperature*10); - // // const auto energy_dist = arch::Maxwellian(local_domain.mesh.metric, - // // local_domain.random_pool, - // // temperature * 2, - // // 10.0, - // // 1); - // const auto injector = arch::UniformInjector( - // energy_dist, - // { 1, 2 }); - // const real_t ndens = 0.01; - // arch::InjectUniform(params, - // local_domain, - // injector, - // ndens); + const auto energy_dist = arch::PowerlawDist(local_domain.mesh.metric, + local_domain.random_pool, + 0.1, 100.0, -3.0); + const auto injector = arch::UniformInjector( + energy_dist, + { 1, 2 }); + const real_t ndens = 0.1; + arch::InjectUniform(params, + local_domain, + injector, + ndens); } } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5d7f0abb4..d75094c2b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,6 +7,7 @@ # - ntt_framework [required] # - ntt_metrics [required] # - ntt_engine [required] +# - ntt_pgen [required] # @uses: # - kokkos [required] # - plog [required] @@ -30,9 +31,12 @@ add_subdirectory(${SRC_DIR}/kernels ${CMAKE_CURRENT_BINARY_DIR}/kernels) add_subdirectory(${SRC_DIR}/archetypes ${CMAKE_CURRENT_BINARY_DIR}/archetypes) add_subdirectory(${SRC_DIR}/framework ${CMAKE_CURRENT_BINARY_DIR}/framework) add_subdirectory(${SRC_DIR}/engines ${CMAKE_CURRENT_BINARY_DIR}/engines) -if (${output} STREQUAL "ON") -add_subdirectory(${SRC_DIR}/output ${CMAKE_CURRENT_BINARY_DIR}/output) + +if (${output}) + add_subdirectory(${SRC_DIR}/output ${CMAKE_CURRENT_BINARY_DIR}/output) + add_subdirectory(${SRC_DIR}/checkpoint ${CMAKE_CURRENT_BINARY_DIR}/checkpoint) endif() + add_subdirectory(${SRC_DIR}/../setups ${CMAKE_CURRENT_BINARY_DIR}/setups) set(libs ntt_global ntt_framework ntt_metrics ntt_engines ntt_pgen) diff --git a/src/archetypes/energy_dist.h b/src/archetypes/energy_dist.h index c5b9d8a87..e9bc9051a 100644 --- a/src/archetypes/energy_dist.h +++ b/src/archetypes/energy_dist.h @@ -3,7 +3,8 @@ * @brief Defines an archetype for energy distributions * @implements * - arch::EnergyDistribution<> - * - arch::ColdDist<> : arch::EnergyDistribution<> + * - arch::Cold<> : arch::EnergyDistribution<> + * - arch::Powerlaw<> : arch::EnergyDistribution<> * - arch::Maxwellian<> : arch::EnergyDistribution<> * @namespaces: * - arch:: @@ -55,8 +56,8 @@ namespace arch { }; template - struct ColdDist : public EnergyDistribution { - ColdDist(const M& metric) : EnergyDistribution { metric } {} + struct Cold : public EnergyDistribution { + Cold(const M& metric) : EnergyDistribution { metric } {} Inline void operator()(const coord_t&, vec_t& v, @@ -67,6 +68,63 @@ namespace arch { } }; + template + struct Powerlaw : public EnergyDistribution { + using EnergyDistribution::metric; + + Powerlaw(const M& metric, + random_number_pool_t& pool, + real_t g_min, + real_t g_max, + real_t pl_ind) + : EnergyDistribution { metric } + , pool { pool } + , g_min { g_min } + , g_max { g_max } + , pl_ind { pl_ind } {} + + Inline void operator()(const coord_t& x_Code, + vec_t& v, + unsigned short sp = 0) const override { + auto rand_gen = pool.get_state(); + auto rand_X1 = Random(rand_gen); + auto rand_gam = ONE; + + // Power-law distribution from uniform (see https://mathworld.wolfram.com/RandomNumber.html) + if (pl_ind != -ONE) { + rand_gam += math::pow( + math::pow(g_min, ONE + pl_ind) + + (-math::pow(g_min, ONE + pl_ind) + math::pow(g_max, ONE + pl_ind)) * + rand_X1, + ONE / (ONE + pl_ind)); + } else { + rand_gam += math::pow(g_min, ONE - rand_X1) * math::pow(g_max, rand_X1); + } + auto rand_u = math::sqrt(SQR(rand_gam) - ONE); + auto rand_X2 = Random(rand_gen); + auto rand_X3 = Random(rand_gen); + v[0] = rand_u * (TWO * rand_X2 - ONE); + v[2] = TWO * rand_u * math::sqrt(rand_X2 * (ONE - rand_X2)); + v[1] = v[2] * math::cos(constant::TWO_PI * rand_X3); + v[2] = v[2] * math::sin(constant::TWO_PI * rand_X3); + + if constexpr (S == SimEngine::GRPIC) { + // convert from the tetrad basis to covariant + vec_t v_Hat; + v_Hat[0] = v[0]; + v_Hat[1] = v[1]; + v_Hat[2] = v[2]; + metric.template transform(x_Code, v_Hat, v); + } + + pool.free_state(rand_gen); + } + + private: + const real_t g_min, g_max, pl_ind; + random_number_pool_t pool; + }; + template struct Maxwellian : public EnergyDistribution { using EnergyDistribution::metric; diff --git a/src/archetypes/particle_injector.h b/src/archetypes/particle_injector.h index 4e75003bb..cbcbbd389 100644 --- a/src/archetypes/particle_injector.h +++ b/src/archetypes/particle_injector.h @@ -142,7 +142,7 @@ namespace arch { }; using energy_dist_t = Maxwellian; - using spatial_dist_t = ReplenishDist; + using spatial_dist_t = Replenish; static_assert(M::is_metric, "M must be a metric class"); static constexpr bool is_nonuniform_injector { true }; static constexpr Dimension D { M::Dim }; diff --git a/src/archetypes/spatial_dist.h b/src/archetypes/spatial_dist.h index 6c19d44d0..be2836da2 100644 --- a/src/archetypes/spatial_dist.h +++ b/src/archetypes/spatial_dist.h @@ -3,8 +3,8 @@ * @brief Spatial distribution class passed to injectors * @implements * - arch::SpatialDistribution<> - * - arch::UniformDist<> : arch::SpatialDistribution<> - * - arch::ReplenishDist<> : arch::SpatialDistribution<> + * - arch::Uniform<> : arch::SpatialDistribution<> + * - arch::Replenish<> : arch::SpatialDistribution<> * @namespace * - arch:: * @note @@ -41,8 +41,8 @@ namespace arch { }; template - struct UniformDist : public SpatialDistribution { - UniformDist(const M& metric) : SpatialDistribution { metric } {} + struct Uniform : public SpatialDistribution { + Uniform(const M& metric) : SpatialDistribution { metric } {} Inline auto operator()(const coord_t&) const -> real_t override { return ONE; @@ -50,7 +50,7 @@ namespace arch { }; template - struct ReplenishDist : public SpatialDistribution { + struct Replenish : public SpatialDistribution { using SpatialDistribution::metric; const ndfield_t density; const unsigned short idx; @@ -58,11 +58,11 @@ namespace arch { const T target_density; const real_t target_max_density; - ReplenishDist(const M& metric, - const ndfield_t& density, - unsigned short idx, - const T& target_density, - real_t target_max_density) + Replenish(const M& metric, + const ndfield_t& density, + unsigned short idx, + const T& target_density, + real_t target_max_density) : SpatialDistribution { metric } , density { density } , idx { idx } diff --git a/src/archetypes/tests/CMakeLists.txt b/src/archetypes/tests/CMakeLists.txt index ceee1edc9..4ffc35322 100644 --- a/src/archetypes/tests/CMakeLists.txt +++ b/src/archetypes/tests/CMakeLists.txt @@ -22,4 +22,5 @@ endfunction() gen_test(energy_dist) gen_test(spatial_dist) -gen_test(field_setter) \ No newline at end of file +gen_test(field_setter) +gen_test(powerlaw) diff --git a/src/archetypes/tests/powerlaw.cpp b/src/archetypes/tests/powerlaw.cpp new file mode 100644 index 000000000..dfcb6b247 --- /dev/null +++ b/src/archetypes/tests/powerlaw.cpp @@ -0,0 +1,171 @@ +#include "enums.h" +#include "global.h" + +#include "utils/error.h" + +#include "metrics/kerr_schild.h" +#include "metrics/kerr_schild_0.h" +#include "metrics/minkowski.h" +#include "metrics/qkerr_schild.h" +#include "metrics/qspherical.h" +#include "metrics/spherical.h" + +#include "archetypes/energy_dist.h" + +#include + +#include + +using namespace ntt; +using namespace metric; +using namespace arch; + +template +struct Caller { + static constexpr auto D = M::Dim; + + Caller(const M& metric, const EnrgDist& dist) + : metric { metric } + , dist { dist } {} + + Inline void operator()(index_t) const { + vec_t vp { ZERO }; + coord_t xp { ZERO }; + for (unsigned short d = 0; d < D; ++d) { + xp[d] = 2.0; + } + dist(xp, vp); + if (not Kokkos::isfinite(vp[0]) or not Kokkos::isfinite(vp[1]) or + not Kokkos::isfinite(vp[2])) { + raise::KernelError(HERE, "Non-finite velocity generated"); + } + if constexpr (S == SimEngine::SRPIC) { + const auto gamma = math::sqrt(ONE + SQR(vp[0]) + SQR(vp[1]) + SQR(vp[2])); + if (gamma < 10 or gamma > 1000) { + raise::KernelError(HERE, "Gamma out of bounds"); + } + } else { + vec_t vup { ZERO }; + metric.template transform(xp, vp, vup); + const auto gamma = math::sqrt( + ONE + vup[0] * vp[0] + vup[1] * vp[1] + vup[2] * vp[2]); + if (gamma < 10 or gamma > 1000) { + raise::KernelError(HERE, "Gamma out of bounds"); + } + } + } + +private: + M metric; + EnrgDist dist; +}; + +template +void testEnergyDist(const std::vector& res, + const boundaries_t& ext, + const std::map& params = {}) { + raise::ErrorIf(res.size() != M::Dim, "res.size() != M::Dim", HERE); + + boundaries_t extent; + if constexpr (M::CoordType == Coord::Cart) { + extent = ext; + } else { + if constexpr (M::Dim == Dim::_2D) { + extent = { + ext[0], + {ZERO, constant::PI} + }; + } else if constexpr (M::Dim == Dim::_3D) { + extent = { + ext[0], + {ZERO, constant::PI}, + {ZERO, constant::TWO_PI} + }; + } + } + raise::ErrorIf(extent.size() != M::Dim, "extent.size() != M::Dim", HERE); + + M metric { res, extent, params }; + + random_number_pool_t pool { constant::RandomSeed }; + Powerlaw plaw { metric, + pool, + static_cast(10), + static_cast(1000), + static_cast(-2.5) }; + Kokkos::parallel_for("Powerlaw", 100, Caller, S, M>(metric, plaw)); +} + +auto main(int argc, char* argv[]) -> int { + Kokkos::initialize(argc, argv); + + try { + using namespace ntt; + testEnergyDist>( + { + 10 + }, + { { 0.0, 55.0 } }); + + testEnergyDist>( + { + 10, + 10 + }, + { { 0.0, 55.0 }, { 0.0, 55.0 } }); + + testEnergyDist>( + { + 10, + 10, + 10 + }, + { { 0.0, 55.0 }, { 0.0, 55.0 }, { 0.0, 55.0 } }); + + testEnergyDist>( + { + 10, + 10 + }, + { { 1.0, 100.0 } }); + + testEnergyDist>( + { + 10, + 10 + }, + { { 1.0, 100.0 } }, + { { "r0", 0.0 }, { "h", 0.25 } }); + + testEnergyDist>( + { + 10, + 10 + }, + { { 1.0, 100.0 } }, + { { "a", 0.9 } }); + + testEnergyDist>( + { + 10, + 10 + }, + { { 1.0, 100.0 } }, + { { "r0", 0.0 }, { "h", 0.25 }, { "a", 0.9 } }); + + testEnergyDist>( + { + 10, + 10 + }, + { { 1.0, 100.0 } }, + { { "a", 0.9 } }); + + } catch (std::exception& e) { + std::cerr << e.what() << std::endl; + Kokkos::finalize(); + return 1; + } + Kokkos::finalize(); + return 0; +} diff --git a/src/checkpoint/CMakeLists.txt b/src/checkpoint/CMakeLists.txt new file mode 100644 index 000000000..d97bd4a34 --- /dev/null +++ b/src/checkpoint/CMakeLists.txt @@ -0,0 +1,31 @@ +# ------------------------------ +# @defines: ntt_checkpoint [STATIC/SHARED] +# @sources: +# - writer.cpp +# - reader.cpp +# @includes: +# - ../ +# @depends: +# - ntt_global [required] +# @uses: +# - kokkos [required] +# - ADIOS2 [required] +# - mpi [optional] +# ------------------------------ + +set(SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}) +set(SOURCES + ${SRC_DIR}/writer.cpp + ${SRC_DIR}/reader.cpp +) +add_library(ntt_checkpoint ${SOURCES}) + +set(libs ntt_global) +add_dependencies(ntt_checkpoint ${libs}) +target_link_libraries(ntt_checkpoint PUBLIC ${libs}) +target_link_libraries(ntt_checkpoint PRIVATE stdc++fs) + +target_include_directories(ntt_checkpoint + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../ + INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/../ +) diff --git a/src/checkpoint/reader.cpp b/src/checkpoint/reader.cpp new file mode 100644 index 000000000..66fcd6757 --- /dev/null +++ b/src/checkpoint/reader.cpp @@ -0,0 +1,172 @@ +#include "checkpoint/reader.h" + +#include "global.h" + +#include "arch/kokkos_aliases.h" +#include "utils/error.h" +#include "utils/formatting.h" +#include "utils/log.h" + +#include +#include + +#if defined(MPI_ENABLED) + #include +#endif + +#include +#include +#include + +namespace checkpoint { + + template + void ReadFields(adios2::IO& io, + adios2::Engine& reader, + const std::string& field, + const adios2::Box& range, + ndfield_t& array) { + logger::Checkpoint(fmt::format("Reading field: %s", field.c_str()), HERE); + auto field_var = io.InquireVariable(field); + if (field_var) { + field_var.SetSelection(range); + + auto array_h = Kokkos::create_mirror_view(array); + reader.Get(field_var, array_h.data(), adios2::Mode::Sync); + Kokkos::deep_copy(array, array_h); + } else { + raise::Error(fmt::format("Field variable: %s not found", field), HERE); + } + } + + auto ReadParticleCount(adios2::IO& io, + adios2::Engine& reader, + unsigned short s, + std::size_t local_dom, + std::size_t ndomains) + -> std::pair { + logger::Checkpoint(fmt::format("Reading particle count for: %d", s + 1), HERE); + auto npart_var = io.InquireVariable( + fmt::format("s%d_npart", s + 1)); + if (npart_var) { + raise::ErrorIf(npart_var.Shape()[0] != ndomains, + "npart_var.Shape()[0] != ndomains", + HERE); + raise::ErrorIf(npart_var.Shape().size() != 1, + "npart_var.Shape().size() != 1", + HERE); + npart_var.SetSelection(adios2::Box({ local_dom }, { 1 })); + std::size_t npart; + reader.Get(npart_var, &npart, adios2::Mode::Sync); + const auto loc_npart = npart; +#if !defined(MPI_ENABLED) + std::size_t offset_npart = 0; +#else + std::vector glob_nparts(ndomains); + MPI_Allgather(&loc_npart, + 1, + mpi::get_type(), + glob_nparts.data(), + 1, + mpi::get_type(), + MPI_COMM_WORLD); + std::size_t offset_npart = 0; + for (auto d { 0u }; d < local_dom; ++d) { + offset_npart += glob_nparts[d]; + } +#endif + return { loc_npart, offset_npart }; + } else { + raise::Error("npart_var is not found", HERE); + return { 0, 0 }; + } + } + + template + void ReadParticleData(adios2::IO& io, + adios2::Engine& reader, + const std::string& quantity, + unsigned short s, + array_t& array, + std::size_t count, + std::size_t offset) { + logger::Checkpoint( + fmt::format("Reading quantity: s%d_%s", s + 1, quantity.c_str()), + HERE); + auto var = io.InquireVariable( + fmt::format("s%d_%s", s + 1, quantity.c_str())); + if (var) { + var.SetSelection(adios2::Box({ offset }, { count })); + const auto slice = std::pair { 0, count }; + auto array_h = Kokkos::create_mirror_view(array); + reader.Get(var, Kokkos::subview(array_h, slice).data(), adios2::Mode::Sync); + Kokkos::deep_copy(Kokkos::subview(array, slice), + Kokkos::subview(array_h, slice)); + } else { + raise::Error( + fmt::format("Variable: s%d_%s not found", s + 1, quantity.c_str()), + HERE); + } + } + + template void ReadFields(adios2::IO&, + adios2::Engine&, + const std::string&, + const adios2::Box&, + ndfield_t&); + template void ReadFields(adios2::IO&, + adios2::Engine&, + const std::string&, + const adios2::Box&, + ndfield_t&); + template void ReadFields(adios2::IO&, + adios2::Engine&, + const std::string&, + const adios2::Box&, + ndfield_t&); + template void ReadFields(adios2::IO&, + adios2::Engine&, + const std::string&, + const adios2::Box&, + ndfield_t&); + template void ReadFields(adios2::IO&, + adios2::Engine&, + const std::string&, + const adios2::Box&, + ndfield_t&); + template void ReadFields(adios2::IO&, + adios2::Engine&, + const std::string&, + const adios2::Box&, + ndfield_t&); + + template void ReadParticleData(adios2::IO&, + adios2::Engine&, + const std::string&, + unsigned short, + array_t&, + std::size_t, + std::size_t); + template void ReadParticleData(adios2::IO&, + adios2::Engine&, + const std::string&, + unsigned short, + array_t&, + std::size_t, + std::size_t); + template void ReadParticleData(adios2::IO&, + adios2::Engine&, + const std::string&, + unsigned short, + array_t&, + std::size_t, + std::size_t); + template void ReadParticleData(adios2::IO&, + adios2::Engine&, + const std::string&, + unsigned short, + array_t&, + std::size_t, + std::size_t); + +} // namespace checkpoint diff --git a/src/checkpoint/reader.h b/src/checkpoint/reader.h new file mode 100644 index 000000000..2ea11bdb1 --- /dev/null +++ b/src/checkpoint/reader.h @@ -0,0 +1,50 @@ +/** + * @file checkpoint/reader.h + * @brief Function for reading field & particle data from checkpoint files + * @implements + * - checkpoint::ReadFields -> void + * - checkpoint::ReadParticleData -> void + * - checkpoint::ReadParticleCount -> std::pair + * @cpp: + * - reader.cpp + * @namespaces: + * - checkpoint:: + */ + +#ifndef CHECKPOINT_READER_H +#define CHECKPOINT_READER_H + +#include "arch/kokkos_aliases.h" + +#include + +#include +#include + +namespace checkpoint { + + template + void ReadFields(adios2::IO&, + adios2::Engine&, + const std::string&, + const adios2::Box&, + ndfield_t&); + + auto ReadParticleCount(adios2::IO&, + adios2::Engine&, + unsigned short, + std::size_t, + std::size_t) -> std::pair; + + template + void ReadParticleData(adios2::IO&, + adios2::Engine&, + const std::string&, + unsigned short, + array_t&, + std::size_t, + std::size_t); + +} // namespace checkpoint + +#endif // CHECKPOINT_READER_H diff --git a/src/checkpoint/tests/CMakeLists.txt b/src/checkpoint/tests/CMakeLists.txt new file mode 100644 index 000000000..3d7475a52 --- /dev/null +++ b/src/checkpoint/tests/CMakeLists.txt @@ -0,0 +1,27 @@ +# ------------------------------ +# @brief: Generates tests for the `ntt_checkpoint` module +# @uses: +# - kokkos [required] +# - adios2 [required] +# - mpi [optional] +# ------------------------------ + +set(SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../) + +function(gen_test title) + set(exec test-output-${title}.xc) + set(src ${title}.cpp) + add_executable(${exec} ${src}) + + set (libs ntt_checkpoint ntt_global) + add_dependencies(${exec} ${libs}) + target_link_libraries(${exec} PRIVATE ${libs} stdc++fs) + + add_test(NAME "CHECKPOINT::${title}" COMMAND "${exec}") +endfunction() + +if (NOT ${mpi}) + gen_test(checkpoint-nompi) +else() + # gen_test(checkpoint-mpi) +endif() diff --git a/src/checkpoint/tests/checkpoint-nompi.cpp b/src/checkpoint/tests/checkpoint-nompi.cpp new file mode 100644 index 000000000..8f7a522fd --- /dev/null +++ b/src/checkpoint/tests/checkpoint-nompi.cpp @@ -0,0 +1,205 @@ +#include "enums.h" +#include "global.h" + +#include "utils/comparators.h" + +#include "checkpoint/reader.h" +#include "checkpoint/writer.h" + +#include +#include +#include + +#include +#include +#include + +using namespace ntt; +using namespace checkpoint; + +void cleanup() { + namespace fs = std::filesystem; + fs::path temp_path { "checkpoints" }; + fs::remove_all(temp_path); +} + +auto main(int argc, char* argv[]) -> int { + Kokkos::initialize(argc, argv); + + try { + constexpr auto nx1 = 10; + constexpr auto nx1_gh = nx1 + 2 * N_GHOSTS; + constexpr auto nx2 = 10; + constexpr auto nx2_gh = nx2 + 2 * N_GHOSTS; + constexpr auto nx3 = 10; + constexpr auto nx3_gh = nx3 + 2 * N_GHOSTS; + constexpr auto i1min = N_GHOSTS; + constexpr auto i2min = N_GHOSTS; + constexpr auto i3min = N_GHOSTS; + constexpr auto i1max = nx1 + N_GHOSTS; + constexpr auto i2max = nx2 + N_GHOSTS; + constexpr auto i3max = nx3 + N_GHOSTS; + constexpr auto npart1 = 100; + constexpr auto npart2 = 100; + + // init data + ndfield_t field1 { "fld1", nx1_gh, nx2_gh, nx3_gh }; + ndfield_t field2 { "fld2", nx1_gh, nx2_gh, nx3_gh }; + + array_t i1 { "i_1", npart1 }; + array_t u1 { "u_1", npart1 }; + array_t i2 { "i_2", npart2 }; + array_t u2 { "u_2", npart2 }; + + { + // fill data + Kokkos::parallel_for( + "fillFlds", + CreateRangePolicy({ i1min, i2min, i3min }, + { i1max, i2max, i3max }), + Lambda(index_t i1, index_t i2, index_t i3) { + field1(i1, i2, i3, 0) = i1 + i2 + i3; + field1(i1, i2, i3, 1) = i1 * i2 / i3; + field1(i1, i2, i3, 2) = i1 / i2 * i3; + field1(i1, i2, i3, 3) = i1 + i2 - i3; + field1(i1, i2, i3, 4) = i1 * i2 + i3; + field1(i1, i2, i3, 5) = i1 / i2 - i3; + field2(i1, i2, i3, 0) = -(i1 + i2 + i3); + field2(i1, i2, i3, 1) = -(i1 * i2 / i3); + field2(i1, i2, i3, 2) = -(i1 / i2 * i3); + field2(i1, i2, i3, 3) = -(i1 + i2 - i3); + field2(i1, i2, i3, 4) = -(i1 * i2 + i3); + field2(i1, i2, i3, 5) = -(i1 / i2 - i3); + }); + Kokkos::parallel_for( + "fillPrtl1", + npart1, + Lambda(index_t p) { + u1(p) = static_cast(p); + i1(p) = static_cast(p); + }); + Kokkos::parallel_for( + "fillPrtl2", + npart2, + Lambda(index_t p) { + u2(p) = -static_cast(p); + i2(p) = -static_cast(p); + }); + } + + adios2::ADIOS adios; + + { + // write checkpoint + Writer writer; + writer.init(&adios, 0, 0.0, 1); + + writer.defineFieldVariables(SimEngine::GRPIC, + { nx1_gh, nx2_gh, nx3_gh }, + { 0, 0, 0 }, + { nx1_gh, nx2_gh, nx3_gh }); + writer.defineParticleVariables(Coord::Sph, Dim::_3D, 2, { 0, 2 }); + + writer.beginSaving(0, 0.0); + + writer.saveField("em", field1); + writer.saveField("em0", field2); + + writer.savePerDomainVariable("s1_npart", 1, 0, npart1); + writer.savePerDomainVariable("s2_npart", 1, 0, npart2); + + writer.saveParticleQuantity("s1_i1", npart1, 0, npart1, i1); + writer.saveParticleQuantity("s1_ux1", npart1, 0, npart1, u1); + writer.saveParticleQuantity("s2_i1", npart2, 0, npart2, i2); + writer.saveParticleQuantity("s2_ux1", npart2, 0, npart2, u2); + + writer.endSaving(); + } + + { + // read checkpoint + ndfield_t field1_read { "fld1_read", nx1_gh, nx2_gh, nx3_gh }; + ndfield_t field2_read { "fld2_read", nx1_gh, nx2_gh, nx3_gh }; + + array_t i1_read { "i_1", npart1 }; + array_t u1_read { "u_1", npart1 }; + array_t i2_read { "i_2", npart2 }; + array_t u2_read { "u_2", npart2 }; + + adios2::IO io = adios.DeclareIO("checkpointRead"); + adios2::Engine reader = io.Open("checkpoints/step-00000000.bp", + adios2::Mode::Read); + reader.BeginStep(); + + auto fieldRange = adios2::Box({ 0, 0, 0, 0 }, + { nx1_gh, nx2_gh, nx3_gh, 6 }); + ReadFields(io, reader, "em", fieldRange, field1_read); + ReadFields(io, reader, "em0", fieldRange, field2_read); + + auto [nprtl1, noff1] = ReadParticleCount(io, reader, 0, 0, 1); + auto [nprtl2, noff2] = ReadParticleCount(io, reader, 1, 0, 1); + + ReadParticleData(io, reader, "ux1", 0, u1_read, nprtl1, noff1); + ReadParticleData(io, reader, "ux1", 1, u2_read, nprtl2, noff2); + ReadParticleData(io, reader, "i1", 0, i1_read, nprtl1, noff1); + ReadParticleData(io, reader, "i1", 1, i2_read, nprtl2, noff2); + + reader.EndStep(); + reader.Close(); + + // check the validity + Kokkos::parallel_for( + "checkFields", + CreateRangePolicy({ 0, 0, 0 }, { nx1_gh, nx2_gh, nx3_gh }), + Lambda(index_t i1, index_t i2, index_t i3) { + for (int i = 0; i < 6; ++i) { + if (not cmp::AlmostEqual(field1(i1, i2, i3, i), + field1_read(i1, i2, i3, i))) { + raise::KernelError(HERE, "Field1 read failed"); + } + if (not cmp::AlmostEqual(field2(i1, i2, i3, i), + field2_read(i1, i2, i3, i))) { + raise::KernelError(HERE, "Field2 read failed"); + } + } + }); + + raise::ErrorIf(npart1 != nprtl1, "Particle count 1 mismatch", HERE); + raise::ErrorIf(npart2 != nprtl2, "Particle count 2 mismatch", HERE); + raise::ErrorIf(noff1 != 0, "Particle offset 1 mismatch", HERE); + raise::ErrorIf(noff2 != 0, "Particle offset 2 mismatch", HERE); + + Kokkos::parallel_for( + "checkPrtl1", + npart1, + Lambda(index_t p) { + if (not cmp::AlmostEqual(u1(p), u1_read(p))) { + raise::KernelError(HERE, "u1 read failed"); + } + if (i1(p) != i1_read(p)) { + raise::KernelError(HERE, "i1 read failed"); + } + }); + Kokkos::parallel_for( + "checkPrtl2", + npart2, + Lambda(index_t p) { + if (not cmp::AlmostEqual(u2(p), u2_read(p))) { + raise::KernelError(HERE, "u2 read failed"); + } + if (i2(p) != i2_read(p)) { + raise::KernelError(HERE, "i2 read failed"); + } + }); + } + + } catch (std::exception& e) { + std::cerr << e.what() << std::endl; + cleanup(); + Kokkos::finalize(); + return 1; + } + cleanup(); + Kokkos::finalize(); + return 0; +} diff --git a/src/checkpoint/writer.cpp b/src/checkpoint/writer.cpp new file mode 100644 index 000000000..9ef0b51c7 --- /dev/null +++ b/src/checkpoint/writer.cpp @@ -0,0 +1,291 @@ +#include "checkpoint/writer.h" + +#include "global.h" + +#include "arch/kokkos_aliases.h" +#include "utils/error.h" +#include "utils/formatting.h" +#include "utils/log.h" + +#include "framework/parameters.h" + +#include +#include + +#include +#include +#include +#include +#include + +namespace checkpoint { + + void Writer::init(adios2::ADIOS* ptr_adios, + std::size_t interval, + long double interval_time, + int keep) { + m_keep = keep; + m_enabled = keep != 0; + if (not m_enabled) { + return; + } + m_tracker.init("checkpoint", interval, interval_time); + p_adios = ptr_adios; + raise::ErrorIf(p_adios == nullptr, "ADIOS pointer is null", HERE); + + m_io = p_adios->DeclareIO("Entity::Checkpoint"); + m_io.SetEngine("BPFile"); + + m_io.DefineVariable("Step"); + m_io.DefineVariable("Time"); + m_io.DefineAttribute("NGhosts", ntt::N_GHOSTS); + + CallOnce([]() { + const std::filesystem::path save_path { "checkpoints" }; + if (!std::filesystem::exists(save_path)) { + std::filesystem::create_directory(save_path); + } + }); + } + + void Writer::defineFieldVariables(const ntt::SimEngine& S, + const std::vector& glob_shape, + const std::vector& loc_corner, + const std::vector& loc_shape) { + auto gs6 = std::vector(glob_shape.begin(), glob_shape.end()); + auto lc6 = std::vector(loc_corner.begin(), loc_corner.end()); + auto ls6 = std::vector(loc_shape.begin(), loc_shape.end()); + gs6.push_back(6); + lc6.push_back(0); + ls6.push_back(6); + + m_io.DefineVariable("em", gs6, lc6, ls6); + if (S == ntt::SimEngine::GRPIC) { + m_io.DefineVariable("em0", gs6, lc6, ls6); + auto gs3 = std::vector(glob_shape.begin(), glob_shape.end()); + auto lc3 = std::vector(loc_corner.begin(), loc_corner.end()); + auto ls3 = std::vector(loc_shape.begin(), loc_shape.end()); + gs3.push_back(3); + lc3.push_back(0); + ls3.push_back(3); + m_io.DefineVariable("cur0", gs3, lc3, ls3); + } + } + + void Writer::defineParticleVariables(const ntt::Coord& C, + Dimension dim, + std::size_t nspec, + const std::vector& nplds) { + raise::ErrorIf(nplds.size() != nspec, + "Number of payloads does not match the number of species", + HERE); + for (auto s { 0u }; s < nspec; ++s) { + m_io.DefineVariable(fmt::format("s%d_npart", s + 1), + { adios2::UnknownDim }, + { adios2::UnknownDim }, + { adios2::UnknownDim }); + for (auto d { 0u }; d < dim; ++d) { + m_io.DefineVariable(fmt::format("s%d_i%d", s + 1, d + 1), + { adios2::UnknownDim }, + { adios2::UnknownDim }, + { adios2::UnknownDim }); + m_io.DefineVariable(fmt::format("s%d_dx%d", s + 1, d + 1), + { adios2::UnknownDim }, + { adios2::UnknownDim }, + { adios2::UnknownDim }); + m_io.DefineVariable(fmt::format("s%d_i%d_prev", s + 1, d + 1), + { adios2::UnknownDim }, + { adios2::UnknownDim }, + { adios2::UnknownDim }); + m_io.DefineVariable(fmt::format("s%d_dx%d_prev", s + 1, d + 1), + { adios2::UnknownDim }, + { adios2::UnknownDim }, + { adios2::UnknownDim }); + } + if (dim == Dim::_2D and C != ntt::Coord::Cart) { + m_io.DefineVariable(fmt::format("s%d_phi", s + 1), + { adios2::UnknownDim }, + { adios2::UnknownDim }, + { adios2::UnknownDim }); + } + for (auto d { 0u }; d < 3; ++d) { + m_io.DefineVariable(fmt::format("s%d_ux%d", s + 1, d + 1), + { adios2::UnknownDim }, + { adios2::UnknownDim }, + { adios2::UnknownDim }); + } + m_io.DefineVariable(fmt::format("s%d_tag", s + 1), + { adios2::UnknownDim }, + { adios2::UnknownDim }, + { adios2::UnknownDim }); + m_io.DefineVariable(fmt::format("s%d_weight", s + 1), + { adios2::UnknownDim }, + { adios2::UnknownDim }, + { adios2::UnknownDim }); + for (auto p { 0u }; p < nplds[s]; ++p) { + m_io.DefineVariable(fmt::format("s%d_pld%d", s + 1, p + 1), + { adios2::UnknownDim }, + { adios2::UnknownDim }, + { adios2::UnknownDim }); + } + } + } + + auto Writer::shouldSave(std::size_t step, long double time) -> bool { + return m_enabled and m_tracker.shouldWrite(step, time); + } + + void Writer::beginSaving(std::size_t step, long double time) { + raise::ErrorIf(!m_enabled, "Checkpoint is not enabled", HERE); + raise::ErrorIf(p_adios == nullptr, "ADIOS pointer is null", HERE); + if (m_writing_mode) { + raise::Fatal("Already writing", HERE); + } + m_writing_mode = true; + try { + auto fname = fmt::format("checkpoints/step-%08lu.bp", step); + m_writer = m_io.Open(fname, adios2::Mode::Write); + auto meta_fname = fmt::format("checkpoints/meta-%08lu.toml", step); + m_written.push_back({ fname, meta_fname }); + logger::Checkpoint(fmt::format("Writing checkpoint to %s and %s", + fname.c_str(), + meta_fname.c_str()), + HERE); + } catch (std::exception& e) { + raise::Fatal(e.what(), HERE); + } + + m_writer.BeginStep(); + m_writer.Put(m_io.InquireVariable("Step"), &step); + m_writer.Put(m_io.InquireVariable("Time"), &time); + } + + void Writer::endSaving() { + raise::ErrorIf(p_adios == nullptr, "ADIOS pointer is null", HERE); + if (!m_writing_mode) { + raise::Fatal("Not writing", HERE); + } + m_writing_mode = false; + m_writer.EndStep(); + m_writer.Close(); + + // optionally remove the oldest checkpoint + CallOnce([&]() { + if (m_keep > 0 and m_written.size() > (std::size_t)m_keep) { + const auto oldest = m_written.front(); + if (std::filesystem::exists(oldest.first) and + std::filesystem::exists(oldest.second)) { + std::filesystem::remove_all(oldest.first); + std::filesystem::remove(oldest.second); + m_written.erase(m_written.begin()); + } else { + raise::Warning("Checkpoint file does not exist for some reason", HERE); + } + } + }); + } + + template + void Writer::savePerDomainVariable(const std::string& varname, + std::size_t total, + std::size_t offset, + T data) { + auto var = m_io.InquireVariable(varname); + var.SetShape({ total }); + var.SetSelection(adios2::Box({ offset }, { 1 })); + m_writer.Put(var, &data); + } + + void Writer::saveAttrs(const ntt::SimulationParams& params, long double time) { + CallOnce([&]() { + std::ofstream metadata; + if (m_written.empty()) { + raise::Fatal("No checkpoint file to save metadata", HERE); + } + metadata.open(m_written.back().second.c_str()); + metadata << "[metadata]\n" + << " time = " << time << "\n\n" + << params.data() << std::endl; + metadata.close(); + }); + } + + template + void Writer::saveField(const std::string& fieldname, + const ndfield_t& field) { + auto field_h = Kokkos::create_mirror_view(field); + Kokkos::deep_copy(field_h, field); + m_writer.Put(m_io.InquireVariable(fieldname), + field_h.data(), + adios2::Mode::Sync); + } + + template + void Writer::saveParticleQuantity(const std::string& quantity, + std::size_t glob_total, + std::size_t loc_offset, + std::size_t loc_size, + const array_t& data) { + const auto slice = range_tuple_t(0, loc_size); + auto var = m_io.InquireVariable(quantity); + + var.SetShape({ glob_total }); + var.SetSelection(adios2::Box({ loc_offset }, { loc_size })); + + auto data_h = Kokkos::create_mirror_view(data); + Kokkos::deep_copy(data_h, data); + auto data_sub = Kokkos::subview(data_h, slice); + m_writer.Put(var, data_sub.data(), adios2::Mode::Sync); + } + + template void Writer::savePerDomainVariable(const std::string&, + std::size_t, + std::size_t, + int); + template void Writer::savePerDomainVariable(const std::string&, + std::size_t, + std::size_t, + float); + template void Writer::savePerDomainVariable(const std::string&, + std::size_t, + std::size_t, + double); + template void Writer::savePerDomainVariable(const std::string&, + std::size_t, + std::size_t, + std::size_t); + + template void Writer::saveField(const std::string&, + const ndfield_t&); + template void Writer::saveField(const std::string&, + const ndfield_t&); + template void Writer::saveField(const std::string&, + const ndfield_t&); + template void Writer::saveField(const std::string&, + const ndfield_t&); + template void Writer::saveField(const std::string&, + const ndfield_t&); + template void Writer::saveField(const std::string&, + const ndfield_t&); + + template void Writer::saveParticleQuantity(const std::string&, + std::size_t, + std::size_t, + std::size_t, + const array_t&); + template void Writer::saveParticleQuantity(const std::string&, + std::size_t, + std::size_t, + std::size_t, + const array_t&); + template void Writer::saveParticleQuantity(const std::string&, + std::size_t, + std::size_t, + std::size_t, + const array_t&); + template void Writer::saveParticleQuantity(const std::string&, + std::size_t, + std::size_t, + std::size_t, + const array_t&); +} // namespace checkpoint diff --git a/src/checkpoint/writer.h b/src/checkpoint/writer.h new file mode 100644 index 000000000..34b5f043f --- /dev/null +++ b/src/checkpoint/writer.h @@ -0,0 +1,89 @@ +/** + * @file checkpoint/writer.h + * @brief Class that dumps checkpoints + * @implements + * - checkpoint::Writer + * @cpp: + * - writer.cpp + * @namespaces: + * - checkpoint:: + */ + +#ifndef CHECKPOINT_WRITER_H +#define CHECKPOINT_WRITER_H + +#include "enums.h" +#include "global.h" + +#include "utils/tools.h" + +#include "framework/parameters.h" + +#include + +#include +#include +#include + +namespace checkpoint { + + class Writer { + adios2::ADIOS* p_adios { nullptr }; + + adios2::IO m_io; + adios2::Engine m_writer; + + tools::Tracker m_tracker {}; + + bool m_writing_mode { false }; + + std::vector> m_written; + + int m_keep; + bool m_enabled; + + public: + Writer() {} + + ~Writer() = default; + + void init(adios2::ADIOS*, std::size_t, long double, int); + + auto shouldSave(std::size_t, long double) -> bool; + + void beginSaving(std::size_t, long double); + void endSaving(); + + void saveAttrs(const ntt::SimulationParams&, long double); + + template + void savePerDomainVariable(const std::string&, std::size_t, std::size_t, T); + + template + void saveField(const std::string&, const ndfield_t&); + + template + void saveParticleQuantity(const std::string&, + std::size_t, + std::size_t, + std::size_t, + const array_t&); + + void defineFieldVariables(const ntt::SimEngine&, + const std::vector&, + const std::vector&, + const std::vector&); + void defineParticleVariables(const ntt::Coord&, + Dimension, + std::size_t, + const std::vector&); + + [[nodiscard]] + auto enabled() const -> bool { + return m_enabled; + } + }; + +} // namespace checkpoint + +#endif // CHECKPOINT_WRITER_H diff --git a/src/engines/CMakeLists.txt b/src/engines/CMakeLists.txt index 2cc61a265..2ab7289b2 100644 --- a/src/engines/CMakeLists.txt +++ b/src/engines/CMakeLists.txt @@ -4,7 +4,6 @@ # - engine_printer.cpp # - engine_init.cpp # - engine_run.cpp -# - engine_step_report.cpp # @includes: # - ../ # @depends: @@ -18,6 +17,7 @@ # @uses: # - kokkos [required] # - plog [required] +# - toml11 [required] # - adios2 [optional] # - hdf5 [optional] # - mpi [optional] @@ -28,7 +28,6 @@ set(SOURCES ${SRC_DIR}/engine_printer.cpp ${SRC_DIR}/engine_init.cpp ${SRC_DIR}/engine_run.cpp - ${SRC_DIR}/engine_step_report.cpp ) add_library(ntt_engines ${SOURCES}) diff --git a/src/engines/engine.hpp b/src/engines/engine.hpp index a57b52d19..5b7caa502 100644 --- a/src/engines/engine.hpp +++ b/src/engines/engine.hpp @@ -25,6 +25,7 @@ #include "utils/error.h" #include "utils/progressbar.h" #include "utils/timer.h" +#include "utils/toml.h" #include "framework/containers/species.h" #include "framework/domain/metadomain.h" @@ -34,6 +35,15 @@ #include +#if defined(OUTPUT_ENABLED) + #include + #include +#endif // OUTPUT_ENABLED + +#if defined(MPI_ENABLED) + #include +#endif // MPI_ENABLED + #include #include @@ -45,15 +55,24 @@ namespace ntt { static_assert(user::PGen::is_pgen, "unrecognized problem generator"); protected: - SimulationParams& m_params; - Metadomain m_metadomain; - user::PGen m_pgen; +#if MPI_ENABLED + adios2::ADIOS m_adios { MPI_COMM_WORLD }; +#else + adios2::ADIOS m_adios; +#endif + SimulationParams m_params; + Metadomain m_metadomain; + user::PGen m_pgen; + + const bool is_resuming; const long double runtime; const real_t dt; const std::size_t max_steps; - long double time { 0.0 }; - std::size_t step { 0 }; + const std::size_t start_step; + const long double start_time; + long double time; + std::size_t step; public: static constexpr bool pgen_is_ok { @@ -65,51 +84,31 @@ namespace ntt { static constexpr Dimension D { M::Dim }; static constexpr bool is_engine { true }; -#if defined(OUTPUT_ENABLED) - Engine(SimulationParams& params) - : m_params { params } - , m_metadomain { params.get("simulation.domain.number"), - params.get>( - "simulation.domain.decomposition"), - params.get>("grid.resolution"), - params.get>("grid.extent"), - params.get>( - "grid.boundaries.fields"), - params.get>( - "grid.boundaries.particles"), - params.get>( - "grid.metric.params"), - params.get>( - "particles.species"), - params.template get("output.format") } - , m_pgen { m_params, m_metadomain } - , runtime { params.get("simulation.runtime") } - , dt { params.get("algorithms.timestep.dt") } - , max_steps { static_cast(runtime / dt) } - -#else // not OUTPUT_ENABLED - Engine(SimulationParams& params) + Engine(const SimulationParams& params) : m_params { params } - , m_metadomain { params.get("simulation.domain.number"), - params.get>( + , m_metadomain { m_params.get("simulation.domain.number"), + m_params.get>( "simulation.domain.decomposition"), - params.get>("grid.resolution"), - params.get>("grid.extent"), - params.get>( + m_params.get>( + "grid.resolution"), + m_params.get>("grid.extent"), + m_params.get>( "grid.boundaries.fields"), - params.get>( + m_params.get>( "grid.boundaries.particles"), - params.get>( + m_params.get>( "grid.metric.params"), - params.get>( + m_params.get>( "particles.species") } , m_pgen { m_params, m_metadomain } - , runtime { params.get("simulation.runtime") } - , dt { params.get("algorithms.timestep.dt") } + , is_resuming { m_params.get("checkpoint.is_resuming") } + , runtime { m_params.get("simulation.runtime") } + , dt { m_params.get("algorithms.timestep.dt") } , max_steps { static_cast(runtime / dt) } -#endif - { - + , start_step { m_params.get("checkpoint.start_step") } + , start_time { m_params.get("checkpoint.start_time") } + , time { start_time } + , step { start_step } { raise::ErrorIf(not pgen_is_ok, "Problem generator is not compatible with the picked engine/metric/dimension", HERE); print_report(); } @@ -118,7 +117,6 @@ namespace ntt { void init(); void print_report() const; - void print_step_report(timer::Timers&, pbar::DurationHistory&, bool, bool) const; virtual void step_forward(timer::Timers&, Domain&) = 0; diff --git a/src/engines/engine_init.cpp b/src/engines/engine_init.cpp index abb8754d9..e4ce9fa5f 100644 --- a/src/engines/engine_init.cpp +++ b/src/engines/engine_init.cpp @@ -11,6 +11,7 @@ #include "metrics/spherical.h" #include "archetypes/field_setter.h" + #include "engines/engine.hpp" #include @@ -21,28 +22,41 @@ namespace ntt { void Engine::init() { if constexpr (pgen_is_ok) { #if defined(OUTPUT_ENABLED) - m_metadomain.InitWriter(m_params); + m_metadomain.InitWriter(&m_adios, m_params, is_resuming); + m_metadomain.InitCheckpointWriter(&m_adios, m_params); #endif logger::Checkpoint("Initializing Engine", HERE); - if constexpr ( - traits::has_member>::value) { - logger::Checkpoint("Initializing fields from problem generator", HERE); - m_metadomain.runOnLocalDomains([&](auto& loc_dom) { - Kokkos::parallel_for( - "InitFields", - loc_dom.mesh.rangeActiveCells(), - arch::SetEMFields_kernel { - loc_dom.fields.em, - m_pgen.init_flds, - loc_dom.mesh.metric }); - }); - } - if constexpr ( - traits::has_member>::value) { - logger::Checkpoint("Initializing particles from problem generator", HERE); - m_metadomain.runOnLocalDomains([&](auto& loc_dom) { - m_pgen.InitPrtls(loc_dom); - }); + if (not is_resuming) { + // start a new simulation with initial conditions + logger::Checkpoint("Loading initial conditions", HERE); + if constexpr ( + traits::has_member>::value) { + logger::Checkpoint("Initializing fields from problem generator", HERE); + m_metadomain.runOnLocalDomains([&](auto& loc_dom) { + Kokkos::parallel_for( + "InitFields", + loc_dom.mesh.rangeActiveCells(), + arch::SetEMFields_kernel { + loc_dom.fields.em, + m_pgen.init_flds, + loc_dom.mesh.metric }); + }); + } + if constexpr ( + traits::has_member>::value) { + logger::Checkpoint("Initializing particles from problem generator", HERE); + m_metadomain.runOnLocalDomains([&](auto& loc_dom) { + m_pgen.InitPrtls(loc_dom); + }); + } + } else { + // read simulation data from the checkpoint + raise::ErrorIf( + m_params.template get("checkpoint.start_step") == 0, + "Resuming simulation from a checkpoint requires a valid start_step", + HERE); + logger::Checkpoint("Resuming simulation from a checkpoint", HERE); + m_metadomain.ContinueFromCheckpoint(&m_adios, m_params); } } } @@ -55,4 +69,4 @@ namespace ntt { template class Engine>; template class Engine>; template class Engine>; -} // namespace ntt \ No newline at end of file +} // namespace ntt diff --git a/src/engines/engine_printer.cpp b/src/engines/engine_printer.cpp index 90dec3326..2608ea2f6 100644 --- a/src/engines/engine_printer.cpp +++ b/src/engines/engine_printer.cpp @@ -344,7 +344,7 @@ namespace ntt { for (unsigned int idx { 0 }; idx < m_metadomain.ndomains(); ++idx) { auto is_local = false; - for (const auto& lidx : m_metadomain.local_subdomain_indices()) { + for (const auto& lidx : m_metadomain.l_subdomain_indices()) { is_local |= (idx == lidx); } if (is_local) { diff --git a/src/engines/engine_run.cpp b/src/engines/engine_run.cpp index 4485e0e40..bec5b8652 100644 --- a/src/engines/engine_run.cpp +++ b/src/engines/engine_run.cpp @@ -1,6 +1,7 @@ #include "enums.h" #include "arch/traits.h" +#include "utils/diag.h" #include "metrics/kerr_schild.h" #include "metrics/kerr_schild_0.h" @@ -26,7 +27,8 @@ namespace ntt { "ParticlePusher", "FieldBoundaries", "ParticleBoundaries", "Communications", "Injector", "Sorting", - "Custom", "Output" }, + "Custom", "Output", + "Checkpoint" }, []() { Kokkos::fence(); }, @@ -56,11 +58,12 @@ namespace ntt { } auto print_sorting = (sort_interval > 0 and step % sort_interval == 0); - // advance time & timestep - ++step; + // advance time & step time += dt; + ++step; - auto print_output = false; + auto print_output = false; + auto print_checkpoint = false; #if defined(OUTPUT_ENABLED) timers.start("Output"); if constexpr ( @@ -73,19 +76,43 @@ namespace ntt { }; print_output = m_metadomain.Write(m_params, step, + step - 1, time, + time - dt, lambda_custom_field_output); } else { - print_output = m_metadomain.Write(m_params, step, time); + print_output = m_metadomain.Write(m_params, step, step - 1, time, time - dt); } timers.stop("Output"); + + timers.start("Checkpoint"); + print_checkpoint = m_metadomain.WriteCheckpoint(m_params, + step, + step - 1, + time, + time - dt); + timers.stop("Checkpoint"); #endif // advance time_history time_history.tick(); - // print final timestep report + // print timestep report if (diag_interval > 0 and step % diag_interval == 0) { - print_step_report(timers, time_history, print_output, print_sorting); + diag::printDiagnostics( + step - 1, + max_steps, + time - dt, + dt, + timers, + time_history, + m_metadomain.l_ncells(), + m_metadomain.species_labels(), + m_metadomain.l_npart_perspec(), + m_metadomain.l_maxnpart_perspec(), + print_sorting, + print_output, + print_checkpoint, + m_params.get("diagnostics.colored_stdout")); } timers.resetAll(); } diff --git a/src/engines/engine_step_report.cpp b/src/engines/engine_step_report.cpp deleted file mode 100644 index f2a35bb82..000000000 --- a/src/engines/engine_step_report.cpp +++ /dev/null @@ -1,293 +0,0 @@ -#include "enums.h" -#include "global.h" - -#include "arch/mpi_aliases.h" -#include "utils/colors.h" -#include "utils/formatting.h" -#include "utils/progressbar.h" -#include "utils/timer.h" - -#include "metrics/kerr_schild.h" -#include "metrics/kerr_schild_0.h" -#include "metrics/minkowski.h" -#include "metrics/qkerr_schild.h" -#include "metrics/qspherical.h" -#include "metrics/spherical.h" - -#include "engines/engine.hpp" - -#include -#include - -namespace ntt { - namespace {} // namespace - - template - void print_particles(const Metadomain&, - unsigned short, - DiagFlags, - std::ostream& = std::cout); - - template - void Engine::print_step_report(timer::Timers& timers, - pbar::DurationHistory& time_history, - bool print_output, - bool print_sorting) const { - DiagFlags diag_flags = Diag::Default; - TimerFlags timer_flags = Timer::Default; - if (not m_params.get("diagnostics.colored_stdout")) { - diag_flags ^= Diag::Colorful; - timer_flags ^= Timer::Colorful; - } - if (m_params.get("particles.nspec") == 0) { - diag_flags ^= Diag::Species; - } - if (print_output) { - timer_flags |= Timer::PrintOutput; - } - if (print_sorting) { - timer_flags |= Timer::PrintSorting; - } - CallOnce( - [diag_flags](auto& time, auto& step, auto& max_steps, auto& dt) { - const auto c_bgreen = color::get_color("bgreen", - diag_flags & Diag::Colorful); - const auto c_bblack = color::get_color("bblack", - diag_flags & Diag::Colorful); - const auto c_reset = color::get_color("reset", diag_flags & Diag::Colorful); - std::cout << fmt::format("Step:%s %-8d%s %s[of %d]%s\n", - c_bgreen.c_str(), - step, - c_reset.c_str(), - c_bblack.c_str(), - max_steps, - c_reset.c_str()); - std::cout << fmt::format("Time:%s %-8.4f%s %s[Δt = %.4f]%s\n", - c_bgreen.c_str(), - (double)time, - c_reset.c_str(), - c_bblack.c_str(), - (double)dt, - c_reset.c_str()) - << std::endl; - }, - time, - step, - max_steps, - dt); - if (diag_flags & Diag::Timers) { - timers.printAll(timer_flags, std::cout); - } - CallOnce([]() { - std::cout << std::endl; - }); - if (diag_flags & Diag::Species) { - CallOnce([diag_flags]() { - std::cout << color::get_color("bblack", diag_flags & Diag::Colorful); -#if defined(MPI_ENABLED) - std::cout << "Particle count:" << std::setw(22) << std::right << "[TOT]" - << std::setw(20) << std::right << "[MIN (%)]" << std::setw(20) - << std::right << "[MAX (%)]"; -#else - std::cout << "Particle count:" << std::setw(25) << std::right - << "[TOT (%)]"; -#endif - std::cout << color::get_color("reset", diag_flags & Diag::Colorful) - << std::endl; - }); - for (std::size_t sp { 0 }; sp < m_metadomain.species_params().size(); ++sp) { - print_particles(m_metadomain, sp, diag_flags, std::cout); - } - CallOnce([]() { - std::cout << std::endl; - }); - } - if (diag_flags & Diag::Progress) { - pbar::ProgressBar(time_history, step, max_steps, diag_flags, std::cout); - } - CallOnce([]() { - std::cout << std::setw(80) << std::setfill('.') << "" << std::endl - << std::endl; - }); - } - - template - void print_particles(const Metadomain& md, - unsigned short sp, - DiagFlags flags, - std::ostream& os) { - - static_assert(M::is_metric, "template arg for Engine class has to be a metric"); - std::size_t npart { 0 }; - std::size_t maxnpart { 0 }; - std::string species_label; - int species_index; - // sum npart & maxnpart over all subdomains on the current rank - md.runOnLocalDomainsConst( - [&npart, &maxnpart, &species_label, &species_index, sp](auto& dom) { - npart += dom.species[sp].npart(); - maxnpart += dom.species[sp].maxnpart(); - species_label = dom.species[sp].label(); - species_index = dom.species[sp].index(); - }); -#if defined(MPI_ENABLED) - int rank, size; - MPI_Comm_rank(MPI_COMM_WORLD, &rank); - MPI_Comm_size(MPI_COMM_WORLD, &size); - std::vector mpi_npart(size, 0); - std::vector mpi_maxnpart(size, 0); - MPI_Gather(&npart, - 1, - mpi::get_type(), - mpi_npart.data(), - 1, - mpi::get_type(), - MPI_ROOT_RANK, - MPI_COMM_WORLD); - MPI_Gather(&maxnpart, - 1, - mpi::get_type(), - mpi_maxnpart.data(), - 1, - mpi::get_type(), - MPI_ROOT_RANK, - MPI_COMM_WORLD); - if (rank != MPI_ROOT_RANK) { - return; - } - auto tot_npart = std::accumulate(mpi_npart.begin(), mpi_npart.end(), 0); - std::size_t npart_max = *std::max_element(mpi_npart.begin(), mpi_npart.end()); - std::size_t npart_min = *std::min_element(mpi_npart.begin(), mpi_npart.end()); - std::vector mpi_load(size, 0.0); - for (auto r { 0 }; r < size; ++r) { - mpi_load[r] = 100.0 * (double)(mpi_npart[r]) / (double)(mpi_maxnpart[r]); - } - double load_max = *std::max_element(mpi_load.begin(), mpi_load.end()); - double load_min = *std::min_element(mpi_load.begin(), mpi_load.end()); - auto npart_min_str = npart_min > 9999 - ? fmt::format("%.2Le", (long double)npart_min) - : std::to_string(npart_min); - auto tot_npart_str = tot_npart > 9999 - ? fmt::format("%.2Le", (long double)tot_npart) - : std::to_string(tot_npart); - auto npart_max_str = npart_max > 9999 - ? fmt::format("%.2Le", (long double)npart_max) - : std::to_string(npart_max); - os << " species " << fmt::format("%2d", species_index) << " (" - << species_label << ")"; - - const auto c_bblack = color::get_color("bblack", flags & Diag::Colorful); - const auto c_red = color::get_color("red", flags & Diag::Colorful); - const auto c_yellow = color::get_color("yellow", flags & Diag::Colorful); - const auto c_green = color::get_color("green", flags & Diag::Colorful); - const auto c_reset = color::get_color("reset", flags & Diag::Colorful); - auto c_loadmin = (load_min > 80) ? c_red - : ((load_min > 50) ? c_yellow : c_green); - auto c_loadmax = (load_max > 80) ? c_red - : ((load_max > 50) ? c_yellow : c_green); - const auto raw1 = fmt::format("%s (%4.1f%%)", npart_min_str.c_str(), load_min); - const auto raw2 = fmt::format("%s (%4.1f%%)", npart_max_str.c_str(), load_max); - os << c_bblack - << fmt::pad(tot_npart_str, 20, '.', false).substr(0, 20 - tot_npart_str.size()) - << c_reset << tot_npart_str; - os << fmt::pad(raw1, 20, ' ', false).substr(0, 20 - raw1.size()) - << fmt::format("%s (%s%4.1f%%%s)", - npart_min_str.c_str(), - c_loadmin.c_str(), - load_min, - c_reset.c_str()); - os << fmt::pad(raw2, 20, ' ', false).substr(0, 20 - raw2.size()) - << fmt::format("%s (%s%4.1f%%%s)", - npart_max_str.c_str(), - c_loadmax.c_str(), - load_max, - c_reset.c_str()); -#else // not MPI_ENABLED - auto load = 100.0 * (double)(npart) / (double)(maxnpart); - auto npart_str = npart > 9999 ? fmt::format("%.2Le", (long double)npart) - : std::to_string(npart); - const auto c_bblack = color::get_color("bblack", flags & Diag::Colorful); - const auto c_red = color::get_color("red", flags & Diag::Colorful); - const auto c_yellow = color::get_color("yellow", flags & Diag::Colorful); - const auto c_green = color::get_color("green", flags & Diag::Colorful); - const auto c_reset = color::get_color("reset", flags & Diag::Colorful); - const auto c_load = (load > 80) - ? c_red.c_str() - : ((load > 50) ? c_yellow.c_str() : c_green.c_str()); - os << " species " << species_index << " (" << species_label << ")"; - const auto raw = fmt::format("%s (%4.1f%%)", npart_str.c_str(), load); - os << c_bblack << fmt::pad(raw, 24, '.').substr(0, 24 - raw.size()) << c_reset; - os << fmt::format("%s (%s%4.1f%%%s)", - npart_str.c_str(), - c_load, - load, - c_reset.c_str()); -#endif - os << std::endl; - } - - template class Engine>; - template class Engine>; - template class Engine>; - template class Engine>; - template class Engine>; - template class Engine>; - template class Engine>; - template class Engine>; -} // namespace ntt - -// template -// auto Simulation::PrintDiagnostics(const std::size_t& step, -// const real_t& time, -// const timer::Timers& timers, -// std::vector& tstep_durations, -// const DiagFlags diag_flags, -// std::ostream& os) -> void { -// if (tstep_durations.size() > m_params.diagMaxnForPbar()) { -// tstep_durations.erase(tstep_durations.begin()); -// } -// tstep_durations.push_back(timers.get("Total")); -// if (step % m_params.diagInterval() == 0) { -// auto& mblock = this->meshblock; -// const auto title { -// fmt::format("Time = %f : step = %d : Δt = %f", time, step, mblock.timestep()) -// }; -// PrintOnce( -// [](std::ostream& os, std::string title) { -// os << title << std::endl; -// }, -// os, -// title); -// if (diag_flags & DiagFlags_Timers) { -// timers.printAll("", timer::TimerFlags_Default, os); -// } -// if (diag_flags & DiagFlags_Species) { -// auto header = fmt::format("%s %27s", "[SPECIES]", "[TOT]"); -// #if defined(MPI_ENABLED) -// header += fmt::format("%17s %s", "[MIN (%) :", "MAX (%)]"); -// #endif -// PrintOnce( -// [](std::ostream& os, std::string header) { -// os << header << std::endl; -// }, -// os, -// header); -// for (const auto& species : meshblock.particles) { -// species.PrintParticleCounts(os); -// } -// } -// if (diag_flags & DiagFlags_Progress) { -// PrintOnce( -// [](std::ostream& os) { -// os << std::setw(65) << std::setfill('-') << "" << std::endl; -// }, -// os); -// ProgressBar(tstep_durations, time, m_params.totalRuntime(), os); -// } -// PrintOnce( -// [](std::ostream& os) { -// os << std::setw(65) << std::setfill('=') << "" << std::endl; -// }, -// os); -// } -// } diff --git a/src/engines/grpic.hpp b/src/engines/grpic.hpp index 148c1c5c5..d082d617f 100644 --- a/src/engines/grpic.hpp +++ b/src/engines/grpic.hpp @@ -15,8 +15,10 @@ #include "enums.h" #include "utils/timer.h" +#include "utils/toml.h" #include "framework/domain/domain.h" +#include "framework/parameters.h" #include "engines/engine.hpp" @@ -24,14 +26,15 @@ namespace ntt { template class GRPICEngine : public Engine { + using base_t = Engine; + using Engine::m_params; using Engine::m_metadomain; public: static constexpr auto S { SimEngine::SRPIC }; - GRPICEngine(SimulationParams& params) - : Engine { params } {} + GRPICEngine(const SimulationParams& params) : base_t { params } {} ~GRPICEngine() = default; diff --git a/src/engines/srpic.hpp b/src/engines/srpic.hpp index bddc557c9..78c8f371e 100644 --- a/src/engines/srpic.hpp +++ b/src/engines/srpic.hpp @@ -21,6 +21,7 @@ #include "utils/log.h" #include "utils/numeric.h" #include "utils/timer.h" +#include "utils/toml.h" #include "archetypes/particle_injector.h" #include "framework/domain/domain.h" @@ -70,7 +71,7 @@ namespace ntt { public: static constexpr auto S { SimEngine::SRPIC }; - SRPICEngine(SimulationParams& params) : base_t { params } {} + SRPICEngine(const SimulationParams& params) : base_t { params } {} ~SRPICEngine() = default; @@ -884,8 +885,15 @@ namespace ntt { xi_min.size() != static_cast(M::Dim), "Invalid range size", HERE); - for (const unsigned short comp : - { normal_b_comp, tang_e_comp1, tang_e_comp2 }) { + std::vector comps; + if (tags & BC::E) { + comps.push_back(tang_e_comp1); + comps.push_back(tang_e_comp2); + } + if (tags & BC::B) { + comps.push_back(normal_b_comp); + } + for (const auto& comp : comps) { if constexpr (M::Dim == Dim::_1D) { Kokkos::deep_copy(Kokkos::subview(domain.fields.em, std::make_pair(xi_min[0], xi_max[0]), diff --git a/src/framework/CMakeLists.txt b/src/framework/CMakeLists.txt index c7c3ba8a1..241780575 100644 --- a/src/framework/CMakeLists.txt +++ b/src/framework/CMakeLists.txt @@ -6,6 +6,7 @@ # - domain/grid.cpp # - domain/metadomain.cpp # - domain/communications.cpp +# - domain/checkpoint.cpp # - containers/particles.cpp # - containers/fields.cpp # - domain/output.cpp @@ -31,22 +32,26 @@ set(SOURCES ${SRC_DIR}/domain/grid.cpp ${SRC_DIR}/domain/metadomain.cpp ${SRC_DIR}/domain/communications.cpp + ${SRC_DIR}/domain/checkpoint.cpp ${SRC_DIR}/containers/particles.cpp ${SRC_DIR}/containers/fields.cpp ) if (${output}) list(APPEND SOURCES ${SRC_DIR}/domain/output.cpp) + list(APPEND SOURCES ${SRC_DIR}/domain/checkpoint.cpp) endif() add_library(ntt_framework ${SOURCES}) set(libs ntt_global ntt_metrics ntt_kernels) if(${output}) list(APPEND libs ntt_output) + list(APPEND libs ntt_checkpoint) endif() add_dependencies(ntt_framework ${libs}) target_link_libraries(ntt_framework PUBLIC ${libs}) +target_link_libraries(ntt_framework PRIVATE stdc++fs) target_include_directories(ntt_framework PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../ INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/../ -) \ No newline at end of file +) diff --git a/src/framework/domain/checkpoint.cpp b/src/framework/domain/checkpoint.cpp new file mode 100644 index 000000000..3d309c090 --- /dev/null +++ b/src/framework/domain/checkpoint.cpp @@ -0,0 +1,484 @@ +#include "enums.h" +#include "global.h" + +#include "utils/error.h" +#include "utils/formatting.h" +#include "utils/log.h" + +#include "metrics/kerr_schild.h" +#include "metrics/kerr_schild_0.h" +#include "metrics/minkowski.h" +#include "metrics/qkerr_schild.h" +#include "metrics/qspherical.h" +#include "metrics/spherical.h" + +#include "checkpoint/reader.h" +#include "checkpoint/writer.h" +#include "framework/domain/metadomain.h" +#include "framework/parameters.h" + +namespace ntt { + + template + void Metadomain::InitCheckpointWriter(adios2::ADIOS* ptr_adios, + const SimulationParams& params) { + raise::ErrorIf(ptr_adios == nullptr, "adios == nullptr", HERE); + raise::ErrorIf( + l_subdomain_indices().size() != 1, + "Checkpoint writing for now is only supported for one subdomain per rank", + HERE); + auto local_domain = subdomain_ptr(l_subdomain_indices()[0]); + raise::ErrorIf(local_domain->is_placeholder(), + "local_domain is a placeholder", + HERE); + + std::vector glob_shape_with_ghosts, off_ncells_with_ghosts; + for (auto d { 0u }; d < M::Dim; ++d) { + off_ncells_with_ghosts.push_back( + local_domain->offset_ncells()[d] + + 2 * N_GHOSTS * local_domain->offset_ndomains()[d]); + glob_shape_with_ghosts.push_back( + mesh().n_active()[d] + 2 * N_GHOSTS * ndomains_per_dim()[d]); + } + auto loc_shape_with_ghosts = local_domain->mesh.n_all(); + + std::vector nplds; + for (auto s { 0u }; s < local_domain->species.size(); ++s) { + nplds.push_back(local_domain->species[s].npld()); + } + + g_checkpoint_writer.init( + ptr_adios, + params.template get("checkpoint.interval"), + params.template get("checkpoint.interval_time"), + params.template get("checkpoint.keep")); + if (g_checkpoint_writer.enabled()) { + g_checkpoint_writer.defineFieldVariables(S, + glob_shape_with_ghosts, + off_ncells_with_ghosts, + loc_shape_with_ghosts); + g_checkpoint_writer.defineParticleVariables(M::CoordType, + M::Dim, + local_domain->species.size(), + nplds); + } + } + + template + auto Metadomain::WriteCheckpoint(const SimulationParams& params, + std::size_t current_step, + std::size_t finished_step, + long double current_time, + long double finished_time) -> bool { + raise::ErrorIf( + l_subdomain_indices().size() != 1, + "Checkpointing for now is only supported for one subdomain per rank", + HERE); + if (not g_checkpoint_writer.shouldSave(finished_step, finished_time) or + finished_step <= 1) { + return false; + } + auto local_domain = subdomain_ptr(l_subdomain_indices()[0]); + raise::ErrorIf(local_domain->is_placeholder(), + "local_domain is a placeholder", + HERE); + logger::Checkpoint("Writing checkpoint", HERE); + g_checkpoint_writer.beginSaving(current_step, current_time); + { + g_checkpoint_writer.saveAttrs(params, current_time); + g_checkpoint_writer.saveField("em", local_domain->fields.em); + if constexpr (S == SimEngine::GRPIC) { + g_checkpoint_writer.saveField("em0", local_domain->fields.em0); + g_checkpoint_writer.saveField("cur0", local_domain->fields.cur0); + } + std::size_t dom_offset = 0, dom_tot = 1; +#if defined(MPI_ENABLED) + dom_offset = g_mpi_rank; + dom_tot = g_mpi_size; +#endif // MPI_ENABLED + + for (auto s { 0u }; s < local_domain->species.size(); ++s) { + auto npart = local_domain->species[s].npart(); + std::size_t offset = 0; + auto glob_tot = npart; +#if defined(MPI_ENABLED) + auto glob_npart = std::vector(g_ndomains); + MPI_Allgather(&npart, + 1, + mpi::get_type(), + glob_npart.data(), + 1, + mpi::get_type(), + MPI_COMM_WORLD); + glob_tot = 0; + for (auto r = 0; r < g_mpi_size; ++r) { + if (r < g_mpi_rank) { + offset += glob_npart[r]; + } + glob_tot += glob_npart[r]; + } +#endif // MPI_ENABLED + g_checkpoint_writer.savePerDomainVariable( + fmt::format("s%d_npart", s + 1), + dom_tot, + dom_offset, + npart); + if constexpr (M::Dim == Dim::_1D or M::Dim == Dim::_2D or + M::Dim == Dim::_3D) { + g_checkpoint_writer.saveParticleQuantity( + fmt::format("s%d_i1", s + 1), + glob_tot, + offset, + npart, + local_domain->species[s].i1); + g_checkpoint_writer.saveParticleQuantity( + fmt::format("s%d_dx1", s + 1), + glob_tot, + offset, + npart, + local_domain->species[s].dx1); + g_checkpoint_writer.saveParticleQuantity( + fmt::format("s%d_i1_prev", s + 1), + glob_tot, + offset, + npart, + local_domain->species[s].i1_prev); + g_checkpoint_writer.saveParticleQuantity( + fmt::format("s%d_dx1_prev", s + 1), + glob_tot, + offset, + npart, + local_domain->species[s].dx1_prev); + } + if constexpr (M::Dim == Dim::_2D or M::Dim == Dim::_3D) { + g_checkpoint_writer.saveParticleQuantity( + fmt::format("s%d_i2", s + 1), + glob_tot, + offset, + npart, + local_domain->species[s].i2); + g_checkpoint_writer.saveParticleQuantity( + fmt::format("s%d_dx2", s + 1), + glob_tot, + offset, + npart, + local_domain->species[s].dx2); + g_checkpoint_writer.saveParticleQuantity( + fmt::format("s%d_i2_prev", s + 1), + glob_tot, + offset, + npart, + local_domain->species[s].i2_prev); + g_checkpoint_writer.saveParticleQuantity( + fmt::format("s%d_dx2_prev", s + 1), + glob_tot, + offset, + npart, + local_domain->species[s].dx2_prev); + } + if constexpr (M::Dim == Dim::_3D) { + g_checkpoint_writer.saveParticleQuantity( + fmt::format("s%d_i3", s + 1), + glob_tot, + offset, + npart, + local_domain->species[s].i3); + g_checkpoint_writer.saveParticleQuantity( + fmt::format("s%d_dx3", s + 1), + glob_tot, + offset, + npart, + local_domain->species[s].dx3); + g_checkpoint_writer.saveParticleQuantity( + fmt::format("s%d_i3_prev", s + 1), + glob_tot, + offset, + npart, + local_domain->species[s].i3_prev); + g_checkpoint_writer.saveParticleQuantity( + fmt::format("s%d_dx3_prev", s + 1), + glob_tot, + offset, + npart, + local_domain->species[s].dx3_prev); + } + if constexpr (M::Dim == Dim::_2D and M::CoordType != Coord::Cart) { + g_checkpoint_writer.saveParticleQuantity( + fmt::format("s%d_phi", s + 1), + glob_tot, + offset, + npart, + local_domain->species[s].phi); + } + g_checkpoint_writer.saveParticleQuantity( + fmt::format("s%d_ux1", s + 1), + glob_tot, + offset, + npart, + local_domain->species[s].ux1); + g_checkpoint_writer.saveParticleQuantity( + fmt::format("s%d_ux2", s + 1), + glob_tot, + offset, + npart, + local_domain->species[s].ux2); + g_checkpoint_writer.saveParticleQuantity( + fmt::format("s%d_ux3", s + 1), + glob_tot, + offset, + npart, + local_domain->species[s].ux3); + g_checkpoint_writer.saveParticleQuantity( + fmt::format("s%d_tag", s + 1), + glob_tot, + offset, + npart, + local_domain->species[s].tag); + g_checkpoint_writer.saveParticleQuantity( + fmt::format("s%d_weight", s + 1), + glob_tot, + offset, + npart, + local_domain->species[s].weight); + + auto nplds = local_domain->species[s].npld(); + for (auto p { 0u }; p < nplds; ++p) { + g_checkpoint_writer.saveParticleQuantity( + fmt::format("s%d_pld%d", s + 1, p + 1), + glob_tot, + offset, + npart, + local_domain->species[s].pld[p]); + } + } + } + g_checkpoint_writer.endSaving(); + logger::Checkpoint("Checkpoint written", HERE); + return true; + } + + template + void Metadomain::ContinueFromCheckpoint(adios2::ADIOS* ptr_adios, + const SimulationParams& params) { + raise::ErrorIf(ptr_adios == nullptr, "adios == nullptr", HERE); + auto fname = fmt::format( + "checkpoints/step-%08lu.bp", + params.template get("checkpoint.start_step")); + logger::Checkpoint(fmt::format("Reading checkpoint from %s", fname.c_str()), + HERE); + + adios2::IO io = ptr_adios->DeclareIO("Entity::CheckpointRead"); + io.SetEngine("BPFile"); +#if !defined(MPI_ENABLED) + adios2::Engine reader = io.Open(fname, adios2::Mode::Read); +#else + adios2::Engine reader = io.Open(fname, adios2::Mode::Read, MPI_COMM_SELF); +#endif + + reader.BeginStep(); + for (auto& ldidx : l_subdomain_indices()) { + auto& domain = g_subdomains[ldidx]; + adios2::Box range; + for (auto d { 0u }; d < M::Dim; ++d) { + range.first.push_back(domain.offset_ncells()[d] + + 2 * N_GHOSTS * domain.offset_ndomains()[d]); + range.second.push_back(domain.mesh.n_all()[d]); + } + range.first.push_back(0); + range.second.push_back(6); + checkpoint::ReadFields(io, reader, "em", range, domain.fields.em); + if constexpr (S == ntt::SimEngine::GRPIC) { + checkpoint::ReadFields(io, + reader, + "em0", + range, + domain.fields.em0); + adios2::Box range3; + for (auto d { 0u }; d < M::Dim; ++d) { + range3.first.push_back(domain.offset_ncells()[d] + + 2 * N_GHOSTS * domain.offset_ndomains()[d]); + range3.second.push_back(domain.mesh.n_all()[d]); + } + range3.first.push_back(0); + range3.second.push_back(3); + checkpoint::ReadFields(io, + reader, + "cur0", + range3, + domain.fields.cur0); + } + for (auto s { 0u }; s < (unsigned short)(domain.species.size()); ++s) { + const auto [loc_npart, offset_npart] = + checkpoint::ReadParticleCount(io, reader, s, ldidx, ndomains()); + raise::ErrorIf(loc_npart > domain.species[s].maxnpart(), + "loc_npart > domain.species[s].maxnpart()", + HERE); + if (loc_npart == 0) { + continue; + } + if constexpr (M::Dim == Dim::_1D or M::Dim == Dim::_2D or + M::Dim == Dim::_3D) { + checkpoint::ReadParticleData(io, + reader, + "i1", + s, + domain.species[s].i1, + loc_npart, + offset_npart); + checkpoint::ReadParticleData(io, + reader, + "dx1", + s, + domain.species[s].dx1, + loc_npart, + offset_npart); + checkpoint::ReadParticleData(io, + reader, + "i1_prev", + s, + domain.species[s].i1_prev, + loc_npart, + offset_npart); + checkpoint::ReadParticleData(io, + reader, + "dx1_prev", + s, + domain.species[s].dx1_prev, + loc_npart, + offset_npart); + } + if constexpr (M::Dim == Dim::_2D or M::Dim == Dim::_3D) { + checkpoint::ReadParticleData(io, + reader, + "i2", + s, + domain.species[s].i2, + loc_npart, + offset_npart); + checkpoint::ReadParticleData(io, + reader, + "dx2", + s, + domain.species[s].dx2, + loc_npart, + offset_npart); + checkpoint::ReadParticleData(io, + reader, + "i2_prev", + s, + domain.species[s].i2_prev, + loc_npart, + offset_npart); + checkpoint::ReadParticleData(io, + reader, + "dx2_prev", + s, + domain.species[s].dx2_prev, + loc_npart, + offset_npart); + } + if constexpr (M::Dim == Dim::_3D) { + checkpoint::ReadParticleData(io, + reader, + "i3", + s, + domain.species[s].i3, + loc_npart, + offset_npart); + checkpoint::ReadParticleData(io, + reader, + "dx3", + s, + domain.species[s].dx3, + loc_npart, + offset_npart); + checkpoint::ReadParticleData(io, + reader, + "i3_prev", + s, + domain.species[s].i3_prev, + loc_npart, + offset_npart); + checkpoint::ReadParticleData(io, + reader, + "dx3_prev", + s, + domain.species[s].dx3_prev, + loc_npart, + offset_npart); + } + if constexpr (M::Dim == Dim::_2D and M::CoordType != Coord::Cart) { + checkpoint::ReadParticleData(io, + reader, + "phi", + s, + domain.species[s].phi, + loc_npart, + offset_npart); + } + checkpoint::ReadParticleData(io, + reader, + "ux1", + s, + domain.species[s].ux1, + loc_npart, + offset_npart); + checkpoint::ReadParticleData(io, + reader, + "ux2", + s, + domain.species[s].ux2, + loc_npart, + offset_npart); + checkpoint::ReadParticleData(io, + reader, + "ux3", + s, + domain.species[s].ux3, + loc_npart, + offset_npart); + checkpoint::ReadParticleData(io, + reader, + "tag", + s, + domain.species[s].tag, + loc_npart, + offset_npart); + checkpoint::ReadParticleData(io, + reader, + "weight", + s, + domain.species[s].weight, + loc_npart, + offset_npart); + for (auto p { 0u }; p < domain.species[s].npld(); ++p) { + checkpoint::ReadParticleData(io, + reader, + fmt::format("pld%d", p + 1), + s, + domain.species[s].pld[p], + loc_npart, + offset_npart); + } + domain.species[s].set_npart(loc_npart); + } // species loop + + } // local subdomain loop + + reader.EndStep(); + reader.Close(); + logger::Checkpoint( + fmt::format("Checkpoint reading done from %s", fname.c_str()), + HERE); + } + + template struct Metadomain>; + template struct Metadomain>; + template struct Metadomain>; + template struct Metadomain>; + template struct Metadomain>; + template struct Metadomain>; + template struct Metadomain>; + template struct Metadomain>; + +} // namespace ntt diff --git a/src/framework/domain/metadomain.cpp b/src/framework/domain/metadomain.cpp index b13475fd6..5e66bc366 100644 --- a/src/framework/domain/metadomain.cpp +++ b/src/framework/domain/metadomain.cpp @@ -37,21 +37,12 @@ namespace ntt { const boundaries_t& global_flds_bc, const boundaries_t& global_prtl_bc, const std::map& metric_params, - const std::vector& species_params -#if defined(OUTPUT_ENABLED) - , - const std::string& output_engine -#endif - ) + const std::vector& species_params) : g_ndomains { global_ndomains } , g_decomposition { global_decomposition } , g_mesh { global_ncells, global_extent, metric_params, global_flds_bc, global_prtl_bc } , g_metric_params { metric_params } - , g_species_params { species_params } -#if defined(OUTPUT_ENABLED) - , g_writer { output_engine } -#endif - { + , g_species_params { species_params } { #if defined(MPI_ENABLED) MPI_Comm_size(MPI_COMM_WORLD, &g_mpi_size); MPI_Comm_rank(MPI_COMM_WORLD, &g_mpi_rank); @@ -351,7 +342,7 @@ namespace ntt { } // check that local subdomains are contained in g_local_subdomain_indices auto contained_in_local = false; - for (const auto& gidx : g_local_subdomain_indices) { + for (const auto& gidx : l_subdomain_indices()) { contained_in_local |= (idx == gidx); } #if defined(MPI_ENABLED) diff --git a/src/framework/domain/metadomain.h b/src/framework/domain/metadomain.h index fb81fcfca..027a2982d 100644 --- a/src/framework/domain/metadomain.h +++ b/src/framework/domain/metadomain.h @@ -21,6 +21,7 @@ #include "arch/kokkos_aliases.h" #include "utils/timer.h" +#include "checkpoint/writer.h" #include "framework/containers/species.h" #include "framework/domain/domain.h" #include "framework/domain/mesh.h" @@ -30,9 +31,12 @@ #include #endif // MPI_ENABLED -#if defined OUTPUT_ENABLED +#if defined(OUTPUT_ENABLED) #include "output/writer.h" -#endif + + #include + #include +#endif // OUTPUT_ENABLED #include #include @@ -70,14 +74,14 @@ namespace ntt { template void runOnLocalDomains(Func func, Args&&... args) { - for (auto& ldidx : g_local_subdomain_indices) { + for (auto& ldidx : l_subdomain_indices()) { func(g_subdomains[ldidx], std::forward(args)...); } } template void runOnLocalDomainsConst(Func func, Args&&... args) const { - for (auto& ldidx : g_local_subdomain_indices) { + for (auto& ldidx : l_subdomain_indices()) { func(g_subdomains[ldidx], std::forward(args)...); } } @@ -95,7 +99,6 @@ namespace ntt { * @param global_prtl_bc boundary conditions for particles * @param metric_params parameters for the metric * @param species_params parameters for the particle species - * @param output_params parameters for the output */ Metadomain(unsigned int, const std::vector&, @@ -104,29 +107,34 @@ namespace ntt { const boundaries_t&, const boundaries_t&, const std::map&, - const std::vector& -#if defined(OUTPUT_ENABLED) - , - const std::string& -#endif - ); + const std::vector&); + + Metadomain(const Metadomain&) = delete; + Metadomain& operator=(const Metadomain&) = delete; + + ~Metadomain() = default; #if defined(OUTPUT_ENABLED) - void InitWriter(const SimulationParams&); + void InitWriter(adios2::ADIOS*, const SimulationParams&, bool is_resuming); auto Write(const SimulationParams&, std::size_t, + std::size_t, + long double, long double, std::function&, std::size_t, const Domain&)> = {}) -> bool; + void InitCheckpointWriter(adios2::ADIOS*, const SimulationParams&); + auto WriteCheckpoint(const SimulationParams&, + std::size_t, + std::size_t, + long double, + long double) -> bool; + + void ContinueFromCheckpoint(adios2::ADIOS*, const SimulationParams&); #endif - Metadomain(const Metadomain&) = delete; - Metadomain& operator=(const Metadomain&) = delete; - - ~Metadomain() = default; - /* setters -------------------------------------------------------------- */ /* getters -------------------------------------------------------------- */ @@ -163,10 +171,60 @@ namespace ntt { } [[nodiscard]] - auto local_subdomain_indices() const -> std::vector { + auto l_subdomain_indices() const -> std::vector { return g_local_subdomain_indices; } + [[nodiscard]] + auto l_npart_perspec() const -> std::vector { + std::vector npart(g_species_params.size(), 0); + for (const auto& ldidx : l_subdomain_indices()) { + for (std::size_t i = 0; i < g_species_params.size(); ++i) { + npart[i] += g_subdomains[ldidx].species[i].npart(); + } + } + return npart; + } + + [[nodiscard]] + auto l_maxnpart_perspec() const -> std::vector { + std::vector maxnpart(g_species_params.size(), 0); + for (const auto& ldidx : l_subdomain_indices()) { + for (std::size_t i = 0; i < g_species_params.size(); ++i) { + maxnpart[i] += g_subdomains[ldidx].species[i].maxnpart(); + } + } + return maxnpart; + } + + [[nodiscard]] + auto l_npart() const -> std::size_t { + const auto npart = l_npart_perspec(); + return std::accumulate(npart.begin(), npart.end(), 0); + } + + [[nodiscard]] + auto l_ncells() const -> std::size_t { + std::size_t ncells_local = 0; + for (const auto& ldidx : l_subdomain_indices()) { + std::size_t ncells = 1; + for (const auto& n : g_subdomains[ldidx].mesh.n_all()) { + ncells *= n; + } + ncells_local += ncells; + } + return ncells_local; + } + + [[nodiscard]] + auto species_labels() const -> std::vector { + std::vector labels; + for (const auto& sp : g_species_params) { + labels.push_back(sp.label()); + } + return labels; + } + private: // domain information unsigned int g_ndomains; @@ -184,7 +242,8 @@ namespace ntt { const std::vector g_species_params; #if defined(OUTPUT_ENABLED) - out::Writer g_writer; + out::Writer g_writer; + checkpoint::Writer g_checkpoint_writer; #endif #if defined(MPI_ENABLED) diff --git a/src/framework/domain/output.cpp b/src/framework/domain/output.cpp index cabb37093..0918eb2d3 100644 --- a/src/framework/domain/output.cpp +++ b/src/framework/domain/output.cpp @@ -26,23 +26,29 @@ #include #include +#if defined(MPI_ENABLED) + #include +#endif // MPI_ENABLED + #include +#include #include #include namespace ntt { template - void Metadomain::InitWriter(const SimulationParams& params) { + void Metadomain::InitWriter(adios2::ADIOS* ptr_adios, + const SimulationParams& params, + bool is_resuming) { raise::ErrorIf( - local_subdomain_indices().size() != 1, + l_subdomain_indices().size() != 1, "Output for now is only supported for one subdomain per rank", HERE); - auto local_domain = subdomain_ptr(local_subdomain_indices()[0]); + auto local_domain = subdomain_ptr(l_subdomain_indices()[0]); raise::ErrorIf(local_domain->is_placeholder(), "local_domain is a placeholder", HERE); - const auto incl_ghosts = params.template get("output.debug.ghosts"); auto glob_shape_with_ghosts = mesh().n_active(); @@ -57,6 +63,9 @@ namespace ntt { } } + g_writer.init(ptr_adios, + params.template get("output.format"), + params.template get("simulation.name")); g_writer.defineMeshLayout(glob_shape_with_ghosts, off_ncells_with_ghosts, loc_shape_with_ghosts, @@ -89,7 +98,11 @@ namespace ntt { params.template get( "output." + std::string(type) + ".interval_time")); } - g_writer.writeAttrs(params); + if (is_resuming and std::filesystem::exists(g_writer.fname())) { + g_writer.setMode(adios2::Mode::Append); + } else { + g_writer.writeAttrs(params); + } } template @@ -166,36 +179,41 @@ namespace ntt { template auto Metadomain::Write( const SimulationParams& params, - std::size_t step, - long double time, + std::size_t current_step, + std::size_t finished_step, + long double current_time, + long double finished_time, std::function< void(const std::string&, ndfield_t&, std::size_t, const Domain&)> CustomFieldOutput) -> bool { raise::ErrorIf( - local_subdomain_indices().size() != 1, + l_subdomain_indices().size() != 1, "Output for now is only supported for one subdomain per rank", HERE); const auto write_fields = params.template get( "output.fields.enable") and - g_writer.shouldWrite("fields", step, time); + g_writer.shouldWrite("fields", + finished_step, + finished_time); const auto write_particles = params.template get( "output.particles.enable") and - g_writer.shouldWrite("particles", step, time); + g_writer.shouldWrite("particles", + finished_step, + finished_time); const auto write_spectra = params.template get( "output.spectra.enable") and - g_writer.shouldWrite("spectra", step, time); + g_writer.shouldWrite("spectra", + finished_step, + finished_time); if (not(write_fields or write_particles or write_spectra)) { return false; } - auto local_domain = subdomain_ptr(local_subdomain_indices()[0]); + auto local_domain = subdomain_ptr(l_subdomain_indices()[0]); raise::ErrorIf(local_domain->is_placeholder(), "local_domain is a placeholder", HERE); logger::Checkpoint("Writing output", HERE); - g_writer.beginWriting(params.template get("simulation.name"), - step, - time); - + g_writer.beginWriting(current_step, current_time); if (write_fields) { const auto incl_ghosts = params.template get("output.debug.ghosts"); @@ -458,34 +476,55 @@ namespace ntt { ((D == Dim::_2D) and (M::CoordType != Coord::Cart))) { buff_x3 = array_t { "x3", nout }; } - // clang-format off - Kokkos::parallel_for( - "PrtlToPhys", - nout, - kernel::PrtlToPhys_kernel(prtl_stride, - buff_x1, buff_x2, buff_x3, - buff_ux1, buff_ux2, buff_ux3, - buff_wei, - species.i1, species.i2, species.i3, - species.dx1, species.dx2, species.dx3, - species.ux1, species.ux2, species.ux3, - species.phi, species.weight, - local_domain->mesh.metric)); - // clang-format on - g_writer.writeParticleQuantity(buff_wei, prtl.name("W", 0)); - g_writer.writeParticleQuantity(buff_ux1, prtl.name("U", 1)); - g_writer.writeParticleQuantity(buff_ux2, prtl.name("U", 2)); - g_writer.writeParticleQuantity(buff_ux3, prtl.name("U", 3)); + if (nout > 0) { + // clang-format off + Kokkos::parallel_for( + "PrtlToPhys", + nout, + kernel::PrtlToPhys_kernel(prtl_stride, + buff_x1, buff_x2, buff_x3, + buff_ux1, buff_ux2, buff_ux3, + buff_wei, + species.i1, species.i2, species.i3, + species.dx1, species.dx2, species.dx3, + species.ux1, species.ux2, species.ux3, + species.phi, species.weight, + local_domain->mesh.metric)); + // clang-format on + } + std::size_t offset = 0; + std::size_t glob_tot = nout; +#if defined(MPI_ENABLED) + auto glob_nout = std::vector(g_ndomains); + MPI_Allgather(&nout, + 1, + mpi::get_type(), + glob_nout.data(), + 1, + mpi::get_type(), + MPI_COMM_WORLD); + glob_tot = 0; + for (auto r = 0; r < g_mpi_size; ++r) { + if (r < g_mpi_rank) { + offset += glob_nout[r]; + } + glob_tot += glob_nout[r]; + } +#endif // MPI_ENABLED + g_writer.writeParticleQuantity(buff_wei, glob_tot, offset, prtl.name("W", 0)); + g_writer.writeParticleQuantity(buff_ux1, glob_tot, offset, prtl.name("U", 1)); + g_writer.writeParticleQuantity(buff_ux2, glob_tot, offset, prtl.name("U", 2)); + g_writer.writeParticleQuantity(buff_ux3, glob_tot, offset, prtl.name("U", 3)); if constexpr (M::Dim == Dim::_1D or M::Dim == Dim::_2D or M::Dim == Dim::_3D) { - g_writer.writeParticleQuantity(buff_x1, prtl.name("X", 1)); + g_writer.writeParticleQuantity(buff_x1, glob_tot, offset, prtl.name("X", 1)); } if constexpr (M::Dim == Dim::_2D or M::Dim == Dim::_3D) { - g_writer.writeParticleQuantity(buff_x2, prtl.name("X", 2)); + g_writer.writeParticleQuantity(buff_x2, glob_tot, offset, prtl.name("X", 2)); } if constexpr (M::Dim == Dim::_3D or ((D == Dim::_2D) and (M::CoordType != Coord::Cart))) { - g_writer.writeParticleQuantity(buff_x3, prtl.name("X", 3)); + g_writer.writeParticleQuantity(buff_x3, glob_tot, offset, prtl.name("X", 3)); } } } // end shouldWrite("particles", step, time) diff --git a/src/framework/parameters.cpp b/src/framework/parameters.cpp index 6e581a6b7..1d4672212 100644 --- a/src/framework/parameters.cpp +++ b/src/framework/parameters.cpp @@ -8,6 +8,7 @@ #include "utils/formatting.h" #include "utils/log.h" #include "utils/numeric.h" +#include "utils/toml.h" #include "metrics/kerr_schild.h" #include "metrics/kerr_schild_0.h" @@ -18,8 +19,6 @@ #include "framework/containers/species.h" -#include - #if defined(MPI_ENABLED) #include #endif @@ -46,15 +45,15 @@ namespace ntt { return { dx0, V0 }; } - SimulationParams::SimulationParams(const toml::value& raw_data) { + /* + * . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + * Parameters that must not be changed during after the checkpoint restart + * . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + */ + void SimulationParams::setImmutableParams(const toml::value& toml_data) { /* [simulation] --------------------------------------------------------- */ - set("simulation.name", toml::find(raw_data, "simulation", "name")); - set("simulation.runtime", - toml::find(raw_data, "simulation", "runtime")); - - const auto engine = fmt::toLower( - toml::find(raw_data, "simulation", "engine")); - const auto engine_enum = SimEngine::pick(engine.c_str()); + const auto engine_enum = SimEngine::pick( + fmt::toLower(toml::find(toml_data, "simulation", "engine")).c_str()); set("simulation.engine", engine_enum); int default_ndomains = 1; @@ -63,7 +62,7 @@ namespace ntt { "MPI_Comm_size failed", HERE); #endif - const auto ndoms = toml::find_or(raw_data, + const auto ndoms = toml::find_or(toml_data, "simulation", "domain", "number", @@ -71,7 +70,7 @@ namespace ntt { set("simulation.domain.number", (unsigned int)ndoms); auto decomposition = toml::find_or>( - raw_data, + toml_data, "simulation", "domain", "decomposition", @@ -79,7 +78,7 @@ namespace ntt { promiseToDefine("simulation.domain.decomposition"); /* [grid] --------------------------------------------------------------- */ - const auto res = toml::find>(raw_data, + const auto res = toml::find>(toml_data, "grid", "resolution"); raise::ErrorIf(res.size() < 1 || res.size() > 3, @@ -98,7 +97,7 @@ namespace ntt { HERE); set("simulation.domain.decomposition", decomposition); - auto extent = toml::find>>(raw_data, + auto extent = toml::find>>(toml_data, "grid", "extent"); raise::ErrorIf(extent.size() < 1 || extent.size() > 3, @@ -107,17 +106,18 @@ namespace ntt { promiseToDefine("grid.extent"); /* [grid.metric] -------------------------------------------------------- */ - const auto metric = fmt::toLower( - toml::find(raw_data, "grid", "metric", "metric")); - const auto metric_enum = Metric::pick(metric.c_str()); + const auto metric_enum = Metric::pick( + fmt::toLower(toml::find(toml_data, "grid", "metric", "metric")) + .c_str()); promiseToDefine("grid.metric.metric"); std::string coord; - if (metric == "minkowski") { + if (metric_enum == Metric::Minkowski) { raise::ErrorIf(engine_enum != SimEngine::SRPIC, "minkowski metric is only supported for SRPIC", HERE); coord = "cart"; - } else if (metric[0] == 'q') { + } else if (metric_enum == Metric::QKerr_Schild or + metric_enum == Metric::QSpherical) { // quasi-spherical geometry raise::ErrorIf(dim == Dim::_1D, "not enough dimensions for qspherical geometry", @@ -127,9 +127,9 @@ namespace ntt { HERE); coord = "qsph"; set("grid.metric.qsph_r0", - toml::find_or(raw_data, "grid", "metric", "qsph_r0", defaults::qsph::r0)); + toml::find_or(toml_data, "grid", "metric", "qsph_r0", defaults::qsph::r0)); set("grid.metric.qsph_h", - toml::find_or(raw_data, "grid", "metric", "qsph_h", defaults::qsph::h)); + toml::find_or(toml_data, "grid", "metric", "qsph_h", defaults::qsph::h)); } else { // spherical geometry raise::ErrorIf(dim == Dim::_1D, @@ -142,7 +142,7 @@ namespace ntt { } if ((engine_enum == SimEngine::GRPIC) && (metric_enum != Metric::Kerr_Schild_0)) { - const auto ks_a = toml::find_or(raw_data, + const auto ks_a = toml::find_or(toml_data, "grid", "metric", "ks_a", @@ -153,9 +153,193 @@ namespace ntt { const auto coord_enum = Coord::pick(coord.c_str()); set("grid.metric.coord", coord_enum); + /* [scales] ------------------------------------------------------------- */ + const auto larmor0 = toml::find(toml_data, "scales", "larmor0"); + const auto skindepth0 = toml::find(toml_data, "scales", "skindepth0"); + raise::ErrorIf(larmor0 <= ZERO || skindepth0 <= ZERO, + "larmor0 and skindepth0 must be positive", + HERE); + set("scales.larmor0", larmor0); + set("scales.skindepth0", skindepth0); + promiseToDefine("scales.dx0"); + promiseToDefine("scales.V0"); + promiseToDefine("scales.n0"); + promiseToDefine("scales.q0"); + set("scales.sigma0", SQR(skindepth0 / larmor0)); + set("scales.B0", ONE / larmor0); + set("scales.omegaB0", ONE / larmor0); + + /* [particles] ---------------------------------------------------------- */ + const auto ppc0 = toml::find(toml_data, "particles", "ppc0"); + set("particles.ppc0", ppc0); + raise::ErrorIf(ppc0 <= 0.0, "ppc0 must be positive", HERE); + set("particles.use_weights", + toml::find_or(toml_data, "particles", "use_weights", false)); + + /* [particles.species] -------------------------------------------------- */ + std::vector species; + const auto species_tab = toml::find_or(toml_data, + "particles", + "species", + toml::array {}); + set("particles.nspec", species_tab.size()); + + unsigned short idx = 1; + for (const auto& sp : species_tab) { + const auto label = toml::find_or(sp, + "label", + "s" + std::to_string(idx)); + const auto mass = toml::find(sp, "mass"); + const auto charge = toml::find(sp, "charge"); + raise::ErrorIf((charge != 0.0f) && (mass == 0.0f), + "mass of the charged species must be non-zero", + HERE); + const auto is_massless = (mass == 0.0f) && (charge == 0.0f); + const auto def_pusher = (is_massless ? defaults::ph_pusher + : defaults::em_pusher); + const auto maxnpart_real = toml::find(sp, "maxnpart"); + const auto maxnpart = static_cast(maxnpart_real); + auto pusher = toml::find_or(sp, "pusher", std::string(def_pusher)); + const auto npayloads = toml::find_or(sp, + "n_payloads", + static_cast(0)); + const auto cooling = toml::find_or(sp, "cooling", std::string("None")); + raise::ErrorIf((fmt::toLower(cooling) != "none") && is_massless, + "cooling is only applicable to massive particles", + HERE); + raise::ErrorIf((fmt::toLower(pusher) == "photon") && !is_massless, + "photon pusher is only applicable to massless particles", + HERE); + bool use_gca = false; + if (pusher.find(',') != std::string::npos) { + raise::ErrorIf(fmt::toLower(pusher.substr(pusher.find(',') + 1, + pusher.size())) != "gca", + "invalid pusher syntax", + HERE); + use_gca = true; + pusher = pusher.substr(0, pusher.find(',')); + } + const auto pusher_enum = PrtlPusher::pick(pusher.c_str()); + const auto cooling_enum = Cooling::pick(cooling.c_str()); + if (use_gca) { + raise::ErrorIf(engine_enum != SimEngine::SRPIC, + "GCA pushers are only supported for SRPIC", + HERE); + promiseToDefine("algorithms.gca.e_ovr_b_max"); + promiseToDefine("algorithms.gca.larmor_max"); + } + if (cooling_enum == Cooling::SYNCHROTRON) { + raise::ErrorIf(engine_enum != SimEngine::SRPIC, + "Synchrotron cooling is only supported for SRPIC", + HERE); + promiseToDefine("algorithms.synchrotron.gamma_rad"); + } + + species.emplace_back(ParticleSpecies(idx, + label, + mass, + charge, + maxnpart, + pusher_enum, + use_gca, + cooling_enum, + npayloads)); + idx += 1; + } + set("particles.species", species); + + /* inferred variables --------------------------------------------------- */ + // extent + if (extent.size() > dim) { + extent.erase(extent.begin() + (std::size_t)(dim), extent.end()); + } + raise::ErrorIf(extent[0].size() != 2, "invalid `grid.extent[0]`", HERE); + if (coord_enum != Coord::Cart) { + raise::ErrorIf(extent.size() > 1, + "invalid `grid.extent` for non-cartesian geometry", + HERE); + extent.push_back({ ZERO, constant::PI }); + if (dim == Dim::_3D) { + extent.push_back({ ZERO, TWO * constant::PI }); + } + } + raise::ErrorIf(extent.size() != dim, "invalid inferred `grid.extent`", HERE); + boundaries_t extent_pairwise; + for (unsigned short d = 0; d < (unsigned short)dim; ++d) { + raise::ErrorIf(extent[d].size() != 2, + fmt::format("invalid inferred `grid.extent[%d]`", d), + HERE); + extent_pairwise.push_back({ extent[d][0], extent[d][1] }); + } + set("grid.extent", extent_pairwise); + + // metric, dx0, V0, n0, q0 + { + boundaries_t ext; + for (const auto& e : extent) { + ext.push_back({ e[0], e[1] }); + } + std::map params; + if (coord_enum == Coord::Qsph) { + params["r0"] = get("grid.metric.qsph_r0"); + params["h"] = get("grid.metric.qsph_h"); + } + if ((engine_enum == SimEngine::GRPIC) && + (metric_enum != Metric::Kerr_Schild_0)) { + params["a"] = get("grid.metric.ks_a"); + } + set("grid.metric.params", params); + + std::pair dx0_V0; + if (metric_enum == Metric::Minkowski) { + if (dim == Dim::_1D) { + dx0_V0 = get_dx0_V0>(res, ext, params); + } else if (dim == Dim::_2D) { + dx0_V0 = get_dx0_V0>(res, ext, params); + } else { + dx0_V0 = get_dx0_V0>(res, ext, params); + } + } else if (metric_enum == Metric::Spherical) { + dx0_V0 = get_dx0_V0>(res, ext, params); + } else if (metric_enum == Metric::QSpherical) { + dx0_V0 = get_dx0_V0>(res, ext, params); + } else if (metric_enum == Metric::Kerr_Schild) { + dx0_V0 = get_dx0_V0>(res, ext, params); + } else if (metric_enum == Metric::Kerr_Schild_0) { + dx0_V0 = get_dx0_V0>(res, ext, params); + } else if (metric_enum == Metric::QKerr_Schild) { + dx0_V0 = get_dx0_V0>(res, ext, params); + } + auto [dx0, V0] = dx0_V0; + set("scales.dx0", dx0); + set("scales.V0", V0); + set("scales.n0", ppc0 / V0); + set("scales.q0", V0 / (ppc0 * SQR(skindepth0))); + + set("grid.metric.metric", metric_enum); + } + } + + /* + * . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + * Parameters that may be changed during after the checkpoint restart + * . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + */ + void SimulationParams::setMutableParams(const toml::value& toml_data) { + const auto engine_enum = get("simulation.engine"); + const auto coord_enum = get("grid.metric.coord"); + const auto dim = get("grid.dim"); + const auto extent_pairwise = get>("grid.extent"); + + /* [simulation] --------------------------------------------------------- */ + set("simulation.name", + toml::find(toml_data, "simulation", "name")); + set("simulation.runtime", + toml::find(toml_data, "simulation", "runtime")); + /* [grid.boundaraies] --------------------------------------------------- */ auto flds_bc = toml::find>>( - raw_data, + toml_data, "grid", "boundaries", "fields"); @@ -188,7 +372,7 @@ namespace ntt { } auto prtl_bc = toml::find>>( - raw_data, + toml_data, "grid", "boundaries", "particles"); @@ -220,41 +404,26 @@ namespace ntt { } } - /* [scales] ------------------------------------------------------------- */ - const auto larmor0 = toml::find(raw_data, "scales", "larmor0"); - const auto skindepth0 = toml::find(raw_data, "scales", "skindepth0"); - raise::ErrorIf(larmor0 <= ZERO || skindepth0 <= ZERO, - "larmor0 and skindepth0 must be positive", - HERE); - set("scales.larmor0", larmor0); - set("scales.skindepth0", skindepth0); - promiseToDefine("scales.dx0"); - promiseToDefine("scales.V0"); - promiseToDefine("scales.n0"); - promiseToDefine("scales.q0"); - set("scales.sigma0", SQR(skindepth0 / larmor0)); - set("scales.B0", ONE / larmor0); - set("scales.omegaB0", ONE / larmor0); - /* [algorithms] --------------------------------------------------------- */ set("algorithms.current_filters", - toml::find_or(raw_data, + toml::find_or(toml_data, "algorithms", "current_filters", defaults::current_filters)); /* [algorithms.toggles] ------------------------------------------------- */ set("algorithms.toggles.fieldsolver", - toml::find_or(raw_data, "algorithms", "toggles", "fieldsolver", true)); + toml::find_or(toml_data, "algorithms", "toggles", "fieldsolver", true)); set("algorithms.toggles.deposit", - toml::find_or(raw_data, "algorithms", "toggles", "deposit", true)); + toml::find_or(toml_data, "algorithms", "toggles", "deposit", true)); /* [algorithms.timestep] ------------------------------------------------ */ set("algorithms.timestep.CFL", - toml::find_or(raw_data, "algorithms", "timestep", "CFL", defaults::cfl)); - promiseToDefine("algorithms.timestep.dt"); + toml::find_or(toml_data, "algorithms", "timestep", "CFL", defaults::cfl)); + set("algorithms.timestep.dt", + get("algorithms.timestep.CFL") * get("scales.dx0")); set("algorithms.timestep.correction", - toml::find_or(raw_data, + toml::find_or(toml_data, "algorithms", "timestep", "correction", @@ -263,116 +432,37 @@ namespace ntt { /* [algorithms.gr] ------------------------------------------------------ */ if (engine_enum == SimEngine::GRPIC) { set("algorithms.gr.pusher_eps", - toml::find_or(raw_data, + toml::find_or(toml_data, "algorithms", "gr", "pusher_eps", defaults::gr::pusher_eps)); set("algorithms.gr.pusher_niter", - toml::find_or(raw_data, + toml::find_or(toml_data, "algorithms", "gr", "pusher_niter", defaults::gr::pusher_niter)); } - /* [particles] ---------------------------------------------------------- */ - const auto ppc0 = toml::find(raw_data, "particles", "ppc0"); - set("particles.ppc0", ppc0); - raise::ErrorIf(ppc0 <= 0.0, "ppc0 must be positive", HERE); - set("particles.use_weights", - toml::find_or(raw_data, "particles", "use_weights", false)); - #if defined(MPI_ENABLED) const std::size_t sort_interval = 1; #else - const std::size_t sort_interval = toml::find_or(raw_data, + const std::size_t sort_interval = toml::find_or(toml_data, "particles", "sort_interval", defaults::sort_interval); #endif set("particles.sort_interval", sort_interval); - /* [particles.species] -------------------------------------------------- */ - std::vector species; - const auto species_tab = toml::find_or(raw_data, - "particles", - "species", - toml::array {}); - set("particles.nspec", species_tab.size()); - - unsigned short idx = 1; - for (const auto& sp : species_tab) { - const auto label = toml::find_or(sp, - "label", - "s" + std::to_string(idx)); - const auto mass = toml::find(sp, "mass"); - const auto charge = toml::find(sp, "charge"); - raise::ErrorIf((charge != 0.0f) && (mass == 0.0f), - "mass of the charged species must be non-zero", - HERE); - const auto is_massless = (mass == 0.0f) && (charge == 0.0f); - const auto def_pusher = (is_massless ? defaults::ph_pusher - : defaults::em_pusher); - const auto maxnpart_real = toml::find(sp, "maxnpart"); - const auto maxnpart = static_cast(maxnpart_real); - auto pusher = toml::find_or(sp, "pusher", std::string(def_pusher)); - const auto npayloads = toml::find_or(sp, - "n_payloads", - static_cast(0)); - const auto cooling = toml::find_or(sp, "cooling", std::string("None")); - raise::ErrorIf((fmt::toLower(cooling) != "none") && is_massless, - "cooling is only applicable to massive particles", - HERE); - raise::ErrorIf((fmt::toLower(pusher) == "photon") && !is_massless, - "photon pusher is only applicable to massless particles", - HERE); - bool use_gca = false; - if (pusher.find(',') != std::string::npos) { - raise::ErrorIf(fmt::toLower(pusher.substr(pusher.find(',') + 1, - pusher.size())) != "gca", - "invalid pusher syntax", - HERE); - use_gca = true; - pusher = pusher.substr(0, pusher.find(',')); - } - const auto pusher_enum = PrtlPusher::pick(pusher.c_str()); - const auto cooling_enum = Cooling::pick(cooling.c_str()); - if (use_gca) { - raise::ErrorIf(engine_enum != SimEngine::SRPIC, - "GCA pushers are only supported for SRPIC", - HERE); - promiseToDefine("algorithms.gca.e_ovr_b_max"); - promiseToDefine("algorithms.gca.larmor_max"); - } - if (cooling_enum == Cooling::SYNCHROTRON) { - raise::ErrorIf(engine_enum != SimEngine::SRPIC, - "Synchrotron cooling is only supported for SRPIC", - HERE); - promiseToDefine("algorithms.synchrotron.gamma_rad"); - } - - species.emplace_back(ParticleSpecies(idx, - label, - mass, - charge, - maxnpart, - pusher_enum, - use_gca, - cooling_enum, - npayloads)); - idx += 1; - } - set("particles.species", species); - /* [output] ------------------------------------------------------------- */ // fields set("output.format", - toml::find_or(raw_data, "output", "format", defaults::output::format)); + toml::find_or(toml_data, "output", "format", defaults::output::format)); set("output.interval", - toml::find_or(raw_data, "output", "interval", defaults::output::interval)); + toml::find_or(toml_data, "output", "interval", defaults::output::interval)); set("output.interval_time", - toml::find_or(raw_data, "output", "interval_time", -1.0)); + toml::find_or(toml_data, "output", "interval_time", -1.0)); promiseToDefine("output.fields.interval"); promiseToDefine("output.fields.interval_time"); promiseToDefine("output.fields.enable"); @@ -383,12 +473,12 @@ namespace ntt { promiseToDefine("output.spectra.interval_time"); promiseToDefine("output.spectra.enable"); - const auto flds_out = toml::find_or(raw_data, + const auto flds_out = toml::find_or(toml_data, "output", "fields", "quantities", std::vector {}); - const auto custom_flds_out = toml::find_or(raw_data, + const auto custom_flds_out = toml::find_or(toml_data, "output", "fields", "custom", @@ -399,23 +489,27 @@ namespace ntt { set("output.fields.quantities", flds_out); set("output.fields.custom", custom_flds_out); set("output.fields.mom_smooth", - toml::find_or(raw_data, + toml::find_or(toml_data, "output", "fields", "mom_smooth", defaults::output::mom_smooth)); set("output.fields.stride", - toml::find_or(raw_data, "output", "fields", "stride", defaults::output::flds_stride)); + toml::find_or(toml_data, + "output", + "fields", + "stride", + defaults::output::flds_stride)); // particles - const auto prtl_out = toml::find_or(raw_data, + const auto prtl_out = toml::find_or(toml_data, "output", "particles", "species", std::vector {}); set("output.particles.species", prtl_out); set("output.particles.stride", - toml::find_or(raw_data, + toml::find_or(toml_data, "output", "particles", "stride", @@ -423,32 +517,36 @@ namespace ntt { // spectra set("output.spectra.e_min", - toml::find_or(raw_data, "output", "spectra", "e_min", defaults::output::spec_emin)); + toml::find_or(toml_data, "output", "spectra", "e_min", defaults::output::spec_emin)); set("output.spectra.e_max", - toml::find_or(raw_data, "output", "spectra", "e_max", defaults::output::spec_emax)); + toml::find_or(toml_data, "output", "spectra", "e_max", defaults::output::spec_emax)); set("output.spectra.log_bins", - toml::find_or(raw_data, + toml::find_or(toml_data, "output", "spectra", "log_bins", defaults::output::spec_log)); set("output.spectra.n_bins", - toml::find_or(raw_data, "output", "spectra", "n_bins", defaults::output::spec_nbins)); + toml::find_or(toml_data, + "output", + "spectra", + "n_bins", + defaults::output::spec_nbins)); // intervals for (const auto& type : { "fields", "particles", "spectra" }) { - const auto q_int = toml::find_or(raw_data, + const auto q_int = toml::find_or(toml_data, "output", std::string(type), "interval", 0); - const auto q_int_time = toml::find_or(raw_data, + const auto q_int_time = toml::find_or(toml_data, "output", std::string(type), "interval_time", -1.0); set("output." + std::string(type) + ".enable", - toml::find_or(raw_data, "output", std::string(type), "enable", true)); + toml::find_or(toml_data, "output", std::string(type), "enable", true)); if (q_int == 0 && q_int_time == -1.0) { set("output." + std::string(type) + ".interval", get("output.interval")); @@ -462,43 +560,30 @@ namespace ntt { /* [output.debug] ------------------------------------------------------- */ set("output.debug.as_is", - toml::find_or(raw_data, "output", "debug", "as_is", false)); + toml::find_or(toml_data, "output", "debug", "as_is", false)); set("output.debug.ghosts", - toml::find_or(raw_data, "output", "debug", "ghosts", false)); + toml::find_or(toml_data, "output", "debug", "ghosts", false)); + + /* [checkpoint] --------------------------------------------------------- */ + set("checkpoint.interval", + toml::find_or(toml_data, + "checkpoint", + "interval", + defaults::checkpoint::interval)); + set("checkpoint.interval_time", + toml::find_or(toml_data, "checkpoint", "interval_time", -1.0)); + set("checkpoint.keep", + toml::find_or(toml_data, "checkpoint", "keep", defaults::checkpoint::keep)); /* [diagnostics] -------------------------------------------------------- */ set("diagnostics.interval", - toml::find_or(raw_data, "diagnostics", "interval", defaults::diag::interval)); + toml::find_or(toml_data, "diagnostics", "interval", defaults::diag::interval)); set("diagnostics.blocking_timers", - toml::find_or(raw_data, "diagnostics", "blocking_timers", false)); + toml::find_or(toml_data, "diagnostics", "blocking_timers", false)); set("diagnostics.colored_stdout", - toml::find_or(raw_data, "diagnostics", "colored_stdout", false)); + toml::find_or(toml_data, "diagnostics", "colored_stdout", false)); /* inferred variables --------------------------------------------------- */ - // extent - if (extent.size() > dim) { - extent.erase(extent.begin() + (std::size_t)(dim), extent.end()); - } - raise::ErrorIf(extent[0].size() != 2, "invalid `grid.extent[0]`", HERE); - if (coord_enum != Coord::Cart) { - raise::ErrorIf(extent.size() > 1, - "invalid `grid.extent` for non-cartesian geometry", - HERE); - extent.push_back({ ZERO, constant::PI }); - if (dim == Dim::_3D) { - extent.push_back({ ZERO, TWO * constant::PI }); - } - } - raise::ErrorIf(extent.size() != dim, "invalid inferred `grid.extent`", HERE); - boundaries_t extent_parwise; - for (unsigned short d = 0; d < (unsigned short)dim; ++d) { - raise::ErrorIf(extent[d].size() != 2, - fmt::format("invalid inferred `grid.extent[%d]`", d), - HERE); - extent_parwise.push_back({ extent[d][0], extent[d][1] }); - } - set("grid.extent", extent_parwise); - // fields/particle boundaries std::vector> flds_bc_enum; std::vector> prtl_bc_enum; @@ -629,20 +714,20 @@ namespace ntt { if (isPromised("grid.boundaries.absorb.ds")) { if (coord_enum == Coord::Cart) { auto min_extent = std::numeric_limits::max(); - for (const auto& e : extent) { - min_extent = std::min(min_extent, e[1] - e[0]); + for (const auto& e : extent_pairwise) { + min_extent = std::min(min_extent, e.second - e.first); } set("grid.boundaries.absorb.ds", - toml::find_or(raw_data, + toml::find_or(toml_data, "grid", "boundaries", "absorb", "ds", min_extent * defaults::bc::absorb::ds_frac)); } else { - auto r_extent = extent[0][1] - extent[0][0]; + auto r_extent = extent_pairwise[0].second - extent_pairwise[0].first; set("grid.boundaries.absorb.ds", - toml::find_or(raw_data, + toml::find_or(toml_data, "grid", "boundaries", "absorb", @@ -650,7 +735,7 @@ namespace ntt { r_extent * defaults::bc::absorb::ds_frac)); } set("grid.boundaries.absorb.coeff", - toml::find_or(raw_data, + toml::find_or(toml_data, "grid", "boundaries", "absorb", @@ -659,25 +744,25 @@ namespace ntt { } if (isPromised("grid.boundaries.atmosphere.temperature")) { - const auto atm_T = toml::find(raw_data, + const auto atm_T = toml::find(toml_data, "grid", "boundaries", "atmosphere", "temperature"); - const auto atm_h = toml::find(raw_data, + const auto atm_h = toml::find(toml_data, "grid", "boundaries", "atmosphere", "height"); set("grid.boundaries.atmosphere.temperature", atm_T); set("grid.boundaries.atmosphere.density", - toml::find(raw_data, "grid", "boundaries", "atmosphere", "density")); + toml::find(toml_data, "grid", "boundaries", "atmosphere", "density")); set("grid.boundaries.atmosphere.ds", - toml::find_or(raw_data, "grid", "boundaries", "atmosphere", "ds", ZERO)); + toml::find_or(toml_data, "grid", "boundaries", "atmosphere", "ds", ZERO)); set("grid.boundaries.atmosphere.height", atm_h); set("grid.boundaries.atmosphere.g", atm_T / atm_h); const auto atm_species = toml::find>( - raw_data, + toml_data, "grid", "boundaries", "atmosphere", @@ -688,78 +773,29 @@ namespace ntt { // gca if (isPromised("algorithms.gca.e_ovr_b_max")) { set("algorithms.gca.e_ovr_b_max", - toml::find_or(raw_data, + toml::find_or(toml_data, "algorithms", "gca", "e_ovr_b_max", defaults::gca::EovrB_max)); set("algorithms.gca.larmor_max", - toml::find_or(raw_data, "algorithms", "gca", "larmor_max", ZERO)); + toml::find_or(toml_data, "algorithms", "gca", "larmor_max", ZERO)); } // cooling if (isPromised("algorithms.synchrotron.gamma_rad")) { set("algorithms.synchrotron.gamma_rad", - toml::find_or(raw_data, + toml::find_or(toml_data, "algorithms", "synchrotron", "gamma_rad", defaults::synchrotron::gamma_rad)); } + } - // metric, dx0, V0, n0, q0 - { - boundaries_t ext; - for (const auto& e : extent) { - ext.push_back({ e[0], e[1] }); - } - std::map params; - if (coord_enum == Coord::Qsph) { - params["r0"] = get("grid.metric.qsph_r0"); - params["h"] = get("grid.metric.qsph_h"); - } - if ((engine_enum == SimEngine::GRPIC) && - (metric_enum != Metric::Kerr_Schild_0)) { - params["a"] = get("grid.metric.ks_a"); - } - set("grid.metric.params", params); - - std::pair dx0_V0; - if (metric_enum == Metric::Minkowski) { - if (dim == Dim::_1D) { - dx0_V0 = get_dx0_V0>(res, ext, params); - } else if (dim == Dim::_2D) { - dx0_V0 = get_dx0_V0>(res, ext, params); - } else { - dx0_V0 = get_dx0_V0>(res, ext, params); - } - } else if (metric_enum == Metric::Spherical) { - dx0_V0 = get_dx0_V0>(res, ext, params); - } else if (metric_enum == Metric::QSpherical) { - dx0_V0 = get_dx0_V0>(res, ext, params); - } else if (metric_enum == Metric::Kerr_Schild) { - dx0_V0 = get_dx0_V0>(res, ext, params); - } else if (metric_enum == Metric::Kerr_Schild_0) { - dx0_V0 = get_dx0_V0>(res, ext, params); - } else if (metric_enum == Metric::QKerr_Schild) { - dx0_V0 = get_dx0_V0>(res, ext, params); - } - auto [dx0, V0] = dx0_V0; - set("scales.dx0", dx0); - set("scales.V0", V0); - set("scales.n0", ppc0 / V0); - set("scales.q0", V0 / (ppc0 * SQR(skindepth0))); - - set("grid.metric.metric", metric_enum); - set("algorithms.timestep.dt", get("algorithms.timestep.CFL") * dx0); - } - - raise::ErrorIf(!promisesFulfilled(), - "Have not defined all the necessary variables", - HERE); - + void SimulationParams::setSetupParams(const toml::value& toml_data) { /* [setup] -------------------------------------------------------------- */ - const auto& setup = toml::find_or(raw_data, "setup", toml::table {}); + const auto& setup = toml::find_or(toml_data, "setup", toml::table {}); for (const auto& [key, val] : setup) { if (val.is_boolean()) { set("setup." + key, (bool)(val.as_boolean())); @@ -807,4 +843,18 @@ namespace ntt { } } } + + void SimulationParams::setCheckpointParams(bool is_resuming, + std::size_t start_step, + long double start_time) { + set("checkpoint.is_resuming", is_resuming); + set("checkpoint.start_step", start_step); + set("checkpoint.start_time", start_time); + } + + void SimulationParams::checkPromises() const { + raise::ErrorIf(!promisesFulfilled(), + "Have not defined all the necessary variables", + HERE); + } } // namespace ntt diff --git a/src/framework/parameters.h b/src/framework/parameters.h index 301f7053f..0f9e29370 100644 --- a/src/framework/parameters.h +++ b/src/framework/parameters.h @@ -18,22 +18,40 @@ #define FRAMEWORK_PARAMETERS_H #include "utils/param_container.h" - -#include +#include "utils/toml.h" namespace ntt { struct SimulationParams : public prm::Parameters { - SimulationParams() = default; - SimulationParams(const toml::value&); + + SimulationParams() {} SimulationParams& operator=(const SimulationParams& other) { vars = std::move(other.vars); promises = std::move(other.promises); + raw_data = std::move(other.raw_data); return *this; } ~SimulationParams() = default; + + void setImmutableParams(const toml::value&); + void setMutableParams(const toml::value&); + void setCheckpointParams(bool, std::size_t, long double); + void setSetupParams(const toml::value&); + void checkPromises() const; + + [[nodiscard]] + auto data() const -> const toml::value& { + return raw_data; + } + + void setRawData(const toml::value& data) { + raw_data = data; + } + + private: + toml::value raw_data; }; } // namespace ntt diff --git a/src/framework/simulation.cpp b/src/framework/simulation.cpp index b913379b4..9961eb3eb 100644 --- a/src/framework/simulation.cpp +++ b/src/framework/simulation.cpp @@ -1,40 +1,102 @@ #include "framework/simulation.h" #include "defaults.h" +#include "enums.h" #include "global.h" #include "utils/cargs.h" #include "utils/error.h" #include "utils/formatting.h" +#include "utils/log.h" #include "utils/plog.h" +#include "utils/toml.h" #include "framework/parameters.h" -#include - +#include #include namespace ntt { Simulation::Simulation(int argc, char* argv[]) { - GlobalInitialize(argc, argv); - cargs::CommandLineArguments cl_args; cl_args.readCommandLineArguments(argc, argv); const auto inputfname = static_cast( cl_args.getArgument("-input", defaults::input_filename)); - const auto outputdir = static_cast( - cl_args.getArgument("-output", defaults::output_path)); - const auto inputdata = toml::parse(inputfname); - const auto sim_name = toml::find(inputdata, "simulation", "name"); + const bool is_resuming = (cl_args.isSpecified("-continue") or + cl_args.isSpecified("-restart") or + cl_args.isSpecified("-resume") or + cl_args.isSpecified("-checkpoint")); + GlobalInitialize(argc, argv); + + const auto raw_params = toml::parse(inputfname); + const auto sim_name = toml::find(raw_params, "simulation", "name"); logger::initPlog(sim_name); - params = SimulationParams(inputdata); + m_requested_engine = SimEngine::pick( + fmt::toLower(toml::find(raw_params, "simulation", "engine")).c_str()); + m_requested_metric = Metric::pick( + fmt::toLower(toml::find(raw_params, "grid", "metric", "metric")) + .c_str()); + + const auto res = toml::find>(raw_params, + "grid", + "resolution"); + raise::ErrorIf(res.size() < 1 || res.size() > 3, + "invalid `grid.resolution`", + HERE); + m_requested_dimension = static_cast(res.size()); + + // !TODO: when mixing checkpoint metadata with input, + // ... need to properly take care of the diffs + m_params.setRawData(raw_params); + std::size_t checkpoint_step = 0; + if (is_resuming) { + logger::Checkpoint("Reading params from a checkpoint", HERE); + if (not std::filesystem::exists("checkpoints")) { + raise::Fatal("No checkpoints found", HERE); + } + for (const auto& entry : + std::filesystem::directory_iterator("checkpoints")) { + const auto fname = entry.path().filename().string(); + if (fname.find("step-") == 0) { + const std::size_t step = std::stoi(fname.substr(5, fname.size() - 5 - 3)); + if (step > checkpoint_step) { + checkpoint_step = step; + } + } + } + std::string checkpoint_inputfname = fmt::format( + "checkpoints/meta-%08lu.toml", + checkpoint_step); + if (not std::filesystem::exists(checkpoint_inputfname)) { + raise::Fatal( + fmt::format("metainformation for %lu not found", checkpoint_step), + HERE); + checkpoint_inputfname = inputfname; + } + logger::Checkpoint(fmt::format("Using %08lu", checkpoint_step), HERE); + const auto raw_checkpoint_params = toml::parse(checkpoint_inputfname); + const auto start_time = toml::find(raw_checkpoint_params, + "metadata", + "time"); + m_params.setImmutableParams(raw_checkpoint_params); + m_params.setMutableParams(raw_params); + m_params.setCheckpointParams(true, checkpoint_step, start_time); + m_params.setSetupParams(raw_checkpoint_params); + } else { + logger::Checkpoint("Defining new params", HERE); + m_params.setImmutableParams(raw_params); + m_params.setMutableParams(raw_params); + m_params.setCheckpointParams(false, 0, 0.0); + m_params.setSetupParams(raw_params); + } + m_params.checkPromises(); } Simulation::~Simulation() { GlobalFinalize(); } -} // namespace ntt \ No newline at end of file +} // namespace ntt diff --git a/src/framework/simulation.h b/src/framework/simulation.h index 2f4d3d321..3cc664995 100644 --- a/src/framework/simulation.h +++ b/src/framework/simulation.h @@ -18,6 +18,7 @@ #include "arch/traits.h" #include "utils/error.h" +#include "utils/toml.h" #include "framework/parameters.h" @@ -27,7 +28,11 @@ namespace ntt { class Simulation { - SimulationParams params; + SimulationParams m_params; + + Dimension m_requested_dimension; + SimEngine m_requested_engine { SimEngine::INVALID }; + Metric m_requested_metric { Metric::INVALID }; public: Simulation(int argc, char* argv[]); @@ -41,7 +46,7 @@ namespace ntt { static_assert(traits::has_method::value, "Engine must contain a ::run() method"); try { - engine_t engine { params }; + engine_t engine { m_params }; engine.run(); } catch (const std::exception& e) { raise::Fatal(e.what(), HERE); @@ -50,17 +55,17 @@ namespace ntt { [[nodiscard]] inline auto requested_dimension() const -> Dimension { - return params.get("grid.dim"); + return m_requested_dimension; } [[nodiscard]] inline auto requested_engine() const -> SimEngine { - return params.get("simulation.engine"); + return m_requested_engine; } [[nodiscard]] inline auto requested_metric() const -> Metric { - return params.get("grid.metric.metric"); + return m_requested_metric; } }; diff --git a/src/framework/tests/CMakeLists.txt b/src/framework/tests/CMakeLists.txt index c09d4ecc0..56ad0783b 100644 --- a/src/framework/tests/CMakeLists.txt +++ b/src/framework/tests/CMakeLists.txt @@ -26,16 +26,16 @@ function(gen_test title) endfunction() if (${mpi}) -gen_test(comm_mpi) + gen_test(comm_mpi) else() -gen_test(parameters) -gen_test(particles) -gen_test(fields) -gen_test(grid_mesh) -if (${DEBUG}) - gen_test(metadomain) -endif() -gen_test(comm_nompi) + gen_test(parameters) + gen_test(particles) + gen_test(fields) + gen_test(grid_mesh) + if (${DEBUG}) + gen_test(metadomain) + endif() + gen_test(comm_nompi) endif() diff --git a/src/framework/tests/parameters.cpp b/src/framework/tests/parameters.cpp index 8f02d1750..8d30355b9 100644 --- a/src/framework/tests/parameters.cpp +++ b/src/framework/tests/parameters.cpp @@ -5,11 +5,11 @@ #include "utils/comparators.h" #include "utils/error.h" +#include "utils/toml.h" #include "framework/containers/species.h" #include -#include #include #include @@ -242,7 +242,11 @@ auto main(int argc, char* argv[]) -> int { using namespace ntt; { - const auto params_mink_1d = SimulationParams(mink_1d); + auto params_mink_1d = SimulationParams(); + params_mink_1d.setImmutableParams(mink_1d); + params_mink_1d.setMutableParams(mink_1d); + params_mink_1d.setSetupParams(mink_1d); + params_mink_1d.checkPromises(); assert_equal(params_mink_1d.get("grid.metric.metric"), Metric::Minkowski, @@ -314,7 +318,11 @@ auto main(int argc, char* argv[]) -> int { } { - const auto params_sph_2d = SimulationParams(sph_2d); + auto params_sph_2d = SimulationParams(); + params_sph_2d.setImmutableParams(sph_2d); + params_sph_2d.setMutableParams(sph_2d); + params_sph_2d.setSetupParams(sph_2d); + params_sph_2d.checkPromises(); assert_equal(params_sph_2d.get("grid.metric.metric"), Metric::Spherical, @@ -427,7 +435,11 @@ auto main(int argc, char* argv[]) -> int { } { - const auto params_qks_2d = SimulationParams(qks_2d); + auto params_qks_2d = SimulationParams(); + params_qks_2d.setImmutableParams(qks_2d); + params_qks_2d.setMutableParams(qks_2d); + params_qks_2d.setSetupParams(qks_2d); + params_qks_2d.checkPromises(); assert_equal(params_qks_2d.get("grid.metric.metric"), Metric::QKerr_Schild, diff --git a/src/global/CMakeLists.txt b/src/global/CMakeLists.txt index d00689283..334ce078d 100644 --- a/src/global/CMakeLists.txt +++ b/src/global/CMakeLists.txt @@ -4,6 +4,10 @@ # - global.cpp # - arch/kokkos_aliases.cpp # - utils/cargs.cpp +# - utils/param_container.cpp +# - utils/timer.cpp +# - utils/diag.cpp +# - utils/progressbar.cpp # @includes: # - ./ # @uses: @@ -17,10 +21,16 @@ set(SOURCES ${SRC_DIR}/global.cpp ${SRC_DIR}/arch/kokkos_aliases.cpp ${SRC_DIR}/utils/cargs.cpp + ${SRC_DIR}/utils/timer.cpp + ${SRC_DIR}/utils/diag.cpp + ${SRC_DIR}/utils/progressbar.cpp ) +if (${output}) + list(APPEND SOURCES ${SRC_DIR}/utils/param_container.cpp) +endif() add_library(ntt_global ${SOURCES}) target_include_directories(ntt_global PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} ) -target_link_libraries(ntt_global PRIVATE stdc++fs) \ No newline at end of file +target_link_libraries(ntt_global PRIVATE stdc++fs) diff --git a/src/global/arch/mpi_aliases.h b/src/global/arch/mpi_aliases.h index 9669d6210..1f9a87f7b 100644 --- a/src/global/arch/mpi_aliases.h +++ b/src/global/arch/mpi_aliases.h @@ -14,6 +14,7 @@ #ifndef GLOBAL_ARCH_MPI_ALIASES_H #define GLOBAL_ARCH_MPI_ALIASES_H +#include #include #if defined(MPI_ENABLED) @@ -103,4 +104,4 @@ namespace mpi { #endif // MPI_ENABLED -#endif // GLOBAL_ARCH_MPI_ALIASES_H \ No newline at end of file +#endif // GLOBAL_ARCH_MPI_ALIASES_H diff --git a/src/global/defaults.h b/src/global/defaults.h index d238a3492..ee9a65af5 100644 --- a/src/global/defaults.h +++ b/src/global/defaults.h @@ -16,7 +16,6 @@ namespace ntt::defaults { constexpr std::string_view input_filename = "input"; - constexpr std::string_view output_path = "output"; const real_t correction = 1.0; const real_t cfl = 0.95; @@ -60,6 +59,11 @@ namespace ntt::defaults { const std::size_t spec_nbins = 200; } // namespace output + namespace checkpoint { + const std::size_t interval = 1000; + const int keep = 2; + } // namespace checkpoint + namespace diag { const std::size_t interval = 1; } // namespace diag diff --git a/src/global/global.h b/src/global/global.h index ca067d547..ad524fb0e 100644 --- a/src/global/global.h +++ b/src/global/global.h @@ -204,18 +204,15 @@ typedef int PrepareOutputFlags; namespace Timer { enum TimerFlags_ { - None = 0, - PrintRelative = 1 << 0, - PrintUnits = 1 << 1, - PrintIndents = 1 << 2, - PrintTotal = 1 << 3, - PrintTitle = 1 << 4, - AutoConvert = 1 << 5, - Colorful = 1 << 6, - PrintOutput = 1 << 7, - PrintSorting = 1 << 8, - Default = PrintRelative | PrintUnits | PrintIndents | PrintTotal | - PrintTitle | AutoConvert | Colorful, + None = 0, + PrintTotal = 1 << 0, + PrintTitle = 1 << 1, + AutoConvert = 1 << 2, + PrintOutput = 1 << 3, + PrintSorting = 1 << 4, + PrintCheckpoint = 1 << 5, + PrintNormed = 1 << 6, + Default = PrintNormed | PrintTotal | PrintTitle | AutoConvert, }; } // namespace Timer diff --git a/src/global/utils/colors.h b/src/global/utils/colors.h index 512ad81c7..6f5e775cf 100644 --- a/src/global/utils/colors.h +++ b/src/global/utils/colors.h @@ -53,7 +53,8 @@ namespace color { return msg_nocol; } - inline auto get_color(const std::string& s, bool eight_bit) -> std::string { + inline auto get_color(const std::string& s, bool eight_bit = true) + -> std::string { if (not eight_bit) { return ""; } else { diff --git a/src/global/utils/diag.cpp b/src/global/utils/diag.cpp new file mode 100644 index 000000000..0a499dd56 --- /dev/null +++ b/src/global/utils/diag.cpp @@ -0,0 +1,246 @@ +#include "utils/diag.h" + +#include "global.h" + +#include "utils/colors.h" +#include "utils/formatting.h" +#include "utils/progressbar.h" +#include "utils/timer.h" + +#if defined(MPI_ENABLED) + #include "arch/mpi_aliases.h" + + #include +#endif // MPI_ENABLED + +#include +#include +#include +#include +#include +#include + +namespace diag { + auto npart_stats(std::size_t npart, std::size_t maxnpart) + -> std::vector> { + auto stats = std::vector>(); +#if !defined(MPI_ENABLED) + stats.push_back( + { npart, + static_cast( + 100.0f * static_cast(npart) / static_cast(maxnpart)) }); +#else + int rank, size; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + std::vector mpi_npart(size, 0); + std::vector mpi_maxnpart(size, 0); + MPI_Gather(&npart, + 1, + mpi::get_type(), + mpi_npart.data(), + 1, + mpi::get_type(), + MPI_ROOT_RANK, + MPI_COMM_WORLD); + MPI_Gather(&maxnpart, + 1, + mpi::get_type(), + mpi_maxnpart.data(), + 1, + mpi::get_type(), + MPI_ROOT_RANK, + MPI_COMM_WORLD); + if (rank != MPI_ROOT_RANK) { + return stats; + } + auto tot_npart = std::accumulate(mpi_npart.begin(), mpi_npart.end(), 0); + const auto max_idx = std::distance( + mpi_npart.begin(), + std::max_element(mpi_npart.begin(), mpi_npart.end())); + const auto min_idx = std::distance( + mpi_npart.begin(), + std::min_element(mpi_npart.begin(), mpi_npart.end())); + stats.push_back({ tot_npart, 0u }); + stats.push_back({ mpi_npart[min_idx], + static_cast( + 100.0f * static_cast(mpi_npart[min_idx]) / + static_cast(mpi_maxnpart[min_idx])) }); + stats.push_back({ mpi_npart[max_idx], + static_cast( + 100.0f * static_cast(mpi_npart[max_idx]) / + static_cast(mpi_maxnpart[max_idx])) }); +#endif + return stats; + } + + void printDiagnostics(std::size_t step, + std::size_t tot_steps, + long double time, + long double dt, + timer::Timers& timers, + pbar::DurationHistory& time_history, + std::size_t ncells, + const std::vector& species_labels, + const std::vector& species_npart, + const std::vector& species_maxnpart, + bool print_sorting, + bool print_output, + bool print_checkpoint, + bool print_colors) { + DiagFlags diag_flags = Diag::Default; + TimerFlags timer_flags = Timer::Default; + if (not print_colors) { + diag_flags ^= Diag::Colorful; + } + if (species_labels.size() == 0) { + diag_flags ^= Diag::Species; + } + if (print_sorting) { + timer_flags |= Timer::PrintSorting; + } + if (print_output) { + timer_flags |= Timer::PrintOutput; + } + if (print_checkpoint) { + timer_flags |= Timer::PrintCheckpoint; + } + + std::stringstream ss; + + const auto c_red = color::get_color("red"); + const auto c_yellow = color::get_color("yellow"); + const auto c_green = color::get_color("green"); + const auto c_bgreen = color::get_color("bgreen"); + const auto c_bblack = color::get_color("bblack"); + const auto c_reset = color::get_color("reset"); + + // basic info + CallOnce([&]() { + ss << fmt::alignedTable( + { "Step:", fmt::format("%lu", step), fmt::format("[of %lu]", tot_steps) }, + { c_reset, c_bgreen, c_bblack }, + { 0, -6, -15 }, + { ' ', ' ', ' ' }, + c_bblack, + c_reset); + + ss << fmt::alignedTable( + { "Time:", fmt::format("%.4Lf", time), fmt::format("[Δt = %.4Lf]", dt) }, + { c_reset, c_bgreen, c_bblack }, + { 0, -6, -15 }, + { ' ', ' ', ' ' }, + c_bblack, + c_reset); + }); + + // substep timers + if (diag_flags & Diag::Timers) { + const auto total_npart = std::accumulate(species_npart.begin(), + species_npart.end(), + 0); + const auto timer_diag = timers.printAll(timer_flags, total_npart, ncells); + CallOnce([&]() { + ss << std::endl << timer_diag << std::endl; + }); + } + + // particle counts + if (diag_flags & Diag::Species) { +#if defined(MPI_ENABLED) + CallOnce([&]() { + ss << fmt::alignedTable( + { "[PARTICLE SPECIES]", "[TOTAL]", "[% MIN", "MAX]", "[MIN", "MAX]" }, + { c_bblack, c_bblack, c_bblack, c_bblack, c_bblack, c_bblack }, + { 0, 37, 45, -48, 63, -66 }, + { ' ', ' ', ' ', ':', ' ', ':' }, + c_bblack, + c_reset); + }); +#else + CallOnce([&]() { + ss << fmt::alignedTable({ "[PARTICLE SPECIES]", "[TOTAL]", "[% TOT]" }, + { c_bblack, c_bblack, c_bblack }, + { 0, 37, 45 }, + { ' ', ' ', ' ' }, + c_bblack, + c_reset); + }); +#endif + for (std::size_t i = 0; i < species_labels.size(); ++i) { + const auto part_stats = npart_stats(species_npart[i], species_maxnpart[i]); + if (part_stats.size() == 0) { + continue; + } + const auto tot_npart = part_stats[0].first; +#if defined(MPI_ENABLED) + const auto min_npart = part_stats[1].first; + const auto min_pct = part_stats[1].second; + const auto max_npart = part_stats[2].first; + const auto max_pct = part_stats[2].second; + ss << fmt::alignedTable( + { + fmt::format("species %2lu (%s)", i, species_labels[i].c_str()), + tot_npart > 9999 ? fmt::format("%.2Le", (long double)tot_npart) + : std::to_string(tot_npart), + std::to_string(min_pct) + "%", + std::to_string(max_pct) + "%", + min_npart > 9999 ? fmt::format("%.2Le", (long double)min_npart) + : std::to_string(min_npart), + max_npart > 9999 ? fmt::format("%.2Le", (long double)max_npart) + : std::to_string(max_npart), + }, + { + c_reset, + c_reset, + (min_pct > 80) ? c_red : ((min_pct > 50) ? c_yellow : c_green), + (max_pct > 80) ? c_red : ((max_pct > 50) ? c_yellow : c_green), + c_reset, + c_reset, + }, + { -2, 37, 45, -48, 63, -66 }, + { ' ', '.', ' ', ':', ' ', ':' }, + c_bblack, + c_reset); +#else + const auto tot_pct = part_stats[0].second; + ss << fmt::alignedTable( + { + fmt::format("species %2lu (%s)", i, species_labels[i].c_str()), + tot_npart > 9999 ? fmt::format("%.2Le", (long double)tot_npart) + : std::to_string(tot_npart), + std::to_string(tot_pct) + "%", + }, + { + c_reset, + c_reset, + (tot_pct > 80) ? c_red : ((tot_pct > 50) ? c_yellow : c_green), + }, + { -2, 37, 45 }, + { ' ', '.', ' ' }, + c_bblack, + c_reset); +#endif + } + CallOnce([&]() { + ss << std::endl; + }); + } + + // progress bar + if (diag_flags & Diag::Progress) { + const auto progbar = pbar::ProgressBar(time_history, step, tot_steps, diag_flags); + CallOnce([&]() { + ss << progbar; + }); + } + + // separator + CallOnce([&]() { + ss << std::setw(80) << std::setfill('.') << "" << std::endl << std::endl; + }); + + std::cout << ((diag_flags & Diag::Colorful) ? ss.str() + : color::strip(ss.str())); + } +} // namespace diag diff --git a/src/global/utils/diag.h b/src/global/utils/diag.h new file mode 100644 index 000000000..9951602f8 --- /dev/null +++ b/src/global/utils/diag.h @@ -0,0 +1,59 @@ +/** + * @file utils/diag.h + * @brief Routines for diagnostics output at every step + * @implements + * - diag::printDiagnostics -> void + * @cpp: + * - diag.cpp + * @namespces: + * - diag:: + * @macros: + * - MPI_ENABLED + */ + +#ifndef GLOBAL_UTILS_DIAG_H +#define GLOBAL_UTILS_DIAG_H + +#include "utils/progressbar.h" +#include "utils/timer.h" + +#include +#include + +namespace diag { + + /** + * @brief Print diagnostics to the console + * @param step + * @param tot_steps + * @param time + * @param dt + * @param timers + * @param duration_history + * @param ncells (total) + * @param species_labels (vector of particle labels) + * @param npart (per each species) + * @param maxnpart (per each species) + * @param sorting_step (if true, particles were sorted) + * @param output_step (if true, output was written) + * @param checkpoint_step (if true, checkpoint was written) + * @param colorful_print (if true, print with colors) + */ + void printDiagnostics(std::size_t, + std::size_t, + long double, + long double, + timer::Timers&, + pbar::DurationHistory&, + std::size_t, + const std::vector&, + const std::vector&, + const std::vector&, + bool, + bool, + bool, + bool); + +} // namespace diag + +#endif // GLOBAL_UTILS_DIAG_H diff --git a/src/global/utils/formatting.h b/src/global/utils/formatting.h index 85f4e4b43..8dc7b6ba8 100644 --- a/src/global/utils/formatting.h +++ b/src/global/utils/formatting.h @@ -8,6 +8,8 @@ * - fmt::splitString -> std::vector * - fmt::repeat -> std::string * - fmt::formatVector -> std::string + * - fmt::strlen_utf8 -> std::size_t + * - fmt::alignedTable -> std::string * @namespaces: * - fmt:: */ @@ -132,6 +134,66 @@ namespace fmt { return result; } + inline auto repeat(char s, std::size_t n) -> std::string { + return repeat(std::string(1, s), n); + } + + /** + * @brief Calculate the length of a UTF-8 string + * @param str UTF-8 string + */ + inline auto strlenUTF8(const std::string& str) -> std::size_t { + std::size_t length = 0; + for (char c : str) { + if ((c & 0xC0) != 0x80) { + ++length; + } + } + return length; + } + + /** + * @brief Create a table with aligned columns and custom colors & separators + * @param columns Vector of column strings + * @param colors Vector of colors + * @param anchors Vector of column anchors (position of edge, negative means left-align) + * @param fillers Vector of separators + * @param c_bblack Black color + * @param c_reset Reset color + */ + inline auto alignedTable(const std::vector& columns, + const std::vector& colors, + const std::vector& anchors, + const std::vector& fillers, + const std::string& c_bblack, + const std::string& c_reset) -> std::string { + std::string result { c_reset }; + std::size_t cntr { 0 }; + for (auto i { 0u }; i < columns.size(); ++i) { + const auto anch { static_cast(anchors[i] < 0 ? -anchors[i] + : anchors[i]) }; + const auto leftalign { anchors[i] <= 0 }; + const auto cmn { columns[i] }; + const auto cmn_len { strlenUTF8(cmn) }; + std::string left { c_bblack }; + if (leftalign) { + if (fillers[i] == ':') { + left += " :"; + left += repeat(' ', anch - cntr - 2); + } else { + left += repeat(fillers[i], anch - cntr); + } + cntr += anch - cntr; + } else { + left += repeat(fillers[i], anch - cntr - cmn_len); + cntr += anch - cntr - cmn_len; + } + result += left + colors[i] + cmn + c_reset; + cntr += cmn_len; + } + return result + c_reset + "\n"; + } + } // namespace fmt #endif // GLOBAL_UTILS_FORMATTING_H diff --git a/src/global/utils/log.h b/src/global/utils/log.h index ac5bc4059..2434414a4 100644 --- a/src/global/utils/log.h +++ b/src/global/utils/log.h @@ -34,6 +34,8 @@ #endif namespace raise { + using namespace files; + inline void Warning(const std::string& msg, const std::string& file, const std::string& func, diff --git a/src/global/utils/param_container.cpp b/src/global/utils/param_container.cpp new file mode 100644 index 000000000..6e4f5e0f4 --- /dev/null +++ b/src/global/utils/param_container.cpp @@ -0,0 +1,333 @@ +#if defined(OUTPUT_ENABLED) + #include "utils/param_container.h" + + #include "enums.h" + #include "global.h" + + #include + #include + + #include + #include + #include + #include + #include + #include + #include + #include + #include + +namespace prm { + template + struct has_to_string : std::false_type {}; + + template + struct has_to_string().to_string())>> + : std::true_type {}; + + template + auto write(adios2::IO& io, const std::string& name, T var) -> + typename std::enable_if::value, void>::type { + io.DefineAttribute(name, std::string(var.to_string())); + } + + template + auto write(adios2::IO& io, const std::string& name, T var) + -> decltype(void(T()), void()) { + io.DefineAttribute(name, var); + } + + template <> + void write(adios2::IO& io, const std::string& name, bool var) { + io.DefineAttribute(name, var ? 1 : 0); + } + + template <> + void write(adios2::IO& io, const std::string& name, Dimension var) { + io.DefineAttribute(name, (unsigned short)var); + } + + template + auto write_pair(adios2::IO& io, const std::string& name, std::pair var) + -> typename std::enable_if::value, void>::type { + std::vector var_str; + var_str.push_back(var.first.to_string()); + var_str.push_back(var.second.to_string()); + io.DefineAttribute(name, var_str.data(), var_str.size()); + } + + template + auto write_pair(adios2::IO& io, const std::string& name, std::pair var) + -> decltype(void(T()), void()) { + std::vector var_vec; + var_vec.push_back(var.first); + var_vec.push_back(var.second); + io.DefineAttribute(name, var_vec.data(), var_vec.size()); + } + + template + auto write_vec(adios2::IO& io, const std::string& name, std::vector var) -> + typename std::enable_if::value, void>::type { + std::vector var_str; + for (const auto& v : var) { + var_str.push_back(v.to_string()); + } + io.DefineAttribute(name, var_str.data(), var_str.size()); + } + + template + auto write_vec(adios2::IO& io, const std::string& name, std::vector var) + -> decltype(void(T()), void()) { + io.DefineAttribute(name, var.data(), var.size()); + } + + template + auto write_vec_pair(adios2::IO& io, + const std::string& name, + std::vector> var) -> + typename std::enable_if::value, void>::type { + std::vector var_str; + for (const auto& v : var) { + var_str.push_back(v.first.to_string()); + var_str.push_back(v.second.to_string()); + } + io.DefineAttribute(name, var_str.data(), var_str.size()); + } + + template + auto write_vec_pair(adios2::IO& io, + const std::string& name, + std::vector> var) + -> decltype(void(T()), void()) { + std::vector var_vec; + for (const auto& v : var) { + var_vec.push_back(v.first); + var_vec.push_back(v.second); + } + io.DefineAttribute(name, var_vec.data(), var_vec.size()); + } + + template + auto write_vec_vec(adios2::IO& io, + const std::string& name, + std::vector> var) -> + typename std::enable_if::value, void>::type { + std::vector var_str; + for (const auto& vec : var) { + for (const auto& v : vec) { + var_str.push_back(v.to_string()); + } + } + io.DefineAttribute(name, var_str.data(), var_str.size()); + } + + template + auto write_vec_vec(adios2::IO& io, + const std::string& name, + std::vector> var) + -> decltype(void(T()), void()) { + std::vector var_vec; + for (const auto& vec : var) { + for (const auto& v : vec) { + var_vec.push_back(v); + } + } + io.DefineAttribute(name, var_vec.data(), var_vec.size()); + } + + template + auto write_dict(adios2::IO& io, + const std::string& name, + std::map var) -> + typename std::enable_if::value, void>::type { + for (const auto& [key, v] : var) { + io.DefineAttribute(name + "_" + key, v.to_string()); + } + } + + template + auto write_dict(adios2::IO& io, + const std::string& name, + std::map var) -> decltype(void(T()), void()) { + for (const auto& [key, v] : var) { + io.DefineAttribute(name + "_" + key, v); + } + } + + std::map> + write_functions; + + template + void register_write_function() { + write_functions[std::type_index(typeid(T))] = + [](adios2::IO& io, const std::string& name, std::any a) { + write(io, name, std::any_cast(a)); + }; + } + + template + void register_write_function_for_pair() { + write_functions[std::type_index(typeid(std::pair))] = + [](adios2::IO& io, const std::string& name, std::any a) { + write_pair(io, name, std::any_cast>(a)); + }; + } + + template + void register_write_function_for_vector() { + write_functions[std::type_index(typeid(std::vector))] = + [](adios2::IO& io, const std::string& name, std::any a) { + write_vec(io, name, std::any_cast>(a)); + }; + } + + template + void register_write_function_for_vector_of_pair() { + write_functions[std::type_index(typeid(std::vector>))] = + [](adios2::IO& io, const std::string& name, std::any a) { + write_vec_pair(io, name, std::any_cast>>(a)); + }; + } + + template + void register_write_function_for_vector_of_vector() { + write_functions[std::type_index(typeid(std::vector>))] = + [](adios2::IO& io, const std::string& name, std::any a) { + write_vec_vec(io, name, std::any_cast>>(a)); + }; + } + + template + void register_write_function_for_dict() { + write_functions[std::type_index(typeid(std::map))] = + [](adios2::IO& io, const std::string& name, std::any a) { + write_dict(io, name, std::any_cast>(a)); + }; + } + + void write_any(adios2::IO& io, const std::string& name, std::any a) { + auto it = write_functions.find(a.type()); + if (it != write_functions.end()) { + it->second(io, name, a); + } else { + throw std::runtime_error("No write function registered for this type"); + } + } + + void Parameters::write(adios2::IO& io) const { + register_write_function(); + register_write_function(); + register_write_function(); + register_write_function(); + register_write_function(); + register_write_function(); + register_write_function(); + register_write_function(); + register_write_function(); + register_write_function(); + register_write_function(); + register_write_function(); + register_write_function(); + register_write_function(); + register_write_function(); + register_write_function(); + register_write_function(); + register_write_function(); + register_write_function(); + + register_write_function_for_pair(); + register_write_function_for_pair(); + register_write_function_for_pair(); + register_write_function_for_pair(); + register_write_function_for_pair(); + register_write_function_for_pair(); + register_write_function_for_pair(); + register_write_function_for_pair(); + register_write_function_for_pair(); + register_write_function_for_pair(); + register_write_function_for_pair(); + register_write_function_for_pair(); + register_write_function_for_pair(); + register_write_function_for_pair(); + register_write_function_for_pair(); + register_write_function_for_pair(); + register_write_function_for_pair(); + + register_write_function_for_vector(); + register_write_function_for_vector(); + register_write_function_for_vector(); + register_write_function_for_vector(); + register_write_function_for_vector(); + register_write_function_for_vector(); + register_write_function_for_vector(); + register_write_function_for_vector(); + register_write_function_for_vector(); + register_write_function_for_vector(); + register_write_function_for_vector(); + register_write_function_for_vector(); + register_write_function_for_vector(); + register_write_function_for_vector(); + register_write_function_for_vector(); + register_write_function_for_vector(); + register_write_function_for_vector(); + + register_write_function_for_vector_of_pair(); + register_write_function_for_vector_of_pair(); + register_write_function_for_vector_of_pair(); + register_write_function_for_vector_of_pair(); + register_write_function_for_vector_of_pair(); + register_write_function_for_vector_of_pair(); + register_write_function_for_vector_of_pair(); + register_write_function_for_vector_of_pair(); + register_write_function_for_vector_of_pair(); + register_write_function_for_vector_of_pair(); + register_write_function_for_vector_of_pair(); + register_write_function_for_vector_of_pair(); + register_write_function_for_vector_of_pair(); + register_write_function_for_vector_of_pair(); + register_write_function_for_vector_of_pair(); + register_write_function_for_vector_of_pair(); + register_write_function_for_vector_of_pair(); + + register_write_function_for_vector_of_vector(); + register_write_function_for_vector_of_vector(); + register_write_function_for_vector_of_vector(); + register_write_function_for_vector_of_vector(); + register_write_function_for_vector_of_vector(); + register_write_function_for_vector_of_vector(); + register_write_function_for_vector_of_vector(); + register_write_function_for_vector_of_vector(); + register_write_function_for_vector_of_vector(); + register_write_function_for_vector_of_vector(); + register_write_function_for_vector_of_vector(); + register_write_function_for_vector_of_vector(); + register_write_function_for_vector_of_vector(); + register_write_function_for_vector_of_vector(); + register_write_function_for_vector_of_vector(); + register_write_function_for_vector_of_vector(); + register_write_function_for_vector_of_vector(); + + register_write_function_for_dict(); + register_write_function_for_dict(); + register_write_function_for_dict(); + register_write_function_for_dict(); + register_write_function_for_dict(); + register_write_function_for_dict(); + register_write_function_for_dict(); + register_write_function_for_dict(); + register_write_function_for_dict(); + register_write_function_for_dict(); + register_write_function_for_dict(); + + for (auto& [key, value] : allVars()) { + try { + write_any(io, key, value); + } catch (const std::exception& e) { + continue; + } + } + } + +} // namespace prm + +#endif diff --git a/src/global/utils/param_container.h b/src/global/utils/param_container.h index dccf91d09..679b80552 100644 --- a/src/global/utils/param_container.h +++ b/src/global/utils/param_container.h @@ -16,6 +16,11 @@ #include "utils/formatting.h" #include "utils/log.h" +#if defined(OUTPUT_ENABLED) + #include + #include +#endif + #include #include #include @@ -172,6 +177,10 @@ namespace prm { } return result.str(); } + +#if defined(OUTPUT_ENABLED) + void write(adios2::IO& io) const; +#endif }; } // namespace prm diff --git a/src/global/utils/progressbar.cpp b/src/global/utils/progressbar.cpp new file mode 100644 index 000000000..74f952382 --- /dev/null +++ b/src/global/utils/progressbar.cpp @@ -0,0 +1,120 @@ +#include "utils/progressbar.h" + +#include "utils/error.h" +#include "utils/formatting.h" + +#if defined(MPI_ENABLED) + #include "arch/mpi_aliases.h" + + #include +#endif // MPI_ENABLED + +#include +#include +#include +#include +#include +#include +#include + +namespace pbar { + + auto normalize_duration_fmt(long double t, const std::string& u) + -> std::pair { + const std::vector> units { + {"µs", 1e0}, + { "ms", 1e3}, + { "s", 1e6}, + {"min", 6e7}, + { "hr", 3.6e9} + }; + auto it = std::find_if(units.begin(), units.end(), [&u](const auto& pr) { + return pr.first == u; + }); + int u_idx = (it != units.end()) ? std::distance(units.begin(), it) : -1; + raise::ErrorIf(u_idx < 0, "Invalid unit", HERE); + int shift = 0; + if (t < 1) { + shift = -1; + } else if (1e3 <= t && t < 1e6) { + shift = 1; + } else if (1e6 <= t && t < 6e7) { + shift += 2; + } else if (6e7 <= t && t < 3.6e9) { + shift += 3; + } else if (3.6e9 <= t) { + shift += 4; + } + auto newu_idx = std::min(std::max(0, u_idx + shift), + static_cast(units.size())); + return { t * (units[u_idx].second / units[newu_idx].second), + units[newu_idx].first }; + } + + auto to_human_readable(long double t, const std::string& u) -> std::string { + const auto [tt, tu] = normalize_duration_fmt(t, u); + const auto t1 = static_cast(tt); + const auto t2 = tt - static_cast(t1); + const auto [tt2, tu2] = normalize_duration_fmt(t2, tu); + return fmt::format("%d%s %d%s", t1, tu.c_str(), static_cast(tt2), tu2.c_str()); + } + + auto ProgressBar(const DurationHistory& history, + std::size_t step, + std::size_t max_steps, + DiagFlags& flags) -> std::string { + auto avg_duration = history.average(); + +#if defined(MPI_ENABLED) + int rank, size; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + std::vector mpi_avg_durations(size, 0.0); + MPI_Gather(&avg_duration, + 1, + mpi::get_type(), + mpi_avg_durations.data(), + 1, + mpi::get_type(), + MPI_ROOT_RANK, + MPI_COMM_WORLD); + if (rank != MPI_ROOT_RANK) { + return ""; + } + avg_duration = *std::max_element(mpi_avg_durations.begin(), + mpi_avg_durations.end()); +#endif + + const auto avg = to_human_readable(avg_duration, "µs"); + const auto elapsed = to_human_readable(history.elapsed(), "µs"); + const auto remain = to_human_readable( + static_cast(max_steps - step) * avg_duration, + "µs"); + + const auto pct = static_cast(step) / + static_cast(max_steps); + const int nfilled = std::min(static_cast(pct * params::width), + params::width); + const int nempty = params::width - nfilled; + const auto c_bmagenta = color::get_color("bmagenta", flags & Diag::Colorful); + const auto c_reset = color::get_color("reset", flags & Diag::Colorful); + + std::stringstream ss; + + ss << "Timestep duration: " << c_bmagenta << avg << c_reset << std::endl; + ss << "Remaining time: " << c_bmagenta << remain << c_reset << std::endl; + ss << "Elapsed time: " << c_bmagenta << elapsed << c_reset << std::endl; + ss << params::start; + for (auto i { 0 }; i < nfilled; ++i) { + ss << params::fill; + } + for (auto i { 0 }; i < nempty; ++i) { + ss << params::empty; + } + ss << params::end << " " << std::fixed << std::setprecision(2) + << std::setfill(' ') << std::setw(6) << std::right << pct * 100.0 << "%\n"; + + return ss.str(); + } + +} // namespace pbar diff --git a/src/global/utils/progressbar.h b/src/global/utils/progressbar.h index ccbc6215e..f218bc3f4 100644 --- a/src/global/utils/progressbar.h +++ b/src/global/utils/progressbar.h @@ -3,6 +3,8 @@ * @brief Progress bar for logging the simulation progress * @implements * - pbar::ProgressBar -> void + * @cpp: + * - progressbar.cpp * @namespaces: * - pbar:: * @macros: @@ -16,22 +18,15 @@ #include "utils/colors.h" #include "utils/error.h" +#include "utils/formatting.h" #include -#include -#include #include #include #include #include #include -#if defined(MPI_ENABLED) - #include "arch/mpi_aliases.h" - - #include -#endif // MPI_ENABLED - namespace pbar { namespace params { inline constexpr int width { 70 }; @@ -81,96 +76,15 @@ namespace pbar { } }; - inline auto normalize_duration_fmt(long double t, const std::string& u) - -> std::pair { - const std::vector> units { - {"µs", 1e0}, - { "ms", 1e3}, - { "s", 1e6}, - {"min", 6e7}, - { "hr", 3.6e9} - }; - auto it = std::find_if(units.begin(), units.end(), [&u](const auto& pr) { - return pr.first == u; - }); - int u_idx = (it != units.end()) ? std::distance(units.begin(), it) : -1; - raise::ErrorIf(u_idx < 0, "Invalid unit", HERE); - int shift = 0; - if (t < 1e-2) { - shift = -1; - } else if (1e3 <= t && t < 1e6) { - shift = 1; - } else if (1e6 <= t && t < 6e7) { - shift += 2; - } else if (6e7 <= t && t < 3.6e9) { - shift += 3; - } else if (3.6e9 <= t) { - shift += 4; - } - auto newu_idx = std::min(std::max(0, u_idx + shift), - static_cast(units.size())); - return { t * (units[u_idx].second / units[newu_idx].second), - units[newu_idx].first }; - } - - inline void ProgressBar(const DurationHistory& history, - std::size_t step, - std::size_t max_steps, - DiagFlags& flags, - std::ostream& os = std::cout) { - auto avg_duration = history.average(); + auto normalize_duration_fmt(long double t, const std::string& u) + -> std::pair; -#if defined(MPI_ENABLED) - int rank, size; - MPI_Comm_rank(MPI_COMM_WORLD, &rank); - MPI_Comm_size(MPI_COMM_WORLD, &size); - std::vector mpi_avg_durations(size, 0.0); - MPI_Gather(&avg_duration, - 1, - mpi::get_type(), - mpi_avg_durations.data(), - 1, - mpi::get_type(), - MPI_ROOT_RANK, - MPI_COMM_WORLD); - if (rank != MPI_ROOT_RANK) { - return; - } - avg_duration = *std::max_element(mpi_avg_durations.begin(), - mpi_avg_durations.end()); -#endif - auto [avg_reduced, avg_units] = normalize_duration_fmt(avg_duration, "µs"); - - const auto remain_nsteps = max_steps - step; - auto [remain_time, remain_units] = normalize_duration_fmt( - static_cast(remain_nsteps) * avg_duration, - "µs"); - auto [elapsed_time, - elapsed_units] = normalize_duration_fmt(history.elapsed(), "µs"); + auto to_human_readable(long double t, const std::string& u) -> std::string; - const auto pct = static_cast(step) / - static_cast(max_steps); - const int nfilled = std::min(static_cast(pct * params::width), - params::width); - const int nempty = params::width - nfilled; - const auto c_bmagenta = color::get_color("bmagenta", flags & Diag::Colorful); - const auto c_reset = color::get_color("reset", flags & Diag::Colorful); - os << "Average timestep: " << c_bmagenta << avg_reduced << " " << avg_units - << c_reset << std::endl; - os << "Remaining time: " << c_bmagenta << remain_time << " " << remain_units - << c_reset << std::endl; - os << "Elapsed time: " << c_bmagenta << elapsed_time << " " << elapsed_units - << c_reset << std::endl; - os << params::start; - for (auto i { 0 }; i < nfilled; ++i) { - os << params::fill; - } - for (auto i { 0 }; i < nempty; ++i) { - os << params::empty; - } - os << params::end << " " << std::fixed << std::setprecision(2) - << std::setfill(' ') << std::setw(6) << std::right << pct * 100.0 << "%\n"; - } + auto ProgressBar(const DurationHistory& history, + std::size_t step, + std::size_t max_steps, + DiagFlags& flags) -> std::string; } // namespace pbar diff --git a/src/global/utils/timer.cpp b/src/global/utils/timer.cpp new file mode 100644 index 000000000..b5f4408ca --- /dev/null +++ b/src/global/utils/timer.cpp @@ -0,0 +1,283 @@ +#include "utils/timer.h" + +#include "utils/colors.h" +#include "utils/formatting.h" + +#if defined(MPI_ENABLED) + #include "arch/mpi_aliases.h" + + #include +#endif // MPI_ENABLED + +#include +#include +#include +#include +#include + +namespace timer { + + auto Timers::gather(const std::vector& ignore_in_tot, + std::size_t npart, + std::size_t ncells) const + -> std::map> { + auto timer_stats = std::map< + std::string, + std::tuple> {}; +#if defined(MPI_ENABLED) + int rank, size; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + std::map> all_timers {}; + + // accumulate timers from MPI blocks + for (auto& [name, timer] : m_timers) { + all_timers.insert({ name, std::vector(size, 0.0) }); + MPI_Gather(&timer.second, + 1, + mpi::get_type(), + all_timers[name].data(), + 1, + mpi::get_type(), + MPI_ROOT_RANK, + MPI_COMM_WORLD); + } + // accumulate nparts and ncells from MPI blocks + auto all_nparts = std::vector(size, 0); + auto all_ncells = std::vector(size, 0); + MPI_Gather(&npart, + 1, + mpi::get_type(), + all_nparts.data(), + 1, + mpi::get_type(), + MPI_ROOT_RANK, + MPI_COMM_WORLD); + MPI_Gather(&ncells, + 1, + mpi::get_type(), + all_ncells.data(), + 1, + mpi::get_type(), + MPI_ROOT_RANK, + MPI_COMM_WORLD); + if (rank != MPI_ROOT_RANK) { + return {}; + } + std::vector all_totals(size, 0.0); + for (auto i { 0u }; i < size; ++i) { + for (auto& [name, timer] : m_timers) { + if (std::find(ignore_in_tot.begin(), ignore_in_tot.end(), name) == + ignore_in_tot.end()) { + all_totals[i] += all_timers[name][i]; + } + } + } + for (auto& [name, timer] : m_timers) { + const auto max_time = *std::max_element(all_timers[name].begin(), + all_timers[name].end()); + const auto max_idx = std::distance( + all_timers[name].begin(), + std::max_element(all_timers[name].begin(), all_timers[name].end())); + + const auto per_npart = all_nparts[max_idx] > 0 + ? max_time / + static_cast(all_nparts[max_idx]) + : 0.0; + const auto per_ncells = all_ncells[max_idx] > 0 + ? max_time / + static_cast(all_ncells[max_idx]) + : 0.0; + const auto pcent = static_cast( + (max_time / all_totals[max_idx]) * 100.0); + timer_stats.insert( + { name, + std::make_tuple(max_time, + per_npart, + per_ncells, + pcent, + tools::ArrayImbalance(all_timers[name])) }); + } + const auto max_tot = *std::max_element(all_totals.begin(), all_totals.end()); + const auto tot_imb = tools::ArrayImbalance(all_totals); + timer_stats.insert( + { "Total", std::make_tuple(max_tot, 0.0, 0.0, 100u, tot_imb) }); +#else + duration_t local_tot = 0.0; + for (auto& [name, timer] : m_timers) { + if (std::find(ignore_in_tot.begin(), ignore_in_tot.end(), name) == + ignore_in_tot.end()) { + local_tot += timer.second; + } + } + for (auto& [name, timer] : m_timers) { + const auto pcent = static_cast( + (timer.second / local_tot) * 100.0); + timer_stats.insert( + { name, + std::make_tuple(timer.second, + timer.second / static_cast(npart), + timer.second / static_cast(ncells), + pcent, + 0u) }); + } + timer_stats.insert({ "Total", std::make_tuple(local_tot, 0.0, 0.0, 100u, 0u) }); +#endif + return timer_stats; + } + + auto Timers::printAll(TimerFlags flags, std::size_t npart, std::size_t ncells) const + -> std::string { + const std::vector extras { "Sorting", "Output", "Checkpoint" }; + const auto stats = gather(extras, npart, ncells); + if (stats.empty()) { + return ""; + } + +#if defined(MPI_ENABLED) + const auto multi_rank = true; +#else + const auto multi_rank = false; +#endif + + std::stringstream ss; + + const auto c_bblack = color::get_color("bblack"); + const auto c_reset = color::get_color("reset"); + const auto c_byellow = color::get_color("byellow"); + const auto c_blue = color::get_color("blue"); + const auto c_red = color::get_color("red"); + const auto c_yellow = color::get_color("yellow"); + const auto c_green = color::get_color("green"); + + if (multi_rank and flags & Timer::PrintTitle) { + ss << fmt::alignedTable( + { "[SUBSTEP]", "[MAX DURATION]", "[% TOT", "VAR]", "[per PRTL", "CELL]" }, + { c_bblack, c_bblack, c_bblack, c_bblack, c_bblack, c_bblack }, + { 0, 37, 45, -48, 63, -66 }, + { ' ', '.', ' ', ':', ' ', ':' }, + c_bblack, + c_reset); + } else { + ss << fmt::alignedTable( + { "[SUBSTEP]", "[DURATION]", "[% TOT]", "[per PRTL", "CELL]" }, + { c_bblack, c_bblack, c_bblack, c_bblack, c_bblack }, + { 0, 37, 45, 55, -58 }, + { ' ', '.', ' ', ' ', ':' }, + c_bblack, + c_reset); + } + + for (auto& [name, timers] : m_timers) { + if (std::find(extras.begin(), extras.end(), name) != extras.end()) { + continue; + } + std::string units = "µs", units_npart = "µs", units_ncells = "µs"; + auto time = std::get<0>(stats.at(name)); + auto per_npart = std::get<1>(stats.at(name)); + auto per_ncells = std::get<2>(stats.at(name)); + const auto tot_pct = std::get<3>(stats.at(name)); + const auto var_pct = std::get<4>(stats.at(name)); + if (flags & Timer::AutoConvert) { + convertTime(time, units); + convertTime(per_npart, units_npart); + convertTime(per_ncells, units_ncells); + } + + if (multi_rank) { + ss << fmt::alignedTable( + { name, + fmt::format("%.2Lf", time) + " " + units, + std::to_string(tot_pct) + "%", + std::to_string(var_pct) + "%", + fmt::format("%.2Lf", per_npart) + " " + units_npart, + fmt::format("%.2Lf", per_ncells) + " " + units_ncells }, + { c_reset, + c_yellow, + ((tot_pct > 60) ? c_red : ((tot_pct > 40) ? c_yellow : c_green)), + ((var_pct > 50) ? c_red : ((var_pct > 30) ? c_yellow : c_green)), + c_yellow, + c_yellow }, + { -2, 37, 45, -48, 63, -66 }, + { ' ', '.', ' ', ':', ' ', ':' }, + c_bblack, + c_reset); + } else { + ss << fmt::alignedTable( + { name, + fmt::format("%.2Lf", time) + " " + units, + std::to_string(tot_pct) + "%", + fmt::format("%.2Lf", per_npart) + " " + units_npart, + fmt::format("%.2Lf", per_ncells) + " " + units_ncells }, + { c_reset, + c_yellow, + ((tot_pct > 60) ? c_red : ((tot_pct > 40) ? c_yellow : c_green)), + c_yellow, + c_yellow }, + { -2, 37, 45, 55, -58 }, + { ' ', '.', ' ', ' ', ':' }, + c_bblack, + c_reset); + } + } + + // total + if (flags & Timer::PrintTotal) { + std::string units = "µs"; + auto time = std::get<0>(stats.at("Total")); + const auto var_pct = std::get<4>(stats.at("Total")); + if (flags & Timer::AutoConvert) { + convertTime(time, units); + } + if (multi_rank) { + ss << fmt::alignedTable( + { "Total", + fmt::format("%.2Lf", time) + " " + units, + std::to_string(var_pct) + "%" }, + { c_reset, + c_blue, + ((var_pct > 50) ? c_red : ((var_pct > 30) ? c_yellow : c_green)) }, + { 0, 37, -48 }, + { ' ', ' ', ' ' }, + c_bblack, + c_reset); + } else { + ss << fmt::alignedTable( + { "Total", fmt::format("%.2Lf", time) + " " + units }, + { c_reset, c_blue }, + { 0, 37 }, + { ' ', ' ' }, + c_bblack, + c_reset); + } + } + + // print extra timers for output/checkpoint/sorting + const std::vector extras_f { Timer::PrintSorting, + Timer::PrintOutput, + Timer::PrintCheckpoint }; + for (auto i { 0u }; i < extras.size(); ++i) { + const auto name = extras[i]; + const auto active = flags & extras_f[i]; + std::string units = "µs"; + auto time = std::get<0>(stats.at(name)); + const auto tot_pct = std::get<3>(stats.at(name)); + if (flags & Timer::AutoConvert) { + convertTime(time, units); + } + ss << fmt::alignedTable({ name, + fmt::format("%.2Lf", time) + " " + units, + std::to_string(tot_pct) + "%" }, + { (active ? c_reset : c_bblack), + (active ? c_byellow : c_bblack), + (active ? c_byellow : c_bblack) }, + { -2, 37, 45 }, + { ' ', '.', ' ' }, + c_bblack, + c_reset); + } + return ss.str(); + } + +} // namespace timer diff --git a/src/global/utils/timer.h b/src/global/utils/timer.h index 79f325f0e..3e9c1433e 100644 --- a/src/global/utils/timer.h +++ b/src/global/utils/timer.h @@ -4,6 +4,8 @@ * @implements * - timer::Timers * - enum timer::TimerFlags + * @cpp: + * - timer.cpp * @namespces: * - timer:: * @macros: @@ -21,21 +23,28 @@ #include "utils/error.h" #include "utils/formatting.h" #include "utils/numeric.h" +#include "utils/tools.h" + +#if defined(MPI_ENABLED) + #include "arch/mpi_aliases.h" + + #include +#endif // MPI_ENABLED #include #include -#include -#include #include #include #include +#include #include #include namespace timer { - using timestamp = std::chrono::time_point; + using timestamp = std::chrono::time_point; + using duration_t = long double; - inline void convertTime(long double& value, std::string& units) { + inline void convertTime(duration_t& value, std::string& units) { if (value > 1e6) { value /= 1e6; units = " s"; @@ -49,10 +58,10 @@ namespace timer { } class Timers { - std::map> m_timers; - std::vector m_names; - const bool m_blocking; - const std::function m_synchronize; + std::map> m_timers; + std::vector m_names; + const bool m_blocking; + const std::function m_synchronize; public: Timers(std::initializer_list names, @@ -103,9 +112,9 @@ namespace timer { } [[nodiscard]] - auto get(const std::string& name) const -> long double { + auto get(const std::string& name) const -> duration_t { if (name == "Total") { - long double total = 0.0; + duration_t total = 0.0; for (auto& timer : m_timers) { total += timer.second.second; } @@ -116,174 +125,29 @@ namespace timer { } } - void printAll(const TimerFlags flags = Timer::Default, - std::ostream& os = std::cout) const { - std::string header = fmt::format("%s %27s", "[SUBSTEP]", "[DURATION]"); - - const auto c_bblack = color::get_color("bblack", flags & Timer::Colorful); - const auto c_reset = color::get_color("reset", flags & Timer::Colorful); - const auto c_byellow = color::get_color("byellow", flags & Timer::Colorful); - const auto c_blue = color::get_color("blue", flags & Timer::Colorful); + /** + * @brief Gather all timers from all ranks + * @param ignore_in_tot: vector of timer names to ignore in computing the total + * @return map: + * key: timer name + * value: vector of numbers + * - max duration across ranks + * - max duration per particle + * - max duration per cell + * - max duration as % of total on that rank + * - imbalance % of the given timer + */ + [[nodiscard]] + auto gather(const std::vector& ignore_in_tot, + std::size_t npart, + std::size_t ncells) const + -> std::map>; - if (flags & Timer::PrintRelative) { - header += " [% TOT]"; - } -#if defined(MPI_ENABLED) - header += " [MIN : MAX]"; -#endif - header = c_bblack + header + c_reset; - CallOnce( - [](std::ostream& os, std::string header) { - os << header << std::endl; - }, - os, - header); -#if defined(MPI_ENABLED) - int rank, size; - MPI_Comm_rank(MPI_COMM_WORLD, &rank); - MPI_Comm_size(MPI_COMM_WORLD, &size); - std::map> mpi_timers {}; - // accumulate timers from MPI blocks - for (auto& [name, timer] : m_timers) { - mpi_timers[name] = std::vector(size, 0.0); - MPI_Gather(&timer.second, - 1, - mpi::get_type(), - mpi_timers[name].data(), - 1, - mpi::get_type(), - MPI_ROOT_RANK, - MPI_COMM_WORLD); - } - if (rank != MPI_ROOT_RANK) { - return; - } - long double total = 0.0; - for (auto& [name, timer] : m_timers) { - auto timers = mpi_timers[name]; - long double tot = std::accumulate(timers.begin(), timers.end(), 0.0); - if (name != "Output") { - total += tot; - } - } - for (auto& [name, timers] : mpi_timers) { - // compute min, max, mean - long double min_time = *std::min_element(timers.begin(), timers.end()); - long double max_time = *std::max_element(timers.begin(), timers.end()); - long double mean_time = std::accumulate(timers.begin(), timers.end(), 0.0) / - size; - std::string mean_units = "µs"; - const auto min_pct = mean_time > ZERO - ? (int)(((mean_time - min_time) / mean_time) * 100.0) - : 0; - const auto max_pct = mean_time > ZERO - ? (int)(((max_time - mean_time) / mean_time) * 100.0) - : 0; - const auto tot_pct = (cmp::AlmostZero_host(total) - ? 0 - : (mean_time * size / total) * 100.0); - if (flags & Timer::AutoConvert) { - convertTime(mean_time, mean_units); - } - if (flags & Timer::PrintIndents) { - os << " "; - } - os << ((name != "Sorting" or flags & Timer::PrintSorting) ? c_reset - : c_bblack) - << name << c_reset << c_bblack - << fmt::pad(name, 20, '.', true).substr(name.size(), 20); - os << std::setw(17) << std::right << std::setfill('.') - << fmt::format("%s%.2Lf", - (name != "Sorting" or flags & Timer::PrintSorting) - ? c_byellow.c_str() - : c_bblack.c_str(), - mean_time); - if (flags & Timer::PrintUnits) { - os << " " << mean_units << " "; - } - if (flags & Timer::PrintRelative) { - os << " " << std::setw(5) << std::right << std::setfill(' ') - << std::fixed << std::setprecision(2) << tot_pct << "%"; - } - os << fmt::format("%+7s : %-7s", - fmt::format("-%d%%", min_pct).c_str(), - fmt::format("+%d%%", max_pct).c_str()); - os << c_reset << std::endl; - } - total /= size; -#else // not MPI_ENABLED - long double total = 0.0; - for (auto& [name, timer] : m_timers) { - if (name != "Output") { - total += timer.second; - } - } - for (auto& [name, timer] : m_timers) { - if (name == "Output") { - continue; - } - std::string units = "µs"; - auto value = timer.second; - if (flags & Timer::AutoConvert) { - convertTime(value, units); - } - if (flags & Timer::PrintIndents) { - os << " "; - } - os << ((name != "Sorting" or flags & Timer::PrintSorting) ? c_reset - : c_bblack) - << name << c_bblack - << fmt::pad(name, 20, '.', true).substr(name.size(), 20); - os << std::setw(17) << std::right << std::setfill('.') - << fmt::format("%s%.2Lf", - (name != "Sorting" or flags & Timer::PrintSorting) - ? c_byellow.c_str() - : c_bblack.c_str(), - value); - if (flags & Timer::PrintUnits) { - os << " " << units; - } - if (flags & Timer::PrintRelative) { - os << " " << std::setw(7) << std::right << std::setfill(' ') - << std::fixed << std::setprecision(2) - << (cmp::AlmostZero_host(total) ? 0 : (timer.second / total) * 100.0); - } - os << c_reset << std::endl; - } -#endif // MPI_ENABLED - if (flags & Timer::PrintTotal) { - std::string units = "µs"; - auto value = total; - if (flags & Timer::AutoConvert) { - convertTime(value, units); - } - os << c_bblack << std::setw(22) << std::left << std::setfill(' ') - << "Total" << c_reset; - os << c_blue << std::setw(12) << std::right << std::setfill(' ') << value; - if (flags & Timer::PrintUnits) { - os << " " << units; - } - os << c_reset << std::endl; - } - { - std::string units = "µs"; - auto value = get("Output"); - if (flags & Timer::AutoConvert) { - convertTime(value, units); - } - os << ((flags & Timer::PrintOutput) ? c_reset : c_bblack) << "Output" - << c_bblack << fmt::pad("Output", 22, '.', true).substr(6, 22); - os << std::setw(17) << std::right << std::setfill('.') - << fmt::format("%s%.2Lf", - (flags & Timer::PrintOutput) ? c_byellow.c_str() - : c_bblack.c_str(), - value); - if (flags & Timer::PrintUnits) { - os << " " << units; - } - os << c_reset << std::endl; - } - } + [[nodiscard]] + auto printAll(TimerFlags flags = Timer::Default, + std::size_t npart = 0, + std::size_t ncells = 0) const -> std::string; }; } // namespace timer diff --git a/src/global/utils/toml.h b/src/global/utils/toml.h new file mode 100644 index 000000000..16b79f72d --- /dev/null +++ b/src/global/utils/toml.h @@ -0,0 +1,17938 @@ +#ifndef TOML11_VERSION_HPP +#define TOML11_VERSION_HPP + +#define TOML11_VERSION_MAJOR 4 +#define TOML11_VERSION_MINOR 1 +#define TOML11_VERSION_PATCH 0 + +#ifndef __cplusplus + #error "__cplusplus is not defined" +#endif + +// Since MSVC does not define `__cplusplus` correctly unless you pass +// `/Zc:__cplusplus` when compiling, the workaround macros are added. +// +// The value of `__cplusplus` macro is defined in the C++ standard spec, but +// MSVC ignores the value, maybe because of backward compatibility. Instead, +// MSVC defines _MSVC_LANG that has the same value as __cplusplus defined in +// the C++ standard. So we check if _MSVC_LANG is defined before using `__cplusplus`. +// +// FYI: https://docs.microsoft.com/en-us/cpp/build/reference/zc-cplusplus?view=msvc-170 +// https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros?view=msvc-170 +// + +#if defined(_MSVC_LANG) && defined(_MSC_VER) && 190024210 <= _MSC_FULL_VER + #define TOML11_CPLUSPLUS_STANDARD_VERSION _MSVC_LANG +#else + #define TOML11_CPLUSPLUS_STANDARD_VERSION __cplusplus +#endif + +#if TOML11_CPLUSPLUS_STANDARD_VERSION < 201103L + #error "toml11 requires C++11 or later." +#endif + +#if !defined(__has_include) + #define __has_include(x) 0 +#endif + +#if !defined(__has_cpp_attribute) + #define __has_cpp_attribute(x) 0 +#endif + +#if !defined(__has_builtin) + #define __has_builtin(x) 0 +#endif + +// hard to remember + +#ifndef TOML11_CXX14_VALUE + #define TOML11_CXX14_VALUE 201402L +#endif // TOML11_CXX14_VALUE + +#ifndef TOML11_CXX17_VALUE + #define TOML11_CXX17_VALUE 201703L +#endif // TOML11_CXX17_VALUE + +#ifndef TOML11_CXX20_VALUE + #define TOML11_CXX20_VALUE 202002L +#endif // TOML11_CXX20_VALUE + +#if defined(__cpp_char8_t) + #if __cpp_char8_t >= 201811L + #define TOML11_HAS_CHAR8_T 1 + #endif +#endif + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX17_VALUE + #if __has_include() + #define TOML11_HAS_STRING_VIEW 1 + #endif +#endif + +#ifndef TOML11_DISABLE_STD_FILESYSTEM + #if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX17_VALUE + #if __has_include() + #define TOML11_HAS_FILESYSTEM 1 + #endif + #endif +#endif + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX17_VALUE + #if __has_include() + #define TOML11_HAS_OPTIONAL 1 + #endif +#endif + +#if defined(TOML11_COMPILE_SOURCES) + #define TOML11_INLINE +#else + #define TOML11_INLINE inline +#endif + +namespace toml { + + inline const char* license_notice() noexcept { + return R"(The MIT License (MIT) + +Copyright (c) 2017-now Toru Niina + +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.)"; + } + +} // namespace toml +#endif // TOML11_VERSION_HPP +#ifndef TOML11_FORMAT_HPP +#define TOML11_FORMAT_HPP + +#ifndef TOML11_FORMAT_FWD_HPP + #define TOML11_FORMAT_FWD_HPP + + #include + #include + #include + #include + #include + +namespace toml { + + // toml types with serialization info + + enum class indent_char : std::uint8_t { + space, // use space + tab, // use tab + none // no indent + }; + + std::ostream& operator<<(std::ostream& os, const indent_char& c); + std::string to_string(const indent_char c); + + // ---------------------------------------------------------------------------- + // boolean + + struct boolean_format_info { + // nothing, for now + }; + + inline bool operator==(const boolean_format_info&, + const boolean_format_info&) noexcept { + return true; + } + + inline bool operator!=(const boolean_format_info&, + const boolean_format_info&) noexcept { + return false; + } + + // ---------------------------------------------------------------------------- + // integer + + enum class integer_format : std::uint8_t { + dec = 0, + bin = 1, + oct = 2, + hex = 3, + }; + + std::ostream& operator<<(std::ostream& os, const integer_format f); + std::string to_string(const integer_format); + + struct integer_format_info { + integer_format fmt = integer_format::dec; + bool uppercase = true; // hex with uppercase + std::size_t width = 0; // minimal width (may exceed) + std::size_t spacer = 0; // position of `_` (if 0, no spacer) + std::string suffix = ""; // _suffix (library extension) + }; + + bool operator==(const integer_format_info&, const integer_format_info&) noexcept; + bool operator!=(const integer_format_info&, const integer_format_info&) noexcept; + + // ---------------------------------------------------------------------------- + // floating + + enum class floating_format : std::uint8_t { + defaultfloat = 0, + fixed = 1, // does not include exponential part + scientific = 2, // always include exponential part + hex = 3 // hexfloat extension + }; + + std::ostream& operator<<(std::ostream& os, const floating_format f); + std::string to_string(const floating_format); + + struct floating_format_info { + floating_format fmt = floating_format::defaultfloat; + std::size_t prec = 0; // precision (if 0, use the default) + std::string suffix = ""; // 1.0e+2_suffix (library extension) + }; + + bool operator==(const floating_format_info&, const floating_format_info&) noexcept; + bool operator!=(const floating_format_info&, const floating_format_info&) noexcept; + + // ---------------------------------------------------------------------------- + // string + + enum class string_format : std::uint8_t { + basic = 0, + literal = 1, + multiline_basic = 2, + multiline_literal = 3 + }; + + std::ostream& operator<<(std::ostream& os, const string_format f); + std::string to_string(const string_format); + + struct string_format_info { + string_format fmt = string_format::basic; + bool start_with_newline = false; + }; + + bool operator==(const string_format_info&, const string_format_info&) noexcept; + bool operator!=(const string_format_info&, const string_format_info&) noexcept; + + // ---------------------------------------------------------------------------- + // datetime + + enum class datetime_delimiter_kind : std::uint8_t { + upper_T = 0, + lower_t = 1, + space = 2, + }; + std::ostream& operator<<(std::ostream& os, const datetime_delimiter_kind d); + std::string to_string(const datetime_delimiter_kind); + + struct offset_datetime_format_info { + datetime_delimiter_kind delimiter = datetime_delimiter_kind::upper_T; + bool has_seconds = true; + std::size_t subsecond_precision = 6; // [us] + }; + + bool operator==(const offset_datetime_format_info&, + const offset_datetime_format_info&) noexcept; + bool operator!=(const offset_datetime_format_info&, + const offset_datetime_format_info&) noexcept; + + struct local_datetime_format_info { + datetime_delimiter_kind delimiter = datetime_delimiter_kind::upper_T; + bool has_seconds = true; + std::size_t subsecond_precision = 6; // [us] + }; + + bool operator==(const local_datetime_format_info&, + const local_datetime_format_info&) noexcept; + bool operator!=(const local_datetime_format_info&, + const local_datetime_format_info&) noexcept; + + struct local_date_format_info { + // nothing, for now + }; + + bool operator==(const local_date_format_info&, + const local_date_format_info&) noexcept; + bool operator!=(const local_date_format_info&, + const local_date_format_info&) noexcept; + + struct local_time_format_info { + bool has_seconds = true; + std::size_t subsecond_precision = 6; // [us] + }; + + bool operator==(const local_time_format_info&, + const local_time_format_info&) noexcept; + bool operator!=(const local_time_format_info&, + const local_time_format_info&) noexcept; + + // ---------------------------------------------------------------------------- + // array + + enum class array_format : std::uint8_t { + default_format = 0, + oneline = 1, + multiline = 2, + array_of_tables = 3 // [[format.in.this.way]] + }; + + std::ostream& operator<<(std::ostream& os, const array_format f); + std::string to_string(const array_format); + + struct array_format_info { + array_format fmt = array_format::default_format; + indent_char indent_type = indent_char::space; + std::int32_t body_indent = 4; // indent in case of multiline + std::int32_t closing_indent = 0; // indent of `]` + }; + + bool operator==(const array_format_info&, const array_format_info&) noexcept; + bool operator!=(const array_format_info&, const array_format_info&) noexcept; + + // ---------------------------------------------------------------------------- + // table + + enum class table_format : std::uint8_t { + multiline = 0, // [foo] \n bar = "baz" + oneline = 1, // foo = {bar = "baz"} + dotted = 2, // foo.bar = "baz" + multiline_oneline = 3, // foo = { \n bar = "baz" \n } + implicit = 4 // [x] defined by [x.y.z]. skip in serializer. + }; + + std::ostream& operator<<(std::ostream& os, const table_format f); + std::string to_string(const table_format); + + struct table_format_info { + table_format fmt = table_format::multiline; + indent_char indent_type = indent_char::space; + std::int32_t body_indent = 0; // indent of values + std::int32_t name_indent = 0; // indent of [table] + std::int32_t closing_indent = 0; // in case of {inline-table} + }; + + bool operator==(const table_format_info&, const table_format_info&) noexcept; + bool operator!=(const table_format_info&, const table_format_info&) noexcept; + + // ---------------------------------------------------------------------------- + // wrapper + + namespace detail { + template + struct value_with_format { + using value_type = T; + using format_type = F; + + value_with_format() = default; + ~value_with_format() = default; + value_with_format(const value_with_format&) = default; + value_with_format(value_with_format&&) = default; + value_with_format& operator=(const value_with_format&) = default; + value_with_format& operator=(value_with_format&&) = default; + + value_with_format(value_type v, format_type f) + : value { std::move(v) } + , format { std::move(f) } {} + + template + value_with_format(value_with_format other) + : value { std::move(other.value) } + , format { std::move(other.format) } {} + + value_type value; + format_type format; + }; + } // namespace detail + +} // namespace toml +#endif // TOML11_FORMAT_FWD_HPP + +#if !defined(TOML11_COMPILE_SOURCES) + #ifndef TOML11_FORMAT_IMPL_HPP + #define TOML11_FORMAT_IMPL_HPP + + #include + #include + +namespace toml { + + // toml types with serialization info + + TOML11_INLINE std::ostream& operator<<(std::ostream& os, const indent_char& c) { + switch (c) { + case indent_char::space: { + os << "space"; + break; + } + case indent_char::tab: { + os << "tab"; + break; + } + case indent_char::none: { + os << "none"; + break; + } + default: { + os << "unknown indent char: " << static_cast(c); + } + } + return os; + } + + TOML11_INLINE std::string to_string(const indent_char c) { + std::ostringstream oss; + oss << c; + return oss.str(); + } + + // ---------------------------------------------------------------------------- + // boolean + + // ---------------------------------------------------------------------------- + // integer + + TOML11_INLINE std::ostream& operator<<(std::ostream& os, const integer_format f) { + switch (f) { + case integer_format::dec: { + os << "dec"; + break; + } + case integer_format::bin: { + os << "bin"; + break; + } + case integer_format::oct: { + os << "oct"; + break; + } + case integer_format::hex: { + os << "hex"; + break; + } + default: { + os << "unknown integer_format: " << static_cast(f); + break; + } + } + return os; + } + + TOML11_INLINE std::string to_string(const integer_format c) { + std::ostringstream oss; + oss << c; + return oss.str(); + } + + TOML11_INLINE bool operator==(const integer_format_info& lhs, + const integer_format_info& rhs) noexcept { + return lhs.fmt == rhs.fmt && lhs.uppercase == rhs.uppercase && + lhs.width == rhs.width && lhs.spacer == rhs.spacer && + lhs.suffix == rhs.suffix; + } + + TOML11_INLINE bool operator!=(const integer_format_info& lhs, + const integer_format_info& rhs) noexcept { + return !(lhs == rhs); + } + + // ---------------------------------------------------------------------------- + // floating + + TOML11_INLINE std::ostream& operator<<(std::ostream& os, const floating_format f) { + switch (f) { + case floating_format::defaultfloat: { + os << "defaultfloat"; + break; + } + case floating_format::fixed: { + os << "fixed"; + break; + } + case floating_format::scientific: { + os << "scientific"; + break; + } + case floating_format::hex: { + os << "hex"; + break; + } + default: { + os << "unknown floating_format: " << static_cast(f); + break; + } + } + return os; + } + + TOML11_INLINE std::string to_string(const floating_format c) { + std::ostringstream oss; + oss << c; + return oss.str(); + } + + TOML11_INLINE bool operator==(const floating_format_info& lhs, + const floating_format_info& rhs) noexcept { + return lhs.fmt == rhs.fmt && lhs.prec == rhs.prec && lhs.suffix == rhs.suffix; + } + + TOML11_INLINE bool operator!=(const floating_format_info& lhs, + const floating_format_info& rhs) noexcept { + return !(lhs == rhs); + } + + // ---------------------------------------------------------------------------- + // string + + TOML11_INLINE std::ostream& operator<<(std::ostream& os, const string_format f) { + switch (f) { + case string_format::basic: { + os << "basic"; + break; + } + case string_format::literal: { + os << "literal"; + break; + } + case string_format::multiline_basic: { + os << "multiline_basic"; + break; + } + case string_format::multiline_literal: { + os << "multiline_literal"; + break; + } + default: { + os << "unknown string_format: " << static_cast(f); + break; + } + } + return os; + } + + TOML11_INLINE std::string to_string(const string_format c) { + std::ostringstream oss; + oss << c; + return oss.str(); + } + + TOML11_INLINE bool operator==(const string_format_info& lhs, + const string_format_info& rhs) noexcept { + return lhs.fmt == rhs.fmt && lhs.start_with_newline == rhs.start_with_newline; + } + + TOML11_INLINE bool operator!=(const string_format_info& lhs, + const string_format_info& rhs) noexcept { + return !(lhs == rhs); + } + + // ---------------------------------------------------------------------------- + // datetime + + TOML11_INLINE std::ostream& operator<<(std::ostream& os, + const datetime_delimiter_kind d) { + switch (d) { + case datetime_delimiter_kind::upper_T: { + os << "upper_T, "; + break; + } + case datetime_delimiter_kind::lower_t: { + os << "lower_t, "; + break; + } + case datetime_delimiter_kind::space: { + os << "space, "; + break; + } + default: { + os << "unknown datetime delimiter: " << static_cast(d); + break; + } + } + return os; + } + + TOML11_INLINE std::string to_string(const datetime_delimiter_kind c) { + std::ostringstream oss; + oss << c; + return oss.str(); + } + + TOML11_INLINE bool operator==(const offset_datetime_format_info& lhs, + const offset_datetime_format_info& rhs) noexcept { + return lhs.delimiter == rhs.delimiter && lhs.has_seconds == rhs.has_seconds && + lhs.subsecond_precision == rhs.subsecond_precision; + } + + TOML11_INLINE bool operator!=(const offset_datetime_format_info& lhs, + const offset_datetime_format_info& rhs) noexcept { + return !(lhs == rhs); + } + + TOML11_INLINE bool operator==(const local_datetime_format_info& lhs, + const local_datetime_format_info& rhs) noexcept { + return lhs.delimiter == rhs.delimiter && lhs.has_seconds == rhs.has_seconds && + lhs.subsecond_precision == rhs.subsecond_precision; + } + + TOML11_INLINE bool operator!=(const local_datetime_format_info& lhs, + const local_datetime_format_info& rhs) noexcept { + return !(lhs == rhs); + } + + TOML11_INLINE bool operator==(const local_date_format_info&, + const local_date_format_info&) noexcept { + return true; + } + + TOML11_INLINE bool operator!=(const local_date_format_info& lhs, + const local_date_format_info& rhs) noexcept { + return !(lhs == rhs); + } + + TOML11_INLINE bool operator==(const local_time_format_info& lhs, + const local_time_format_info& rhs) noexcept { + return lhs.has_seconds == rhs.has_seconds && + lhs.subsecond_precision == rhs.subsecond_precision; + } + + TOML11_INLINE bool operator!=(const local_time_format_info& lhs, + const local_time_format_info& rhs) noexcept { + return !(lhs == rhs); + } + + // ---------------------------------------------------------------------------- + // array + + TOML11_INLINE std::ostream& operator<<(std::ostream& os, const array_format f) { + switch (f) { + case array_format::default_format: { + os << "default_format"; + break; + } + case array_format::oneline: { + os << "oneline"; + break; + } + case array_format::multiline: { + os << "multiline"; + break; + } + case array_format::array_of_tables: { + os << "array_of_tables"; + break; + } + default: { + os << "unknown array_format: " << static_cast(f); + break; + } + } + return os; + } + + TOML11_INLINE std::string to_string(const array_format c) { + std::ostringstream oss; + oss << c; + return oss.str(); + } + + TOML11_INLINE bool operator==(const array_format_info& lhs, + const array_format_info& rhs) noexcept { + return lhs.fmt == rhs.fmt && lhs.indent_type == rhs.indent_type && + lhs.body_indent == rhs.body_indent && + lhs.closing_indent == rhs.closing_indent; + } + + TOML11_INLINE bool operator!=(const array_format_info& lhs, + const array_format_info& rhs) noexcept { + return !(lhs == rhs); + } + + // ---------------------------------------------------------------------------- + // table + + TOML11_INLINE std::ostream& operator<<(std::ostream& os, const table_format f) { + switch (f) { + case table_format::multiline: { + os << "multiline"; + break; + } + case table_format::oneline: { + os << "oneline"; + break; + } + case table_format::dotted: { + os << "dotted"; + break; + } + case table_format::multiline_oneline: { + os << "multiline_oneline"; + break; + } + case table_format::implicit: { + os << "implicit"; + break; + } + default: { + os << "unknown table_format: " << static_cast(f); + break; + } + } + return os; + } + + TOML11_INLINE std::string to_string(const table_format c) { + std::ostringstream oss; + oss << c; + return oss.str(); + } + + TOML11_INLINE bool operator==(const table_format_info& lhs, + const table_format_info& rhs) noexcept { + return lhs.fmt == rhs.fmt && lhs.indent_type == rhs.indent_type && + lhs.body_indent == rhs.body_indent && + lhs.name_indent == rhs.name_indent && + lhs.closing_indent == rhs.closing_indent; + } + + TOML11_INLINE bool operator!=(const table_format_info& lhs, + const table_format_info& rhs) noexcept { + return !(lhs == rhs); + } + +} // namespace toml + #endif // TOML11_FORMAT_IMPL_HPP +#endif + +#endif // TOML11_FORMAT_HPP +#ifndef TOML11_DATETIME_HPP +#define TOML11_DATETIME_HPP + +#ifndef TOML11_DATETIME_FWD_HPP + #define TOML11_DATETIME_FWD_HPP + + #include + #include + #include + #include + #include + #include + +namespace toml { + + enum class month_t : std::uint8_t { + Jan = 0, + Feb = 1, + Mar = 2, + Apr = 3, + May = 4, + Jun = 5, + Jul = 6, + Aug = 7, + Sep = 8, + Oct = 9, + Nov = 10, + Dec = 11 + }; + + // ---------------------------------------------------------------------------- + + struct local_date { + std::int16_t year { 0 }; // A.D. (like, 2018) + std::uint8_t month { 0 }; // [0, 11] + std::uint8_t day { 0 }; // [1, 31] + + local_date(int y, month_t m, int d) + : year { static_cast(y) } + , month { static_cast(m) } + , day { static_cast(d) } {} + + explicit local_date(const std::tm& t) + : year { static_cast(t.tm_year + 1900) } + , month { static_cast(t.tm_mon) } + , day { static_cast(t.tm_mday) } {} + + explicit local_date(const std::chrono::system_clock::time_point& tp); + explicit local_date(const std::time_t t); + + operator std::chrono::system_clock::time_point() const; + operator std::time_t() const; + + local_date() = default; + ~local_date() = default; + local_date(const local_date&) = default; + local_date(local_date&&) = default; + local_date& operator=(const local_date&) = default; + local_date& operator=(local_date&&) = default; + }; + + bool operator==(const local_date& lhs, const local_date& rhs); + bool operator!=(const local_date& lhs, const local_date& rhs); + bool operator<(const local_date& lhs, const local_date& rhs); + bool operator<=(const local_date& lhs, const local_date& rhs); + bool operator>(const local_date& lhs, const local_date& rhs); + bool operator>=(const local_date& lhs, const local_date& rhs); + + std::ostream& operator<<(std::ostream& os, const local_date& date); + std::string to_string(const local_date& date); + + // ----------------------------------------------------------------------------- + + struct local_time { + std::uint8_t hour { 0 }; // [0, 23] + std::uint8_t minute { 0 }; // [0, 59] + std::uint8_t second { 0 }; // [0, 60] + std::uint16_t millisecond { 0 }; // [0, 999] + std::uint16_t microsecond { 0 }; // [0, 999] + std::uint16_t nanosecond { 0 }; // [0, 999] + + local_time(int h, int m, int s, int ms = 0, int us = 0, int ns = 0) + : hour { static_cast(h) } + , minute { static_cast(m) } + , second { static_cast(s) } + , millisecond { static_cast(ms) } + , microsecond { static_cast(us) } + , nanosecond { static_cast(ns) } {} + + explicit local_time(const std::tm& t) + : hour { static_cast(t.tm_hour) } + , minute { static_cast(t.tm_min) } + , second { static_cast(t.tm_sec) } + , millisecond { 0 } + , microsecond { 0 } + , nanosecond { 0 } {} + + template + explicit local_time(const std::chrono::duration& t) { + const auto h = std::chrono::duration_cast(t); + this->hour = static_cast(h.count()); + const auto t2 = t - h; + const auto m = std::chrono::duration_cast(t2); + this->minute = static_cast(m.count()); + const auto t3 = t2 - m; + const auto s = std::chrono::duration_cast(t3); + this->second = static_cast(s.count()); + const auto t4 = t3 - s; + const auto ms = std::chrono::duration_cast(t4); + this->millisecond = static_cast(ms.count()); + const auto t5 = t4 - ms; + const auto us = std::chrono::duration_cast(t5); + this->microsecond = static_cast(us.count()); + const auto t6 = t5 - us; + const auto ns = std::chrono::duration_cast(t6); + this->nanosecond = static_cast(ns.count()); + } + + operator std::chrono::nanoseconds() const; + + local_time() = default; + ~local_time() = default; + local_time(const local_time&) = default; + local_time(local_time&&) = default; + local_time& operator=(const local_time&) = default; + local_time& operator=(local_time&&) = default; + }; + + bool operator==(const local_time& lhs, const local_time& rhs); + bool operator!=(const local_time& lhs, const local_time& rhs); + bool operator<(const local_time& lhs, const local_time& rhs); + bool operator<=(const local_time& lhs, const local_time& rhs); + bool operator>(const local_time& lhs, const local_time& rhs); + bool operator>=(const local_time& lhs, const local_time& rhs); + + std::ostream& operator<<(std::ostream& os, const local_time& time); + std::string to_string(const local_time& time); + + // ---------------------------------------------------------------------------- + + struct time_offset { + std::int8_t hour { 0 }; // [-12, 12] + std::int8_t minute { 0 }; // [-59, 59] + + time_offset(int h, int m) + : hour { static_cast(h) } + , minute { static_cast(m) } {} + + operator std::chrono::minutes() const; + + time_offset() = default; + ~time_offset() = default; + time_offset(const time_offset&) = default; + time_offset(time_offset&&) = default; + time_offset& operator=(const time_offset&) = default; + time_offset& operator=(time_offset&&) = default; + }; + + bool operator==(const time_offset& lhs, const time_offset& rhs); + bool operator!=(const time_offset& lhs, const time_offset& rhs); + bool operator<(const time_offset& lhs, const time_offset& rhs); + bool operator<=(const time_offset& lhs, const time_offset& rhs); + bool operator>(const time_offset& lhs, const time_offset& rhs); + bool operator>=(const time_offset& lhs, const time_offset& rhs); + + std::ostream& operator<<(std::ostream& os, const time_offset& offset); + + std::string to_string(const time_offset& offset); + + // ----------------------------------------------------------------------------- + + struct local_datetime { + local_date date {}; + local_time time {}; + + local_datetime(local_date d, local_time t) : date { d }, time { t } {} + + explicit local_datetime(const std::tm& t) : date { t }, time { t } {} + + explicit local_datetime(const std::chrono::system_clock::time_point& tp); + explicit local_datetime(const std::time_t t); + + operator std::chrono::system_clock::time_point() const; + operator std::time_t() const; + + local_datetime() = default; + ~local_datetime() = default; + local_datetime(const local_datetime&) = default; + local_datetime(local_datetime&&) = default; + local_datetime& operator=(const local_datetime&) = default; + local_datetime& operator=(local_datetime&&) = default; + }; + + bool operator==(const local_datetime& lhs, const local_datetime& rhs); + bool operator!=(const local_datetime& lhs, const local_datetime& rhs); + bool operator<(const local_datetime& lhs, const local_datetime& rhs); + bool operator<=(const local_datetime& lhs, const local_datetime& rhs); + bool operator>(const local_datetime& lhs, const local_datetime& rhs); + bool operator>=(const local_datetime& lhs, const local_datetime& rhs); + + std::ostream& operator<<(std::ostream& os, const local_datetime& dt); + + std::string to_string(const local_datetime& dt); + + // ----------------------------------------------------------------------------- + + struct offset_datetime { + local_date date {}; + local_time time {}; + time_offset offset {}; + + offset_datetime(local_date d, local_time t, time_offset o) + : date { d } + , time { t } + , offset { o } {} + + offset_datetime(const local_datetime& dt, time_offset o) + : date { dt.date } + , time { dt.time } + , offset { o } {} + + // use the current local timezone offset + explicit offset_datetime(const local_datetime& ld); + explicit offset_datetime(const std::chrono::system_clock::time_point& tp); + explicit offset_datetime(const std::time_t& t); + explicit offset_datetime(const std::tm& t); + + operator std::chrono::system_clock::time_point() const; + + operator std::time_t() const; + + offset_datetime() = default; + ~offset_datetime() = default; + offset_datetime(const offset_datetime&) = default; + offset_datetime(offset_datetime&&) = default; + offset_datetime& operator=(const offset_datetime&) = default; + offset_datetime& operator=(offset_datetime&&) = default; + + private: + static time_offset get_local_offset(const std::time_t* tp); + }; + + bool operator==(const offset_datetime& lhs, const offset_datetime& rhs); + bool operator!=(const offset_datetime& lhs, const offset_datetime& rhs); + bool operator<(const offset_datetime& lhs, const offset_datetime& rhs); + bool operator<=(const offset_datetime& lhs, const offset_datetime& rhs); + bool operator>(const offset_datetime& lhs, const offset_datetime& rhs); + bool operator>=(const offset_datetime& lhs, const offset_datetime& rhs); + + std::ostream& operator<<(std::ostream& os, const offset_datetime& dt); + + std::string to_string(const offset_datetime& dt); + +} // namespace toml +#endif // TOML11_DATETIME_FWD_HPP + +#if !defined(TOML11_COMPILE_SOURCES) + #ifndef TOML11_DATETIME_IMPL_HPP + #define TOML11_DATETIME_IMPL_HPP + + #include + #include + #include + #include + #include + #include + #include + +namespace toml { + + // To avoid non-threadsafe std::localtime. In C11 (not C++11!), localtime_s is + // provided in the absolutely same purpose, but C++11 is actually not + // compatible with C11. We need to dispatch the function depending on the OS. + namespace detail { + // TODO: find more sophisticated way to handle this + #if defined(_MSC_VER) + TOML11_INLINE std::tm localtime_s(const std::time_t* src) { + std::tm dst; + const auto result = ::localtime_s(&dst, src); + if (result) { + throw std::runtime_error("localtime_s failed."); + } + return dst; + } + + TOML11_INLINE std::tm gmtime_s(const std::time_t* src) { + std::tm dst; + const auto result = ::gmtime_s(&dst, src); + if (result) { + throw std::runtime_error("gmtime_s failed."); + } + return dst; + } + #elif (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 1) || \ + defined(_XOPEN_SOURCE) || defined(_BSD_SOURCE) || \ + defined(_SVID_SOURCE) || defined(_POSIX_SOURCE) + TOML11_INLINE std::tm localtime_s(const std::time_t* src) { + std::tm dst; + const auto result = ::localtime_r(src, &dst); + if (!result) { + throw std::runtime_error("localtime_r failed."); + } + return dst; + } + + TOML11_INLINE std::tm gmtime_s(const std::time_t* src) { + std::tm dst; + const auto result = ::gmtime_r(src, &dst); + if (!result) { + throw std::runtime_error("gmtime_r failed."); + } + return dst; + } + #else // fallback. not threadsafe + TOML11_INLINE std::tm localtime_s(const std::time_t* src) { + const auto result = std::localtime(src); + if (!result) { + throw std::runtime_error("localtime failed."); + } + return *result; + } + + TOML11_INLINE std::tm gmtime_s(const std::time_t* src) { + const auto result = std::gmtime(src); + if (!result) { + throw std::runtime_error("gmtime failed."); + } + return *result; + } + #endif + } // namespace detail + + // ---------------------------------------------------------------------------- + + TOML11_INLINE local_date::local_date( + const std::chrono::system_clock::time_point& tp) { + const auto t = std::chrono::system_clock::to_time_t(tp); + const auto time = detail::localtime_s(&t); + *this = local_date(time); + } + + TOML11_INLINE local_date::local_date(const std::time_t t) + : local_date { std::chrono::system_clock::from_time_t(t) } {} + + TOML11_INLINE local_date::operator std::chrono::system_clock::time_point() const { + // std::mktime returns date as local time zone. no conversion needed + std::tm t; + t.tm_sec = 0; + t.tm_min = 0; + t.tm_hour = 0; + t.tm_mday = static_cast(this->day); + t.tm_mon = static_cast(this->month); + t.tm_year = static_cast(this->year) - 1900; + t.tm_wday = 0; // the value will be ignored + t.tm_yday = 0; // the value will be ignored + t.tm_isdst = -1; + return std::chrono::system_clock::from_time_t(std::mktime(&t)); + } + + TOML11_INLINE local_date::operator std::time_t() const { + return std::chrono::system_clock::to_time_t( + std::chrono::system_clock::time_point(*this)); + } + + TOML11_INLINE bool operator==(const local_date& lhs, const local_date& rhs) { + return std::make_tuple(lhs.year, lhs.month, lhs.day) == + std::make_tuple(rhs.year, rhs.month, rhs.day); + } + + TOML11_INLINE bool operator!=(const local_date& lhs, const local_date& rhs) { + return !(lhs == rhs); + } + + TOML11_INLINE bool operator<(const local_date& lhs, const local_date& rhs) { + return std::make_tuple(lhs.year, lhs.month, lhs.day) < + std::make_tuple(rhs.year, rhs.month, rhs.day); + } + + TOML11_INLINE bool operator<=(const local_date& lhs, const local_date& rhs) { + return (lhs < rhs) || (lhs == rhs); + } + + TOML11_INLINE bool operator>(const local_date& lhs, const local_date& rhs) { + return !(lhs <= rhs); + } + + TOML11_INLINE bool operator>=(const local_date& lhs, const local_date& rhs) { + return !(lhs < rhs); + } + + TOML11_INLINE std::ostream& operator<<(std::ostream& os, const local_date& date) { + os << std::setfill('0') << std::setw(4) << static_cast(date.year) << '-'; + os << std::setfill('0') << std::setw(2) << static_cast(date.month) + 1 + << '-'; + os << std::setfill('0') << std::setw(2) << static_cast(date.day); + return os; + } + + TOML11_INLINE std::string to_string(const local_date& date) { + std::ostringstream oss; + oss.imbue(std::locale::classic()); + oss << date; + return oss.str(); + } + + // ----------------------------------------------------------------------------- + + TOML11_INLINE local_time::operator std::chrono::nanoseconds() const { + return std::chrono::nanoseconds(this->nanosecond) + + std::chrono::microseconds(this->microsecond) + + std::chrono::milliseconds(this->millisecond) + + std::chrono::seconds(this->second) + + std::chrono::minutes(this->minute) + std::chrono::hours(this->hour); + } + + TOML11_INLINE bool operator==(const local_time& lhs, const local_time& rhs) { + return std::make_tuple(lhs.hour, + lhs.minute, + lhs.second, + lhs.millisecond, + lhs.microsecond, + lhs.nanosecond) == std::make_tuple(rhs.hour, + rhs.minute, + rhs.second, + rhs.millisecond, + rhs.microsecond, + rhs.nanosecond); + } + + TOML11_INLINE bool operator!=(const local_time& lhs, const local_time& rhs) { + return !(lhs == rhs); + } + + TOML11_INLINE bool operator<(const local_time& lhs, const local_time& rhs) { + return std::make_tuple(lhs.hour, + lhs.minute, + lhs.second, + lhs.millisecond, + lhs.microsecond, + lhs.nanosecond) < std::make_tuple(rhs.hour, + rhs.minute, + rhs.second, + rhs.millisecond, + rhs.microsecond, + rhs.nanosecond); + } + + TOML11_INLINE bool operator<=(const local_time& lhs, const local_time& rhs) { + return (lhs < rhs) || (lhs == rhs); + } + + TOML11_INLINE bool operator>(const local_time& lhs, const local_time& rhs) { + return !(lhs <= rhs); + } + + TOML11_INLINE bool operator>=(const local_time& lhs, const local_time& rhs) { + return !(lhs < rhs); + } + + TOML11_INLINE std::ostream& operator<<(std::ostream& os, const local_time& time) { + os << std::setfill('0') << std::setw(2) << static_cast(time.hour) << ':'; + os << std::setfill('0') << std::setw(2) << static_cast(time.minute) << ':'; + os << std::setfill('0') << std::setw(2) << static_cast(time.second); + if (time.millisecond != 0 || time.microsecond != 0 || time.nanosecond != 0) { + os << '.'; + os << std::setfill('0') << std::setw(3) + << static_cast(time.millisecond); + if (time.microsecond != 0 || time.nanosecond != 0) { + os << std::setfill('0') << std::setw(3) + << static_cast(time.microsecond); + if (time.nanosecond != 0) { + os << std::setfill('0') << std::setw(3) + << static_cast(time.nanosecond); + } + } + } + return os; + } + + TOML11_INLINE std::string to_string(const local_time& time) { + std::ostringstream oss; + oss.imbue(std::locale::classic()); + oss << time; + return oss.str(); + } + + // ---------------------------------------------------------------------------- + + TOML11_INLINE time_offset::operator std::chrono::minutes() const { + return std::chrono::minutes(this->minute) + std::chrono::hours(this->hour); + } + + TOML11_INLINE bool operator==(const time_offset& lhs, const time_offset& rhs) { + return std::make_tuple(lhs.hour, lhs.minute) == + std::make_tuple(rhs.hour, rhs.minute); + } + + TOML11_INLINE bool operator!=(const time_offset& lhs, const time_offset& rhs) { + return !(lhs == rhs); + } + + TOML11_INLINE bool operator<(const time_offset& lhs, const time_offset& rhs) { + return std::make_tuple(lhs.hour, lhs.minute) < + std::make_tuple(rhs.hour, rhs.minute); + } + + TOML11_INLINE bool operator<=(const time_offset& lhs, const time_offset& rhs) { + return (lhs < rhs) || (lhs == rhs); + } + + TOML11_INLINE bool operator>(const time_offset& lhs, const time_offset& rhs) { + return !(lhs <= rhs); + } + + TOML11_INLINE bool operator>=(const time_offset& lhs, const time_offset& rhs) { + return !(lhs < rhs); + } + + TOML11_INLINE std::ostream& operator<<(std::ostream& os, + const time_offset& offset) { + if (offset.hour == 0 && offset.minute == 0) { + os << 'Z'; + return os; + } + int minute = static_cast(offset.hour) * 60 + offset.minute; + if (minute < 0) { + os << '-'; + minute = std::abs(minute); + } else { + os << '+'; + } + os << std::setfill('0') << std::setw(2) << minute / 60 << ':'; + os << std::setfill('0') << std::setw(2) << minute % 60; + return os; + } + + TOML11_INLINE std::string to_string(const time_offset& offset) { + std::ostringstream oss; + oss.imbue(std::locale::classic()); + oss << offset; + return oss.str(); + } + + // ----------------------------------------------------------------------------- + + TOML11_INLINE local_datetime::local_datetime( + const std::chrono::system_clock::time_point& tp) { + const auto t = std::chrono::system_clock::to_time_t(tp); + std::tm ltime = detail::localtime_s(&t); + + this->date = local_date(ltime); + this->time = local_time(ltime); + + // std::tm lacks subsecond information, so diff between tp and tm + // can be used to get millisecond & microsecond information. + const auto t_diff = tp - std::chrono::system_clock::from_time_t( + std::mktime(<ime)); + this->time.millisecond = static_cast( + std::chrono::duration_cast(t_diff).count()); + this->time.microsecond = static_cast( + std::chrono::duration_cast(t_diff).count()); + this->time.nanosecond = static_cast( + std::chrono::duration_cast(t_diff).count()); + } + + TOML11_INLINE local_datetime::local_datetime(const std::time_t t) + : local_datetime { std::chrono::system_clock::from_time_t(t) } {} + + TOML11_INLINE local_datetime::operator std::chrono::system_clock::time_point() const { + using internal_duration = typename std::chrono::system_clock::time_point::duration; + + // Normally DST begins at A.M. 3 or 4. If we re-use conversion operator + // of local_date and local_time independently, the conversion fails if + // it is the day when DST begins or ends. Since local_date considers the + // time is 00:00 A.M. and local_time does not consider DST because it + // does not have any date information. We need to consider both date and + // time information at the same time to convert it correctly. + + std::tm t; + t.tm_sec = static_cast(this->time.second); + t.tm_min = static_cast(this->time.minute); + t.tm_hour = static_cast(this->time.hour); + t.tm_mday = static_cast(this->date.day); + t.tm_mon = static_cast(this->date.month); + t.tm_year = static_cast(this->date.year) - 1900; + t.tm_wday = 0; // the value will be ignored + t.tm_yday = 0; // the value will be ignored + t.tm_isdst = -1; + + // std::mktime returns date as local time zone. no conversion needed + auto dt = std::chrono::system_clock::from_time_t(std::mktime(&t)); + dt += std::chrono::duration_cast( + std::chrono::milliseconds(this->time.millisecond) + + std::chrono::microseconds(this->time.microsecond) + + std::chrono::nanoseconds(this->time.nanosecond)); + return dt; + } + + TOML11_INLINE local_datetime::operator std::time_t() const { + return std::chrono::system_clock::to_time_t( + std::chrono::system_clock::time_point(*this)); + } + + TOML11_INLINE bool operator==(const local_datetime& lhs, + const local_datetime& rhs) { + return std::make_tuple(lhs.date, lhs.time) == + std::make_tuple(rhs.date, rhs.time); + } + + TOML11_INLINE bool operator!=(const local_datetime& lhs, + const local_datetime& rhs) { + return !(lhs == rhs); + } + + TOML11_INLINE bool operator<(const local_datetime& lhs, + const local_datetime& rhs) { + return std::make_tuple(lhs.date, lhs.time) < + std::make_tuple(rhs.date, rhs.time); + } + + TOML11_INLINE bool operator<=(const local_datetime& lhs, + const local_datetime& rhs) { + return (lhs < rhs) || (lhs == rhs); + } + + TOML11_INLINE bool operator>(const local_datetime& lhs, + const local_datetime& rhs) { + return !(lhs <= rhs); + } + + TOML11_INLINE bool operator>=(const local_datetime& lhs, + const local_datetime& rhs) { + return !(lhs < rhs); + } + + TOML11_INLINE std::ostream& operator<<(std::ostream& os, + const local_datetime& dt) { + os << dt.date << 'T' << dt.time; + return os; + } + + TOML11_INLINE std::string to_string(const local_datetime& dt) { + std::ostringstream oss; + oss.imbue(std::locale::classic()); + oss << dt; + return oss.str(); + } + + // ----------------------------------------------------------------------------- + + TOML11_INLINE offset_datetime::offset_datetime(const local_datetime& ld) + : date { ld.date } + , time { ld.time } + , offset { get_local_offset(nullptr) } + // use the current local timezone offset + {} + + TOML11_INLINE offset_datetime::offset_datetime( + const std::chrono::system_clock::time_point& tp) + : offset { 0, 0 } // use gmtime + { + const auto timet = std::chrono::system_clock::to_time_t(tp); + const auto tm = detail::gmtime_s(&timet); + this->date = local_date(tm); + this->time = local_time(tm); + } + + TOML11_INLINE offset_datetime::offset_datetime(const std::time_t& t) + : offset { 0, 0 } // use gmtime + { + const auto tm = detail::gmtime_s(&t); + this->date = local_date(tm); + this->time = local_time(tm); + } + + TOML11_INLINE offset_datetime::offset_datetime(const std::tm& t) + : offset { 0, 0 } // assume gmtime + { + this->date = local_date(t); + this->time = local_time(t); + } + + TOML11_INLINE offset_datetime::operator std::chrono::system_clock::time_point() const { + // get date-time + using internal_duration = typename std::chrono::system_clock::time_point::duration; + + // first, convert it to local date-time information in the same way as + // local_datetime does. later we will use time_t to adjust time offset. + std::tm t; + t.tm_sec = static_cast(this->time.second); + t.tm_min = static_cast(this->time.minute); + t.tm_hour = static_cast(this->time.hour); + t.tm_mday = static_cast(this->date.day); + t.tm_mon = static_cast(this->date.month); + t.tm_year = static_cast(this->date.year) - 1900; + t.tm_wday = 0; // the value will be ignored + t.tm_yday = 0; // the value will be ignored + t.tm_isdst = -1; + const std::time_t tp_loc = std::mktime(std::addressof(t)); + + auto tp = std::chrono::system_clock::from_time_t(tp_loc); + tp += std::chrono::duration_cast( + std::chrono::milliseconds(this->time.millisecond) + + std::chrono::microseconds(this->time.microsecond) + + std::chrono::nanoseconds(this->time.nanosecond)); + + // Since mktime uses local time zone, it should be corrected. + // `12:00:00+09:00` means `03:00:00Z`. So mktime returns `03:00:00Z` if + // we are in `+09:00` timezone. To represent `12:00:00Z` there, we need + // to add `+09:00` to `03:00:00Z`. + // Here, it uses the time_t converted from date-time info to handle + // daylight saving time. + const auto ofs = get_local_offset(std::addressof(tp_loc)); + tp += std::chrono::hours(ofs.hour); + tp += std::chrono::minutes(ofs.minute); + + // We got `12:00:00Z` by correcting local timezone applied by mktime. + // Then we will apply the offset. Let's say `12:00:00-08:00` is given. + // And now, we have `12:00:00Z`. `12:00:00-08:00` means `20:00:00Z`. + // So we need to subtract the offset. + tp -= std::chrono::minutes(this->offset); + return tp; + } + + TOML11_INLINE offset_datetime::operator std::time_t() const { + return std::chrono::system_clock::to_time_t( + std::chrono::system_clock::time_point(*this)); + } + + TOML11_INLINE time_offset offset_datetime::get_local_offset(const std::time_t* tp) { + // get local timezone with the same date-time information as mktime + const auto t = detail::localtime_s(tp); + + std::array buf; + const auto result = std::strftime(buf.data(), 6, "%z", &t); // +hhmm\0 + if (result != 5) { + throw std::runtime_error("toml::offset_datetime: cannot obtain " + "timezone information of current env"); + } + const int ofs = std::atoi(buf.data()); + const int ofs_h = ofs / 100; + const int ofs_m = ofs - (ofs_h * 100); + return time_offset(ofs_h, ofs_m); + } + + TOML11_INLINE bool operator==(const offset_datetime& lhs, + const offset_datetime& rhs) { + return std::make_tuple(lhs.date, lhs.time, lhs.offset) == + std::make_tuple(rhs.date, rhs.time, rhs.offset); + } + + TOML11_INLINE bool operator!=(const offset_datetime& lhs, + const offset_datetime& rhs) { + return !(lhs == rhs); + } + + TOML11_INLINE bool operator<(const offset_datetime& lhs, + const offset_datetime& rhs) { + return std::make_tuple(lhs.date, lhs.time, lhs.offset) < + std::make_tuple(rhs.date, rhs.time, rhs.offset); + } + + TOML11_INLINE bool operator<=(const offset_datetime& lhs, + const offset_datetime& rhs) { + return (lhs < rhs) || (lhs == rhs); + } + + TOML11_INLINE bool operator>(const offset_datetime& lhs, + const offset_datetime& rhs) { + return !(lhs <= rhs); + } + + TOML11_INLINE bool operator>=(const offset_datetime& lhs, + const offset_datetime& rhs) { + return !(lhs < rhs); + } + + TOML11_INLINE std::ostream& operator<<(std::ostream& os, + const offset_datetime& dt) { + os << dt.date << 'T' << dt.time << dt.offset; + return os; + } + + TOML11_INLINE std::string to_string(const offset_datetime& dt) { + std::ostringstream oss; + oss.imbue(std::locale::classic()); + oss << dt; + return oss.str(); + } + +} // namespace toml + #endif // TOML11_DATETIME_IMPL_HPP +#endif + +#endif // TOML11_DATETIME_HPP +#ifndef TOML11_COMPAT_HPP +#define TOML11_COMPAT_HPP + +#include +#include +#include +#include +#include +#include + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX20_VALUE + #if __has_include() + #include + #endif +#endif + +#include + +// ---------------------------------------------------------------------------- + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX14_VALUE + #if __has_cpp_attribute(deprecated) + #define TOML11_HAS_ATTR_DEPRECATED 1 + #endif +#endif + +#if defined(TOML11_HAS_ATTR_DEPRECATED) + #define TOML11_DEPRECATED(msg) [[deprecated(msg)]] +#elif defined(__GNUC__) + #define TOML11_DEPRECATED(msg) __attribute__((deprecated(msg))) +#elif defined(_MSC_VER) + #define TOML11_DEPRECATED(msg) __declspec(deprecated(msg)) +#else + #define TOML11_DEPRECATED(msg) +#endif + +// ---------------------------------------------------------------------------- + +#if defined(__cpp_if_constexpr) + #if __cpp_if_constexpr >= 201606L + #define TOML11_HAS_CONSTEXPR_IF 1 + #endif +#endif + +#if defined(TOML11_HAS_CONSTEXPR_IF) + #define TOML11_CONSTEXPR_IF if constexpr +#else + #define TOML11_CONSTEXPR_IF if +#endif + +// ---------------------------------------------------------------------------- + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX14_VALUE + #if defined(__cpp_lib_make_unique) + #if __cpp_lib_make_unique >= 201304L + #define TOML11_HAS_STD_MAKE_UNIQUE 1 + #endif + #endif +#endif + +namespace toml { + namespace cxx { + +#if defined(TOML11_HAS_STD_MAKE_UNIQUE) + + using std::make_unique; + +#else + + template + std::unique_ptr make_unique(Ts&&... args) { + return std::unique_ptr(new T(std::forward(args)...)); + } + +#endif // TOML11_HAS_STD_MAKE_UNIQUE + + } // namespace cxx +} // namespace toml + +// --------------------------------------------------------------------------- + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX14_VALUE + #if defined(__cpp_lib_make_reverse_iterator) + #if __cpp_lib_make_reverse_iterator >= 201402L + #define TOML11_HAS_STD_MAKE_REVERSE_ITERATOR 1 + #endif + #endif +#endif + +namespace toml { + namespace cxx { +#if defined(TOML11_HAS_STD_MAKE_REVERSE_ITERATOR) + + using std::make_reverse_iterator; + +#else + + template + std::reverse_iterator make_reverse_iterator(Iterator iter) { + return std::reverse_iterator(iter); + } + +#endif // TOML11_HAS_STD_MAKE_REVERSE_ITERATOR + + } // namespace cxx +} // namespace toml + +// --------------------------------------------------------------------------- + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX20_VALUE + #if defined(__cpp_lib_clamp) + #if __cpp_lib_clamp >= 201603L + #define TOML11_HAS_STD_CLAMP 1 + #endif + #endif +#endif + +namespace toml { + namespace cxx { +#if defined(TOML11_HAS_STD_CLAMP) + + using std::clamp; + +#else + + template + T clamp(const T& x, const T& low, const T& high) noexcept { + assert(low <= high); + return (std::min)((std::max)(x, low), high); + } + +#endif // TOML11_HAS_STD_CLAMP + + } // namespace cxx +} // namespace toml + +// --------------------------------------------------------------------------- + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX20_VALUE + #if defined(__cpp_lib_bit_cast) + #if __cpp_lib_bit_cast >= 201806L + #define TOML11_HAS_STD_BIT_CAST 1 + #endif + #endif +#endif + +namespace toml { + namespace cxx { +#if defined(TOML11_HAS_STD_BIT_CAST) + + using std::bit_cast; + +#else + + template + U bit_cast(const T& x) noexcept { + static_assert(sizeof(T) == sizeof(U), ""); + static_assert(std::is_default_constructible::value, ""); + + U z; + std::memcpy(reinterpret_cast(std::addressof(z)), + reinterpret_cast(std::addressof(x)), + sizeof(T)); + + return z; + } + +#endif // TOML11_HAS_STD_BIT_CAST + + } // namespace cxx +} // namespace toml + +// --------------------------------------------------------------------------- +// C++20 remove_cvref_t + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX20_VALUE + #if defined(__cpp_lib_remove_cvref) + #if __cpp_lib_remove_cvref >= 201711L + #define TOML11_HAS_STD_REMOVE_CVREF 1 + #endif + #endif +#endif + +namespace toml { + namespace cxx { +#if defined(TOML11_HAS_STD_REMOVE_CVREF) + + using std::remove_cvref; + using std::remove_cvref_t; + +#else + + template + struct remove_cvref { + using type = typename std::remove_cv::type>::type; + }; + + template + using remove_cvref_t = typename remove_cvref::type; + +#endif // TOML11_HAS_STD_REMOVE_CVREF + + } // namespace cxx +} // namespace toml + +// --------------------------------------------------------------------------- +// C++17 and/or/not + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX17_VALUE + #if defined(__cpp_lib_logical_traits) + #if __cpp_lib_logical_traits >= 201510L + #define TOML11_HAS_STD_CONJUNCTION 1 + #endif + #endif +#endif + +namespace toml { + namespace cxx { +#if defined(TOML11_HAS_STD_CONJUNCTION) + + using std::conjunction; + using std::disjunction; + using std::negation; + +#else + + template + struct conjunction : std::true_type {}; + + template + struct conjunction : T {}; + + template + struct conjunction + : std::conditional(T::value), conjunction, T>::type { + }; + + template + struct disjunction : std::false_type {}; + + template + struct disjunction : T {}; + + template + struct disjunction + : std::conditional(T::value), T, disjunction>::type { + }; + + template + struct negation + : std::integral_constant(T::value)> {}; + +#endif // TOML11_HAS_STD_CONJUNCTION + + } // namespace cxx +} // namespace toml + +// --------------------------------------------------------------------------- +// C++14 index_sequence + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX14_VALUE + #if defined(__cpp_lib_integer_sequence) + #if __cpp_lib_integer_sequence >= 201304L + #define TOML11_HAS_STD_INTEGER_SEQUENCE 1 + #endif + #endif +#endif + +namespace toml { + namespace cxx { +#if defined(TOML11_HAS_STD_INTEGER_SEQUENCE) + + using std::index_sequence; + using std::make_index_sequence; + +#else + + template + struct index_sequence {}; + + template + struct double_index_sequence; + + template + struct double_index_sequence> { + using type = index_sequence; + }; + + template + struct double_index_sequence> { + using type = index_sequence; + }; + + template + struct index_sequence_maker { + using type = + typename double_index_sequence::type>::type; + }; + + template <> + struct index_sequence_maker<0> { + using type = index_sequence<>; + }; + + template + using make_index_sequence = typename index_sequence_maker::type; + +#endif // TOML11_HAS_STD_INTEGER_SEQUENCE + + } // namespace cxx +} // namespace toml + +// --------------------------------------------------------------------------- +// C++14 enable_if_t + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX14_VALUE + #if defined(__cpp_lib_transformation_trait_aliases) + #if __cpp_lib_transformation_trait_aliases >= 201304L + #define TOML11_HAS_STD_ENABLE_IF_T 1 + #endif + #endif +#endif + +namespace toml { + namespace cxx { +#if defined(TOML11_HAS_STD_ENABLE_IF_T) + + using std::enable_if_t; + +#else + + template + using enable_if_t = typename std::enable_if::type; + +#endif // TOML11_HAS_STD_ENABLE_IF_T + + } // namespace cxx +} // namespace toml + +// --------------------------------------------------------------------------- +// return_type_of_t + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX17_VALUE + #if defined(__cpp_lib_is_invocable) + #if __cpp_lib_is_invocable >= 201703 + #define TOML11_HAS_STD_INVOKE_RESULT 1 + #endif + #endif +#endif + +namespace toml { + namespace cxx { +#if defined(TOML11_HAS_STD_INVOKE_RESULT) + + template + using return_type_of_t = std::invoke_result_t; + +#else + + // result_of is deprecated after C++17 + template + using return_type_of_t = typename std::result_of::type; + +#endif // TOML11_HAS_STD_INVOKE_RESULT + + } // namespace cxx +} // namespace toml + +// ---------------------------------------------------------------------------- +// (subset of) source_location + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= 202002L + #if __has_include() + #define TOML11_HAS_STD_SOURCE_LOCATION + #endif // has_include +#endif // c++20 + +#if !defined(TOML11_HAS_STD_SOURCE_LOCATION) + #if defined(__GNUC__) && !defined(__clang__) + #if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX14_VALUE + #if __has_include() + #define TOML11_HAS_EXPERIMENTAL_SOURCE_LOCATION + #endif + #endif + #endif // GNU g++ +#endif // not TOML11_HAS_STD_SOURCE_LOCATION + +#if !defined(TOML11_HAS_STD_SOURCE_LOCATION) && \ + !defined(TOML11_HAS_EXPERIMENTAL_SOURCE_LOCATION) + #if defined(__GNUC__) && !defined(__clang__) + #if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9)) + #define TOML11_HAS_BUILTIN_FILE_LINE 1 + #define TOML11_BUILTIN_LINE_TYPE int + #endif + #elif defined(__clang__) // clang 9.0.0 implements builtin_FILE/LINE + #if __has_builtin(__builtin_FILE) && __has_builtin(__builtin_LINE) + #define TOML11_HAS_BUILTIN_FILE_LINE 1 + #define TOML11_BUILTIN_LINE_TYPE unsigned int + #endif + #elif defined(_MSVC_LANG) && defined(_MSC_VER) + #if _MSC_VER > 1926 + #define TOML11_HAS_BUILTIN_FILE_LINE 1 + #define TOML11_BUILTIN_LINE_TYPE int + #endif + #endif +#endif + +#if defined(TOML11_HAS_STD_SOURCE_LOCATION) + #include + +namespace toml { + namespace cxx { + using source_location = std::source_location; + + inline std::string to_string(const source_location& loc) { + return std::string(" at line ") + std::to_string(loc.line()) + + std::string(" in file ") + std::string(loc.file_name()); + } + } // namespace cxx +} // namespace toml +#elif defined(TOML11_HAS_EXPERIMENTAL_SOURCE_LOCATION) + #include + +namespace toml { + namespace cxx { + using source_location = std::experimental::source_location; + + inline std::string to_string(const source_location& loc) { + return std::string(" at line ") + std::to_string(loc.line()) + + std::string(" in file ") + std::string(loc.file_name()); + } + } // namespace cxx +} // namespace toml +#elif defined(TOML11_HAS_BUILTIN_FILE_LINE) +namespace toml { + namespace cxx { + struct source_location { + using line_type = TOML11_BUILTIN_LINE_TYPE; + + static source_location current(const line_type line = __builtin_LINE(), + const char* file = __builtin_FILE()) { + return source_location(line, file); + } + + source_location(const line_type line, const char* file) + : line_(line) + , file_name_(file) {} + + line_type line() const noexcept { + return line_; + } + + const char* file_name() const noexcept { + return file_name_; + } + + private: + line_type line_; + const char* file_name_; + }; + + inline std::string to_string(const source_location& loc) { + return std::string(" at line ") + std::to_string(loc.line()) + + std::string(" in file ") + std::string(loc.file_name()); + } + } // namespace cxx +} // namespace toml +#else // no builtin +namespace toml { + namespace cxx { + struct source_location { + static source_location current() { + return source_location {}; + } + }; + + inline std::string to_string(const source_location&) { + return std::string(""); + } + } // namespace cxx +} // namespace toml +#endif // TOML11_HAS_STD_SOURCE_LOCATION + +// ---------------------------------------------------------------------------- +// (subset of) optional + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX17_VALUE + #if __has_include() + #include + #endif // has_include(optional) +#endif // C++17 + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX17_VALUE + #if defined(__cpp_lib_optional) + #if __cpp_lib_optional >= 201606L + #define TOML11_HAS_STD_OPTIONAL 1 + #endif + #endif +#endif + +#if defined(TOML11_HAS_STD_OPTIONAL) + +namespace toml { + namespace cxx { + using std::optional; + + inline std::nullopt_t make_nullopt() { + return std::nullopt; + } + + template + std::basic_ostream& operator<<( + std::basic_ostream& os, + const std::nullopt_t&) { + os << "nullopt"; + return os; + } + + } // namespace cxx +} // namespace toml + +#else // TOML11_HAS_STD_OPTIONAL + +namespace toml { + namespace cxx { + + struct nullopt_t {}; + + inline nullopt_t make_nullopt() { + return nullopt_t {}; + } + + inline bool operator==(const nullopt_t&, const nullopt_t&) noexcept { + return true; + } + + inline bool operator!=(const nullopt_t&, const nullopt_t&) noexcept { + return false; + } + + inline bool operator<(const nullopt_t&, const nullopt_t&) noexcept { + return false; + } + + inline bool operator<=(const nullopt_t&, const nullopt_t&) noexcept { + return true; + } + + inline bool operator>(const nullopt_t&, const nullopt_t&) noexcept { + return false; + } + + inline bool operator>=(const nullopt_t&, const nullopt_t&) noexcept { + return true; + } + + template + std::basic_ostream& operator<<( + std::basic_ostream& os, + const nullopt_t&) { + os << "nullopt"; + return os; + } + + template + class optional { + public: + using value_type = T; + + public: + optional() noexcept : has_value_(false), null_('\0') {} + + optional(nullopt_t) noexcept : has_value_(false), null_('\0') {} + + optional(const T& x) : has_value_(true), value_(x) {} + + optional(T&& x) : has_value_(true), value_(std::move(x)) {} + + template ::value, std::nullptr_t> = nullptr> + explicit optional(U&& x) : has_value_(true) + , value_(std::forward(x)) {} + + optional(const optional& rhs) : has_value_(rhs.has_value_) { + if (rhs.has_value_) { + this->assigner(rhs.value_); + } + } + + optional(optional&& rhs) : has_value_(rhs.has_value_) { + if (this->has_value_) { + this->assigner(std::move(rhs.value_)); + } + } + + optional& operator=(const optional& rhs) { + if (this == std::addressof(rhs)) { + return *this; + } + + this->cleanup(); + this->has_value_ = rhs.has_value_; + if (this->has_value_) { + this->assigner(rhs.value_); + } + return *this; + } + + optional& operator=(optional&& rhs) { + if (this == std::addressof(rhs)) { + return *this; + } + + this->cleanup(); + this->has_value_ = rhs.has_value_; + if (this->has_value_) { + this->assigner(std::move(rhs.value_)); + } + return *this; + } + + template >, + std::is_constructible>::value, + std::nullptr_t> = nullptr> + explicit optional(const optional& rhs) + : has_value_(rhs.has_value_) + , null_('\0') { + if (rhs.has_value_) { + this->assigner(rhs.value_); + } + } + + template >, + std::is_constructible>::value, + std::nullptr_t> = nullptr> + explicit optional(optional&& rhs) + : has_value_(rhs.has_value_) + , null_('\0') { + if (this->has_value_) { + this->assigner(std::move(rhs.value_)); + } + } + + template >, + std::is_constructible>::value, + std::nullptr_t> = nullptr> + optional& operator=(const optional& rhs) { + if (this == std::addressof(rhs)) { + return *this; + } + + this->cleanup(); + this->has_value_ = rhs.has_value_; + if (this->has_value_) { + this->assigner(rhs.value_); + } + return *this; + } + + template >, + std::is_constructible>::value, + std::nullptr_t> = nullptr> + optional& operator=(optional&& rhs) { + if (this == std::addressof(rhs)) { + return *this; + } + + this->cleanup(); + this->has_value_ = rhs.has_value_; + if (this->has_value_) { + this->assigner(std::move(rhs.value_)); + } + return *this; + } + + ~optional() noexcept { + this->cleanup(); + } + + explicit operator bool() const noexcept { + return has_value_; + } + + bool has_value() const noexcept { + return has_value_; + } + + const value_type& value(source_location loc = source_location::current()) const { + if (!this->has_value_) { + throw std::runtime_error( + "optional::value(): bad_unwrap" + to_string(loc)); + } + return this->value_; + } + + value_type& value(source_location loc = source_location::current()) { + if (!this->has_value_) { + throw std::runtime_error( + "optional::value(): bad_unwrap" + to_string(loc)); + } + return this->value_; + } + + const value_type& value_or(const value_type& opt) const { + if (this->has_value_) { + return this->value_; + } else { + return opt; + } + } + + value_type& value_or(value_type& opt) { + if (this->has_value_) { + return this->value_; + } else { + return opt; + } + } + + private: + void cleanup() noexcept { + if (this->has_value_) { + value_.~T(); + } + } + + template + void assigner(U&& x) { + const auto tmp = ::new (std::addressof(this->value_)) + value_type(std::forward(x)); + assert(tmp == std::addressof(this->value_)); + (void)tmp; + } + + private: + bool has_value_; + + union { + char null_; + T value_; + }; + }; + } // namespace cxx +} // namespace toml +#endif // TOML11_HAS_STD_OPTIONAL + +#endif // TOML11_COMPAT_HPP +#ifndef TOML11_VALUE_T_HPP +#define TOML11_VALUE_T_HPP + +#ifndef TOML11_VALUE_T_FWD_HPP + #define TOML11_VALUE_T_FWD_HPP + + #include + #include + #include + #include + +namespace toml { + + // forward decl + template + class basic_value; + + // ---------------------------------------------------------------------------- + // enum representing toml types + + enum class value_t : std::uint8_t { + empty = 0, + boolean = 1, + integer = 2, + floating = 3, + string = 4, + offset_datetime = 5, + local_datetime = 6, + local_date = 7, + local_time = 8, + array = 9, + table = 10 + }; + + std::ostream& operator<<(std::ostream& os, value_t t); + std::string to_string(value_t t); + + // ---------------------------------------------------------------------------- + // meta functions for internal use + + namespace detail { + + template + using value_t_constant = std::integral_constant; + + template + struct type_to_enum : value_t_constant {}; + + template + struct type_to_enum + : value_t_constant {}; + + template + struct type_to_enum + : value_t_constant {}; + + template + struct type_to_enum + : value_t_constant {}; + + template + struct type_to_enum + : value_t_constant {}; + + template + struct type_to_enum + : value_t_constant {}; + + template + struct type_to_enum + : value_t_constant {}; + + template + struct type_to_enum + : value_t_constant {}; + + template + struct type_to_enum + : value_t_constant {}; + + template + struct type_to_enum + : value_t_constant {}; + + template + struct type_to_enum + : value_t_constant {}; + + template + struct enum_to_type { + using type = void; + }; + + template + struct enum_to_type { + using type = typename V::boolean_type; + }; + + template + struct enum_to_type { + using type = typename V::integer_type; + }; + + template + struct enum_to_type { + using type = typename V::floating_type; + }; + + template + struct enum_to_type { + using type = typename V::string_type; + }; + + template + struct enum_to_type { + using type = typename V::offset_datetime_type; + }; + + template + struct enum_to_type { + using type = typename V::local_datetime_type; + }; + + template + struct enum_to_type { + using type = typename V::local_date_type; + }; + + template + struct enum_to_type { + using type = typename V::local_time_type; + }; + + template + struct enum_to_type { + using type = typename V::array_type; + }; + + template + struct enum_to_type { + using type = typename V::table_type; + }; + + template + using enum_to_type_t = typename enum_to_type::type; + + template + struct enum_to_fmt_type { + using type = void; + }; + + template <> + struct enum_to_fmt_type { + using type = boolean_format_info; + }; + + template <> + struct enum_to_fmt_type { + using type = integer_format_info; + }; + + template <> + struct enum_to_fmt_type { + using type = floating_format_info; + }; + + template <> + struct enum_to_fmt_type { + using type = string_format_info; + }; + + template <> + struct enum_to_fmt_type { + using type = offset_datetime_format_info; + }; + + template <> + struct enum_to_fmt_type { + using type = local_datetime_format_info; + }; + + template <> + struct enum_to_fmt_type { + using type = local_date_format_info; + }; + + template <> + struct enum_to_fmt_type { + using type = local_time_format_info; + }; + + template <> + struct enum_to_fmt_type { + using type = array_format_info; + }; + + template <> + struct enum_to_fmt_type { + using type = table_format_info; + }; + + template + using enum_to_fmt_type_t = typename enum_to_fmt_type::type; + + template + struct is_exact_toml_type0 + : cxx::disjunction, + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same> {}; + + template + struct is_exact_toml_type : is_exact_toml_type0, V> {}; + + template + struct is_not_toml_type : cxx::negation> {}; + + } // namespace detail +} // namespace toml +#endif // TOML11_VALUE_T_FWD_HPP + +#if !defined(TOML11_COMPILE_SOURCES) + #ifndef TOML11_VALUE_T_IMPL_HPP + #define TOML11_VALUE_T_IMPL_HPP + + #include + #include + #include + +namespace toml { + + TOML11_INLINE std::ostream& operator<<(std::ostream& os, value_t t) { + switch (t) { + case value_t::boolean: + os << "boolean"; + return os; + case value_t::integer: + os << "integer"; + return os; + case value_t::floating: + os << "floating"; + return os; + case value_t::string: + os << "string"; + return os; + case value_t::offset_datetime: + os << "offset_datetime"; + return os; + case value_t::local_datetime: + os << "local_datetime"; + return os; + case value_t::local_date: + os << "local_date"; + return os; + case value_t::local_time: + os << "local_time"; + return os; + case value_t::array: + os << "array"; + return os; + case value_t::table: + os << "table"; + return os; + case value_t::empty: + os << "empty"; + return os; + default: + os << "unknown"; + return os; + } + } + + TOML11_INLINE std::string to_string(value_t t) { + std::ostringstream oss; + oss << t; + return oss.str(); + } + +} // namespace toml + #endif // TOML11_VALUE_T_IMPL_HPP +#endif + +#endif // TOML11_VALUE_T_HPP +#ifndef TOML11_STORAGE_HPP +#define TOML11_STORAGE_HPP + +namespace toml { + namespace detail { + + // It owns a pointer to T. It does deep-copy when copied. + // This struct is introduced to implement a recursive type. + // + // `toml::value` contains `std::vector` to represent a toml + // array. But, in the definition of `toml::value`, `toml::value` is still + // incomplete. `std::vector` of an incomplete type is not allowed in C++11 + // (it is allowed after C++17). To avoid this, we need to use a pointer to + // `toml::value`, like `std::vector>`. Although + // `std::unique_ptr` is noncopyable, we want to make `toml::value` copyable. + // `storage` is introduced to resolve those problems. + template + struct storage { + using value_type = T; + + explicit storage(value_type v) + : ptr_(cxx::make_unique(std::move(v))) {} + + ~storage() = default; + + storage(const storage& rhs) : ptr_(cxx::make_unique(*rhs.ptr_)) {} + + storage& operator=(const storage& rhs) { + this->ptr_ = cxx::make_unique(*rhs.ptr_); + return *this; + } + + storage(storage&&) = default; + storage& operator=(storage&&) = default; + + bool is_ok() const noexcept { + return static_cast(ptr_); + } + + value_type& get() const noexcept { + return *ptr_; + } + + private: + std::unique_ptr ptr_; + }; + + } // namespace detail +} // namespace toml +#endif // TOML11_STORAGE_HPP +#ifndef TOML11_COMMENTS_HPP +#define TOML11_COMMENTS_HPP + +#ifndef TOML11_COMMENTS_FWD_HPP + #define TOML11_COMMENTS_FWD_HPP + + // to use __has_builtin + + #include + #include + #include + #include + #include + #include + #include + #include + #include + +// This file provides mainly two classes, `preserve_comments` and `discard_comments`. +// Those two are a container that have the same interface as `std::vector` +// but bahaves in the opposite way. `preserve_comments` is just the same as +// `std::vector` and each `std::string` corresponds to a comment line. +// Conversely, `discard_comments` discards all the strings and ignores everything +// assigned in it. `discard_comments` is always empty and you will encounter an +// error whenever you access to the element. +namespace toml { + class discard_comments; // forward decl + + class preserve_comments { + public: + // `container_type` is not provided in discard_comments. + // do not use this inner-type in a generic code. + using container_type = std::vector; + + using size_type = container_type::size_type; + using difference_type = container_type::difference_type; + using value_type = container_type::value_type; + using reference = container_type::reference; + using const_reference = container_type::const_reference; + using pointer = container_type::pointer; + using const_pointer = container_type::const_pointer; + using iterator = container_type::iterator; + using const_iterator = container_type::const_iterator; + using reverse_iterator = container_type::reverse_iterator; + using const_reverse_iterator = container_type::const_reverse_iterator; + + public: + preserve_comments() = default; + ~preserve_comments() = default; + preserve_comments(const preserve_comments&) = default; + preserve_comments(preserve_comments&&) = default; + preserve_comments& operator=(const preserve_comments&) = default; + preserve_comments& operator=(preserve_comments&&) = default; + + explicit preserve_comments(const std::vector& c) + : comments(c) {} + + explicit preserve_comments(std::vector&& c) + : comments(std::move(c)) {} + + preserve_comments& operator=(const std::vector& c) { + comments = c; + return *this; + } + + preserve_comments& operator=(std::vector&& c) { + comments = std::move(c); + return *this; + } + + explicit preserve_comments(const discard_comments&) {} + + explicit preserve_comments(size_type n) : comments(n) {} + + preserve_comments(size_type n, const std::string& x) : comments(n, x) {} + + preserve_comments(std::initializer_list x) : comments(x) {} + + template + preserve_comments(InputIterator first, InputIterator last) + : comments(first, last) {} + + template + void assign(InputIterator first, InputIterator last) { + comments.assign(first, last); + } + + void assign(std::initializer_list ini) { + comments.assign(ini); + } + + void assign(size_type n, const std::string& val) { + comments.assign(n, val); + } + + // Related to the issue #97. + // + // `std::vector::insert` and `std::vector::erase` in the STL implementation + // included in GCC 4.8.5 takes `std::vector::iterator` instead of + // `std::vector::const_iterator`. It causes compilation error in GCC 4.8.5. + #if defined(__GNUC__) && defined(__GNUC_MINOR__) && \ + defined(__GNUC_PATCHLEVEL__) && !defined(__clang__) + #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) <= 40805 + #define TOML11_WORKAROUND_GCC_4_8_X_STANDARD_LIBRARY_IMPLEMENTATION + #endif + #endif + + #ifdef TOML11_WORKAROUND_GCC_4_8_X_STANDARD_LIBRARY_IMPLEMENTATION + iterator insert(iterator p, const std::string& x) { + return comments.insert(p, x); + } + + iterator insert(iterator p, std::string&& x) { + return comments.insert(p, std::move(x)); + } + + void insert(iterator p, size_type n, const std::string& x) { + return comments.insert(p, n, x); + } + + template + void insert(iterator p, InputIterator first, InputIterator last) { + return comments.insert(p, first, last); + } + + void insert(iterator p, std::initializer_list ini) { + return comments.insert(p, ini); + } + + template + iterator emplace(iterator p, Ts&&... args) { + return comments.emplace(p, std::forward(args)...); + } + + iterator erase(iterator pos) { + return comments.erase(pos); + } + + iterator erase(iterator first, iterator last) { + return comments.erase(first, last); + } + #else + iterator insert(const_iterator p, const std::string& x) { + return comments.insert(p, x); + } + + iterator insert(const_iterator p, std::string&& x) { + return comments.insert(p, std::move(x)); + } + + iterator insert(const_iterator p, size_type n, const std::string& x) { + return comments.insert(p, n, x); + } + + template + iterator insert(const_iterator p, InputIterator first, InputIterator last) { + return comments.insert(p, first, last); + } + + iterator insert(const_iterator p, std::initializer_list ini) { + return comments.insert(p, ini); + } + + template + iterator emplace(const_iterator p, Ts&&... args) { + return comments.emplace(p, std::forward(args)...); + } + + iterator erase(const_iterator pos) { + return comments.erase(pos); + } + + iterator erase(const_iterator first, const_iterator last) { + return comments.erase(first, last); + } + #endif + + void swap(preserve_comments& other) { + comments.swap(other.comments); + } + + void push_back(const std::string& v) { + comments.push_back(v); + } + + void push_back(std::string&& v) { + comments.push_back(std::move(v)); + } + + void pop_back() { + comments.pop_back(); + } + + template + void emplace_back(Ts&&... args) { + comments.emplace_back(std::forward(args)...); + } + + void clear() { + comments.clear(); + } + + size_type size() const noexcept { + return comments.size(); + } + + size_type max_size() const noexcept { + return comments.max_size(); + } + + size_type capacity() const noexcept { + return comments.capacity(); + } + + bool empty() const noexcept { + return comments.empty(); + } + + void reserve(size_type n) { + comments.reserve(n); + } + + void resize(size_type n) { + comments.resize(n); + } + + void resize(size_type n, const std::string& c) { + comments.resize(n, c); + } + + void shrink_to_fit() { + comments.shrink_to_fit(); + } + + reference operator[](const size_type n) noexcept { + return comments[n]; + } + + const_reference operator[](const size_type n) const noexcept { + return comments[n]; + } + + reference at(const size_type n) { + return comments.at(n); + } + + const_reference at(const size_type n) const { + return comments.at(n); + } + + reference front() noexcept { + return comments.front(); + } + + const_reference front() const noexcept { + return comments.front(); + } + + reference back() noexcept { + return comments.back(); + } + + const_reference back() const noexcept { + return comments.back(); + } + + pointer data() noexcept { + return comments.data(); + } + + const_pointer data() const noexcept { + return comments.data(); + } + + iterator begin() noexcept { + return comments.begin(); + } + + iterator end() noexcept { + return comments.end(); + } + + const_iterator begin() const noexcept { + return comments.begin(); + } + + const_iterator end() const noexcept { + return comments.end(); + } + + const_iterator cbegin() const noexcept { + return comments.cbegin(); + } + + const_iterator cend() const noexcept { + return comments.cend(); + } + + reverse_iterator rbegin() noexcept { + return comments.rbegin(); + } + + reverse_iterator rend() noexcept { + return comments.rend(); + } + + const_reverse_iterator rbegin() const noexcept { + return comments.rbegin(); + } + + const_reverse_iterator rend() const noexcept { + return comments.rend(); + } + + const_reverse_iterator crbegin() const noexcept { + return comments.crbegin(); + } + + const_reverse_iterator crend() const noexcept { + return comments.crend(); + } + + friend bool operator==(const preserve_comments&, const preserve_comments&); + friend bool operator!=(const preserve_comments&, const preserve_comments&); + friend bool operator<(const preserve_comments&, const preserve_comments&); + friend bool operator<=(const preserve_comments&, const preserve_comments&); + friend bool operator>(const preserve_comments&, const preserve_comments&); + friend bool operator>=(const preserve_comments&, const preserve_comments&); + + friend void swap(preserve_comments&, std::vector&); + friend void swap(std::vector&, preserve_comments&); + + private: + container_type comments; + }; + + bool operator==(const preserve_comments& lhs, const preserve_comments& rhs); + bool operator!=(const preserve_comments& lhs, const preserve_comments& rhs); + bool operator<(const preserve_comments& lhs, const preserve_comments& rhs); + bool operator<=(const preserve_comments& lhs, const preserve_comments& rhs); + bool operator>(const preserve_comments& lhs, const preserve_comments& rhs); + bool operator>=(const preserve_comments& lhs, const preserve_comments& rhs); + + void swap(preserve_comments& lhs, preserve_comments& rhs); + void swap(preserve_comments& lhs, std::vector& rhs); + void swap(std::vector& lhs, preserve_comments& rhs); + + std::ostream& operator<<(std::ostream& os, const preserve_comments& com); + + namespace detail { + + // To provide the same interface with `preserve_comments`, + // `discard_comments` should have an iterator. But it does not contain + // anything, so we need to add an iterator that points nothing. + // + // It always points null, so DO NOT unwrap this iterator. It always crashes + // your program. + template + struct empty_iterator { + using value_type = T; + using reference_type = typename std::conditional::type; + using pointer_type = typename std::conditional::type; + using difference_type = std::ptrdiff_t; + using iterator_category = std::random_access_iterator_tag; + + empty_iterator() = default; + ~empty_iterator() = default; + empty_iterator(const empty_iterator&) = default; + empty_iterator(empty_iterator&&) = default; + empty_iterator& operator=(const empty_iterator&) = default; + empty_iterator& operator=(empty_iterator&&) = default; + + // DO NOT call these operators. + reference_type operator*() const noexcept { + std::terminate(); + } + + pointer_type operator->() const noexcept { + return nullptr; + } + + reference_type operator[](difference_type) const noexcept { + return this->operator*(); + } + + // These operators do nothing. + empty_iterator& operator++() noexcept { + return *this; + } + + empty_iterator operator++(int) noexcept { + return *this; + } + + empty_iterator& operator--() noexcept { + return *this; + } + + empty_iterator operator--(int) noexcept { + return *this; + } + + empty_iterator& operator+=(difference_type) noexcept { + return *this; + } + + empty_iterator& operator-=(difference_type) noexcept { + return *this; + } + + empty_iterator operator+(difference_type) const noexcept { + return *this; + } + + empty_iterator operator-(difference_type) const noexcept { + return *this; + } + }; + + template + bool operator==(const empty_iterator&, + const empty_iterator&) noexcept { + return true; + } + + template + bool operator!=(const empty_iterator&, + const empty_iterator&) noexcept { + return false; + } + + template + bool operator<(const empty_iterator&, + const empty_iterator&) noexcept { + return false; + } + + template + bool operator<=(const empty_iterator&, + const empty_iterator&) noexcept { + return true; + } + + template + bool operator>(const empty_iterator&, + const empty_iterator&) noexcept { + return false; + } + + template + bool operator>=(const empty_iterator&, + const empty_iterator&) noexcept { + return true; + } + + template + typename empty_iterator::difference_type operator-( + const empty_iterator&, + const empty_iterator&) noexcept { + return 0; + } + + template + empty_iterator operator+(typename empty_iterator::difference_type, + const empty_iterator& rhs) noexcept { + return rhs; + } + + template + empty_iterator operator+( + const empty_iterator& lhs, + typename empty_iterator::difference_type) noexcept { + return lhs; + } + + } // namespace detail + + // The default comment type. It discards all the comments. It requires only one + // byte to contain, so the memory footprint is smaller than preserve_comments. + // + // It just ignores `push_back`, `insert`, `erase`, and any other modifications. + // IT always returns size() == 0, the iterator taken by `begin()` is always the + // same as that of `end()`, and accessing through `operator[]` or iterators + // always causes a segmentation fault. DO NOT access to the element of this. + // + // Why this is chose as the default type is because the last version (2.x.y) + // does not contain any comments in a value. To minimize the impact on the + // efficiency, this is chosen as a default. + // + // To reduce the memory footprint, later we can try empty base optimization (EBO). + class discard_comments { + public: + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using value_type = std::string; + using reference = std::string&; + using const_reference = const std::string&; + using pointer = std::string*; + using const_pointer = const std::string*; + using iterator = detail::empty_iterator; + using const_iterator = detail::empty_iterator; + using reverse_iterator = detail::empty_iterator; + using const_reverse_iterator = detail::empty_iterator; + + public: + discard_comments() = default; + ~discard_comments() = default; + discard_comments(const discard_comments&) = default; + discard_comments(discard_comments&&) = default; + discard_comments& operator=(const discard_comments&) = default; + discard_comments& operator=(discard_comments&&) = default; + + explicit discard_comments(const std::vector&) noexcept {} + + explicit discard_comments(std::vector&&) noexcept {} + + discard_comments& operator=(const std::vector&) noexcept { + return *this; + } + + discard_comments& operator=(std::vector&&) noexcept { + return *this; + } + + explicit discard_comments(const preserve_comments&) noexcept {} + + explicit discard_comments(size_type) noexcept {} + + discard_comments(size_type, const std::string&) noexcept {} + + discard_comments(std::initializer_list) noexcept {} + + template + discard_comments(InputIterator, InputIterator) noexcept {} + + template + void assign(InputIterator, InputIterator) noexcept {} + + void assign(std::initializer_list) noexcept {} + + void assign(size_type, const std::string&) noexcept {} + + iterator insert(const_iterator, const std::string&) { + return iterator {}; + } + + iterator insert(const_iterator, std::string&&) { + return iterator {}; + } + + iterator insert(const_iterator, size_type, const std::string&) { + return iterator {}; + } + + template + iterator insert(const_iterator, InputIterator, InputIterator) { + return iterator {}; + } + + iterator insert(const_iterator, std::initializer_list) { + return iterator {}; + } + + template + iterator emplace(const_iterator, Ts&&...) { + return iterator {}; + } + + iterator erase(const_iterator) { + return iterator {}; + } + + iterator erase(const_iterator, const_iterator) { + return iterator {}; + } + + void swap(discard_comments&) { + return; + } + + void push_back(const std::string&) { + return; + } + + void push_back(std::string&&) { + return; + } + + void pop_back() { + return; + } + + template + void emplace_back(Ts&&...) { + return; + } + + void clear() { + return; + } + + size_type size() const noexcept { + return 0; + } + + size_type max_size() const noexcept { + return 0; + } + + size_type capacity() const noexcept { + return 0; + } + + bool empty() const noexcept { + return true; + } + + void reserve(size_type) { + return; + } + + void resize(size_type) { + return; + } + + void resize(size_type, const std::string&) { + return; + } + + void shrink_to_fit() { + return; + } + + // DO NOT access to the element of this container. This container is always + // empty, so accessing through operator[], front/back, data causes address + // error. + + reference operator[](const size_type) noexcept { + never_call("toml::discard_comment::operator[]"); + } + + const_reference operator[](const size_type) const noexcept { + never_call("toml::discard_comment::operator[]"); + } + + reference at(const size_type) { + throw std::out_of_range("toml::discard_comment is always empty."); + } + + const_reference at(const size_type) const { + throw std::out_of_range("toml::discard_comment is always empty."); + } + + reference front() noexcept { + never_call("toml::discard_comment::front"); + } + + const_reference front() const noexcept { + never_call("toml::discard_comment::front"); + } + + reference back() noexcept { + never_call("toml::discard_comment::back"); + } + + const_reference back() const noexcept { + never_call("toml::discard_comment::back"); + } + + pointer data() noexcept { + return nullptr; + } + + const_pointer data() const noexcept { + return nullptr; + } + + iterator begin() noexcept { + return iterator {}; + } + + iterator end() noexcept { + return iterator {}; + } + + const_iterator begin() const noexcept { + return const_iterator {}; + } + + const_iterator end() const noexcept { + return const_iterator {}; + } + + const_iterator cbegin() const noexcept { + return const_iterator {}; + } + + const_iterator cend() const noexcept { + return const_iterator {}; + } + + reverse_iterator rbegin() noexcept { + return iterator {}; + } + + reverse_iterator rend() noexcept { + return iterator {}; + } + + const_reverse_iterator rbegin() const noexcept { + return const_iterator {}; + } + + const_reverse_iterator rend() const noexcept { + return const_iterator {}; + } + + const_reverse_iterator crbegin() const noexcept { + return const_iterator {}; + } + + const_reverse_iterator crend() const noexcept { + return const_iterator {}; + } + + private: + [[noreturn]] + static void never_call(const char* const this_function) { + #if __has_builtin(__builtin_unreachable) + __builtin_unreachable(); + #endif + throw std::logic_error { this_function }; + } + }; + + inline bool operator==(const discard_comments&, const discard_comments&) noexcept { + return true; + } + + inline bool operator!=(const discard_comments&, const discard_comments&) noexcept { + return false; + } + + inline bool operator<(const discard_comments&, const discard_comments&) noexcept { + return false; + } + + inline bool operator<=(const discard_comments&, const discard_comments&) noexcept { + return true; + } + + inline bool operator>(const discard_comments&, const discard_comments&) noexcept { + return false; + } + + inline bool operator>=(const discard_comments&, const discard_comments&) noexcept { + return true; + } + + inline void swap(const discard_comments&, const discard_comments&) noexcept { + return; + } + + inline std::ostream& operator<<(std::ostream& os, const discard_comments&) { + return os; + } + +} // namespace toml +#endif // TOML11_COMMENTS_FWD_HPP + +#if !defined(TOML11_COMPILE_SOURCES) + #ifndef TOML11_COMMENTS_IMPL_HPP + #define TOML11_COMMENTS_IMPL_HPP + +namespace toml { + + TOML11_INLINE bool operator==(const preserve_comments& lhs, + const preserve_comments& rhs) { + return lhs.comments == rhs.comments; + } + + TOML11_INLINE bool operator!=(const preserve_comments& lhs, + const preserve_comments& rhs) { + return lhs.comments != rhs.comments; + } + + TOML11_INLINE bool operator<(const preserve_comments& lhs, + const preserve_comments& rhs) { + return lhs.comments < rhs.comments; + } + + TOML11_INLINE bool operator<=(const preserve_comments& lhs, + const preserve_comments& rhs) { + return lhs.comments <= rhs.comments; + } + + TOML11_INLINE bool operator>(const preserve_comments& lhs, + const preserve_comments& rhs) { + return lhs.comments > rhs.comments; + } + + TOML11_INLINE bool operator>=(const preserve_comments& lhs, + const preserve_comments& rhs) { + return lhs.comments >= rhs.comments; + } + + TOML11_INLINE void swap(preserve_comments& lhs, preserve_comments& rhs) { + lhs.swap(rhs); + return; + } + + TOML11_INLINE void swap(preserve_comments& lhs, std::vector& rhs) { + lhs.comments.swap(rhs); + return; + } + + TOML11_INLINE void swap(std::vector& lhs, preserve_comments& rhs) { + lhs.swap(rhs.comments); + return; + } + + TOML11_INLINE std::ostream& operator<<(std::ostream& os, + const preserve_comments& com) { + for (const auto& c : com) { + if (c.front() != '#') { + os << '#'; + } + os << c << '\n'; + } + return os; + } + +} // namespace toml + #endif // TOML11_COMMENTS_IMPL_HPP +#endif + +#endif // TOML11_COMMENTS_HPP +#ifndef TOML11_COLOR_HPP +#define TOML11_COLOR_HPP + +#ifndef TOML11_COLOR_FWD_HPP + #define TOML11_COLOR_FWD_HPP + + #include + + #ifdef TOML11_COLORIZE_ERROR_MESSAGE + #define TOML11_ERROR_MESSAGE_COLORIZED true + #else + #define TOML11_ERROR_MESSAGE_COLORIZED false + #endif + + #ifdef TOML11_USE_THREAD_LOCAL_COLORIZATION + #define TOML11_THREAD_LOCAL_COLORIZATION thread_local + #else + #define TOML11_THREAD_LOCAL_COLORIZATION + #endif + +namespace toml { + namespace color { + // put ANSI escape sequence to ostream + inline namespace ansi { + namespace detail { + + // Control color mode globally + class color_mode { + public: + void enable() noexcept { + should_color_ = true; + } + + void disable() noexcept { + should_color_ = false; + } + + bool should_color() const noexcept { + return should_color_; + } + + private: + bool should_color_ = TOML11_ERROR_MESSAGE_COLORIZED; + }; + + inline color_mode& color_status() noexcept { + static TOML11_THREAD_LOCAL_COLORIZATION color_mode status; + return status; + } + + } // namespace detail + + std::ostream& reset(std::ostream& os); + std::ostream& bold(std::ostream& os); + std::ostream& grey(std::ostream& os); + std::ostream& gray(std::ostream& os); + std::ostream& red(std::ostream& os); + std::ostream& green(std::ostream& os); + std::ostream& yellow(std::ostream& os); + std::ostream& blue(std::ostream& os); + std::ostream& magenta(std::ostream& os); + std::ostream& cyan(std::ostream& os); + std::ostream& white(std::ostream& os); + + } // namespace ansi + + inline void enable() { + return detail::color_status().enable(); + } + + inline void disable() { + return detail::color_status().disable(); + } + + inline bool should_color() { + return detail::color_status().should_color(); + } + + } // namespace color +} // namespace toml +#endif // TOML11_COLOR_FWD_HPP + +#if !defined(TOML11_COMPILE_SOURCES) + #ifndef TOML11_COLOR_IMPL_HPP + #define TOML11_COLOR_IMPL_HPP + + #include + +namespace toml { + namespace color { + // put ANSI escape sequence to ostream + inline namespace ansi { + + TOML11_INLINE std::ostream& reset(std::ostream& os) { + if (detail::color_status().should_color()) { + os << "\033[00m"; + } + return os; + } + + TOML11_INLINE std::ostream& bold(std::ostream& os) { + if (detail::color_status().should_color()) { + os << "\033[01m"; + } + return os; + } + + TOML11_INLINE std::ostream& grey(std::ostream& os) { + if (detail::color_status().should_color()) { + os << "\033[30m"; + } + return os; + } + + TOML11_INLINE std::ostream& gray(std::ostream& os) { + if (detail::color_status().should_color()) { + os << "\033[30m"; + } + return os; + } + + TOML11_INLINE std::ostream& red(std::ostream& os) { + if (detail::color_status().should_color()) { + os << "\033[31m"; + } + return os; + } + + TOML11_INLINE std::ostream& green(std::ostream& os) { + if (detail::color_status().should_color()) { + os << "\033[32m"; + } + return os; + } + + TOML11_INLINE std::ostream& yellow(std::ostream& os) { + if (detail::color_status().should_color()) { + os << "\033[33m"; + } + return os; + } + + TOML11_INLINE std::ostream& blue(std::ostream& os) { + if (detail::color_status().should_color()) { + os << "\033[34m"; + } + return os; + } + + TOML11_INLINE std::ostream& magenta(std::ostream& os) { + if (detail::color_status().should_color()) { + os << "\033[35m"; + } + return os; + } + + TOML11_INLINE std::ostream& cyan(std::ostream& os) { + if (detail::color_status().should_color()) { + os << "\033[36m"; + } + return os; + } + + TOML11_INLINE std::ostream& white(std::ostream& os) { + if (detail::color_status().should_color()) { + os << "\033[37m"; + } + return os; + } + + } // namespace ansi + } // namespace color +} // namespace toml + #endif // TOML11_COLOR_IMPL_HPP +#endif + +#endif // TOML11_COLOR_HPP +#ifndef TOML11_SPEC_HPP +#define TOML11_SPEC_HPP + +#include +#include +#include + +namespace toml { + + struct semantic_version { + constexpr semantic_version(std::uint32_t mjr, + std::uint32_t mnr, + std::uint32_t p) noexcept + : major { mjr } + , minor { mnr } + , patch { p } {} + + std::uint32_t major; + std::uint32_t minor; + std::uint32_t patch; + }; + + constexpr inline semantic_version make_semver(std::uint32_t mjr, + std::uint32_t mnr, + std::uint32_t p) noexcept { + return semantic_version(mjr, mnr, p); + } + + constexpr inline bool operator==(const semantic_version& lhs, + const semantic_version& rhs) noexcept { + return lhs.major == rhs.major && lhs.minor == rhs.minor && + lhs.patch == rhs.patch; + } + + constexpr inline bool operator!=(const semantic_version& lhs, + const semantic_version& rhs) noexcept { + return !(lhs == rhs); + } + + constexpr inline bool operator<(const semantic_version& lhs, + const semantic_version& rhs) noexcept { + return lhs.major < rhs.major || + (lhs.major == rhs.major && lhs.minor < rhs.minor) || + (lhs.major == rhs.major && lhs.minor == rhs.minor && + lhs.patch < rhs.patch); + } + + constexpr inline bool operator>(const semantic_version& lhs, + const semantic_version& rhs) noexcept { + return rhs < lhs; + } + + constexpr inline bool operator<=(const semantic_version& lhs, + const semantic_version& rhs) noexcept { + return !(lhs > rhs); + } + + constexpr inline bool operator>=(const semantic_version& lhs, + const semantic_version& rhs) noexcept { + return !(lhs < rhs); + } + + inline std::ostream& operator<<(std::ostream& os, const semantic_version& v) { + os << v.major << '.' << v.minor << '.' << v.patch; + return os; + } + + inline std::string to_string(const semantic_version& v) { + std::ostringstream oss; + oss << v; + return oss.str(); + } + + struct spec { + constexpr static spec default_version() noexcept { + return spec::v(1, 0, 0); + } + + constexpr static spec v(std::uint32_t mjr, + std::uint32_t mnr, + std::uint32_t p) noexcept { + return spec(make_semver(mjr, mnr, p)); + } + + constexpr explicit spec(const semantic_version& semver) noexcept + : version{semver}, + v1_1_0_allow_control_characters_in_comments {semantic_version{1, 1, 0} <= semver}, + v1_1_0_allow_newlines_in_inline_tables {semantic_version{1, 1, 0} <= semver}, + v1_1_0_allow_trailing_comma_in_inline_tables{semantic_version{1, 1, 0} <= semver}, + v1_1_0_allow_non_english_in_bare_keys {semantic_version{1, 1, 0} <= semver}, + v1_1_0_add_escape_sequence_e {semantic_version{1, 1, 0} <= semver}, + v1_1_0_add_escape_sequence_x {semantic_version{1, 1, 0} <= semver}, + v1_1_0_make_seconds_optional {semantic_version{1, 1, 0} <= semver}, + ext_hex_float {false}, + ext_num_suffix{false}, + ext_null_value{false} + {} + + semantic_version version; // toml version + + // diff from v1.0.0 -> v1.1.0 + bool v1_1_0_allow_control_characters_in_comments; + bool v1_1_0_allow_newlines_in_inline_tables; + bool v1_1_0_allow_trailing_comma_in_inline_tables; + bool v1_1_0_allow_non_english_in_bare_keys; + bool v1_1_0_add_escape_sequence_e; + bool v1_1_0_add_escape_sequence_x; + bool v1_1_0_make_seconds_optional; + + // library extensions + bool ext_hex_float; // allow hex float (in C++ style) + bool ext_num_suffix; // allow number suffix (in C++ style) + bool ext_null_value; // allow `null` as a value + }; + +} // namespace toml +#endif // TOML11_SPEC_HPP +#ifndef TOML11_ORDERED_MAP_HPP +#define TOML11_ORDERED_MAP_HPP + +#include +#include +#include +#include + +namespace toml { + + namespace detail { + template + struct ordered_map_ebo_container { + Cmp cmp_; // empty base optimization for empty Cmp type + }; + } // namespace detail + + template , + typename Allocator = std::allocator>> + class ordered_map : detail::ordered_map_ebo_container { + public: + using key_type = Key; + using mapped_type = Val; + using value_type = std::pair; + + using key_compare = Cmp; + using allocator_type = Allocator; + + using container_type = std::vector; + using reference = typename container_type::reference; + using pointer = typename container_type::pointer; + using const_reference = typename container_type::const_reference; + using const_pointer = typename container_type::const_pointer; + using iterator = typename container_type::iterator; + using const_iterator = typename container_type::const_iterator; + using size_type = typename container_type::size_type; + using difference_type = typename container_type::difference_type; + + private: + using ebo_base = detail::ordered_map_ebo_container; + + public: + ordered_map() = default; + ~ordered_map() = default; + ordered_map(const ordered_map&) = default; + ordered_map(ordered_map&&) = default; + ordered_map& operator=(const ordered_map&) = default; + ordered_map& operator=(ordered_map&&) = default; + + ordered_map(const ordered_map& other, const Allocator& alloc) + : container_(other.container_, alloc) {} + + ordered_map(ordered_map&& other, const Allocator& alloc) + : container_(std::move(other.container_), alloc) {} + + explicit ordered_map(const Cmp& cmp, const Allocator& alloc = Allocator()) + : ebo_base { cmp } + , container_(alloc) {} + + explicit ordered_map(const Allocator& alloc) : container_(alloc) {} + + template + ordered_map(InputIterator first, + InputIterator last, + const Cmp& cmp = Cmp(), + const Allocator& alloc = Allocator()) + : ebo_base { cmp } + , container_(first, last, alloc) {} + + template + ordered_map(InputIterator first, InputIterator last, const Allocator& alloc) + : container_(first, last, alloc) {} + + ordered_map(std::initializer_list v, + const Cmp& cmp = Cmp(), + const Allocator& alloc = Allocator()) + : ebo_base { cmp } + , container_(std::move(v), alloc) {} + + ordered_map(std::initializer_list v, const Allocator& alloc) + : container_(std::move(v), alloc) {} + + ordered_map& operator=(std::initializer_list v) { + this->container_ = std::move(v); + return *this; + } + + iterator begin() noexcept { + return container_.begin(); + } + + iterator end() noexcept { + return container_.end(); + } + + const_iterator begin() const noexcept { + return container_.begin(); + } + + const_iterator end() const noexcept { + return container_.end(); + } + + const_iterator cbegin() const noexcept { + return container_.cbegin(); + } + + const_iterator cend() const noexcept { + return container_.cend(); + } + + bool empty() const noexcept { + return container_.empty(); + } + + std::size_t size() const noexcept { + return container_.size(); + } + + std::size_t max_size() const noexcept { + return container_.max_size(); + } + + void clear() { + container_.clear(); + } + + void push_back(const value_type& v) { + if (this->contains(v.first)) { + throw std::out_of_range("ordered_map: value already exists"); + } + container_.push_back(v); + } + + void push_back(value_type&& v) { + if (this->contains(v.first)) { + throw std::out_of_range("ordered_map: value already exists"); + } + container_.push_back(std::move(v)); + } + + void emplace_back(key_type k, mapped_type v) { + if (this->contains(k)) { + throw std::out_of_range("ordered_map: value already exists"); + } + container_.emplace_back(std::move(k), std::move(v)); + } + + void pop_back() { + container_.pop_back(); + } + + void insert(value_type kv) { + if (this->contains(kv.first)) { + throw std::out_of_range("ordered_map: value already exists"); + } + container_.push_back(std::move(kv)); + } + + void emplace(key_type k, mapped_type v) { + if (this->contains(k)) { + throw std::out_of_range("ordered_map: value already exists"); + } + container_.emplace_back(std::move(k), std::move(v)); + } + + std::size_t count(const key_type& key) const { + if (this->find(key) != this->end()) { + return 1; + } else { + return 0; + } + } + + bool contains(const key_type& key) const { + return this->find(key) != this->end(); + } + + iterator find(const key_type& key) noexcept { + return std::find_if(this->begin(), + this->end(), + [&key, this](const value_type& v) { + return this->cmp_(v.first, key); + }); + } + + const_iterator find(const key_type& key) const noexcept { + return std::find_if(this->begin(), + this->end(), + [&key, this](const value_type& v) { + return this->cmp_(v.first, key); + }); + } + + mapped_type& at(const key_type& k) { + const auto iter = this->find(k); + if (iter == this->end()) { + throw std::out_of_range("ordered_map: no such element"); + } + return iter->second; + } + + const mapped_type& at(const key_type& k) const { + const auto iter = this->find(k); + if (iter == this->end()) { + throw std::out_of_range("ordered_map: no such element"); + } + return iter->second; + } + + mapped_type& operator[](const key_type& k) { + const auto iter = this->find(k); + if (iter == this->end()) { + this->container_.emplace_back(k, mapped_type {}); + return this->container_.back().second; + } + return iter->second; + } + + const mapped_type& operator[](const key_type& k) const { + const auto iter = this->find(k); + if (iter == this->end()) { + throw std::out_of_range("ordered_map: no such element"); + } + return iter->second; + } + + key_compare key_comp() const { + return this->cmp_; + } + + void swap(ordered_map& other) { + container_.swap(other.container_); + } + + private: + container_type container_; + }; + + template + bool operator==(const ordered_map& lhs, + const ordered_map& rhs) { + return lhs.size() == rhs.size() && + std::equal(lhs.begin(), lhs.end(), rhs.begin()); + } + + template + bool operator!=(const ordered_map& lhs, + const ordered_map& rhs) { + return !(lhs == rhs); + } + + template + bool operator<(const ordered_map& lhs, + const ordered_map& rhs) { + return std::lexicographical_compare(lhs.begin(), + lhs.end(), + rhs.begin(), + rhs.end()); + } + + template + bool operator>(const ordered_map& lhs, + const ordered_map& rhs) { + return rhs < lhs; + } + + template + bool operator<=(const ordered_map& lhs, + const ordered_map& rhs) { + return !(lhs > rhs); + } + + template + bool operator>=(const ordered_map& lhs, + const ordered_map& rhs) { + return !(lhs < rhs); + } + + template + void swap(ordered_map& lhs, ordered_map& rhs) { + lhs.swap(rhs); + return; + } + +} // namespace toml +#endif // TOML11_ORDERED_MAP_HPP +#ifndef TOML11_INTO_HPP +#define TOML11_INTO_HPP + +namespace toml { + + template + struct into; + // { + // static toml::value into_toml(const T& user_defined_type) + // { + // // User-defined conversions ... + // } + // }; + +} // namespace toml +#endif // TOML11_INTO_HPP +#ifndef TOML11_FROM_HPP +#define TOML11_FROM_HPP + +namespace toml { + + template + struct from; + // { + // static T from_toml(const toml::value& v) + // { + // // User-defined conversions ... + // } + // }; + +} // namespace toml +#endif // TOML11_FROM_HPP +#ifndef TOML11_TRAITS_HPP +#define TOML11_TRAITS_HPP + +#include +#include +#include +#include +#include +#include +#include + +#if defined(TOML11_HAS_STRING_VIEW) + #include +#endif + +namespace toml { + template + class basic_value; + + namespace detail { + // --------------------------------------------------------------------------- + // check whether type T is a kind of container/map class + + struct has_iterator_impl { + template + static std::true_type check(typename T::iterator*); + template + static std::false_type check(...); + }; + + struct has_value_type_impl { + template + static std::true_type check(typename T::value_type*); + template + static std::false_type check(...); + }; + + struct has_key_type_impl { + template + static std::true_type check(typename T::key_type*); + template + static std::false_type check(...); + }; + + struct has_mapped_type_impl { + template + static std::true_type check(typename T::mapped_type*); + template + static std::false_type check(...); + }; + + struct has_reserve_method_impl { + template + static std::false_type check(...); + template + static std::true_type check( + decltype(std::declval().reserve(std::declval()))*); + }; + + struct has_push_back_method_impl { + template + static std::false_type check(...); + template + static std::true_type check(decltype(std::declval().push_back( + std::declval()))*); + }; + + struct is_comparable_impl { + template + static std::false_type check(...); + template + static std::true_type check(decltype(std::declval() < std::declval())*); + }; + + struct has_from_toml_method_impl { + template + static std::true_type check(decltype(std::declval().from_toml( + std::declval<::toml::basic_value>()))*); + + template + static std::false_type check(...); + }; + + struct has_into_toml_method_impl { + template + static std::true_type check(decltype(std::declval().into_toml())*); + template + static std::false_type check(...); + }; + + struct has_template_into_toml_method_impl { + template + static std::true_type check( + decltype(std::declval().template into_toml())*); + template + static std::false_type check(...); + }; + + struct has_specialized_from_impl { + template + static std::false_type check(...); + template )> + static std::true_type check(::toml::from*); + }; + + struct has_specialized_into_impl { + template + static std::false_type check(...); + template )> + static std::true_type check(::toml::into*); + }; + +/// Intel C++ compiler can not use decltype in parent class declaration, here +/// is a hack to work around it. https://stackoverflow.com/a/23953090/4692076 +#ifdef __INTEL_COMPILER + #define decltype(...) std::enable_if::type +#endif + + template + struct has_iterator : decltype(has_iterator_impl::check(nullptr)) {}; + + template + struct has_value_type : decltype(has_value_type_impl::check(nullptr)) {}; + + template + struct has_key_type : decltype(has_key_type_impl::check(nullptr)) {}; + + template + struct has_mapped_type : decltype(has_mapped_type_impl::check(nullptr)) {}; + + template + struct has_reserve_method + : decltype(has_reserve_method_impl::check(nullptr)) {}; + + template + struct has_push_back_method + : decltype(has_push_back_method_impl::check(nullptr)) {}; + + template + struct is_comparable : decltype(is_comparable_impl::check(nullptr)) {}; + + template + struct has_from_toml_method + : decltype(has_from_toml_method_impl::check(nullptr)) {}; + + template + struct has_into_toml_method + : decltype(has_into_toml_method_impl::check(nullptr)) {}; + + template + struct has_template_into_toml_method + : decltype(has_template_into_toml_method_impl::check(nullptr)) { + }; + + template + struct has_specialized_from + : decltype(has_specialized_from_impl::check(nullptr)) {}; + + template + struct has_specialized_into + : decltype(has_specialized_into_impl::check(nullptr)) {}; + +#ifdef __INTEL_COMPILER + #undef decltype +#endif + + // --------------------------------------------------------------------------- + // type checkers + + template + struct is_std_pair_impl : std::false_type {}; + + template + struct is_std_pair_impl> : std::true_type {}; + + template + using is_std_pair = is_std_pair_impl>; + + template + struct is_std_tuple_impl : std::false_type {}; + + template + struct is_std_tuple_impl> : std::true_type {}; + + template + using is_std_tuple = is_std_tuple_impl>; + + template + struct is_std_array_impl : std::false_type {}; + + template + struct is_std_array_impl> : std::true_type {}; + + template + using is_std_array = is_std_array_impl>; + + template + struct is_std_forward_list_impl : std::false_type {}; + + template + struct is_std_forward_list_impl> : std::true_type {}; + + template + using is_std_forward_list = is_std_forward_list_impl>; + + template + struct is_std_basic_string_impl : std::false_type {}; + + template + struct is_std_basic_string_impl> : std::true_type { + }; + + template + using is_std_basic_string = is_std_basic_string_impl>; + + template + struct is_1byte_std_basic_string_impl : std::false_type {}; + + template + struct is_1byte_std_basic_string_impl> + : std::integral_constant {}; + + template + using is_1byte_std_basic_string = is_std_basic_string_impl>; + +#if defined(TOML11_HAS_STRING_VIEW) + template + struct is_std_basic_string_view_impl : std::false_type {}; + + template + struct is_std_basic_string_view_impl> + : std::true_type {}; + + template + using is_std_basic_string_view = + is_std_basic_string_view_impl>; + + template + struct is_string_view_of : std::false_type {}; + + template + struct is_string_view_of, std::basic_string> + : std::true_type {}; +#endif + + template + struct is_chrono_duration_impl : std::false_type {}; + + template + struct is_chrono_duration_impl> + : std::true_type {}; + + template + using is_chrono_duration = is_chrono_duration_impl>; + + template + struct is_map_impl + : cxx::conjunction< // map satisfies all the following conditions + has_iterator, // has T::iterator + has_value_type, // has T::value_type + has_key_type, // has T::key_type + has_mapped_type // has T::mapped_type + > {}; + + template + using is_map = is_map_impl>; + + template + struct is_container_impl + : cxx::conjunction>, // not a map + cxx::negation>, // not a std::string +#ifdef TOML11_HAS_STRING_VIEW + cxx::negation>, // not a std::string_view +#endif + has_iterator, // has T::iterator + has_value_type // has T::value_type + > { + }; + + template + using is_container = is_container_impl>; + + template + struct is_basic_value_impl : std::false_type {}; + + template + struct is_basic_value_impl<::toml::basic_value> : std::true_type {}; + + template + using is_basic_value = is_basic_value_impl>; + + } // namespace detail +} // namespace toml +#endif // TOML11_TRAITS_HPP +#ifndef TOML11_EXCEPTION_HPP +#define TOML11_EXCEPTION_HPP + +#include + +namespace toml { + + struct exception : public std::exception { + public: + virtual ~exception() noexcept override = default; + + virtual const char* what() const noexcept override { + return ""; + } + }; + +} // namespace toml +#endif // TOMl11_EXCEPTION_HPP +#ifndef TOML11_RESULT_HPP +#define TOML11_RESULT_HPP + +#include +#include +#include +#include +#include + +namespace toml { + + struct bad_result_access final : public ::toml::exception { + public: + explicit bad_result_access(std::string what_arg) + : what_(std::move(what_arg)) {} + + ~bad_result_access() noexcept override = default; + + const char* what() const noexcept override { + return what_.c_str(); + } + + private: + std::string what_; + }; + + // ----------------------------------------------------------------------------- + + template + struct success { + static_assert(!std::is_same::value, ""); + + using value_type = T; + + explicit success(value_type v) noexcept( + std::is_nothrow_move_constructible::value) + : value(std::move(v)) {} + + template , T>::value, + std::nullptr_t> = nullptr> + explicit success(U&& v) : value(std::forward(v)) {} + + template + explicit success(success v) : value(std::move(v.value)) {} + + ~success() = default; + success(const success&) = default; + success(success&&) = default; + success& operator=(const success&) = default; + success& operator=(success&&) = default; + + value_type& get() noexcept { + return value; + } + + const value_type& get() const noexcept { + return value; + } + + private: + value_type value; + }; + + template + struct success> { + static_assert(!std::is_same::value, ""); + + using value_type = T; + + explicit success(std::reference_wrapper v) noexcept + : value(std::move(v)) {} + + ~success() = default; + success(const success&) = default; + success(success&&) = default; + success& operator=(const success&) = default; + success& operator=(success&&) = default; + + value_type& get() noexcept { + return value.get(); + } + + const value_type& get() const noexcept { + return value.get(); + } + + private: + std::reference_wrapper value; + }; + + template + success::type> ok(T&& v) { + return success::type>(std::forward(v)); + } + + template + success ok(const char (&literal)[N]) { + return success(std::string(literal)); + } + + // ----------------------------------------------------------------------------- + + template + struct failure { + using value_type = T; + + explicit failure(value_type v) noexcept( + std::is_nothrow_move_constructible::value) + : value(std::move(v)) {} + + template , T>::value, + std::nullptr_t> = nullptr> + explicit failure(U&& v) : value(std::forward(v)) {} + + template + explicit failure(failure v) : value(std::move(v.value)) {} + + ~failure() = default; + failure(const failure&) = default; + failure(failure&&) = default; + failure& operator=(const failure&) = default; + failure& operator=(failure&&) = default; + + value_type& get() noexcept { + return value; + } + + const value_type& get() const noexcept { + return value; + } + + private: + value_type value; + }; + + template + struct failure> { + using value_type = T; + + explicit failure(std::reference_wrapper v) noexcept + : value(std::move(v)) {} + + ~failure() = default; + failure(const failure&) = default; + failure(failure&&) = default; + failure& operator=(const failure&) = default; + failure& operator=(failure&&) = default; + + value_type& get() noexcept { + return value.get(); + } + + const value_type& get() const noexcept { + return value.get(); + } + + private: + std::reference_wrapper value; + }; + + template + failure::type> err(T&& v) { + return failure::type>(std::forward(v)); + } + + template + failure err(const char (&literal)[N]) { + return failure(std::string(literal)); + } + + /* ============================================================================ + * _ _ + * _ _ ___ ____ _| | |_ + * | '_/ -_|_-< || | | _| + * |_| \___/__/\_,_|_|\__| + */ + + template + struct result { + using success_type = success; + using failure_type = failure; + using value_type = typename success_type::value_type; + using error_type = typename failure_type::value_type; + + result(success_type s) : is_ok_(true), succ_(std::move(s)) {} + + result(failure_type f) : is_ok_(false), fail_(std::move(f)) {} + + template < + typename U, + cxx::enable_if_t< + cxx::conjunction, value_type>>, + std::is_convertible, value_type>>::value, + std::nullptr_t> = nullptr> + result(success s) : is_ok_(true) + , succ_(std::move(s.value)) {} + + template < + typename U, + cxx::enable_if_t< + cxx::conjunction, error_type>>, + std::is_convertible, error_type>>::value, + std::nullptr_t> = nullptr> + result(failure f) : is_ok_(false) + , fail_(std::move(f.value)) {} + + result& operator=(success_type s) { + this->cleanup(); + this->is_ok_ = true; + auto tmp = ::new (std::addressof(this->succ_)) success_type(std::move(s)); + assert(tmp == std::addressof(this->succ_)); + (void)tmp; + return *this; + } + + result& operator=(failure_type f) { + this->cleanup(); + this->is_ok_ = false; + auto tmp = ::new (std::addressof(this->fail_)) failure_type(std::move(f)); + assert(tmp == std::addressof(this->fail_)); + (void)tmp; + return *this; + } + + template + result& operator=(success s) { + this->cleanup(); + this->is_ok_ = true; + auto tmp = ::new (std::addressof(this->succ_)) + success_type(std::move(s.value)); + assert(tmp == std::addressof(this->succ_)); + (void)tmp; + return *this; + } + + template + result& operator=(failure f) { + this->cleanup(); + this->is_ok_ = false; + auto tmp = ::new (std::addressof(this->fail_)) + failure_type(std::move(f.value)); + assert(tmp == std::addressof(this->fail_)); + (void)tmp; + return *this; + } + + ~result() noexcept { + this->cleanup(); + } + + result(const result& other) : is_ok_(other.is_ok()) { + if (other.is_ok()) { + auto tmp = ::new (std::addressof(this->succ_)) success_type(other.succ_); + assert(tmp == std::addressof(this->succ_)); + (void)tmp; + } else { + auto tmp = ::new (std::addressof(this->fail_)) failure_type(other.fail_); + assert(tmp == std::addressof(this->fail_)); + (void)tmp; + } + } + + result(result&& other) : is_ok_(other.is_ok()) { + if (other.is_ok()) { + auto tmp = ::new (std::addressof(this->succ_)) + success_type(std::move(other.succ_)); + assert(tmp == std::addressof(this->succ_)); + (void)tmp; + } else { + auto tmp = ::new (std::addressof(this->fail_)) + failure_type(std::move(other.fail_)); + assert(tmp == std::addressof(this->fail_)); + (void)tmp; + } + } + + result& operator=(const result& other) { + this->cleanup(); + if (other.is_ok()) { + auto tmp = ::new (std::addressof(this->succ_)) success_type(other.succ_); + assert(tmp == std::addressof(this->succ_)); + (void)tmp; + } else { + auto tmp = ::new (std::addressof(this->fail_)) failure_type(other.fail_); + assert(tmp == std::addressof(this->fail_)); + (void)tmp; + } + is_ok_ = other.is_ok(); + return *this; + } + + result& operator=(result&& other) { + this->cleanup(); + if (other.is_ok()) { + auto tmp = ::new (std::addressof(this->succ_)) + success_type(std::move(other.succ_)); + assert(tmp == std::addressof(this->succ_)); + (void)tmp; + } else { + auto tmp = ::new (std::addressof(this->fail_)) + failure_type(std::move(other.fail_)); + assert(tmp == std::addressof(this->fail_)); + (void)tmp; + } + is_ok_ = other.is_ok(); + return *this; + } + + template < + typename U, + typename F, + cxx::enable_if_t< + cxx::conjunction, value_type>>, + cxx::negation, error_type>>, + std::is_convertible, value_type>, + std::is_convertible, error_type>>::value, + std::nullptr_t> = nullptr> + result(result other) : is_ok_(other.is_ok()) { + if (other.is_ok()) { + auto tmp = ::new (std::addressof(this->succ_)) + success_type(std::move(other.as_ok())); + assert(tmp == std::addressof(this->succ_)); + (void)tmp; + } else { + auto tmp = ::new (std::addressof(this->fail_)) + failure_type(std::move(other.as_err())); + assert(tmp == std::addressof(this->fail_)); + (void)tmp; + } + } + + template < + typename U, + typename F, + cxx::enable_if_t< + cxx::conjunction, value_type>>, + cxx::negation, error_type>>, + std::is_convertible, value_type>, + std::is_convertible, error_type>>::value, + std::nullptr_t> = nullptr> + result& operator=(result other) { + this->cleanup(); + if (other.is_ok()) { + auto tmp = ::new (std::addressof(this->succ_)) + success_type(std::move(other.as_ok())); + assert(tmp == std::addressof(this->succ_)); + (void)tmp; + } else { + auto tmp = ::new (std::addressof(this->fail_)) + failure_type(std::move(other.as_err())); + assert(tmp == std::addressof(this->fail_)); + (void)tmp; + } + is_ok_ = other.is_ok(); + return *this; + } + + bool is_ok() const noexcept { + return is_ok_; + } + + bool is_err() const noexcept { + return !is_ok_; + } + + explicit operator bool() const noexcept { + return is_ok_; + } + + value_type& unwrap(cxx::source_location loc = cxx::source_location::current()) { + if (this->is_err()) { + throw bad_result_access("toml::result: bad unwrap" + cxx::to_string(loc)); + } + return this->succ_.get(); + } + + const value_type& unwrap( + cxx::source_location loc = cxx::source_location::current()) const { + if (this->is_err()) { + throw bad_result_access("toml::result: bad unwrap" + cxx::to_string(loc)); + } + return this->succ_.get(); + } + + value_type& unwrap_or(value_type& opt) noexcept { + if (this->is_err()) { + return opt; + } + return this->succ_.get(); + } + + const value_type& unwrap_or(const value_type& opt) const noexcept { + if (this->is_err()) { + return opt; + } + return this->succ_.get(); + } + + error_type& unwrap_err( + cxx::source_location loc = cxx::source_location::current()) { + if (this->is_ok()) { + throw bad_result_access( + "toml::result: bad unwrap_err" + cxx::to_string(loc)); + } + return this->fail_.get(); + } + + const error_type& unwrap_err( + cxx::source_location loc = cxx::source_location::current()) const { + if (this->is_ok()) { + throw bad_result_access( + "toml::result: bad unwrap_err" + cxx::to_string(loc)); + } + return this->fail_.get(); + } + + value_type& as_ok() noexcept { + assert(this->is_ok()); + return this->succ_.get(); + } + + const value_type& as_ok() const noexcept { + assert(this->is_ok()); + return this->succ_.get(); + } + + error_type& as_err() noexcept { + assert(this->is_err()); + return this->fail_.get(); + } + + const error_type& as_err() const noexcept { + assert(this->is_err()); + return this->fail_.get(); + } + + private: + void cleanup() noexcept { +#if defined(__GNUC__) && !defined(__clang__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wduplicated-branches" +#endif + + if (this->is_ok_) { + this->succ_.~success_type(); + } else { + this->fail_.~failure_type(); + } + +#if defined(__GNUC__) && !defined(__clang__) + #pragma GCC diagnostic pop +#endif + return; + } + + private: + bool is_ok_; + + union { + success_type succ_; + failure_type fail_; + }; + }; + + // ---------------------------------------------------------------------------- + + namespace detail { + struct none_t {}; + + inline bool operator==(const none_t&, const none_t&) noexcept { + return true; + } + + inline bool operator!=(const none_t&, const none_t&) noexcept { + return false; + } + + inline bool operator<(const none_t&, const none_t&) noexcept { + return false; + } + + inline bool operator<=(const none_t&, const none_t&) noexcept { + return true; + } + + inline bool operator>(const none_t&, const none_t&) noexcept { + return false; + } + + inline bool operator>=(const none_t&, const none_t&) noexcept { + return true; + } + + inline std::ostream& operator<<(std::ostream& os, const none_t&) { + os << "none"; + return os; + } + } // namespace detail + + inline success ok() noexcept { + return success(detail::none_t {}); + } + + inline failure err() noexcept { + return failure(detail::none_t {}); + } + +} // namespace toml +#endif // TOML11_RESULT_HPP +#ifndef TOML11_UTILITY_HPP +#define TOML11_UTILITY_HPP + +#include +#include +#include +#include +#include + +namespace toml { + namespace detail { + + // to output character in an error message. + inline std::string show_char(const int c) { + using char_type = unsigned char; + if (std::isgraph(c)) { + return std::string(1, static_cast(c)); + } else { + std::array buf; + buf.fill('\0'); + const auto r = std::snprintf(buf.data(), buf.size(), "0x%02x", c & 0xFF); + assert(r == static_cast(buf.size()) - 1); + (void)r; // Unused variable warning + auto in_hex = std::string(buf.data()); + switch (c) { + case char_type('\0'): { + in_hex += "(NUL)"; + break; + } + case char_type(' '): { + in_hex += "(SPACE)"; + break; + } + case char_type('\n'): { + in_hex += "(LINE FEED)"; + break; + } + case char_type('\r'): { + in_hex += "(CARRIAGE RETURN)"; + break; + } + case char_type('\t'): { + in_hex += "(TAB)"; + break; + } + case char_type('\v'): { + in_hex += "(VERTICAL TAB)"; + break; + } + case char_type('\f'): { + in_hex += "(FORM FEED)"; + break; + } + case char_type('\x1B'): { + in_hex += "(ESCAPE)"; + break; + } + default: + break; + } + return in_hex; + } + } + + // --------------------------------------------------------------------------- + + template + void try_reserve_impl(Container& container, std::size_t N, std::true_type) { + container.reserve(N); + return; + } + + template + void try_reserve_impl(Container&, std::size_t, std::false_type) noexcept { + return; + } + + template + void try_reserve(Container& container, std::size_t N) { + try_reserve_impl(container, N, has_reserve_method {}); + return; + } + + // --------------------------------------------------------------------------- + + template + result from_string(const std::string& str) { + T v; + std::istringstream iss(str); + iss >> v; + if (iss.fail()) { + return err(); + } + return ok(v); + } + + // --------------------------------------------------------------------------- + + // helper function to avoid std::string(0, 'c') or std::string(iter, iter) + template + std::string make_string(Iterator first, Iterator last) { + if (first == last) { + return ""; + } + return std::string(first, last); + } + + inline std::string make_string(std::size_t len, char c) { + if (len == 0) { + return ""; + } + return std::string(len, c); + } + + // --------------------------------------------------------------------------- + + template + struct string_conv_impl { + static_assert(sizeof(Char) == sizeof(char), ""); + static_assert(sizeof(Char2) == sizeof(char), ""); + + static std::basic_string invoke( + std::basic_string s) { + std::basic_string retval; + std::transform(s.begin(), + s.end(), + std::back_inserter(retval), + [](const Char2 c) { + return static_cast(c); + }); + return retval; + } + + template + static std::basic_string invoke(const Char2 (&s)[N]) { + std::basic_string retval; + // "string literal" has null-char at the end. to skip it, we use prev. + std::transform(std::begin(s), + std::prev(std::end(s)), + std::back_inserter(retval), + [](const Char2 c) { + return static_cast(c); + }); + return retval; + } + }; + + template + struct string_conv_impl { + static_assert(sizeof(Char) == sizeof(char), ""); + + static std::basic_string invoke( + std::basic_string s) { + return s; + } + + template + static std::basic_string invoke(const Char (&s)[N]) { + return std::basic_string(s); + } + }; + + template + cxx::enable_if_t::value, S> string_conv( + std::basic_string s) { + using C = typename S::value_type; + using T = typename S::traits_type; + using A = typename S::allocator_type; + return string_conv_impl::invoke( + std::move(s)); + } + + template + cxx::enable_if_t::value, S> string_conv( + const char (&s)[N]) { + using C = typename S::value_type; + using T = typename S::traits_type; + using A = typename S::allocator_type; + using C2 = char; + using T2 = std::char_traits; + using A2 = std::allocator; + + return string_conv_impl::template invoke(s); + } + + } // namespace detail +} // namespace toml +#endif // TOML11_UTILITY_HPP +#ifndef TOML11_LOCATION_HPP +#define TOML11_LOCATION_HPP + +#ifndef TOML11_LOCATION_FWD_HPP + #define TOML11_LOCATION_FWD_HPP + + #include + #include + #include + +namespace toml { + namespace detail { + + class region; // fwd decl + + // + // To represent where we are reading in the parse functions. + // Since it "points" somewhere in the input stream, the length is always 1. + // + class location { + public: + using char_type = unsigned char; // must be unsigned + using container_type = std::vector; + using difference_type = + typename container_type::difference_type; // to suppress sign-conversion warning + using source_ptr = std::shared_ptr; + + public: + location(source_ptr src, std::string src_name) + : source_(std::move(src)) + , source_name_(std::move(src_name)) + , location_(0) + , line_number_(1) {} + + location(const location&) = default; + location(location&&) = default; + location& operator=(const location&) = default; + location& operator=(location&&) = default; + ~location() = default; + + void advance(std::size_t n = 1) noexcept; + void retrace(std::size_t n = 1) noexcept; + + bool is_ok() const noexcept { + return static_cast(this->source_); + } + + bool eof() const noexcept; + char_type current() const; + + char_type peek(); + + std::size_t get_location() const noexcept { + return this->location_; + } + + void set_location(const std::size_t loc) noexcept; + + std::size_t line_number() const noexcept { + return this->line_number_; + } + + std::string get_line() const; + std::size_t column_number() const noexcept; + + const source_ptr& source() const noexcept { + return this->source_; + } + + const std::string& source_name() const noexcept { + return this->source_name_; + } + + private: + void advance_line_number(const std::size_t n); + void retrace_line_number(const std::size_t n); + + private: + friend region; + + private: + source_ptr source_; + std::string source_name_; + std::size_t location_; // std::vector<>::difference_type is signed + std::size_t line_number_; + }; + + bool operator==(const location& lhs, const location& rhs) noexcept; + bool operator!=(const location& lhs, const location& rhs); + + location prev(const location& loc); + location next(const location& loc); + location make_temporary_location(const std::string& str) noexcept; + + template + result find_if(const location& first, + const location& last, + const F& func) noexcept { + if (first.source() != last.source()) { + return err(); + } + if (first.get_location() >= last.get_location()) { + return err(); + } + + auto loc = first; + while (loc.get_location() != last.get_location()) { + if (func(loc.current())) { + return ok(loc); + } + loc.advance(); + } + return err(); + } + + template + result rfind_if(location first, + const location& last, + const F& func) { + if (first.source() != last.source()) { + return err(); + } + if (first.get_location() >= last.get_location()) { + return err(); + } + + auto loc = last; + while (loc.get_location() != first.get_location()) { + if (func(loc.current())) { + return ok(loc); + } + loc.retrace(); + } + if (func(first.current())) { + return ok(first); + } + return err(); + } + + result find(const location& first, + const location& last, + const location::char_type val); + result rfind(const location& first, + const location& last, + const location::char_type val); + + std::size_t count(const location& first, + const location& last, + const location::char_type& c); + + } // namespace detail +} // namespace toml +#endif // TOML11_LOCATION_FWD_HPP + +#if !defined(TOML11_COMPILE_SOURCES) + #ifndef TOML11_LOCATION_IMPL_HPP + #define TOML11_LOCATION_IMPL_HPP + +namespace toml { + namespace detail { + + TOML11_INLINE void location::advance(std::size_t n) noexcept { + assert(this->is_ok()); + if (this->location_ + n < this->source_->size()) { + this->advance_line_number(n); + this->location_ += n; + } else { + this->advance_line_number(this->source_->size() - this->location_); + this->location_ = this->source_->size(); + } + } + + TOML11_INLINE void location::retrace(std::size_t n) noexcept { + assert(this->is_ok()); + if (this->location_ < n) { + this->location_ = 0; + this->line_number_ = 1; + } else { + this->retrace_line_number(n); + this->location_ -= n; + } + } + + TOML11_INLINE bool location::eof() const noexcept { + assert(this->is_ok()); + return this->location_ >= this->source_->size(); + } + + TOML11_INLINE location::char_type location::current() const { + assert(this->is_ok()); + if (this->eof()) { + return '\0'; + } + + assert(this->location_ < this->source_->size()); + return this->source_->at(this->location_); + } + + TOML11_INLINE location::char_type location::peek() { + assert(this->is_ok()); + if (this->location_ >= this->source_->size()) { + return '\0'; + } else { + return this->source_->at(this->location_ + 1); + } + } + + TOML11_INLINE void location::set_location(const std::size_t loc) noexcept { + if (this->location_ == loc) { + return; + } + + if (loc == 0) { + this->line_number_ = 1; + } else if (this->location_ < loc) { + const auto d = loc - this->location_; + this->advance_line_number(d); + } else { + const auto d = this->location_ - loc; + this->retrace_line_number(d); + } + this->location_ = loc; + } + + TOML11_INLINE std::string location::get_line() const { + assert(this->is_ok()); + const auto iter = std::next(this->source_->cbegin(), + static_cast(this->location_)); + const auto riter = cxx::make_reverse_iterator(iter); + + const auto prev = std::find(riter, this->source_->crend(), char_type('\n')); + const auto next = std::find(iter, this->source_->cend(), char_type('\n')); + + return make_string(std::next(prev.base()), next); + } + + TOML11_INLINE std::size_t location::column_number() const noexcept { + assert(this->is_ok()); + const auto iter = std::next(this->source_->cbegin(), + static_cast(this->location_)); + const auto riter = cxx::make_reverse_iterator(iter); + const auto prev = std::find(riter, this->source_->crend(), char_type('\n')); + + assert(prev.base() <= iter); + return static_cast(std::distance(prev.base(), iter) + 1); // 1-origin + } + + TOML11_INLINE void location::advance_line_number(const std::size_t n) { + assert(this->is_ok()); + assert(this->location_ + n <= this->source_->size()); + + const auto iter = this->source_->cbegin(); + this->line_number_ += static_cast(std::count( + std::next(iter, static_cast(this->location_)), + std::next(iter, static_cast(this->location_ + n)), + char_type('\n'))); + + return; + } + + TOML11_INLINE void location::retrace_line_number(const std::size_t n) { + assert(this->is_ok()); + assert(n <= this->location_); // loc - n >= 0 + + const auto iter = this->source_->cbegin(); + const auto dline_num = static_cast(std::count( + std::next(iter, static_cast(this->location_ - n)), + std::next(iter, static_cast(this->location_)), + char_type('\n'))); + + if (this->line_number_ <= dline_num) { + this->line_number_ = 1; + } else { + this->line_number_ -= dline_num; + } + return; + } + + TOML11_INLINE bool operator==(const location& lhs, const location& rhs) noexcept { + if (!lhs.is_ok() || !rhs.is_ok()) { + return (!lhs.is_ok()) && (!rhs.is_ok()); + } + return lhs.source() == rhs.source() && + lhs.source_name() == rhs.source_name() && + lhs.get_location() == rhs.get_location(); + } + + TOML11_INLINE bool operator!=(const location& lhs, const location& rhs) { + return !(lhs == rhs); + } + + TOML11_INLINE location prev(const location& loc) { + location p(loc); + p.retrace(1); + return p; + } + + TOML11_INLINE location next(const location& loc) { + location p(loc); + p.advance(1); + return p; + } + + TOML11_INLINE location make_temporary_location(const std::string& str) noexcept { + location::container_type cont(str.size()); + std::transform(str.begin(), + str.end(), + cont.begin(), + [](const std::string::value_type& c) { + return cxx::bit_cast(c); + }); + return location( + std::make_shared(std::move(cont)), + "internal temporary"); + } + + TOML11_INLINE result find(const location& first, + const location& last, + const location::char_type val) { + return find_if(first, last, [val](const location::char_type c) { + return c == val; + }); + } + + TOML11_INLINE result rfind(const location& first, + const location& last, + const location::char_type val) { + return rfind_if(first, last, [val](const location::char_type c) { + return c == val; + }); + } + + TOML11_INLINE std::size_t count(const location& first, + const location& last, + const location::char_type& c) { + if (first.source() != last.source()) { + return 0; + } + if (first.get_location() >= last.get_location()) { + return 0; + } + + auto loc = first; + std::size_t num = 0; + while (loc.get_location() != last.get_location()) { + if (loc.current() == c) { + num += 1; + } + loc.advance(); + } + return num; + } + + } // namespace detail +} // namespace toml + #endif // TOML11_LOCATION_HPP +#endif + +#endif // TOML11_LOCATION_HPP +#ifndef TOML11_REGION_HPP +#define TOML11_REGION_HPP + +#ifndef TOML11_REGION_FWD_HPP + #define TOML11_REGION_FWD_HPP + + #include + #include + #include + +namespace toml { + namespace detail { + + // + // To represent where is a toml::value defined, or where does an error occur. + // Stored in toml::value. source_location will be constructed based on this. + // + class region { + public: + using char_type = location::char_type; + using container_type = location::container_type; + using difference_type = location::difference_type; + using source_ptr = location::source_ptr; + + using iterator = typename container_type::iterator; + using const_iterator = typename container_type::const_iterator; + + public: + // a value that is constructed manually does not have input stream info + region() + : source_(nullptr) + , source_name_("") + , length_(0) + , first_line_(0) + , first_column_(0) + , last_line_(0) + , last_column_(0) {} + + // a value defined in [first, last). + // Those source must be the same. Instread, `region` does not make sense. + region(const location& first, const location& last); + + // shorthand of [loc, loc+1) + explicit region(const location& loc); + + ~region() = default; + region(const region&) = default; + region(region&&) = default; + region& operator=(const region&) = default; + region& operator=(region&&) = default; + + bool is_ok() const noexcept { + return static_cast(this->source_); + } + + operator bool() const noexcept { + return this->is_ok(); + } + + std::size_t length() const noexcept { + return this->length_; + } + + std::size_t first_line_number() const noexcept { + return this->first_line_; + } + + std::size_t first_column_number() const noexcept { + return this->first_column_; + } + + std::size_t last_line_number() const noexcept { + return this->last_line_; + } + + std::size_t last_column_number() const noexcept { + return this->last_column_; + } + + char_type at(std::size_t i) const; + + const_iterator begin() const noexcept; + const_iterator end() const noexcept; + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + + std::string as_string() const; + std::vector as_lines() const; + + const source_ptr& source() const noexcept { + return this->source_; + } + + const std::string& source_name() const noexcept { + return this->source_name_; + } + + private: + source_ptr source_; + std::string source_name_; + std::size_t length_; + std::size_t first_; + std::size_t first_line_; + std::size_t first_column_; + std::size_t last_; + std::size_t last_line_; + std::size_t last_column_; + }; + + } // namespace detail +} // namespace toml +#endif // TOML11_REGION_FWD_HPP + +#if !defined(TOML11_COMPILE_SOURCES) + #ifndef TOML11_REGION_IMPL_HPP + #define TOML11_REGION_IMPL_HPP + + #include + #include + #include + #include + #include + #include + +namespace toml { + namespace detail { + + // a value defined in [first, last). + // Those source must be the same. Instread, `region` does not make sense. + TOML11_INLINE region::region(const location& first, const location& last) + : source_(first.source()) + , source_name_(first.source_name()) + , length_(last.get_location() - first.get_location()) + , first_(first.get_location()) + , first_line_(first.line_number()) + , first_column_(first.column_number()) + , last_(last.get_location()) + , last_line_(last.line_number()) + , last_column_(last.column_number()) { + assert(first.source() == last.source()); + assert(first.source_name() == last.source_name()); + } + + // shorthand of [loc, loc+1) + TOML11_INLINE region::region(const location& loc) + : source_(loc.source()) + , source_name_(loc.source_name()) + , length_(0) + , first_line_(0) + , first_column_(0) + , last_line_(0) + , last_column_(0) { + // if the file ends with LF, the resulting region points no char. + if (loc.eof()) { + if (loc.get_location() == 0) { + this->length_ = 0; + this->first_ = 0; + this->first_line_ = 0; + this->first_column_ = 0; + this->last_ = 0; + this->last_line_ = 0; + this->last_column_ = 0; + } else { + const auto first = prev(loc); + this->first_ = first.get_location(); + this->first_line_ = first.line_number(); + this->first_column_ = first.column_number(); + this->last_ = loc.get_location(); + this->last_line_ = loc.line_number(); + this->last_column_ = loc.column_number(); + this->length_ = 1; + } + } else { + this->first_ = loc.get_location(); + this->first_line_ = loc.line_number(); + this->first_column_ = loc.column_number(); + this->last_ = loc.get_location() + 1; + this->last_line_ = loc.line_number(); + this->last_column_ = loc.column_number() + 1; + this->length_ = 1; + } + } + + TOML11_INLINE region::char_type region::at(std::size_t i) const { + if (this->last_ <= this->first_ + i) { + throw std::out_of_range("range::at: index " + std::to_string(i) + + " exceeds length " + std::to_string(this->length_)); + } + const auto iter = std::next(this->source_->cbegin(), + static_cast(this->first_ + i)); + return *iter; + } + + TOML11_INLINE region::const_iterator region::begin() const noexcept { + return std::next(this->source_->cbegin(), + static_cast(this->first_)); + } + + TOML11_INLINE region::const_iterator region::end() const noexcept { + return std::next(this->source_->cbegin(), + static_cast(this->last_)); + } + + TOML11_INLINE region::const_iterator region::cbegin() const noexcept { + return std::next(this->source_->cbegin(), + static_cast(this->first_)); + } + + TOML11_INLINE region::const_iterator region::cend() const noexcept { + return std::next(this->source_->cbegin(), + static_cast(this->last_)); + } + + TOML11_INLINE std::string region::as_string() const { + if (this->is_ok()) { + const auto begin = std::next(this->source_->cbegin(), + static_cast(this->first_)); + const auto end = std::next(this->source_->cbegin(), + static_cast(this->last_)); + return ::toml::detail::make_string(begin, end); + } else { + return std::string(""); + } + } + + TOML11_INLINE std::vector region::as_lines() const { + assert(this->is_ok()); + if (this->length_ == 0) { + return std::vector { "" }; + } + + // Consider the following toml file + // ``` + // array = [ + // ] # comment + // ``` + // and the region represnets + // ``` + // [ + // ] + // ``` + // but we want to show the following. + // ``` + // array = [ + // ] # comment + // ``` + // So we need to find LFs before `begin` and after `end`. + // + // But, if region ends with LF, it should not include the next line. + // ``` + // a = 42 + // ^^^- with the last LF + // ``` + // So we start from `end-1` when looking for LF. + + const auto begin_idx = static_cast(this->first_); + const auto end_idx = static_cast(this->last_) - 1; + + // length_ != 0, so begin < end. then begin <= end-1 + assert(begin_idx <= end_idx); + + const auto begin = std::next(this->source_->cbegin(), begin_idx); + const auto end = std::next(this->source_->cbegin(), end_idx); + + const auto line_begin = std::find(cxx::make_reverse_iterator(begin), + this->source_->crend(), + char_type('\n')) + .base(); + const auto line_end = std::find(end, this->source_->cend(), char_type('\n')); + + const auto reg_lines = make_string(line_begin, line_end); + + if (reg_lines == "") // the region is an empty line that only contains LF + { + return std::vector { "" }; + } + + std::istringstream iss(reg_lines); + + std::vector lines; + std::string line; + while (std::getline(iss, line)) { + lines.push_back(line); + } + return lines; + } + + } // namespace detail +} // namespace toml + #endif // TOML11_REGION_IMPL_HPP +#endif + +#endif // TOML11_REGION_HPP +#ifndef TOML11_SOURCE_LOCATION_HPP +#define TOML11_SOURCE_LOCATION_HPP + +#ifndef TOML11_SOURCE_LOCATION_FWD_HPP + #define TOML11_SOURCE_LOCATION_FWD_HPP + + #include + #include + #include + +namespace toml { + + // A struct to contain location in a toml file. + struct source_location { + public: + explicit source_location(const detail::region& r); + ~source_location() = default; + source_location(const source_location&) = default; + source_location(source_location&&) = default; + source_location& operator=(const source_location&) = default; + source_location& operator=(source_location&&) = default; + + bool is_ok() const noexcept { + return this->is_ok_; + } + + std::size_t length() const noexcept { + return this->length_; + } + + std::size_t first_line_number() const noexcept { + return this->first_line_; + } + + std::size_t first_column_number() const noexcept { + return this->first_column_; + } + + std::size_t last_line_number() const noexcept { + return this->last_line_; + } + + std::size_t last_column_number() const noexcept { + return this->last_column_; + } + + const std::string& file_name() const noexcept { + return this->file_name_; + } + + std::size_t num_lines() const noexcept { + return this->line_str_.size(); + } + + const std::string& first_line() const; + const std::string& last_line() const; + + const std::vector& lines() const noexcept { + return line_str_; + } + + private: + bool is_ok_; + std::size_t first_line_; + std::size_t first_column_; + std::size_t last_line_; + std::size_t last_column_; + std::size_t length_; + std::string file_name_; + std::vector line_str_; + }; + + namespace detail { + + std::size_t integer_width_base10(std::size_t i) noexcept; + + inline std::size_t line_width() noexcept { + return 0; + } + + template + std::size_t line_width(const source_location& loc, + const std::string& /*msg*/, + const Ts&... tail) noexcept { + return (std::max)(integer_width_base10(loc.last_line_number()), + line_width(tail...)); + } + + std::ostringstream& format_filename(std::ostringstream& oss, + const source_location& loc); + + std::ostringstream& format_empty_line(std::ostringstream& oss, + const std::size_t lnw); + + std::ostringstream& format_line(std::ostringstream& oss, + const std::size_t lnw, + const std::size_t linenum, + const std::string& line); + + std::ostringstream& format_underline(std::ostringstream& oss, + const std::size_t lnw, + const std::size_t col, + const std::size_t len, + const std::string& msg); + + std::string format_location_impl(const std::size_t lnw, + const std::string& prev_fname, + const source_location& loc, + const std::string& msg); + + inline std::string format_location_rec(const std::size_t, const std::string&) { + return ""; + } + + template + std::string format_location_rec(const std::size_t lnw, + const std::string& prev_fname, + const source_location& loc, + const std::string& msg, + const Ts&... tail) { + return format_location_impl(lnw, prev_fname, loc, msg) + + format_location_rec(lnw, loc.file_name(), tail...); + } + + } // namespace detail + + // format a location info without title + template + std::string format_location(const source_location& loc, + const std::string& msg, + const Ts&... tail) { + const auto lnw = detail::line_width(loc, msg, tail...); + + const std::string f(""); // at the 1st iteration, no prev_filename is given + return detail::format_location_rec(lnw, f, loc, msg, tail...); + } + +} // namespace toml +#endif // TOML11_SOURCE_LOCATION_FWD_HPP + +#if !defined(TOML11_COMPILE_SOURCES) + #ifndef TOML11_SOURCE_LOCATION_IMPL_HPP + #define TOML11_SOURCE_LOCATION_IMPL_HPP + + #include + #include + #include + #include + #include + +namespace toml { + + TOML11_INLINE source_location::source_location(const detail::region& r) + : is_ok_(false) + , first_line_(1) + , first_column_(1) + , last_line_(1) + , last_column_(1) + , length_(0) + , file_name_("unknown file") { + if (r.is_ok()) { + this->is_ok_ = true; + this->file_name_ = r.source_name(); + this->first_line_ = r.first_line_number(); + this->first_column_ = r.first_column_number(); + this->last_line_ = r.last_line_number(); + this->last_column_ = r.last_column_number(); + this->length_ = r.length(); + this->line_str_ = r.as_lines(); + } + } + + TOML11_INLINE const std::string& source_location::first_line() const { + if (this->line_str_.size() == 0) { + throw std::out_of_range( + "toml::source_location::first_line: `lines` is empty"); + } + return this->line_str_.front(); + } + + TOML11_INLINE const std::string& source_location::last_line() const { + if (this->line_str_.size() == 0) { + throw std::out_of_range( + "toml::source_location::first_line: `lines` is empty"); + } + return this->line_str_.back(); + } + + namespace detail { + + TOML11_INLINE std::size_t integer_width_base10(std::size_t i) noexcept { + std::size_t width = 0; + while (i != 0) { + i /= 10; + width += 1; + } + return width; + } + + TOML11_INLINE std::ostringstream& format_filename(std::ostringstream& oss, + const source_location& loc) { + // --> example.toml + oss << color::bold << color::blue << " --> " << color::reset + << color::bold << loc.file_name() << '\n' + << color::reset; + return oss; + } + + TOML11_INLINE std::ostringstream& format_empty_line(std::ostringstream& oss, + const std::size_t lnw) { + // | + oss << detail::make_string(lnw + 1, ' ') << color::bold << color::blue + << " |\n" + << color::reset; + return oss; + } + + TOML11_INLINE std::ostringstream& format_line(std::ostringstream& oss, + const std::size_t lnw, + const std::size_t linenum, + const std::string& line) { + // 10 | key = "value" + oss << ' ' << color::bold << color::blue << std::setw(static_cast(lnw)) + << std::right << linenum << " | " << color::reset; + for (const char c : line) { + if (std::isgraph(c) || c == ' ') { + oss << c; + } else { + oss << show_char(c); + } + } + oss << '\n'; + return oss; + } + + TOML11_INLINE std::ostringstream& format_underline(std::ostringstream& oss, + const std::size_t lnw, + const std::size_t col, + const std::size_t len, + const std::string& msg) { + // | ^^^^^^^-- this part + oss << make_string(lnw + 1, ' ') << color::bold << color::blue << " | " + << color::reset; + + oss << make_string(col - 1 /*1-origin*/, ' ') << color::bold << color::red + << make_string(len, '^') << "-- " << color::reset << msg << '\n'; + + return oss; + } + + TOML11_INLINE std::string format_location_impl(const std::size_t lnw, + const std::string& prev_fname, + const source_location& loc, + const std::string& msg) { + std::ostringstream oss; + + if (loc.file_name() != prev_fname) { + format_filename(oss, loc); + if (!loc.lines().empty()) { + format_empty_line(oss, lnw); + } + } + + if (loc.lines().size() == 1) { + // when column points LF, it exceeds the size of the first line. + std::size_t underline_limit = 1; + if (loc.first_line().size() < loc.first_column_number()) { + underline_limit = 1; + } else { + underline_limit = loc.first_line().size() - loc.first_column_number() + 1; + } + const auto underline_len = (std::min)(underline_limit, loc.length()); + + format_line(oss, lnw, loc.first_line_number(), loc.first_line()); + format_underline(oss, lnw, loc.first_column_number(), underline_len, msg); + } else if (loc.lines().size() == 2) { + const auto first_underline_len = loc.first_line().size() - + loc.first_column_number() + 1; + format_line(oss, lnw, loc.first_line_number(), loc.first_line()); + format_underline(oss, lnw, loc.first_column_number(), first_underline_len, ""); + + format_line(oss, lnw, loc.last_line_number(), loc.last_line()); + format_underline(oss, lnw, 1, loc.last_column_number(), msg); + } else if (loc.lines().size() > 2) { + const auto first_underline_len = loc.first_line().size() - + loc.first_column_number() + 1; + format_line(oss, lnw, loc.first_line_number(), loc.first_line()); + format_underline(oss, lnw, loc.first_column_number(), first_underline_len, "and"); + + if (loc.lines().size() == 3) { + format_line(oss, lnw, loc.first_line_number() + 1, loc.lines().at(1)); + format_underline(oss, lnw, 1, loc.lines().at(1).size(), "and"); + } else { + format_line(oss, lnw, loc.first_line_number() + 1, " ..."); + format_empty_line(oss, lnw); + } + format_line(oss, lnw, loc.last_line_number(), loc.last_line()); + format_underline(oss, lnw, 1, loc.last_column_number(), msg); + } + // if loc is empty, do nothing. + return oss.str(); + } + + } // namespace detail +} // namespace toml + #endif // TOML11_SOURCE_LOCATION_IMPL_HPP +#endif + +#endif // TOML11_SOURCE_LOCATION_HPP +#ifndef TOML11_ERROR_INFO_HPP +#define TOML11_ERROR_INFO_HPP + +#ifndef TOML11_ERROR_INFO_FWD_HPP + #define TOML11_ERROR_INFO_FWD_HPP + +namespace toml { + + // error info returned from parser. + struct error_info { + error_info(std::string t, source_location l, std::string m, std::string s = "") + : title_(std::move(t)) + , locations_ { std::make_pair(std::move(l), std::move(m)) } + , suffix_(std::move(s)) {} + + error_info(std::string t, + std::vector> l, + std::string s = "") + : title_(std::move(t)) + , locations_(std::move(l)) + , suffix_(std::move(s)) {} + + const std::string& title() const noexcept { + return title_; + } + + std::string& title() noexcept { + return title_; + } + + const std::vector>& locations() const noexcept { + return locations_; + } + + void add_locations(source_location loc, std::string msg) noexcept { + locations_.emplace_back(std::move(loc), std::move(msg)); + } + + const std::string& suffix() const noexcept { + return suffix_; + } + + std::string& suffix() noexcept { + return suffix_; + } + + private: + std::string title_; + std::vector> locations_; + std::string suffix_; // hint or something like that + }; + + // forward decl + template + class basic_value; + + namespace detail { + inline error_info make_error_info_rec(error_info e) { + return e; + } + + inline error_info make_error_info_rec(error_info e, std::string s) { + e.suffix() = s; + return e; + } + + template + error_info make_error_info_rec(error_info e, + const basic_value& v, + std::string msg, + Ts&&... tail); + + template + error_info make_error_info_rec(error_info e, + source_location loc, + std::string msg, + Ts&&... tail) { + e.add_locations(std::move(loc), std::move(msg)); + return make_error_info_rec(std::move(e), std::forward(tail)...); + } + + } // namespace detail + + template + error_info make_error_info(std::string title, + source_location loc, + std::string msg, + Ts&&... tail) { + error_info ei(std::move(title), std::move(loc), std::move(msg)); + return detail::make_error_info_rec(ei, std::forward(tail)...); + } + + std::string format_error(const std::string& errkind, const error_info& err); + std::string format_error(const error_info& err); + + // for custom error message + template + std::string format_error(std::string title, + source_location loc, + std::string msg, + Ts&&... tail) { + return format_error("", + make_error_info(std::move(title), + std::move(loc), + std::move(msg), + std::forward(tail)...)); + } + + std::ostream& operator<<(std::ostream& os, const error_info& e); + +} // namespace toml +#endif // TOML11_ERROR_INFO_FWD_HPP + +#if !defined(TOML11_COMPILE_SOURCES) + #ifndef TOML11_ERROR_INFO_IMPL_HPP + #define TOML11_ERROR_INFO_IMPL_HPP + + #include + +namespace toml { + + TOML11_INLINE std::string format_error(const std::string& errkind, + const error_info& err) { + std::string errmsg; + if (!errkind.empty()) { + errmsg = errkind; + errmsg += ' '; + } + errmsg += err.title(); + errmsg += '\n'; + + const auto lnw = [&err]() { + std::size_t width = 0; + for (const auto& l : err.locations()) { + width = (std::max)(detail::integer_width_base10(l.first.last_line_number()), + width); + } + return width; + }(); + + bool first = true; + std::string prev_fname; + for (const auto& lm : err.locations()) { + if (!first) { + std::ostringstream oss; + oss << detail::make_string(lnw + 1, ' ') << color::bold << color::blue + << " |" << color::reset << color::bold << " ...\n" + << color::reset; + oss << detail::make_string(lnw + 1, ' ') << color::bold << color::blue + << " |\n" + << color::reset; + errmsg += oss.str(); + } + + const auto& l = lm.first; + const auto& m = lm.second; + + errmsg += detail::format_location_impl(lnw, prev_fname, l, m); + + prev_fname = l.file_name(); + first = false; + } + + errmsg += err.suffix(); + + return errmsg; + } + + TOML11_INLINE std::string format_error(const error_info& err) { + std::ostringstream oss; + oss << color::red << color::bold << "[error]" << color::reset; + return format_error(oss.str(), err); + } + + TOML11_INLINE std::ostream& operator<<(std::ostream& os, const error_info& e) { + os << format_error(e); + return os; + } + +} // namespace toml + #endif // TOML11_ERROR_INFO_IMPL_HPP +#endif + +#endif // TOML11_ERROR_INFO_HPP +#ifndef TOML11_VALUE_HPP +#define TOML11_VALUE_HPP + +#ifdef TOML11_HAS_STRING_VIEW + #include +#endif + +#include + +namespace toml { + template + class basic_value; + + struct type_error final : public ::toml::exception { + public: + type_error(std::string what_arg, source_location loc) + : what_(std::move(what_arg)) + , loc_(std::move(loc)) {} + + ~type_error() noexcept override = default; + + const char* what() const noexcept override { + return what_.c_str(); + } + + const source_location& location() const noexcept { + return loc_; + } + + private: + std::string what_; + source_location loc_; + }; + + // only for internal use + namespace detail { + template + error_info make_type_error(const basic_value&, + const std::string&, + const value_t); + + template + error_info make_not_found_error(const basic_value&, + const std::string&, + const typename basic_value::key_type&); + + template + void change_region_of_value(basic_value&, const basic_value&); + + template + struct getter; + } // namespace detail + + template + class basic_value { + public: + using config_type = TypeConfig; + using key_type = typename config_type::string_type; + using value_type = basic_value; + using boolean_type = typename config_type::boolean_type; + using integer_type = typename config_type::integer_type; + using floating_type = typename config_type::floating_type; + using string_type = typename config_type::string_type; + using local_time_type = ::toml::local_time; + using local_date_type = ::toml::local_date; + using local_datetime_type = ::toml::local_datetime; + using offset_datetime_type = ::toml::offset_datetime; + using array_type = typename config_type::template array_type; + using table_type = typename config_type::template table_type; + using comment_type = typename config_type::comment_type; + using char_type = typename string_type::value_type; + + private: + using region_type = detail::region; + + public: + basic_value() noexcept + : type_(value_t::empty) + , empty_('\0') + , region_ {} + , comments_ {} {} + + ~basic_value() noexcept { + this->cleanup(); + } + + // copy/move constructor/assigner ===================================== {{{ + + basic_value(const basic_value& v) + : type_(v.type_) + , region_(v.region_) + , comments_(v.comments_) { + switch (this->type_) { + case value_t::boolean: + assigner(boolean_, v.boolean_); + break; + case value_t::integer: + assigner(integer_, v.integer_); + break; + case value_t::floating: + assigner(floating_, v.floating_); + break; + case value_t::string: + assigner(string_, v.string_); + break; + case value_t::offset_datetime: + assigner(offset_datetime_, v.offset_datetime_); + break; + case value_t::local_datetime: + assigner(local_datetime_, v.local_datetime_); + break; + case value_t::local_date: + assigner(local_date_, v.local_date_); + break; + case value_t::local_time: + assigner(local_time_, v.local_time_); + break; + case value_t::array: + assigner(array_, v.array_); + break; + case value_t::table: + assigner(table_, v.table_); + break; + default: + assigner(empty_, '\0'); + break; + } + } + + basic_value(basic_value&& v) + : type_(v.type()) + , region_(std::move(v.region_)) + , comments_(std::move(v.comments_)) { + switch (this->type_) { + case value_t::boolean: + assigner(boolean_, std::move(v.boolean_)); + break; + case value_t::integer: + assigner(integer_, std::move(v.integer_)); + break; + case value_t::floating: + assigner(floating_, std::move(v.floating_)); + break; + case value_t::string: + assigner(string_, std::move(v.string_)); + break; + case value_t::offset_datetime: + assigner(offset_datetime_, std::move(v.offset_datetime_)); + break; + case value_t::local_datetime: + assigner(local_datetime_, std::move(v.local_datetime_)); + break; + case value_t::local_date: + assigner(local_date_, std::move(v.local_date_)); + break; + case value_t::local_time: + assigner(local_time_, std::move(v.local_time_)); + break; + case value_t::array: + assigner(array_, std::move(v.array_)); + break; + case value_t::table: + assigner(table_, std::move(v.table_)); + break; + default: + assigner(empty_, '\0'); + break; + } + } + + basic_value& operator=(const basic_value& v) { + if (this == std::addressof(v)) { + return *this; + } + + this->cleanup(); + this->type_ = v.type_; + this->region_ = v.region_; + this->comments_ = v.comments_; + switch (this->type_) { + case value_t::boolean: + assigner(boolean_, v.boolean_); + break; + case value_t::integer: + assigner(integer_, v.integer_); + break; + case value_t::floating: + assigner(floating_, v.floating_); + break; + case value_t::string: + assigner(string_, v.string_); + break; + case value_t::offset_datetime: + assigner(offset_datetime_, v.offset_datetime_); + break; + case value_t::local_datetime: + assigner(local_datetime_, v.local_datetime_); + break; + case value_t::local_date: + assigner(local_date_, v.local_date_); + break; + case value_t::local_time: + assigner(local_time_, v.local_time_); + break; + case value_t::array: + assigner(array_, v.array_); + break; + case value_t::table: + assigner(table_, v.table_); + break; + default: + assigner(empty_, '\0'); + break; + } + return *this; + } + + basic_value& operator=(basic_value&& v) { + if (this == std::addressof(v)) { + return *this; + } + + this->cleanup(); + this->type_ = v.type_; + this->region_ = std::move(v.region_); + this->comments_ = std::move(v.comments_); + switch (this->type_) { + case value_t::boolean: + assigner(boolean_, std::move(v.boolean_)); + break; + case value_t::integer: + assigner(integer_, std::move(v.integer_)); + break; + case value_t::floating: + assigner(floating_, std::move(v.floating_)); + break; + case value_t::string: + assigner(string_, std::move(v.string_)); + break; + case value_t::offset_datetime: + assigner(offset_datetime_, std::move(v.offset_datetime_)); + break; + case value_t::local_datetime: + assigner(local_datetime_, std::move(v.local_datetime_)); + break; + case value_t::local_date: + assigner(local_date_, std::move(v.local_date_)); + break; + case value_t::local_time: + assigner(local_time_, std::move(v.local_time_)); + break; + case value_t::array: + assigner(array_, std::move(v.array_)); + break; + case value_t::table: + assigner(table_, std::move(v.table_)); + break; + default: + assigner(empty_, '\0'); + break; + } + return *this; + } + + // }}} + + // constructor to overwrite commnets ================================== {{{ + + basic_value(basic_value v, std::vector com) + : type_(v.type()) + , region_(std::move(v.region_)) + , comments_(std::move(com)) { + switch (this->type_) { + case value_t::boolean: + assigner(boolean_, std::move(v.boolean_)); + break; + case value_t::integer: + assigner(integer_, std::move(v.integer_)); + break; + case value_t::floating: + assigner(floating_, std::move(v.floating_)); + break; + case value_t::string: + assigner(string_, std::move(v.string_)); + break; + case value_t::offset_datetime: + assigner(offset_datetime_, std::move(v.offset_datetime_)); + break; + case value_t::local_datetime: + assigner(local_datetime_, std::move(v.local_datetime_)); + break; + case value_t::local_date: + assigner(local_date_, std::move(v.local_date_)); + break; + case value_t::local_time: + assigner(local_time_, std::move(v.local_time_)); + break; + case value_t::array: + assigner(array_, std::move(v.array_)); + break; + case value_t::table: + assigner(table_, std::move(v.table_)); + break; + default: + assigner(empty_, '\0'); + break; + } + } + + // }}} + + // conversion between different basic_values ========================== {{{ + + template + basic_value(basic_value other) + : type_(other.type_) + , region_(std::move(other.region_)) + , comments_(std::move(other.comments_)) { + switch (other.type_) { + // use auto-convert in constructor + case value_t::boolean: + assigner(boolean_, std::move(other.boolean_)); + break; + case value_t::integer: + assigner(integer_, std::move(other.integer_)); + break; + case value_t::floating: + assigner(floating_, std::move(other.floating_)); + break; + case value_t::string: + assigner(string_, std::move(other.string_)); + break; + case value_t::offset_datetime: + assigner(offset_datetime_, std::move(other.offset_datetime_)); + break; + case value_t::local_datetime: + assigner(local_datetime_, std::move(other.local_datetime_)); + break; + case value_t::local_date: + assigner(local_date_, std::move(other.local_date_)); + break; + case value_t::local_time: + assigner(local_time_, std::move(other.local_time_)); + break; + + // may have different container type + case value_t::array: { + array_type tmp(std::make_move_iterator(other.array_.value.get().begin()), + std::make_move_iterator(other.array_.value.get().end())); + assigner(array_, + array_storage(detail::storage(std::move(tmp)), + other.array_.format)); + break; + } + case value_t::table: { + table_type tmp(std::make_move_iterator(other.table_.value.get().begin()), + std::make_move_iterator(other.table_.value.get().end())); + assigner(table_, + table_storage(detail::storage(std::move(tmp)), + other.table_.format)); + break; + } + default: + break; + } + } + + template + basic_value(basic_value other, std::vector com) + : type_(other.type_) + , region_(std::move(other.region_)) + , comments_(std::move(com)) { + switch (other.type_) { + // use auto-convert in constructor + case value_t::boolean: + assigner(boolean_, std::move(other.boolean_)); + break; + case value_t::integer: + assigner(integer_, std::move(other.integer_)); + break; + case value_t::floating: + assigner(floating_, std::move(other.floating_)); + break; + case value_t::string: + assigner(string_, std::move(other.string_)); + break; + case value_t::offset_datetime: + assigner(offset_datetime_, std::move(other.offset_datetime_)); + break; + case value_t::local_datetime: + assigner(local_datetime_, std::move(other.local_datetime_)); + break; + case value_t::local_date: + assigner(local_date_, std::move(other.local_date_)); + break; + case value_t::local_time: + assigner(local_time_, std::move(other.local_time_)); + break; + + // may have different container type + case value_t::array: { + array_type tmp(std::make_move_iterator(other.array_.value.get().begin()), + std::make_move_iterator(other.array_.value.get().end())); + assigner(array_, + array_storage(detail::storage(std::move(tmp)), + other.array_.format)); + break; + } + case value_t::table: { + table_type tmp(std::make_move_iterator(other.table_.value.get().begin()), + std::make_move_iterator(other.table_.value.get().end())); + assigner(table_, + table_storage(detail::storage(std::move(tmp)), + other.table_.format)); + break; + } + default: + break; + } + } + + template + basic_value& operator=(basic_value other) { + this->cleanup(); + this->region_ = other.region_; + this->comments_ = comment_type(other.comments_); + this->type_ = other.type_; + switch (other.type_) { + // use auto-convert in constructor + case value_t::boolean: + assigner(boolean_, std::move(other.boolean_)); + break; + case value_t::integer: + assigner(integer_, std::move(other.integer_)); + break; + case value_t::floating: + assigner(floating_, std::move(other.floating_)); + break; + case value_t::string: + assigner(string_, std::move(other.string_)); + break; + case value_t::offset_datetime: + assigner(offset_datetime_, std::move(other.offset_datetime_)); + break; + case value_t::local_datetime: + assigner(local_datetime_, std::move(other.local_datetime_)); + break; + case value_t::local_date: + assigner(local_date_, std::move(other.local_date_)); + break; + case value_t::local_time: + assigner(local_time_, std::move(other.local_time_)); + break; + + // may have different container type + case value_t::array: { + array_type tmp(std::make_move_iterator(other.array_.value.get().begin()), + std::make_move_iterator(other.array_.value.get().end())); + assigner(array_, + array_storage(detail::storage(std::move(tmp)), + other.array_.format)); + break; + } + case value_t::table: { + table_type tmp(std::make_move_iterator(other.table_.value.get().begin()), + std::make_move_iterator(other.table_.value.get().end())); + assigner(table_, + table_storage(detail::storage(std::move(tmp)), + other.table_.format)); + break; + } + default: + break; + } + return *this; + } + + // }}} + + // constructor (boolean) ============================================== {{{ + + basic_value(boolean_type x) + : basic_value(x, + boolean_format_info {}, + std::vector {}, + region_type {}) {} + + basic_value(boolean_type x, boolean_format_info fmt) + : basic_value(x, fmt, std::vector {}, region_type {}) {} + + basic_value(boolean_type x, std::vector com) + : basic_value(x, boolean_format_info {}, std::move(com), region_type {}) {} + + basic_value(boolean_type x, boolean_format_info fmt, std::vector com) + : basic_value(x, fmt, std::move(com), region_type {}) {} + + basic_value(boolean_type x, + boolean_format_info fmt, + std::vector com, + region_type reg) + : type_(value_t::boolean) + , boolean_(boolean_storage(x, fmt)) + , region_(std::move(reg)) + , comments_(std::move(com)) {} + + basic_value& operator=(boolean_type x) { + boolean_format_info fmt; + if (this->is_boolean()) { + fmt = this->as_boolean_fmt(); + } + this->cleanup(); + this->type_ = value_t::boolean; + this->region_ = region_type {}; + assigner(this->boolean_, boolean_storage(x, fmt)); + return *this; + } + + // }}} + + // constructor (integer) ============================================== {{{ + + basic_value(integer_type x) + : basic_value(std::move(x), + integer_format_info {}, + std::vector {}, + region_type {}) {} + + basic_value(integer_type x, integer_format_info fmt) + : basic_value(std::move(x), + std::move(fmt), + std::vector {}, + region_type {}) {} + + basic_value(integer_type x, std::vector com) + : basic_value(std::move(x), + integer_format_info {}, + std::move(com), + region_type {}) {} + + basic_value(integer_type x, integer_format_info fmt, std::vector com) + : basic_value(std::move(x), std::move(fmt), std::move(com), region_type {}) { + } + + basic_value(integer_type x, + integer_format_info fmt, + std::vector com, + region_type reg) + : type_(value_t::integer) + , integer_(integer_storage(std::move(x), std::move(fmt))) + , region_(std::move(reg)) + , comments_(std::move(com)) {} + + basic_value& operator=(integer_type x) { + integer_format_info fmt; + if (this->is_integer()) { + fmt = this->as_integer_fmt(); + } + this->cleanup(); + this->type_ = value_t::integer; + this->region_ = region_type {}; + assigner(this->integer_, integer_storage(std::move(x), std::move(fmt))); + return *this; + } + + private: + template + using enable_if_integer_like_t = cxx::enable_if_t< + cxx::conjunction, boolean_type>>, + cxx::negation, integer_type>>, + std::is_integral>>::value, + std::nullptr_t>; + + public: + template = nullptr> + basic_value(T x) + : basic_value(std::move(x), + integer_format_info {}, + std::vector {}, + region_type {}) {} + + template = nullptr> + basic_value(T x, integer_format_info fmt) + : basic_value(std::move(x), + std::move(fmt), + std::vector {}, + region_type {}) {} + + template = nullptr> + basic_value(T x, std::vector com) + : basic_value(std::move(x), + integer_format_info {}, + std::move(com), + region_type {}) {} + + template = nullptr> + basic_value(T x, integer_format_info fmt, std::vector com) + : basic_value(std::move(x), std::move(fmt), std::move(com), region_type {}) { + } + + template = nullptr> + basic_value(T x, + integer_format_info fmt, + std::vector com, + region_type reg) + : type_(value_t::integer) + , integer_(integer_storage(std::move(x), std::move(fmt))) + , region_(std::move(reg)) + , comments_(std::move(com)) {} + + template = nullptr> + basic_value& operator=(T x) { + integer_format_info fmt; + if (this->is_integer()) { + fmt = this->as_integer_fmt(); + } + this->cleanup(); + this->type_ = value_t::integer; + this->region_ = region_type {}; + assigner(this->integer_, integer_storage(x, std::move(fmt))); + return *this; + } + + // }}} + + // constructor (floating) ============================================= {{{ + + basic_value(floating_type x) + : basic_value(std::move(x), + floating_format_info {}, + std::vector {}, + region_type {}) {} + + basic_value(floating_type x, floating_format_info fmt) + : basic_value(std::move(x), + std::move(fmt), + std::vector {}, + region_type {}) {} + + basic_value(floating_type x, std::vector com) + : basic_value(std::move(x), + floating_format_info {}, + std::move(com), + region_type {}) {} + + basic_value(floating_type x, + floating_format_info fmt, + std::vector com) + : basic_value(std::move(x), std::move(fmt), std::move(com), region_type {}) { + } + + basic_value(floating_type x, + floating_format_info fmt, + std::vector com, + region_type reg) + : type_(value_t::floating) + , floating_(floating_storage(std::move(x), std::move(fmt))) + , region_(std::move(reg)) + , comments_(std::move(com)) {} + + basic_value& operator=(floating_type x) { + floating_format_info fmt; + if (this->is_floating()) { + fmt = this->as_floating_fmt(); + } + this->cleanup(); + this->type_ = value_t::floating; + this->region_ = region_type {}; + assigner(this->floating_, floating_storage(std::move(x), std::move(fmt))); + return *this; + } + + private: + template + using enable_if_floating_like_t = cxx::enable_if_t< + cxx::conjunction, floating_type>>, + std::is_floating_point>>::value, + std::nullptr_t>; + + public: + template = nullptr> + basic_value(T x) + : basic_value(x, + floating_format_info {}, + std::vector {}, + region_type {}) {} + + template = nullptr> + basic_value(T x, floating_format_info fmt) + : basic_value(x, std::move(fmt), std::vector {}, region_type {}) { + } + + template = nullptr> + basic_value(T x, std::vector com) + : basic_value(x, floating_format_info {}, std::move(com), region_type {}) {} + + template = nullptr> + basic_value(T x, floating_format_info fmt, std::vector com) + : basic_value(x, std::move(fmt), std::move(com), region_type {}) {} + + template = nullptr> + basic_value(T x, + floating_format_info fmt, + std::vector com, + region_type reg) + : type_(value_t::floating) + , floating_(floating_storage(x, std::move(fmt))) + , region_(std::move(reg)) + , comments_(std::move(com)) {} + + template = nullptr> + basic_value& operator=(T x) { + floating_format_info fmt; + if (this->is_floating()) { + fmt = this->as_floating_fmt(); + } + this->cleanup(); + this->type_ = value_t::floating; + this->region_ = region_type {}; + assigner(this->floating_, floating_storage(x, std::move(fmt))); + return *this; + } + + // }}} + + // constructor (string) =============================================== {{{ + + basic_value(string_type x) + : basic_value(std::move(x), + string_format_info {}, + std::vector {}, + region_type {}) {} + + basic_value(string_type x, string_format_info fmt) + : basic_value(std::move(x), + std::move(fmt), + std::vector {}, + region_type {}) {} + + basic_value(string_type x, std::vector com) + : basic_value(std::move(x), + string_format_info {}, + std::move(com), + region_type {}) {} + + basic_value(string_type x, string_format_info fmt, std::vector com) + : basic_value(std::move(x), std::move(fmt), std::move(com), region_type {}) { + } + + basic_value(string_type x, + string_format_info fmt, + std::vector com, + region_type reg) + : type_(value_t::string) + , string_(string_storage(std::move(x), std::move(fmt))) + , region_(std::move(reg)) + , comments_(std::move(com)) {} + + basic_value& operator=(string_type x) { + string_format_info fmt; + if (this->is_string()) { + fmt = this->as_string_fmt(); + } + this->cleanup(); + this->type_ = value_t::string; + this->region_ = region_type {}; + assigner(this->string_, string_storage(x, std::move(fmt))); + return *this; + } + + // "string literal" + + basic_value(const typename string_type::value_type* x) + : basic_value(x, + string_format_info {}, + std::vector {}, + region_type {}) {} + + basic_value(const typename string_type::value_type* x, string_format_info fmt) + : basic_value(x, std::move(fmt), std::vector {}, region_type {}) { + } + + basic_value(const typename string_type::value_type* x, + std::vector com) + : basic_value(x, string_format_info {}, std::move(com), region_type {}) {} + + basic_value(const typename string_type::value_type* x, + string_format_info fmt, + std::vector com) + : basic_value(x, std::move(fmt), std::move(com), region_type {}) {} + + basic_value(const typename string_type::value_type* x, + string_format_info fmt, + std::vector com, + region_type reg) + : type_(value_t::string) + , string_(string_storage(string_type(x), std::move(fmt))) + , region_(std::move(reg)) + , comments_(std::move(com)) {} + + basic_value& operator=(const typename string_type::value_type* x) { + string_format_info fmt; + if (this->is_string()) { + fmt = this->as_string_fmt(); + } + this->cleanup(); + this->type_ = value_t::string; + this->region_ = region_type {}; + assigner(this->string_, string_storage(string_type(x), std::move(fmt))); + return *this; + } + +#if defined(TOML11_HAS_STRING_VIEW) + using string_view_type = std::basic_string_view; + + basic_value(string_view_type x) + : basic_value(x, + string_format_info {}, + std::vector {}, + region_type {}) {} + + basic_value(string_view_type x, string_format_info fmt) + : basic_value(x, std::move(fmt), std::vector {}, region_type {}) { + } + + basic_value(string_view_type x, std::vector com) + : basic_value(x, string_format_info {}, std::move(com), region_type {}) {} + + basic_value(string_view_type x, + string_format_info fmt, + std::vector com) + : basic_value(x, std::move(fmt), std::move(com), region_type {}) {} + + basic_value(string_view_type x, + string_format_info fmt, + std::vector com, + region_type reg) + : type_(value_t::string) + , string_(string_storage(string_type(x), std::move(fmt))) + , region_(std::move(reg)) + , comments_(std::move(com)) {} + + basic_value& operator=(string_view_type x) { + string_format_info fmt; + if (this->is_string()) { + fmt = this->as_string_fmt(); + } + this->cleanup(); + this->type_ = value_t::string; + this->region_ = region_type {}; + assigner(this->string_, string_storage(string_type(x), std::move(fmt))); + return *this; + } + +#endif // TOML11_HAS_STRING_VIEW + + template , string_type>>, + detail::is_1byte_std_basic_string>::value, + std::nullptr_t> = nullptr> + basic_value(const T& x) + : basic_value(x, + string_format_info {}, + std::vector {}, + region_type {}) {} + + template , string_type>>, + detail::is_1byte_std_basic_string>::value, + std::nullptr_t> = nullptr> + basic_value(const T& x, string_format_info fmt) + : basic_value(x, std::move(fmt), std::vector {}, region_type {}) { + } + + template , string_type>>, + detail::is_1byte_std_basic_string>::value, + std::nullptr_t> = nullptr> + basic_value(const T& x, std::vector com) + : basic_value(x, string_format_info {}, std::move(com), region_type {}) {} + + template , string_type>>, + detail::is_1byte_std_basic_string>::value, + std::nullptr_t> = nullptr> + basic_value(const T& x, string_format_info fmt, std::vector com) + : basic_value(x, std::move(fmt), std::move(com), region_type {}) {} + + template , string_type>>, + detail::is_1byte_std_basic_string>::value, + std::nullptr_t> = nullptr> + basic_value(const T& x, + string_format_info fmt, + std::vector com, + region_type reg) + : type_(value_t::string) + , string_(string_storage(detail::string_conv(x), std::move(fmt))) + , region_(std::move(reg)) + , comments_(std::move(com)) {} + + template , string_type>>, + detail::is_1byte_std_basic_string>::value, + std::nullptr_t> = nullptr> + basic_value& operator=(const T& x) { + string_format_info fmt; + if (this->is_string()) { + fmt = this->as_string_fmt(); + } + this->cleanup(); + this->type_ = value_t::string; + this->region_ = region_type {}; + assigner(this->string_, + string_storage(detail::string_conv(x), std::move(fmt))); + return *this; + } + + // }}} + + // constructor (local_date) =========================================== {{{ + + basic_value(local_date_type x) + : basic_value(x, + local_date_format_info {}, + std::vector {}, + region_type {}) {} + + basic_value(local_date_type x, local_date_format_info fmt) + : basic_value(x, fmt, std::vector {}, region_type {}) {} + + basic_value(local_date_type x, std::vector com) + : basic_value(x, local_date_format_info {}, std::move(com), region_type {}) { + } + + basic_value(local_date_type x, + local_date_format_info fmt, + std::vector com) + : basic_value(x, fmt, std::move(com), region_type {}) {} + + basic_value(local_date_type x, + local_date_format_info fmt, + std::vector com, + region_type reg) + : type_(value_t::local_date) + , local_date_(local_date_storage(x, fmt)) + , region_(std::move(reg)) + , comments_(std::move(com)) {} + + basic_value& operator=(local_date_type x) { + local_date_format_info fmt; + if (this->is_local_date()) { + fmt = this->as_local_date_fmt(); + } + this->cleanup(); + this->type_ = value_t::local_date; + this->region_ = region_type {}; + assigner(this->local_date_, local_date_storage(x, fmt)); + return *this; + } + + // }}} + + // constructor (local_time) =========================================== {{{ + + basic_value(local_time_type x) + : basic_value(x, + local_time_format_info {}, + std::vector {}, + region_type {}) {} + + basic_value(local_time_type x, local_time_format_info fmt) + : basic_value(x, fmt, std::vector {}, region_type {}) {} + + basic_value(local_time_type x, std::vector com) + : basic_value(x, local_time_format_info {}, std::move(com), region_type {}) { + } + + basic_value(local_time_type x, + local_time_format_info fmt, + std::vector com) + : basic_value(x, fmt, std::move(com), region_type {}) {} + + basic_value(local_time_type x, + local_time_format_info fmt, + std::vector com, + region_type reg) + : type_(value_t::local_time) + , local_time_(local_time_storage(x, fmt)) + , region_(std::move(reg)) + , comments_(std::move(com)) {} + + basic_value& operator=(local_time_type x) { + local_time_format_info fmt; + if (this->is_local_time()) { + fmt = this->as_local_time_fmt(); + } + this->cleanup(); + this->type_ = value_t::local_time; + this->region_ = region_type {}; + assigner(this->local_time_, local_time_storage(x, fmt)); + return *this; + } + + template + basic_value(const std::chrono::duration& x) + : basic_value(local_time_type(x), + local_time_format_info {}, + std::vector {}, + region_type {}) {} + + template + basic_value(const std::chrono::duration& x, + local_time_format_info fmt) + : basic_value(local_time_type(x), + std::move(fmt), + std::vector {}, + region_type {}) {} + + template + basic_value(const std::chrono::duration& x, + std::vector com) + : basic_value(local_time_type(x), + local_time_format_info {}, + std::move(com), + region_type {}) {} + + template + basic_value(const std::chrono::duration& x, + local_time_format_info fmt, + std::vector com) + : basic_value(local_time_type(x), + std::move(fmt), + std::move(com), + region_type {}) {} + + template + basic_value(const std::chrono::duration& x, + local_time_format_info fmt, + std::vector com, + region_type reg) + : basic_value(local_time_type(x), + std::move(fmt), + std::move(com), + std::move(reg)) {} + + template + basic_value& operator=(const std::chrono::duration& x) { + local_time_format_info fmt; + if (this->is_local_time()) { + fmt = this->as_local_time_fmt(); + } + this->cleanup(); + this->type_ = value_t::local_time; + this->region_ = region_type {}; + assigner(this->local_time_, + local_time_storage(local_time_type(x), std::move(fmt))); + return *this; + } + + // }}} + + // constructor (local_datetime) =========================================== {{{ + + basic_value(local_datetime_type x) + : basic_value(x, + local_datetime_format_info {}, + std::vector {}, + region_type {}) {} + + basic_value(local_datetime_type x, local_datetime_format_info fmt) + : basic_value(x, fmt, std::vector {}, region_type {}) {} + + basic_value(local_datetime_type x, std::vector com) + : basic_value(x, local_datetime_format_info {}, std::move(com), region_type {}) { + } + + basic_value(local_datetime_type x, + local_datetime_format_info fmt, + std::vector com) + : basic_value(x, fmt, std::move(com), region_type {}) {} + + basic_value(local_datetime_type x, + local_datetime_format_info fmt, + std::vector com, + region_type reg) + : type_(value_t::local_datetime) + , local_datetime_(local_datetime_storage(x, fmt)) + , region_(std::move(reg)) + , comments_(std::move(com)) {} + + basic_value& operator=(local_datetime_type x) { + local_datetime_format_info fmt; + if (this->is_local_datetime()) { + fmt = this->as_local_datetime_fmt(); + } + this->cleanup(); + this->type_ = value_t::local_datetime; + this->region_ = region_type {}; + assigner(this->local_datetime_, local_datetime_storage(x, fmt)); + return *this; + } + + // }}} + + // constructor (offset_datetime) =========================================== {{{ + + basic_value(offset_datetime_type x) + : basic_value(x, + offset_datetime_format_info {}, + std::vector {}, + region_type {}) {} + + basic_value(offset_datetime_type x, offset_datetime_format_info fmt) + : basic_value(x, fmt, std::vector {}, region_type {}) {} + + basic_value(offset_datetime_type x, std::vector com) + : basic_value(x, + offset_datetime_format_info {}, + std::move(com), + region_type {}) {} + + basic_value(offset_datetime_type x, + offset_datetime_format_info fmt, + std::vector com) + : basic_value(x, fmt, std::move(com), region_type {}) {} + + basic_value(offset_datetime_type x, + offset_datetime_format_info fmt, + std::vector com, + region_type reg) + : type_(value_t::offset_datetime) + , offset_datetime_(offset_datetime_storage(x, fmt)) + , region_(std::move(reg)) + , comments_(std::move(com)) {} + + basic_value& operator=(offset_datetime_type x) { + offset_datetime_format_info fmt; + if (this->is_offset_datetime()) { + fmt = this->as_offset_datetime_fmt(); + } + this->cleanup(); + this->type_ = value_t::offset_datetime; + this->region_ = region_type {}; + assigner(this->offset_datetime_, offset_datetime_storage(x, fmt)); + return *this; + } + + // system_clock::time_point + + basic_value(std::chrono::system_clock::time_point x) + : basic_value(offset_datetime_type(x), + offset_datetime_format_info {}, + std::vector {}, + region_type {}) {} + + basic_value(std::chrono::system_clock::time_point x, + offset_datetime_format_info fmt) + : basic_value(offset_datetime_type(x), + fmt, + std::vector {}, + region_type {}) {} + + basic_value(std::chrono::system_clock::time_point x, + std::vector com) + : basic_value(offset_datetime_type(x), + offset_datetime_format_info {}, + std::move(com), + region_type {}) {} + + basic_value(std::chrono::system_clock::time_point x, + offset_datetime_format_info fmt, + std::vector com) + : basic_value(offset_datetime_type(x), fmt, std::move(com), region_type {}) { + } + + basic_value(std::chrono::system_clock::time_point x, + offset_datetime_format_info fmt, + std::vector com, + region_type reg) + : basic_value(offset_datetime_type(x), + std::move(fmt), + std::move(com), + std::move(reg)) {} + + basic_value& operator=(std::chrono::system_clock::time_point x) { + offset_datetime_format_info fmt; + if (this->is_offset_datetime()) { + fmt = this->as_offset_datetime_fmt(); + } + this->cleanup(); + this->type_ = value_t::offset_datetime; + this->region_ = region_type {}; + assigner(this->offset_datetime_, + offset_datetime_storage(offset_datetime_type(x), fmt)); + return *this; + } + + // }}} + + // constructor (array) ================================================ {{{ + + basic_value(array_type x) + : basic_value(std::move(x), + array_format_info {}, + std::vector {}, + region_type {}) {} + + basic_value(array_type x, array_format_info fmt) + : basic_value(std::move(x), + std::move(fmt), + std::vector {}, + region_type {}) {} + + basic_value(array_type x, std::vector com) + : basic_value(std::move(x), + array_format_info {}, + std::move(com), + region_type {}) {} + + basic_value(array_type x, array_format_info fmt, std::vector com) + : basic_value(std::move(x), fmt, std::move(com), region_type {}) {} + + basic_value(array_type x, + array_format_info fmt, + std::vector com, + region_type reg) + : type_(value_t::array) + , array_(array_storage(detail::storage(std::move(x)), + std::move(fmt))) + , region_(std::move(reg)) + , comments_(std::move(com)) {} + + basic_value& operator=(array_type x) { + array_format_info fmt; + if (this->is_array()) { + fmt = this->as_array_fmt(); + } + this->cleanup(); + this->type_ = value_t::array; + this->region_ = region_type {}; + assigner(this->array_, + array_storage(detail::storage(std::move(x)), + std::move(fmt))); + return *this; + } + + private: + template + using enable_if_array_like_t = cxx::enable_if_t< + cxx::conjunction, + cxx::negation>, + cxx::negation>, +#if defined(TOML11_HAS_STRING_VIEW) + cxx::negation>, +#endif + cxx::negation>, + cxx::negation>>::value, + std::nullptr_t>; + + public: + template = nullptr> + basic_value(T x) + : basic_value(std::move(x), + array_format_info {}, + std::vector {}, + region_type {}) {} + + template = nullptr> + basic_value(T x, array_format_info fmt) + : basic_value(std::move(x), + std::move(fmt), + std::vector {}, + region_type {}) {} + + template = nullptr> + basic_value(T x, std::vector com) + : basic_value(std::move(x), + array_format_info {}, + std::move(com), + region_type {}) {} + + template = nullptr> + basic_value(T x, array_format_info fmt, std::vector com) + : basic_value(std::move(x), fmt, std::move(com), region_type {}) {} + + template = nullptr> + basic_value(T x, array_format_info fmt, std::vector com, region_type reg) + : type_(value_t::array) + , array_(array_storage(detail::storage( + array_type(std::make_move_iterator(x.begin()), + std::make_move_iterator(x.end()))), + std::move(fmt))) + , region_(std::move(reg)) + , comments_(std::move(com)) {} + + template = nullptr> + basic_value& operator=(T x) { + array_format_info fmt; + if (this->is_array()) { + fmt = this->as_array_fmt(); + } + this->cleanup(); + this->type_ = value_t::array; + this->region_ = region_type {}; + + array_type a(std::make_move_iterator(x.begin()), + std::make_move_iterator(x.end())); + assigner(this->array_, + array_storage(detail::storage(std::move(a)), + std::move(fmt))); + return *this; + } + + // }}} + + // constructor (table) ================================================ {{{ + + basic_value(table_type x) + : basic_value(std::move(x), + table_format_info {}, + std::vector {}, + region_type {}) {} + + basic_value(table_type x, table_format_info fmt) + : basic_value(std::move(x), + std::move(fmt), + std::vector {}, + region_type {}) {} + + basic_value(table_type x, std::vector com) + : basic_value(std::move(x), + table_format_info {}, + std::move(com), + region_type {}) {} + + basic_value(table_type x, table_format_info fmt, std::vector com) + : basic_value(std::move(x), fmt, std::move(com), region_type {}) {} + + basic_value(table_type x, + table_format_info fmt, + std::vector com, + region_type reg) + : type_(value_t::table) + , table_(table_storage(detail::storage(std::move(x)), + std::move(fmt))) + , region_(std::move(reg)) + , comments_(std::move(com)) {} + + basic_value& operator=(table_type x) { + table_format_info fmt; + if (this->is_table()) { + fmt = this->as_table_fmt(); + } + this->cleanup(); + this->type_ = value_t::table; + this->region_ = region_type {}; + assigner(this->table_, + table_storage(detail::storage(std::move(x)), + std::move(fmt))); + return *this; + } + + // table-like + + private: + template + using enable_if_table_like_t = cxx::enable_if_t< + cxx::conjunction>, + detail::is_map, + cxx::negation>, + cxx::negation>>::value, + std::nullptr_t>; + + public: + template = nullptr> + basic_value(T x) + : basic_value(std::move(x), + table_format_info {}, + std::vector {}, + region_type {}) {} + + template = nullptr> + basic_value(T x, table_format_info fmt) + : basic_value(std::move(x), + std::move(fmt), + std::vector {}, + region_type {}) {} + + template = nullptr> + basic_value(T x, std::vector com) + : basic_value(std::move(x), + table_format_info {}, + std::move(com), + region_type {}) {} + + template = nullptr> + basic_value(T x, table_format_info fmt, std::vector com) + : basic_value(std::move(x), fmt, std::move(com), region_type {}) {} + + template = nullptr> + basic_value(T x, table_format_info fmt, std::vector com, region_type reg) + : type_(value_t::table) + , table_(table_storage(detail::storage( + table_type(std::make_move_iterator(x.begin()), + std::make_move_iterator(x.end()))), + std::move(fmt))) + , region_(std::move(reg)) + , comments_(std::move(com)) {} + + template = nullptr> + basic_value& operator=(T x) { + table_format_info fmt; + if (this->is_table()) { + fmt = this->as_table_fmt(); + } + this->cleanup(); + this->type_ = value_t::table; + this->region_ = region_type {}; + + table_type t(std::make_move_iterator(x.begin()), + std::make_move_iterator(x.end())); + assigner(this->table_, + table_storage(detail::storage(std::move(t)), + std::move(fmt))); + return *this; + } + + // }}} + + // constructor (user_defined) ========================================= {{{ + + template ::value, std::nullptr_t> = nullptr> + basic_value(const T& ud) + : basic_value( + into>::template into_toml(ud)) {} + + template ::value, std::nullptr_t> = nullptr> + basic_value(const T& ud, std::vector com) + : basic_value( + into>::template into_toml(ud), + std::move(com)) {} + + template ::value, std::nullptr_t> = nullptr> + basic_value& operator=(const T& ud) { + *this = into>::template into_toml(ud); + return *this; + } + + template , + cxx::negation>>::value, + std::nullptr_t> = nullptr> + basic_value(const T& ud) : basic_value(ud.into_toml()) {} + + template , + cxx::negation>>::value, + std::nullptr_t> = nullptr> + basic_value(const T& ud, std::vector com) + : basic_value(ud.into_toml(), std::move(com)) {} + + template , + cxx::negation>>::value, + std::nullptr_t> = nullptr> + basic_value& operator=(const T& ud) { + *this = ud.into_toml(); + return *this; + } + + template , + cxx::negation>>::value, + std::nullptr_t> = nullptr> + basic_value(const T& ud) + : basic_value(ud.template into_toml()) {} + + template , + cxx::negation>>::value, + std::nullptr_t> = nullptr> + basic_value(const T& ud, std::vector com) + : basic_value(ud.template into_toml(), std::move(com)) {} + + template , + cxx::negation>>::value, + std::nullptr_t> = nullptr> + basic_value& operator=(const T& ud) { + *this = ud.template into_toml(); + return *this; + } + + // }}} + + // empty value with region info ======================================= {{{ + + // mainly for `null` extension + basic_value(detail::none_t, region_type reg) noexcept + : type_(value_t::empty) + , empty_('\0') + , region_(std::move(reg)) + , comments_ {} {} + + // }}} + + // type checking ====================================================== {{{ + + template , value_type>::value, + std::nullptr_t> = nullptr> + bool is() const noexcept { + return detail::type_to_enum::value == this->type_; + } + + bool is(value_t t) const noexcept { + return t == this->type_; + } + + bool is_empty() const noexcept { + return this->is(value_t::empty); + } + + bool is_boolean() const noexcept { + return this->is(value_t::boolean); + } + + bool is_integer() const noexcept { + return this->is(value_t::integer); + } + + bool is_floating() const noexcept { + return this->is(value_t::floating); + } + + bool is_string() const noexcept { + return this->is(value_t::string); + } + + bool is_offset_datetime() const noexcept { + return this->is(value_t::offset_datetime); + } + + bool is_local_datetime() const noexcept { + return this->is(value_t::local_datetime); + } + + bool is_local_date() const noexcept { + return this->is(value_t::local_date); + } + + bool is_local_time() const noexcept { + return this->is(value_t::local_time); + } + + bool is_array() const noexcept { + return this->is(value_t::array); + } + + bool is_table() const noexcept { + return this->is(value_t::table); + } + + bool is_array_of_tables() const noexcept { + if (!this->is_array()) { + return false; + } + const auto& a = this->as_array(std::nothrow); // already checked. + + // when you define [[array.of.tables]], at least one empty table will be + // assigned. In case of array of inline tables, `array_of_tables = []`, + // there is no reason to consider this as an array of *tables*. + // So empty array is not an array-of-tables. + if (a.empty()) { + return false; + } + + // since toml v1.0.0 allows array of heterogeneous types, we need to + // check all the elements. if any of the elements is not a table, it + // is a heterogeneous array and cannot be expressed by `[[aot]]` form. + for (const auto& e : a) { + if (!e.is_table()) { + return false; + } + } + return true; + } + + value_t type() const noexcept { + return type_; + } + + // }}} + + // as_xxx (noexcept) version ========================================== {{{ + + template + const detail::enum_to_type_t>& as( + const std::nothrow_t&) const noexcept { + return detail::getter::get_nothrow(*this); + } + + template + detail::enum_to_type_t>& as( + const std::nothrow_t&) noexcept { + return detail::getter::get_nothrow(*this); + } + + const boolean_type& as_boolean(const std::nothrow_t&) const noexcept { + return this->boolean_.value; + } + + const integer_type& as_integer(const std::nothrow_t&) const noexcept { + return this->integer_.value; + } + + const floating_type& as_floating(const std::nothrow_t&) const noexcept { + return this->floating_.value; + } + + const string_type& as_string(const std::nothrow_t&) const noexcept { + return this->string_.value; + } + + const offset_datetime_type& as_offset_datetime( + const std::nothrow_t&) const noexcept { + return this->offset_datetime_.value; + } + + const local_datetime_type& as_local_datetime( + const std::nothrow_t&) const noexcept { + return this->local_datetime_.value; + } + + const local_date_type& as_local_date(const std::nothrow_t&) const noexcept { + return this->local_date_.value; + } + + const local_time_type& as_local_time(const std::nothrow_t&) const noexcept { + return this->local_time_.value; + } + + const array_type& as_array(const std::nothrow_t&) const noexcept { + return this->array_.value.get(); + } + + const table_type& as_table(const std::nothrow_t&) const noexcept { + return this->table_.value.get(); + } + + boolean_type& as_boolean(const std::nothrow_t&) noexcept { + return this->boolean_.value; + } + + integer_type& as_integer(const std::nothrow_t&) noexcept { + return this->integer_.value; + } + + floating_type& as_floating(const std::nothrow_t&) noexcept { + return this->floating_.value; + } + + string_type& as_string(const std::nothrow_t&) noexcept { + return this->string_.value; + } + + offset_datetime_type& as_offset_datetime(const std::nothrow_t&) noexcept { + return this->offset_datetime_.value; + } + + local_datetime_type& as_local_datetime(const std::nothrow_t&) noexcept { + return this->local_datetime_.value; + } + + local_date_type& as_local_date(const std::nothrow_t&) noexcept { + return this->local_date_.value; + } + + local_time_type& as_local_time(const std::nothrow_t&) noexcept { + return this->local_time_.value; + } + + array_type& as_array(const std::nothrow_t&) noexcept { + return this->array_.value.get(); + } + + table_type& as_table(const std::nothrow_t&) noexcept { + return this->table_.value.get(); + } + + // }}} + + // as_xxx (throw) ===================================================== {{{ + + template + const detail::enum_to_type_t>& as() const { + return detail::getter::get(*this); + } + + template + detail::enum_to_type_t>& as() { + return detail::getter::get(*this); + } + + const boolean_type& as_boolean() const { + if (this->type_ != value_t::boolean) { + this->throw_bad_cast("toml::value::as_boolean()", value_t::boolean); + } + return this->boolean_.value; + } + + const integer_type& as_integer() const { + if (this->type_ != value_t::integer) { + this->throw_bad_cast("toml::value::as_integer()", value_t::integer); + } + return this->integer_.value; + } + + const floating_type& as_floating() const { + if (this->type_ != value_t::floating) { + this->throw_bad_cast("toml::value::as_floating()", value_t::floating); + } + return this->floating_.value; + } + + const string_type& as_string() const { + if (this->type_ != value_t::string) { + this->throw_bad_cast("toml::value::as_string()", value_t::string); + } + return this->string_.value; + } + + const offset_datetime_type& as_offset_datetime() const { + if (this->type_ != value_t::offset_datetime) { + this->throw_bad_cast("toml::value::as_offset_datetime()", + value_t::offset_datetime); + } + return this->offset_datetime_.value; + } + + const local_datetime_type& as_local_datetime() const { + if (this->type_ != value_t::local_datetime) { + this->throw_bad_cast("toml::value::as_local_datetime()", + value_t::local_datetime); + } + return this->local_datetime_.value; + } + + const local_date_type& as_local_date() const { + if (this->type_ != value_t::local_date) { + this->throw_bad_cast("toml::value::as_local_date()", value_t::local_date); + } + return this->local_date_.value; + } + + const local_time_type& as_local_time() const { + if (this->type_ != value_t::local_time) { + this->throw_bad_cast("toml::value::as_local_time()", value_t::local_time); + } + return this->local_time_.value; + } + + const array_type& as_array() const { + if (this->type_ != value_t::array) { + this->throw_bad_cast("toml::value::as_array()", value_t::array); + } + return this->array_.value.get(); + } + + const table_type& as_table() const { + if (this->type_ != value_t::table) { + this->throw_bad_cast("toml::value::as_table()", value_t::table); + } + return this->table_.value.get(); + } + + // ------------------------------------------------------------------------ + // nonconst reference + + boolean_type& as_boolean() { + if (this->type_ != value_t::boolean) { + this->throw_bad_cast("toml::value::as_boolean()", value_t::boolean); + } + return this->boolean_.value; + } + + integer_type& as_integer() { + if (this->type_ != value_t::integer) { + this->throw_bad_cast("toml::value::as_integer()", value_t::integer); + } + return this->integer_.value; + } + + floating_type& as_floating() { + if (this->type_ != value_t::floating) { + this->throw_bad_cast("toml::value::as_floating()", value_t::floating); + } + return this->floating_.value; + } + + string_type& as_string() { + if (this->type_ != value_t::string) { + this->throw_bad_cast("toml::value::as_string()", value_t::string); + } + return this->string_.value; + } + + offset_datetime_type& as_offset_datetime() { + if (this->type_ != value_t::offset_datetime) { + this->throw_bad_cast("toml::value::as_offset_datetime()", + value_t::offset_datetime); + } + return this->offset_datetime_.value; + } + + local_datetime_type& as_local_datetime() { + if (this->type_ != value_t::local_datetime) { + this->throw_bad_cast("toml::value::as_local_datetime()", + value_t::local_datetime); + } + return this->local_datetime_.value; + } + + local_date_type& as_local_date() { + if (this->type_ != value_t::local_date) { + this->throw_bad_cast("toml::value::as_local_date()", value_t::local_date); + } + return this->local_date_.value; + } + + local_time_type& as_local_time() { + if (this->type_ != value_t::local_time) { + this->throw_bad_cast("toml::value::as_local_time()", value_t::local_time); + } + return this->local_time_.value; + } + + array_type& as_array() { + if (this->type_ != value_t::array) { + this->throw_bad_cast("toml::value::as_array()", value_t::array); + } + return this->array_.value.get(); + } + + table_type& as_table() { + if (this->type_ != value_t::table) { + this->throw_bad_cast("toml::value::as_table()", value_t::table); + } + return this->table_.value.get(); + } + + // }}} + + // format accessors (noexcept) ======================================== {{{ + + template + const detail::enum_to_fmt_type_t& as_fmt(const std::nothrow_t&) const noexcept { + return detail::getter::get_fmt_nothrow(*this); + } + + template + detail::enum_to_fmt_type_t& as_fmt(const std::nothrow_t&) noexcept { + return detail::getter::get_fmt_nothrow(*this); + } + + boolean_format_info& as_boolean_fmt(const std::nothrow_t&) noexcept { + return this->boolean_.format; + } + + integer_format_info& as_integer_fmt(const std::nothrow_t&) noexcept { + return this->integer_.format; + } + + floating_format_info& as_floating_fmt(const std::nothrow_t&) noexcept { + return this->floating_.format; + } + + string_format_info& as_string_fmt(const std::nothrow_t&) noexcept { + return this->string_.format; + } + + offset_datetime_format_info& as_offset_datetime_fmt( + const std::nothrow_t&) noexcept { + return this->offset_datetime_.format; + } + + local_datetime_format_info& as_local_datetime_fmt(const std::nothrow_t&) noexcept { + return this->local_datetime_.format; + } + + local_date_format_info& as_local_date_fmt(const std::nothrow_t&) noexcept { + return this->local_date_.format; + } + + local_time_format_info& as_local_time_fmt(const std::nothrow_t&) noexcept { + return this->local_time_.format; + } + + array_format_info& as_array_fmt(const std::nothrow_t&) noexcept { + return this->array_.format; + } + + table_format_info& as_table_fmt(const std::nothrow_t&) noexcept { + return this->table_.format; + } + + const boolean_format_info& as_boolean_fmt(const std::nothrow_t&) const noexcept { + return this->boolean_.format; + } + + const integer_format_info& as_integer_fmt(const std::nothrow_t&) const noexcept { + return this->integer_.format; + } + + const floating_format_info& as_floating_fmt(const std::nothrow_t&) const noexcept { + return this->floating_.format; + } + + const string_format_info& as_string_fmt(const std::nothrow_t&) const noexcept { + return this->string_.format; + } + + const offset_datetime_format_info& as_offset_datetime_fmt( + const std::nothrow_t&) const noexcept { + return this->offset_datetime_.format; + } + + const local_datetime_format_info& as_local_datetime_fmt( + const std::nothrow_t&) const noexcept { + return this->local_datetime_.format; + } + + const local_date_format_info& as_local_date_fmt( + const std::nothrow_t&) const noexcept { + return this->local_date_.format; + } + + const local_time_format_info& as_local_time_fmt( + const std::nothrow_t&) const noexcept { + return this->local_time_.format; + } + + const array_format_info& as_array_fmt(const std::nothrow_t&) const noexcept { + return this->array_.format; + } + + const table_format_info& as_table_fmt(const std::nothrow_t&) const noexcept { + return this->table_.format; + } + + // }}} + + // format accessors (throw) =========================================== {{{ + + template + const detail::enum_to_fmt_type_t& as_fmt() const { + return detail::getter::get_fmt(*this); + } + + template + detail::enum_to_fmt_type_t& as_fmt() { + return detail::getter::get_fmt(*this); + } + + const boolean_format_info& as_boolean_fmt() const { + if (this->type_ != value_t::boolean) { + this->throw_bad_cast("toml::value::as_boolean_fmt()", value_t::boolean); + } + return this->boolean_.format; + } + + const integer_format_info& as_integer_fmt() const { + if (this->type_ != value_t::integer) { + this->throw_bad_cast("toml::value::as_integer_fmt()", value_t::integer); + } + return this->integer_.format; + } + + const floating_format_info& as_floating_fmt() const { + if (this->type_ != value_t::floating) { + this->throw_bad_cast("toml::value::as_floating_fmt()", value_t::floating); + } + return this->floating_.format; + } + + const string_format_info& as_string_fmt() const { + if (this->type_ != value_t::string) { + this->throw_bad_cast("toml::value::as_string_fmt()", value_t::string); + } + return this->string_.format; + } + + const offset_datetime_format_info& as_offset_datetime_fmt() const { + if (this->type_ != value_t::offset_datetime) { + this->throw_bad_cast("toml::value::as_offset_datetime_fmt()", + value_t::offset_datetime); + } + return this->offset_datetime_.format; + } + + const local_datetime_format_info& as_local_datetime_fmt() const { + if (this->type_ != value_t::local_datetime) { + this->throw_bad_cast("toml::value::as_local_datetime_fmt()", + value_t::local_datetime); + } + return this->local_datetime_.format; + } + + const local_date_format_info& as_local_date_fmt() const { + if (this->type_ != value_t::local_date) { + this->throw_bad_cast("toml::value::as_local_date_fmt()", + value_t::local_date); + } + return this->local_date_.format; + } + + const local_time_format_info& as_local_time_fmt() const { + if (this->type_ != value_t::local_time) { + this->throw_bad_cast("toml::value::as_local_time_fmt()", + value_t::local_time); + } + return this->local_time_.format; + } + + const array_format_info& as_array_fmt() const { + if (this->type_ != value_t::array) { + this->throw_bad_cast("toml::value::as_array_fmt()", value_t::array); + } + return this->array_.format; + } + + const table_format_info& as_table_fmt() const { + if (this->type_ != value_t::table) { + this->throw_bad_cast("toml::value::as_table_fmt()", value_t::table); + } + return this->table_.format; + } + + // ------------------------------------------------------------------------ + // nonconst reference + + boolean_format_info& as_boolean_fmt() { + if (this->type_ != value_t::boolean) { + this->throw_bad_cast("toml::value::as_boolean_fmt()", value_t::boolean); + } + return this->boolean_.format; + } + + integer_format_info& as_integer_fmt() { + if (this->type_ != value_t::integer) { + this->throw_bad_cast("toml::value::as_integer_fmt()", value_t::integer); + } + return this->integer_.format; + } + + floating_format_info& as_floating_fmt() { + if (this->type_ != value_t::floating) { + this->throw_bad_cast("toml::value::as_floating_fmt()", value_t::floating); + } + return this->floating_.format; + } + + string_format_info& as_string_fmt() { + if (this->type_ != value_t::string) { + this->throw_bad_cast("toml::value::as_string_fmt()", value_t::string); + } + return this->string_.format; + } + + offset_datetime_format_info& as_offset_datetime_fmt() { + if (this->type_ != value_t::offset_datetime) { + this->throw_bad_cast("toml::value::as_offset_datetime_fmt()", + value_t::offset_datetime); + } + return this->offset_datetime_.format; + } + + local_datetime_format_info& as_local_datetime_fmt() { + if (this->type_ != value_t::local_datetime) { + this->throw_bad_cast("toml::value::as_local_datetime_fmt()", + value_t::local_datetime); + } + return this->local_datetime_.format; + } + + local_date_format_info& as_local_date_fmt() { + if (this->type_ != value_t::local_date) { + this->throw_bad_cast("toml::value::as_local_date_fmt()", + value_t::local_date); + } + return this->local_date_.format; + } + + local_time_format_info& as_local_time_fmt() { + if (this->type_ != value_t::local_time) { + this->throw_bad_cast("toml::value::as_local_time_fmt()", + value_t::local_time); + } + return this->local_time_.format; + } + + array_format_info& as_array_fmt() { + if (this->type_ != value_t::array) { + this->throw_bad_cast("toml::value::as_array_fmt()", value_t::array); + } + return this->array_.format; + } + + table_format_info& as_table_fmt() { + if (this->type_ != value_t::table) { + this->throw_bad_cast("toml::value::as_table_fmt()", value_t::table); + } + return this->table_.format; + } + + // }}} + + // table accessors ==================================================== {{{ + + value_type& at(const key_type& k) { + if (!this->is_table()) { + this->throw_bad_cast("toml::value::at(key_type)", value_t::table); + } + auto& table = this->as_table(std::nothrow); + const auto found = table.find(k); + if (found == table.end()) { + this->throw_key_not_found_error("toml::value::at", k); + } + assert(found->first == k); + return found->second; + } + + const value_type& at(const key_type& k) const { + if (!this->is_table()) { + this->throw_bad_cast("toml::value::at(key_type)", value_t::table); + } + const auto& table = this->as_table(std::nothrow); + const auto found = table.find(k); + if (found == table.end()) { + this->throw_key_not_found_error("toml::value::at", k); + } + assert(found->first == k); + return found->second; + } + + value_type& operator[](const key_type& k) { + if (this->is_empty()) { + (*this) = table_type {}; + } else if (!this->is_table()) // initialized, but not a table + { + this->throw_bad_cast("toml::value::operator[](key_type)", value_t::table); + } + return (this->as_table(std::nothrow))[k]; + } + + std::size_t count(const key_type& k) const { + if (!this->is_table()) { + this->throw_bad_cast("toml::value::count(key_type)", value_t::table); + } + return this->as_table(std::nothrow).count(k); + } + + bool contains(const key_type& k) const { + if (!this->is_table()) { + this->throw_bad_cast("toml::value::contains(key_type)", value_t::table); + } + const auto& table = this->as_table(std::nothrow); + return table.find(k) != table.end(); + } + + // }}} + + // array accessors ==================================================== {{{ + + value_type& at(const std::size_t idx) { + if (!this->is_array()) { + this->throw_bad_cast("toml::value::at(idx)", value_t::array); + } + auto& ar = this->as_array(std::nothrow); + + if (ar.size() <= idx) { + std::ostringstream oss; + oss << "actual length (" << ar.size() + << ") is shorter than the specified index (" << idx << ")."; + throw std::out_of_range(format_error( + "toml::value::at(idx): no element corresponding to the index", + this->location(), + oss.str())); + } + return ar.at(idx); + } + + const value_type& at(const std::size_t idx) const { + if (!this->is_array()) { + this->throw_bad_cast("toml::value::at(idx)", value_t::array); + } + const auto& ar = this->as_array(std::nothrow); + + if (ar.size() <= idx) { + std::ostringstream oss; + oss << "actual length (" << ar.size() + << ") is shorter than the specified index (" << idx << ")."; + + throw std::out_of_range(format_error( + "toml::value::at(idx): no element corresponding to the index", + this->location(), + oss.str())); + } + return ar.at(idx); + } + + value_type& operator[](const std::size_t idx) noexcept { + // no check... + return this->as_array(std::nothrow)[idx]; + } + + const value_type& operator[](const std::size_t idx) const noexcept { + // no check... + return this->as_array(std::nothrow)[idx]; + } + + void push_back(const value_type& x) { + if (!this->is_array()) { + this->throw_bad_cast("toml::value::push_back(idx)", value_t::array); + } + this->as_array(std::nothrow).push_back(x); + return; + } + + void push_back(value_type&& x) { + if (!this->is_array()) { + this->throw_bad_cast("toml::value::push_back(idx)", value_t::array); + } + this->as_array(std::nothrow).push_back(std::move(x)); + return; + } + + template + value_type& emplace_back(Ts&&... args) { + if (!this->is_array()) { + this->throw_bad_cast("toml::value::emplace_back(idx)", value_t::array); + } + auto& ar = this->as_array(std::nothrow); + ar.emplace_back(std::forward(args)...); + return ar.back(); + } + + std::size_t size() const { + switch (this->type_) { + case value_t::array: { + return this->as_array(std::nothrow).size(); + } + case value_t::table: { + return this->as_table(std::nothrow).size(); + } + case value_t::string: { + return this->as_string(std::nothrow).size(); + } + default: { + throw type_error( + format_error("toml::value::size(): bad_cast to container types", + this->location(), + "the actual type is " + to_string(this->type_)), + this->location()); + } + } + } + + // }}} + + source_location location() const { + return source_location(this->region_); + } + + const comment_type& comments() const noexcept { + return this->comments_; + } + + comment_type& comments() noexcept { + return this->comments_; + } + + private: + // private helper functions =========================================== {{{ + + void cleanup() noexcept { + switch (this->type_) { + case value_t::boolean: { + boolean_.~boolean_storage(); + break; + } + case value_t::integer: { + integer_.~integer_storage(); + break; + } + case value_t::floating: { + floating_.~floating_storage(); + break; + } + case value_t::string: { + string_.~string_storage(); + break; + } + case value_t::offset_datetime: { + offset_datetime_.~offset_datetime_storage(); + break; + } + case value_t::local_datetime: { + local_datetime_.~local_datetime_storage(); + break; + } + case value_t::local_date: { + local_date_.~local_date_storage(); + break; + } + case value_t::local_time: { + local_time_.~local_time_storage(); + break; + } + case value_t::array: { + array_.~array_storage(); + break; + } + case value_t::table: { + table_.~table_storage(); + break; + } + default: { + break; + } + } + this->type_ = value_t::empty; + return; + } + + template + static void assigner(T& dst, U&& v) { + const auto tmp = ::new (std::addressof(dst)) T(std::forward(v)); + assert(tmp == std::addressof(dst)); + (void)tmp; + } + + [[noreturn]] + void throw_bad_cast(const std::string& funcname, const value_t ty) const { + throw type_error(format_error(detail::make_type_error(*this, funcname, ty)), + this->location()); + } + + [[noreturn]] + void throw_key_not_found_error(const std::string& funcname, + const key_type& key) const { + throw std::out_of_range( + format_error(detail::make_not_found_error(*this, funcname, key))); + } + + template + friend void detail::change_region_of_value(basic_value&, + const basic_value&); + + template + friend class basic_value; + + // }}} + + private: + using boolean_storage = detail::value_with_format; + using integer_storage = detail::value_with_format; + using floating_storage = detail::value_with_format; + using string_storage = detail::value_with_format; + using offset_datetime_storage = + detail::value_with_format; + using local_datetime_storage = + detail::value_with_format; + using local_date_storage = + detail::value_with_format; + using local_time_storage = + detail::value_with_format; + using array_storage = + detail::value_with_format, array_format_info>; + using table_storage = + detail::value_with_format, table_format_info>; + + private: + value_t type_; + + union { + char empty_; // the smallest type + boolean_storage boolean_; + integer_storage integer_; + floating_storage floating_; + string_storage string_; + offset_datetime_storage offset_datetime_; + local_datetime_storage local_datetime_; + local_date_storage local_date_; + local_time_storage local_time_; + array_storage array_; + table_storage table_; + }; + + region_type region_; + comment_type comments_; + }; + + template + bool operator==(const basic_value& lhs, const basic_value& rhs) { + if (lhs.type() != rhs.type()) { + return false; + } + if (lhs.comments() != rhs.comments()) { + return false; + } + + switch (lhs.type()) { + case value_t::boolean: { + return lhs.as_boolean() == rhs.as_boolean(); + } + case value_t::integer: { + return lhs.as_integer() == rhs.as_integer(); + } + case value_t::floating: { + return lhs.as_floating() == rhs.as_floating(); + } + case value_t::string: { + return lhs.as_string() == rhs.as_string(); + } + case value_t::offset_datetime: { + return lhs.as_offset_datetime() == rhs.as_offset_datetime(); + } + case value_t::local_datetime: { + return lhs.as_local_datetime() == rhs.as_local_datetime(); + } + case value_t::local_date: { + return lhs.as_local_date() == rhs.as_local_date(); + } + case value_t::local_time: { + return lhs.as_local_time() == rhs.as_local_time(); + } + case value_t::array: { + return lhs.as_array() == rhs.as_array(); + } + case value_t::table: { + return lhs.as_table() == rhs.as_table(); + } + case value_t::empty: { + return true; + } + default: { + return false; + } + } + } + + template + bool operator!=(const basic_value& lhs, const basic_value& rhs) { + return !(lhs == rhs); + } + + template + cxx::enable_if_t< + cxx::conjunction::array_type>, + detail::is_comparable::table_type>>::value, + bool> + operator<(const basic_value& lhs, const basic_value& rhs) { + if (lhs.type() != rhs.type()) { + return (lhs.type() < rhs.type()); + } + switch (lhs.type()) { + case value_t::boolean: { + return lhs.as_boolean() < rhs.as_boolean() || + (lhs.as_boolean() == rhs.as_boolean() && + lhs.comments() < rhs.comments()); + } + case value_t::integer: { + return lhs.as_integer() < rhs.as_integer() || + (lhs.as_integer() == rhs.as_integer() && + lhs.comments() < rhs.comments()); + } + case value_t::floating: { + return lhs.as_floating() < rhs.as_floating() || + (lhs.as_floating() == rhs.as_floating() && + lhs.comments() < rhs.comments()); + } + case value_t::string: { + return lhs.as_string() < rhs.as_string() || + (lhs.as_string() == rhs.as_string() && + lhs.comments() < rhs.comments()); + } + case value_t::offset_datetime: { + return lhs.as_offset_datetime() < rhs.as_offset_datetime() || + (lhs.as_offset_datetime() == rhs.as_offset_datetime() && + lhs.comments() < rhs.comments()); + } + case value_t::local_datetime: { + return lhs.as_local_datetime() < rhs.as_local_datetime() || + (lhs.as_local_datetime() == rhs.as_local_datetime() && + lhs.comments() < rhs.comments()); + } + case value_t::local_date: { + return lhs.as_local_date() < rhs.as_local_date() || + (lhs.as_local_date() == rhs.as_local_date() && + lhs.comments() < rhs.comments()); + } + case value_t::local_time: { + return lhs.as_local_time() < rhs.as_local_time() || + (lhs.as_local_time() == rhs.as_local_time() && + lhs.comments() < rhs.comments()); + } + case value_t::array: { + return lhs.as_array() < rhs.as_array() || + (lhs.as_array() == rhs.as_array() && + lhs.comments() < rhs.comments()); + } + case value_t::table: { + return lhs.as_table() < rhs.as_table() || + (lhs.as_table() == rhs.as_table() && + lhs.comments() < rhs.comments()); + } + case value_t::empty: { + return lhs.comments() < rhs.comments(); + } + default: { + return lhs.comments() < rhs.comments(); + } + } + } + + template + cxx::enable_if_t< + cxx::conjunction::array_type>, + detail::is_comparable::table_type>>::value, + bool> + operator<=(const basic_value& lhs, const basic_value& rhs) { + return (lhs < rhs) || (lhs == rhs); + } + + template + cxx::enable_if_t< + cxx::conjunction::array_type>, + detail::is_comparable::table_type>>::value, + bool> + operator>(const basic_value& lhs, const basic_value& rhs) { + return !(lhs <= rhs); + } + + template + cxx::enable_if_t< + cxx::conjunction::array_type>, + detail::is_comparable::table_type>>::value, + bool> + operator>=(const basic_value& lhs, const basic_value& rhs) { + return !(lhs < rhs); + } + + // error_info helper + namespace detail { + template + error_info make_error_info_rec(error_info e, + const basic_value& v, + std::string msg, + Ts&&... tail) { + return make_error_info_rec(std::move(e), + v.location(), + std::move(msg), + std::forward(tail)...); + } + } // namespace detail + + template + error_info make_error_info(std::string title, + const basic_value& v, + std::string msg, + Ts&&... tail) { + return make_error_info(std::move(title), + v.location(), + std::move(msg), + std::forward(tail)...); + } + + template + std::string format_error(std::string title, + const basic_value& v, + std::string msg, + Ts&&... tail) { + return format_error(std::move(title), + v.location(), + std::move(msg), + std::forward(tail)...); + } + + namespace detail { + + template + error_info make_type_error(const basic_value& v, + const std::string& fname, + const value_t ty) { + return make_error_info(fname + ": bad_cast to " + to_string(ty), + v.location(), + "the actual type is " + to_string(v.type())); + } + + template + error_info make_not_found_error(const basic_value& v, + const std::string& fname, + const typename basic_value::key_type& key) { + const auto loc = v.location(); + const std::string title = fname + ": key \"" + + string_conv(key) + "\" not found"; + + std::vector> locs; + if (!loc.is_ok()) { + return error_info(title, locs); + } + + if (loc.first_line_number() == 1 && loc.first_column_number() == 1 && + loc.length() == 1) { + // The top-level table has its region at the 0th character of the file. + // That means that, in the case when a key is not found in the top-level + // table, the error message points to the first character. If the file has + // the first table at the first line, the error message would be like this. + // ```console + // [error] key "a" not found + // --> example.toml + // | + // 1 | [table] + // | ^------ in this table + // ``` + // It actually points to the top-level table at the first character, not + // `[table]`. But it is too confusing. To avoid the confusion, the error + // message should explicitly say "key not found in the top-level table". + locs.emplace_back(v.location(), "at the top-level table"); + } else { + locs.emplace_back(v.location(), "in this table"); + } + return error_info(title, locs); + } + +#define TOML11_DETAIL_GENERATE_COMPTIME_GETTER(ty) \ + template \ + struct getter { \ + using value_type = basic_value; \ + using result_type = enum_to_type_t; \ + using format_type = enum_to_fmt_type_t; \ + \ + static result_type& get(value_type& v) { \ + return v.as_##ty(); \ + } \ + static result_type const& get(const value_type& v) { \ + return v.as_##ty(); \ + } \ + \ + static result_type& get_nothrow(value_type& v) noexcept { \ + return v.as_##ty(std::nothrow); \ + } \ + static result_type const& get_nothrow(const value_type& v) noexcept { \ + return v.as_##ty(std::nothrow); \ + } \ + \ + static format_type& get_fmt(value_type& v) { \ + return v.as_##ty##_fmt(); \ + } \ + static format_type const& get_fmt(const value_type& v) { \ + return v.as_##ty##_fmt(); \ + } \ + \ + static format_type& get_fmt_nothrow(value_type& v) noexcept { \ + return v.as_##ty##_fmt(std::nothrow); \ + } \ + static format_type const& get_fmt_nothrow(const value_type& v) noexcept { \ + return v.as_##ty##_fmt(std::nothrow); \ + } \ + }; + + TOML11_DETAIL_GENERATE_COMPTIME_GETTER(boolean) + TOML11_DETAIL_GENERATE_COMPTIME_GETTER(integer) + TOML11_DETAIL_GENERATE_COMPTIME_GETTER(floating) + TOML11_DETAIL_GENERATE_COMPTIME_GETTER(string) + TOML11_DETAIL_GENERATE_COMPTIME_GETTER(offset_datetime) + TOML11_DETAIL_GENERATE_COMPTIME_GETTER(local_datetime) + TOML11_DETAIL_GENERATE_COMPTIME_GETTER(local_date) + TOML11_DETAIL_GENERATE_COMPTIME_GETTER(local_time) + TOML11_DETAIL_GENERATE_COMPTIME_GETTER(array) + TOML11_DETAIL_GENERATE_COMPTIME_GETTER(table) + +#undef TOML11_DETAIL_GENERATE_COMPTIME_GETTER + + template + void change_region_of_value(basic_value& dst, const basic_value& src) { + dst.region_ = std::move(src.region_); + return; + } + + } // namespace detail +} // namespace toml +#endif // TOML11_VALUE_HPP +#ifndef TOML11_VISIT_HPP +#define TOML11_VISIT_HPP + +namespace toml { + + template + cxx::return_type_of_t::boolean_type&> visit( + Visitor&& visitor, + const basic_value& v) { + switch (v.type()) { + case value_t::boolean: { + return visitor(v.as_boolean()); + } + case value_t::integer: { + return visitor(v.as_integer()); + } + case value_t::floating: { + return visitor(v.as_floating()); + } + case value_t::string: { + return visitor(v.as_string()); + } + case value_t::offset_datetime: { + return visitor(v.as_offset_datetime()); + } + case value_t::local_datetime: { + return visitor(v.as_local_datetime()); + } + case value_t::local_date: { + return visitor(v.as_local_date()); + } + case value_t::local_time: { + return visitor(v.as_local_time()); + } + case value_t::array: { + return visitor(v.as_array()); + } + case value_t::table: { + return visitor(v.as_table()); + } + case value_t::empty: + break; + default: + break; + } + throw type_error(format_error("[error] toml::visit: toml::basic_value " + "does not have any valid type.", + v.location(), + "here"), + v.location()); + } + + template + cxx::return_type_of_t::boolean_type&> visit( + Visitor&& visitor, + basic_value& v) { + switch (v.type()) { + case value_t::boolean: { + return visitor(v.as_boolean()); + } + case value_t::integer: { + return visitor(v.as_integer()); + } + case value_t::floating: { + return visitor(v.as_floating()); + } + case value_t::string: { + return visitor(v.as_string()); + } + case value_t::offset_datetime: { + return visitor(v.as_offset_datetime()); + } + case value_t::local_datetime: { + return visitor(v.as_local_datetime()); + } + case value_t::local_date: { + return visitor(v.as_local_date()); + } + case value_t::local_time: { + return visitor(v.as_local_time()); + } + case value_t::array: { + return visitor(v.as_array()); + } + case value_t::table: { + return visitor(v.as_table()); + } + case value_t::empty: + break; + default: + break; + } + throw type_error(format_error("[error] toml::visit: toml::basic_value " + "does not have any valid type.", + v.location(), + "here"), + v.location()); + } + + template + cxx::return_type_of_t::boolean_type&&> visit( + Visitor&& visitor, + basic_value&& v) { + switch (v.type()) { + case value_t::boolean: { + return visitor(std::move(v.as_boolean())); + } + case value_t::integer: { + return visitor(std::move(v.as_integer())); + } + case value_t::floating: { + return visitor(std::move(v.as_floating())); + } + case value_t::string: { + return visitor(std::move(v.as_string())); + } + case value_t::offset_datetime: { + return visitor(std::move(v.as_offset_datetime())); + } + case value_t::local_datetime: { + return visitor(std::move(v.as_local_datetime())); + } + case value_t::local_date: { + return visitor(std::move(v.as_local_date())); + } + case value_t::local_time: { + return visitor(std::move(v.as_local_time())); + } + case value_t::array: { + return visitor(std::move(v.as_array())); + } + case value_t::table: { + return visitor(std::move(v.as_table())); + } + case value_t::empty: + break; + default: + break; + } + throw type_error(format_error("[error] toml::visit: toml::basic_value " + "does not have any valid type.", + v.location(), + "here"), + v.location()); + } + +} // namespace toml +#endif // TOML11_VISIT_HPP +#ifndef TOML11_TYPES_HPP +#define TOML11_TYPES_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace toml { + + // forward decl + template + class basic_value; + + // when you use a special integer type as toml::value::integer_type, parse must + // be able to read it. So, type_config has static member functions that read the + // integer_type as {dec, hex, oct, bin}-integer. But, in most cases, operator<< + // is enough. To make config easy, we provide the default read functions. + // + // Before this functions is called, syntax is checked and prefix(`0x` etc) and + // spacer(`_`) are removed. + + template + result read_dec_int(const std::string& str, + const source_location src) { + constexpr auto max_digits = std::numeric_limits::digits; + assert(!str.empty()); + + T val { 0 }; + std::istringstream iss(str); + iss >> val; + if (iss.fail()) { + return err(make_error_info("toml::parse_dec_integer: " + "too large integer: current max digits = 2^" + + std::to_string(max_digits), + std::move(src), + "must be < 2^" + std::to_string(max_digits))); + } + return ok(val); + } + + template + result read_hex_int(const std::string& str, + const source_location src) { + constexpr auto max_digits = std::numeric_limits::digits; + assert(!str.empty()); + + T val { 0 }; + std::istringstream iss(str); + iss >> std::hex >> val; + if (iss.fail()) { + return err(make_error_info("toml::parse_hex_integer: " + "too large integer: current max value = 2^" + + std::to_string(max_digits), + std::move(src), + "must be < 2^" + std::to_string(max_digits))); + } + return ok(val); + } + + template + result read_oct_int(const std::string& str, + const source_location src) { + constexpr auto max_digits = std::numeric_limits::digits; + assert(!str.empty()); + + T val { 0 }; + std::istringstream iss(str); + iss >> std::oct >> val; + if (iss.fail()) { + return err(make_error_info("toml::parse_oct_integer: " + "too large integer: current max value = 2^" + + std::to_string(max_digits), + std::move(src), + "must be < 2^" + std::to_string(max_digits))); + } + return ok(val); + } + + template + result read_bin_int(const std::string& str, + const source_location src) { + constexpr auto is_bounded = std::numeric_limits::is_bounded; + constexpr auto max_digits = std::numeric_limits::digits; + const auto max_value = (std::numeric_limits::max)(); + + T val { 0 }; + T base { 1 }; + for (auto i = str.rbegin(); i != str.rend(); ++i) { + const auto c = *i; + if (c == '1') { + val += base; + // prevent `base` from overflow + if (is_bounded && max_value / 2 < base && std::next(i) != str.rend()) { + base = 0; + } else { + base *= 2; + } + } else { + assert(c == '0'); + + if (is_bounded && max_value / 2 < base && std::next(i) != str.rend()) { + base = 0; + } else { + base *= 2; + } + } + } + if (base == 0) { + return err(make_error_info("toml::parse_bin_integer: " + "too large integer: current max value = 2^" + + std::to_string(max_digits), + std::move(src), + "must be < 2^" + std::to_string(max_digits))); + } + return ok(val); + } + + template + result read_int(const std::string& str, + const source_location src, + const std::uint8_t base) { + assert(base == 10 || base == 16 || base == 8 || base == 2); + switch (base) { + case 2: { + return read_bin_int(str, src); + } + case 8: { + return read_oct_int(str, src); + } + case 16: { + return read_hex_int(str, src); + } + default: { + assert(base == 10); + return read_dec_int(str, src); + } + } + } + + inline result read_hex_float(const std::string& str, + const source_location src, + float val) { +#if defined(_MSC_VER) && !defined(__clang__) + const auto res = ::sscanf_s(str.c_str(), "%a", std::addressof(val)); +#else + const auto res = std::sscanf(str.c_str(), "%a", std::addressof(val)); +#endif + if (res != 1) { + return err( + make_error_info("toml::parse_floating: " + "failed to read hexadecimal floating point value ", + std::move(src), + "here")); + } + return ok(val); + } + + inline result read_hex_float(const std::string& str, + const source_location src, + double val) { +#if defined(_MSC_VER) && !defined(__clang__) + const auto res = ::sscanf_s(str.c_str(), "%la", std::addressof(val)); +#else + const auto res = std::sscanf(str.c_str(), "%la", std::addressof(val)); +#endif + if (res != 1) { + return err( + make_error_info("toml::parse_floating: " + "failed to read hexadecimal floating point value ", + std::move(src), + "here")); + } + return ok(val); + } + + template + cxx::enable_if_t< + cxx::conjunction, double>>, + cxx::negation, float>>>::value, + result> + read_hex_float(const std::string&, const source_location src, T) { + return err(make_error_info( + "toml::parse_floating: failed to read " + "floating point value because of unknown type in type_config", + std::move(src), + "here")); + } + + template + result read_dec_float(const std::string& str, + const source_location src) { + T val; + std::istringstream iss(str); + iss >> val; + if (iss.fail()) { + return err( + make_error_info("toml::parse_floating: " + "failed to read floating point value from stream", + std::move(src), + "here")); + } + return ok(val); + } + + template + result read_float(const std::string& str, + const source_location src, + const bool is_hex) { + if (is_hex) { + return read_hex_float(str, src, T {}); + } else { + return read_dec_float(str, src); + } + } + + struct type_config { + using comment_type = preserve_comments; + + using boolean_type = bool; + using integer_type = std::int64_t; + using floating_type = double; + using string_type = std::string; + + template + using array_type = std::vector; + template + using table_type = std::unordered_map; + + static result parse_int(const std::string& str, + const source_location src, + const std::uint8_t base) { + return read_int(str, src, base); + } + + static result parse_float(const std::string& str, + const source_location src, + const bool is_hex) { + return read_float(str, src, is_hex); + } + }; + + using value = basic_value; + using table = typename value::table_type; + using array = typename value::array_type; + + struct ordered_type_config { + using comment_type = preserve_comments; + + using boolean_type = bool; + using integer_type = std::int64_t; + using floating_type = double; + using string_type = std::string; + + template + using array_type = std::vector; + template + using table_type = ordered_map; + + static result parse_int(const std::string& str, + const source_location src, + const std::uint8_t base) { + return read_int(str, src, base); + } + + static result parse_float(const std::string& str, + const source_location src, + const bool is_hex) { + return read_float(str, src, is_hex); + } + }; + + using ordered_value = basic_value; + using ordered_table = typename ordered_value::table_type; + using ordered_array = typename ordered_value::array_type; + + // ---------------------------------------------------------------------------- + // meta functions for internal use + + namespace detail { + + // ---------------------------------------------------------------------------- + // check if type T has all the needed member types + + struct has_comment_type_impl { + template + static std::true_type check(typename T::comment_type*); + template + static std::false_type check(...); + }; + + template + using has_comment_type = decltype(has_comment_type_impl::check(nullptr)); + + struct has_integer_type_impl { + template + static std::true_type check(typename T::integer_type*); + template + static std::false_type check(...); + }; + + template + using has_integer_type = decltype(has_integer_type_impl::check(nullptr)); + + struct has_floating_type_impl { + template + static std::true_type check(typename T::floating_type*); + template + static std::false_type check(...); + }; + + template + using has_floating_type = decltype(has_floating_type_impl::check(nullptr)); + + struct has_string_type_impl { + template + static std::true_type check(typename T::string_type*); + template + static std::false_type check(...); + }; + + template + using has_string_type = decltype(has_string_type_impl::check(nullptr)); + + struct has_array_type_impl { + template + static std::true_type check(typename T::template array_type*); + template + static std::false_type check(...); + }; + + template + using has_array_type = decltype(has_array_type_impl::check(nullptr)); + + struct has_table_type_impl { + template + static std::true_type check(typename T::template table_type*); + template + static std::false_type check(...); + }; + + template + using has_table_type = decltype(has_table_type_impl::check(nullptr)); + + struct has_parse_int_impl { + template + static std::true_type check(decltype(std::declval().parse_int( + std::declval(), + std::declval(), + std::declval()))*); + template + static std::false_type check(...); + }; + + template + using has_parse_int = decltype(has_parse_int_impl::check(nullptr)); + + struct has_parse_float_impl { + template + static std::true_type check(decltype(std::declval().parse_float( + std::declval(), + std::declval(), + std::declval()))*); + template + static std::false_type check(...); + }; + + template + using has_parse_float = decltype(has_parse_float_impl::check(nullptr)); + + template + using is_type_config = cxx::conjunction, + has_integer_type, + has_floating_type, + has_string_type, + has_array_type, + has_table_type, + has_parse_int, + has_parse_float>; + + } // namespace detail +} // namespace toml + +#if defined(TOML11_COMPILE_SOURCES) +namespace toml { + extern template class basic_value; + extern template class basic_value; +} // namespace toml +#endif // TOML11_COMPILE_SOURCES + +#endif // TOML11_TYPES_HPP +#ifndef TOML11_GET_HPP +#define TOML11_GET_HPP + +#include + +#if defined(TOML11_HAS_STRING_VIEW) + #include +#endif // string_view + +namespace toml { + + // ============================================================================ + // T is toml::value; identity transformation. + + template + cxx::enable_if_t>::value, T>& get( + basic_value& v) { + return v; + } + + template + const cxx::enable_if_t>::value, T>& get( + const basic_value& v) { + return v; + } + + template + cxx::enable_if_t>::value, T> get( + basic_value&& v) { + return basic_value(std::move(v)); + } + + // ============================================================================ + // exact toml::* type + + template + cxx::enable_if_t>::value, T>& get( + basic_value& v) { + constexpr auto ty = detail::type_to_enum>::value; + return detail::getter::get(v); + } + + template + const cxx::enable_if_t>::value, T>& get( + const basic_value& v) { + constexpr auto ty = detail::type_to_enum>::value; + return detail::getter::get(v); + } + + template + cxx::enable_if_t>::value, T> get( + basic_value&& v) { + constexpr auto ty = detail::type_to_enum>::value; + return detail::getter::get(std::move(v)); + } + + // ============================================================================ + // T is toml::basic_value + + template + cxx::enable_if_t, + cxx::negation>>>::value, + T> + get(basic_value v) { + return T(std::move(v)); + } + + // ============================================================================ + // integer convertible from toml::value::integer_type + + template + cxx::enable_if_t, + cxx::negation>, + detail::is_not_toml_type>, + cxx::negation>, + cxx::negation>>::value, + T> + get(const basic_value& v) { + return static_cast(v.as_integer()); + } + + // ============================================================================ + // floating point convertible from toml::value::floating_type + + template + cxx::enable_if_t, + detail::is_not_toml_type>, + cxx::negation>, + cxx::negation>>::value, + T> + get(const basic_value& v) { + return static_cast(v.as_floating()); + } + + // ============================================================================ + // std::string with different char/trait/allocator + + template + cxx::enable_if_t>, + detail::is_1byte_std_basic_string>::value, + T> + get(const basic_value& v) { + return detail::string_conv>(v.as_string()); + } + + // ============================================================================ + // std::string_view + +#if defined(TOML11_HAS_STRING_VIEW) + + template + cxx::enable_if_t::string_type>::value, T> + get(const basic_value& v) { + return T(v.as_string()); + } + +#endif // string_view + + // ============================================================================ + // std::chrono::duration from toml::local_time + + template + cxx::enable_if_t::value, T> get( + const basic_value& v) { + return std::chrono::duration_cast( + std::chrono::nanoseconds(v.as_local_time())); + } + + // ============================================================================ + // std::chrono::system_clock::time_point from toml::datetime variants + + template + cxx::enable_if_t::value, T> get( + const basic_value& v) { + switch (v.type()) { + case value_t::local_date: { + return std::chrono::system_clock::time_point(v.as_local_date()); + } + case value_t::local_datetime: { + return std::chrono::system_clock::time_point(v.as_local_datetime()); + } + case value_t::offset_datetime: { + return std::chrono::system_clock::time_point(v.as_offset_datetime()); + } + default: { + const auto loc = v.location(); + throw type_error( + format_error("toml::get: " + "bad_cast to std::chrono::system_clock::time_point", + loc, + "the actual type is " + to_string(v.type())), + loc); + } + } + } + + // ============================================================================ + // forward declaration to use this recursively. ignore this and go ahead. + + // array-like (w/ push_back) + template + cxx::enable_if_t< + cxx::conjunction< + detail::is_container, // T is a container + detail::has_push_back_method, // .push_back() works + detail::is_not_toml_type>, // but not toml::array + cxx::negation>, // but not std::basic_string +#if defined(TOML11_HAS_STRING_VIEW) + cxx::negation>, // but not std::basic_string_view +#endif + cxx::negation>, // no T.from_toml() + cxx::negation>, // no toml::from + cxx::negation&>>>::value, + T> + get(const basic_value&); + + // std::array + template + cxx::enable_if_t::value, T> get(const basic_value&); + + // std::forward_list + template + cxx::enable_if_t::value, T> get( + const basic_value&); + + // std::pair + template + cxx::enable_if_t::value, T> get(const basic_value&); + + // std::tuple + template + cxx::enable_if_t::value, T> get(const basic_value&); + + // std::map (key is convertible from toml::value::key_type) + template + cxx::enable_if_t< + cxx::conjunction, // T is map + detail::is_not_toml_type>, // but not toml::table + std::is_convertible::key_type, + typename T::key_type>, // keys are convertible + cxx::negation>, // no T.from_toml() + cxx::negation>, // no toml::from + cxx::negation&>>>::value, + T> + get(const basic_value& v); + + // std::map (key is not convertible from toml::value::key_type, + // but is a std::basic_string) + template + cxx::enable_if_t< + cxx::conjunction< + detail::is_map, // T is map + detail::is_not_toml_type>, // but not toml::table + cxx::negation::key_type, + typename T::key_type>>, // keys are NOT convertible + detail::is_1byte_std_basic_string, // is std::basic_string + cxx::negation>, // no T.from_toml() + cxx::negation>, // no toml::from + cxx::negation&>>>::value, + T> + get(const basic_value& v); + + // toml::from::from_toml(v) + template + cxx::enable_if_t::value, T> get( + const basic_value&); + + // has T.from_toml(v) but no from + template + cxx::enable_if_t, // has T.from_toml() + cxx::negation>, // no toml::from + std::is_default_constructible // T{} works + >::value, + T> + get(const basic_value&); + + // T(const toml::value&) and T is not toml::basic_value, + // and it does not have `from` nor `from_toml`. + template + cxx::enable_if_t&>, // has T(const basic_value&) + cxx::negation>, // but not basic_value itself + cxx::negation>, // no .from_toml() + cxx::negation> // no toml::from + >::value, + T> + get(const basic_value&); + + // ============================================================================ + // array-like types; most likely STL container, like std::vector, etc. + + template + cxx::enable_if_t< + cxx::conjunction< + detail::is_container, // T is a container + detail::has_push_back_method, // .push_back() works + detail::is_not_toml_type>, // but not toml::array + cxx::negation>, // but not std::basic_string +#if defined(TOML11_HAS_STRING_VIEW) + cxx::negation>, // but not std::basic_string_view +#endif + cxx::negation>, // no T.from_toml() + cxx::negation>, // no toml::from + cxx::negation&>>>::value, + T> + get(const basic_value& v) { + using value_type = typename T::value_type; + const auto& a = v.as_array(); + + T container; + detail::try_reserve(container, a.size()); // if T has .reserve(), call it + + for (const auto& elem : a) { + container.push_back(get(elem)); + } + return container; + } + + // ============================================================================ + // std::array + + template + cxx::enable_if_t::value, T> get(const basic_value& v) { + using value_type = typename T::value_type; + const auto& a = v.as_array(); + + T container; + if (a.size() != container.size()) { + const auto loc = v.location(); + throw std::out_of_range( + format_error("toml::get: while converting to an array: " + " array size is " + + std::to_string(container.size()) + " but there are " + + std::to_string(a.size()) + " elements in toml array.", + loc, + "here")); + } + for (std::size_t i = 0; i < a.size(); ++i) { + container.at(i) = ::toml::get(a.at(i)); + } + return container; + } + + // ============================================================================ + // std::forward_list + + template + cxx::enable_if_t::value, T> get( + const basic_value& v) { + using value_type = typename T::value_type; + + T container; + for (const auto& elem : v.as_array()) { + container.push_front(get(elem)); + } + container.reverse(); + return container; + } + + // ============================================================================ + // std::pair + + template + cxx::enable_if_t::value, T> get(const basic_value& v) { + using first_type = typename T::first_type; + using second_type = typename T::second_type; + + const auto& ar = v.as_array(); + if (ar.size() != 2) { + const auto loc = v.location(); + throw std::out_of_range( + format_error("toml::get: while converting std::pair: " + " but there are " + + std::to_string(ar.size()) + " > 2 elements in toml array.", + loc, + "here")); + } + return std::make_pair(::toml::get(ar.at(0)), + ::toml::get(ar.at(1))); + } + + // ============================================================================ + // std::tuple. + + namespace detail { + template + T get_tuple_impl(const Array& a, cxx::index_sequence) { + return std::make_tuple( + ::toml::get::type>(a.at(I))...); + } + } // namespace detail + + template + cxx::enable_if_t::value, T> get(const basic_value& v) { + const auto& ar = v.as_array(); + if (ar.size() != std::tuple_size::value) { + const auto loc = v.location(); + throw std::out_of_range(format_error( + "toml::get: while converting std::tuple: " + " there are " + + std::to_string(ar.size()) + " > " + + std::to_string(std::tuple_size::value) + " elements in toml array.", + loc, + "here")); + } + return detail::get_tuple_impl( + ar, + cxx::make_index_sequence::value> {}); + } + + // ============================================================================ + // map-like types; most likely STL map, like std::map or std::unordered_map. + + // key is convertible from toml::value::key_type + template + cxx::enable_if_t< + cxx::conjunction, // T is map + detail::is_not_toml_type>, // but not toml::table + std::is_convertible::key_type, + typename T::key_type>, // keys are convertible + cxx::negation>, // no T.from_toml() + cxx::negation>, // no toml::from + cxx::negation&>>>::value, + T> + get(const basic_value& v) { + using key_type = typename T::key_type; + using mapped_type = typename T::mapped_type; + static_assert( + std::is_convertible::key_type, key_type>::value, + "toml::get only supports map type of which key_type is " + "convertible from toml::basic_value::key_type."); + + T m; + for (const auto& kv : v.as_table()) { + m.emplace(key_type(kv.first), get(kv.second)); + } + return m; + } + + // key is NOT convertible from toml::value::key_type but std::basic_string + template + cxx::enable_if_t< + cxx::conjunction< + detail::is_map, // T is map + detail::is_not_toml_type>, // but not toml::table + cxx::negation::key_type, + typename T::key_type>>, // keys are NOT convertible + detail::is_1byte_std_basic_string, // is std::basic_string + cxx::negation>, // no T.from_toml() + cxx::negation>, // no toml::from + cxx::negation&>>>::value, + T> + get(const basic_value& v) { + using key_type = typename T::key_type; + using mapped_type = typename T::mapped_type; + + T m; + for (const auto& kv : v.as_table()) { + m.emplace(detail::string_conv(kv.first), + get(kv.second)); + } + return m; + } + + // ============================================================================ + // user-defined, but convertible types. + + // toml::from + template + cxx::enable_if_t::value, T> get( + const basic_value& v) { + return ::toml::from::from_toml(v); + } + + // has T.from_toml(v) but no from + template + cxx::enable_if_t, // has T.from_toml() + cxx::negation>, // no toml::from + std::is_default_constructible // T{} works + >::value, + T> + get(const basic_value& v) { + T ud; + ud.from_toml(v); + return ud; + } + + // T(const toml::value&) and T is not toml::basic_value, + // and it does not have `from` nor `from_toml`. + template + cxx::enable_if_t&>, // has T(const basic_value&) + cxx::negation>, // but not basic_value itself + cxx::negation>, // no .from_toml() + cxx::negation> // no toml::from + >::value, + T> + get(const basic_value& v) { + return T(v); + } + + // ============================================================================ + // get_or(value, fallback) + + template + const cxx::enable_if_t::value, basic_value>& get_or( + const basic_value& v, + const basic_value&) { + return v; + } + + template + cxx::enable_if_t::value, basic_value>& get_or( + basic_value& v, + basic_value&) { + return v; + } + + template + cxx::enable_if_t::value, basic_value> get_or( + basic_value&& v, + basic_value&&) { + return v; + } + + // ---------------------------------------------------------------------------- + // specialization for the exact toml types (return type becomes lvalue ref) + + template + const cxx::enable_if_t>::value, T>& + get_or(const basic_value& v, const T& opt) noexcept { + try { + return get>(v); + } catch (...) { + return opt; + } + } + + template + cxx::enable_if_t>, + detail::is_exact_toml_type>>::value, + T>& + get_or(basic_value& v, T& opt) noexcept { + try { + return get>(v); + } catch (...) { + return opt; + } + } + + template + cxx::enable_if_t, basic_value>::value, + cxx::remove_cvref_t> + get_or(basic_value&& v, T&& opt) noexcept { + try { + return get>(std::move(v)); + } catch (...) { + return cxx::remove_cvref_t(std::forward(opt)); + } + } + + // ---------------------------------------------------------------------------- + // specialization for string literal + + // template + // typename basic_value::string_type + // get_or(const basic_value& v, + // const typename basic_value::string_type::value_type (&opt)[N]) + // { + // try + // { + // return v.as_string(); + // } + // catch(...) + // { + // return typename basic_value::string_type(opt); + // } + // } + // + // The above only matches to the literal, like `get_or(v, "foo");` but not + // ```cpp + // const auto opt = "foo"; + // const auto str = get_or(v, opt); + // ``` + // . And the latter causes an error. + // To match to both `"foo"` and `const auto opt = "foo"`, we take a pointer to + // a character here. + + template + typename basic_value::string_type get_or( + const basic_value& v, + const typename basic_value::string_type::value_type* opt) { + try { + return v.as_string(); + } catch (...) { + return typename basic_value::string_type(opt); + } + } + + // ---------------------------------------------------------------------------- + // others (require type conversion and return type cannot be lvalue reference) + + template + cxx::enable_if_t< + cxx::conjunction< + cxx::negation>, + cxx::negation>>, + cxx::negation, + const typename basic_value::string_type::value_type*>>>::value, + cxx::remove_cvref_t> + get_or(const basic_value& v, T&& opt) { + try { + return get>(v); + } catch (...) { + return cxx::remove_cvref_t(std::forward(opt)); + } + } + +} // namespace toml +#endif // TOML11_GET_HPP +#ifndef TOML11_FIND_HPP +#define TOML11_FIND_HPP + +#include + +#if defined(TOML11_HAS_STRING_VIEW) + #include +#endif + +namespace toml { + + // ---------------------------------------------------------------------------- + // find(value, key); + + template + decltype(::toml::get(std::declval&>())) find( + const basic_value& v, + const typename basic_value::key_type& ky) { + return ::toml::get(v.at(ky)); + } + + template + decltype(::toml::get(std::declval&>())) find( + basic_value& v, + const typename basic_value::key_type& ky) { + return ::toml::get(v.at(ky)); + } + + template + decltype(::toml::get(std::declval&&>())) find( + basic_value&& v, + const typename basic_value::key_type& ky) { + return ::toml::get(std::move(v.at(ky))); + } + + // ---------------------------------------------------------------------------- + // find(value, idx) + + template + decltype(::toml::get(std::declval&>())) find( + const basic_value& v, + const std::size_t idx) { + return ::toml::get(v.at(idx)); + } + + template + decltype(::toml::get(std::declval&>())) find( + basic_value& v, + const std::size_t idx) { + return ::toml::get(v.at(idx)); + } + + template + decltype(::toml::get(std::declval&&>())) find( + basic_value&& v, + const std::size_t idx) { + return ::toml::get(std::move(v.at(idx))); + } + + // ---------------------------------------------------------------------------- + // find(value, key/idx), w/o conversion + + template + cxx::enable_if_t::value, basic_value>& find( + basic_value& v, + const typename basic_value::key_type& ky) { + return v.at(ky); + } + + template + const cxx::enable_if_t::value, basic_value>& find( + const basic_value& v, + const typename basic_value::key_type& ky) { + return v.at(ky); + } + + template + cxx::enable_if_t::value, basic_value> find( + basic_value&& v, + const typename basic_value::key_type& ky) { + return basic_value(std::move(v.at(ky))); + } + + template + cxx::enable_if_t::value, basic_value>& find( + basic_value& v, + const std::size_t idx) { + return v.at(idx); + } + + template + const cxx::enable_if_t::value, basic_value>& find( + const basic_value& v, + const std::size_t idx) { + return v.at(idx); + } + + template + cxx::enable_if_t::value, basic_value> find( + basic_value&& v, + const std::size_t idx) { + return basic_value(std::move(v.at(idx))); + } + + // -------------------------------------------------------------------------- + // toml::find(toml::value, toml::key, Ts&& ... keys) + + namespace detail { + + // It suppresses warnings by -Wsign-conversion when we pass integer literal + // to toml::find. integer literal `0` is deduced as an int, and will be + // converted to std::size_t. This causes sign-conversion. + + template + std::size_t key_cast(const std::size_t& v) noexcept { + return v; + } + + template + cxx::enable_if_t>::value, std::size_t> key_cast( + const T& v) noexcept { + return static_cast(v); + } + + // for string-like (string, string literal, string_view) + + template + const typename basic_value::key_type& key_cast( + const typename basic_value::key_type& v) noexcept { + return v; + } + + template + typename basic_value::key_type key_cast( + const typename basic_value::key_type::value_type* v) { + return typename basic_value::key_type(v); + } +#if defined(TOML11_HAS_STRING_VIEW) + template + typename basic_value::key_type key_cast(const std::string_view v) { + return typename basic_value::key_type(v); + } +#endif // string_view + + } // namespace detail + + // ---------------------------------------------------------------------------- + // find(v, keys...) + + template + const cxx::enable_if_t::value, basic_value>& + find(const basic_value& v, const K1& k1, const K2& k2, const Ks&... ks) { + return find(v.at(detail::key_cast(k1)), detail::key_cast(k2), ks...); + } + + template + cxx::enable_if_t::value, basic_value>& find( + basic_value& v, + const K1& k1, + const K2& k2, + const Ks&... ks) { + return find(v.at(detail::key_cast(k1)), detail::key_cast(k2), ks...); + } + + template + cxx::enable_if_t::value, basic_value> find( + basic_value&& v, + const K1& k1, + const K2& k2, + const Ks&... ks) { + return find(std::move(v.at(detail::key_cast(k1))), + detail::key_cast(k2), + ks...); + } + + // ---------------------------------------------------------------------------- + // find(v, keys...) + + template + decltype(::toml::get(std::declval&>())) find( + const basic_value& v, + const K1& k1, + const K2& k2, + const Ks&... ks) { + return find(v.at(detail::key_cast(k1)), detail::key_cast(k2), ks...); + } + + template + decltype(::toml::get(std::declval&>())) find( + basic_value& v, + const K1& k1, + const K2& k2, + const Ks&... ks) { + return find(v.at(detail::key_cast(k1)), detail::key_cast(k2), ks...); + } + + template + decltype(::toml::get(std::declval&&>())) find( + basic_value&& v, + const K1& k1, + const K2& k2, + const Ks&... ks) { + return find(std::move(v.at(detail::key_cast(k1))), + detail::key_cast(k2), + ks...); + } + + // =========================================================================== + // find_or(value, key, fallback) + + // --------------------------------------------------------------------------- + // find_or(v, key, other_v) + + template + cxx::enable_if_t::value, basic_value>& find_or( + basic_value& v, + const K& k, + basic_value& opt) noexcept { + try { + return ::toml::find(v, detail::key_cast(k)); + } catch (...) { + return opt; + } + } + + template + const cxx::enable_if_t::value, basic_value>& find_or( + const basic_value& v, + const K& k, + const basic_value& opt) noexcept { + try { + return ::toml::find(v, detail::key_cast(k)); + } catch (...) { + return opt; + } + } + + template + cxx::enable_if_t::value, basic_value> find_or( + basic_value&& v, + const K& k, + basic_value&& opt) noexcept { + try { + return ::toml::find(v, detail::key_cast(k)); + } catch (...) { + return opt; + } + } + + // --------------------------------------------------------------------------- + // toml types (return type can be a reference) + + template + cxx::enable_if_t>::value, + const cxx::remove_cvref_t&> + find_or(const basic_value& v, const K& k, const T& opt) { + try { + return ::toml::get(v.at(detail::key_cast(k))); + } catch (...) { + return opt; + } + } + + template + cxx::enable_if_t>, + detail::is_exact_toml_type>>::value, + cxx::remove_cvref_t&> + find_or(basic_value& v, const K& k, T& opt) { + try { + return ::toml::get(v.at(detail::key_cast(k))); + } catch (...) { + return opt; + } + } + + template + cxx::enable_if_t>::value, + cxx::remove_cvref_t> + find_or(basic_value&& v, const K& k, T opt) { + try { + return ::toml::get(std::move(v.at(detail::key_cast(k)))); + } catch (...) { + return T(std::move(opt)); + } + } + + // --------------------------------------------------------------------------- + // string literal (deduced as std::string) + + // XXX to avoid confusion when T is explicitly specified in find_or(), + // we restrict the string type as std::string. + template + cxx::enable_if_t::value, std::string> find_or( + const basic_value& v, + const K& k, + const char* opt) { + try { + return ::toml::get(v.at(detail::key_cast(k))); + } catch (...) { + return std::string(opt); + } + } + + // --------------------------------------------------------------------------- + // other types (requires type conversion and return type cannot be a reference) + + template + cxx::enable_if_t< + cxx::conjunction< + cxx::negation>>, + detail::is_not_toml_type, basic_value>, + cxx::negation, + const typename basic_value::string_type::value_type*>>>::value, + cxx::remove_cvref_t> + find_or(const basic_value& v, const K& ky, T opt) { + try { + return ::toml::get>(v.at(detail::key_cast(ky))); + } catch (...) { + return cxx::remove_cvref_t(std::move(opt)); + } + } + + // ---------------------------------------------------------------------------- + // recursive + + namespace detail { + + template + auto last_one(Ts&&... args) -> decltype(std::get( + std::forward_as_tuple(std::forward(args)...))) { + return std::get( + std::forward_as_tuple(std::forward(args)...)); + } + + } // namespace detail + + template + auto find_or(Value&& v, const K1& k1, const K2& k2, K3&& k3, Ks&&... keys) noexcept + -> cxx::enable_if_t< + detail::is_basic_value>::value, + decltype(find_or(v, k2, std::forward(k3), std::forward(keys)...))> { + try { + return find_or(v.at(k1), k2, std::forward(k3), std::forward(keys)...); + } catch (...) { + return detail::last_one(k3, keys...); + } + } + + template + T find_or(const basic_value& v, + const K1& k1, + const K2& k2, + const K3& k3, + const Ks&... keys) noexcept { + try { + return find_or(v.at(k1), k2, k3, keys...); + } catch (...) { + return static_cast(detail::last_one(k3, keys...)); + } + } + +} // namespace toml +#endif // TOML11_FIND_HPP +#ifndef TOML11_CONVERSION_HPP +#define TOML11_CONVERSION_HPP + +#if defined(TOML11_HAS_OPTIONAL) + + #include + +namespace toml { + namespace detail { + + template + inline constexpr bool is_optional_v = false; + + template + inline constexpr bool is_optional_v> = true; + + template + void find_member_variable_from_value(T& obj, + const basic_value& v, + const char* var_name) { + if constexpr (is_optional_v) { + if (v.contains(var_name)) { + obj = toml::find(v, var_name); + } else { + obj = std::nullopt; + } + } else { + obj = toml::find(v, var_name); + } + } + + template + void assign_member_variable_to_value(const T& obj, + basic_value& v, + const char* var_name) { + if constexpr (is_optional_v) { + if (obj.has_value()) { + v[var_name] = obj.value(); + } + } else { + v[var_name] = obj; + } + } + + } // namespace detail +} // namespace toml + +#else + +namespace toml { + namespace detail { + + template + void find_member_variable_from_value(T& obj, + const basic_value& v, + const char* var_name) { + obj = toml::find(v, var_name); + } + + template + void assign_member_variable_to_value(const T& obj, + basic_value& v, + const char* var_name) { + v[var_name] = obj; + } + + } // namespace detail +} // namespace toml + +#endif // optional + +// use it in the following way. +// ```cpp +// namespace foo +// { +// struct Foo +// { +// std::string s; +// double d; +// int i; +// }; +// } // foo +// +// TOML11_DEFINE_CONVERSION_NON_INTRUSIVE(foo::Foo, s, d, i) +// ``` +// +// And then you can use `toml::get(v)` and `toml::find(file, "foo");` +// + +#define TOML11_STRINGIZE_AUX(x) #x +#define TOML11_STRINGIZE(x) TOML11_STRINGIZE_AUX(x) + +#define TOML11_CONCATENATE_AUX(x, y) x##y +#define TOML11_CONCATENATE(x, y) TOML11_CONCATENATE_AUX(x, y) + +// ============================================================================ +// TOML11_DEFINE_CONVERSION_NON_INTRUSIVE + +#ifndef TOML11_WITHOUT_DEFINE_NON_INTRUSIVE + + // ---------------------------------------------------------------------------- + // TOML11_ARGS_SIZE + + #define TOML11_INDEX_RSEQ() \ + 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, \ + 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 + #define TOML11_ARGS_SIZE_IMPL(ARG1, \ + ARG2, \ + ARG3, \ + ARG4, \ + ARG5, \ + ARG6, \ + ARG7, \ + ARG8, \ + ARG9, \ + ARG10, \ + ARG11, \ + ARG12, \ + ARG13, \ + ARG14, \ + ARG15, \ + ARG16, \ + ARG17, \ + ARG18, \ + ARG19, \ + ARG20, \ + ARG21, \ + ARG22, \ + ARG23, \ + ARG24, \ + ARG25, \ + ARG26, \ + ARG27, \ + ARG28, \ + ARG29, \ + ARG30, \ + ARG31, \ + ARG32, \ + N, \ + ...) \ + N + #define TOML11_ARGS_SIZE_AUX(...) TOML11_ARGS_SIZE_IMPL(__VA_ARGS__) + #define TOML11_ARGS_SIZE(...) \ + TOML11_ARGS_SIZE_AUX(__VA_ARGS__, TOML11_INDEX_RSEQ()) + + // ---------------------------------------------------------------------------- + // TOML11_FOR_EACH_VA_ARGS + + #define TOML11_FOR_EACH_VA_ARGS_AUX_1(FUNCTOR, ARG1) FUNCTOR(ARG1) + #define TOML11_FOR_EACH_VA_ARGS_AUX_2(FUNCTOR, ARG1, ...) \ + FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_1(FUNCTOR, __VA_ARGS__) + #define TOML11_FOR_EACH_VA_ARGS_AUX_3(FUNCTOR, ARG1, ...) \ + FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_2(FUNCTOR, __VA_ARGS__) + #define TOML11_FOR_EACH_VA_ARGS_AUX_4(FUNCTOR, ARG1, ...) \ + FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_3(FUNCTOR, __VA_ARGS__) + #define TOML11_FOR_EACH_VA_ARGS_AUX_5(FUNCTOR, ARG1, ...) \ + FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_4(FUNCTOR, __VA_ARGS__) + #define TOML11_FOR_EACH_VA_ARGS_AUX_6(FUNCTOR, ARG1, ...) \ + FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_5(FUNCTOR, __VA_ARGS__) + #define TOML11_FOR_EACH_VA_ARGS_AUX_7(FUNCTOR, ARG1, ...) \ + FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_6(FUNCTOR, __VA_ARGS__) + #define TOML11_FOR_EACH_VA_ARGS_AUX_8(FUNCTOR, ARG1, ...) \ + FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_7(FUNCTOR, __VA_ARGS__) + #define TOML11_FOR_EACH_VA_ARGS_AUX_9(FUNCTOR, ARG1, ...) \ + FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_8(FUNCTOR, __VA_ARGS__) + #define TOML11_FOR_EACH_VA_ARGS_AUX_10(FUNCTOR, ARG1, ...) \ + FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_9(FUNCTOR, __VA_ARGS__) + #define TOML11_FOR_EACH_VA_ARGS_AUX_11(FUNCTOR, ARG1, ...) \ + FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_10(FUNCTOR, __VA_ARGS__) + #define TOML11_FOR_EACH_VA_ARGS_AUX_12(FUNCTOR, ARG1, ...) \ + FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_11(FUNCTOR, __VA_ARGS__) + #define TOML11_FOR_EACH_VA_ARGS_AUX_13(FUNCTOR, ARG1, ...) \ + FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_12(FUNCTOR, __VA_ARGS__) + #define TOML11_FOR_EACH_VA_ARGS_AUX_14(FUNCTOR, ARG1, ...) \ + FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_13(FUNCTOR, __VA_ARGS__) + #define TOML11_FOR_EACH_VA_ARGS_AUX_15(FUNCTOR, ARG1, ...) \ + FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_14(FUNCTOR, __VA_ARGS__) + #define TOML11_FOR_EACH_VA_ARGS_AUX_16(FUNCTOR, ARG1, ...) \ + FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_15(FUNCTOR, __VA_ARGS__) + #define TOML11_FOR_EACH_VA_ARGS_AUX_17(FUNCTOR, ARG1, ...) \ + FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_16(FUNCTOR, __VA_ARGS__) + #define TOML11_FOR_EACH_VA_ARGS_AUX_18(FUNCTOR, ARG1, ...) \ + FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_17(FUNCTOR, __VA_ARGS__) + #define TOML11_FOR_EACH_VA_ARGS_AUX_19(FUNCTOR, ARG1, ...) \ + FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_18(FUNCTOR, __VA_ARGS__) + #define TOML11_FOR_EACH_VA_ARGS_AUX_20(FUNCTOR, ARG1, ...) \ + FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_19(FUNCTOR, __VA_ARGS__) + #define TOML11_FOR_EACH_VA_ARGS_AUX_21(FUNCTOR, ARG1, ...) \ + FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_20(FUNCTOR, __VA_ARGS__) + #define TOML11_FOR_EACH_VA_ARGS_AUX_22(FUNCTOR, ARG1, ...) \ + FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_21(FUNCTOR, __VA_ARGS__) + #define TOML11_FOR_EACH_VA_ARGS_AUX_23(FUNCTOR, ARG1, ...) \ + FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_22(FUNCTOR, __VA_ARGS__) + #define TOML11_FOR_EACH_VA_ARGS_AUX_24(FUNCTOR, ARG1, ...) \ + FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_23(FUNCTOR, __VA_ARGS__) + #define TOML11_FOR_EACH_VA_ARGS_AUX_25(FUNCTOR, ARG1, ...) \ + FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_24(FUNCTOR, __VA_ARGS__) + #define TOML11_FOR_EACH_VA_ARGS_AUX_26(FUNCTOR, ARG1, ...) \ + FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_25(FUNCTOR, __VA_ARGS__) + #define TOML11_FOR_EACH_VA_ARGS_AUX_27(FUNCTOR, ARG1, ...) \ + FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_26(FUNCTOR, __VA_ARGS__) + #define TOML11_FOR_EACH_VA_ARGS_AUX_28(FUNCTOR, ARG1, ...) \ + FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_27(FUNCTOR, __VA_ARGS__) + #define TOML11_FOR_EACH_VA_ARGS_AUX_29(FUNCTOR, ARG1, ...) \ + FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_28(FUNCTOR, __VA_ARGS__) + #define TOML11_FOR_EACH_VA_ARGS_AUX_30(FUNCTOR, ARG1, ...) \ + FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_29(FUNCTOR, __VA_ARGS__) + #define TOML11_FOR_EACH_VA_ARGS_AUX_31(FUNCTOR, ARG1, ...) \ + FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_30(FUNCTOR, __VA_ARGS__) + #define TOML11_FOR_EACH_VA_ARGS_AUX_32(FUNCTOR, ARG1, ...) \ + FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_31(FUNCTOR, __VA_ARGS__) + + #define TOML11_FOR_EACH_VA_ARGS(FUNCTOR, ...) \ + TOML11_CONCATENATE(TOML11_FOR_EACH_VA_ARGS_AUX_, \ + TOML11_ARGS_SIZE(__VA_ARGS__)) \ + (FUNCTOR, __VA_ARGS__) + + #define TOML11_FIND_MEMBER_VARIABLE_FROM_VALUE(VAR_NAME) \ + toml::detail::find_member_variable_from_value(obj.VAR_NAME, \ + v, \ + TOML11_STRINGIZE(VAR_NAME)); + + #define TOML11_ASSIGN_MEMBER_VARIABLE_TO_VALUE(VAR_NAME) \ + toml::detail::assign_member_variable_to_value(obj.VAR_NAME, \ + v, \ + TOML11_STRINGIZE(VAR_NAME)); + + #define TOML11_DEFINE_CONVERSION_NON_INTRUSIVE(NAME, ...) \ + namespace toml { \ + template <> \ + struct from { \ + template \ + static NAME from_toml(const basic_value& v) { \ + NAME obj; \ + TOML11_FOR_EACH_VA_ARGS(TOML11_FIND_MEMBER_VARIABLE_FROM_VALUE, \ + __VA_ARGS__) \ + return obj; \ + } \ + }; \ + template <> \ + struct into { \ + template \ + static basic_value into_toml(const NAME& obj) { \ + ::toml::basic_value v = typename ::toml::basic_value::table_type {}; \ + TOML11_FOR_EACH_VA_ARGS(TOML11_ASSIGN_MEMBER_VARIABLE_TO_VALUE, \ + __VA_ARGS__) \ + return v; \ + } \ + }; \ + } /* toml */ + +#endif // TOML11_WITHOUT_DEFINE_NON_INTRUSIVE + +#endif // TOML11_CONVERSION_HPP +#ifndef TOML11_CONTEXT_HPP +#define TOML11_CONTEXT_HPP + +#include + +namespace toml { + namespace detail { + + template + class context { + public: + explicit context(const spec& toml_spec) + : toml_spec_(toml_spec) + , errors_ {} {} + + bool has_error() const noexcept { + return !errors_.empty(); + } + + const std::vector& errors() const noexcept { + return errors_; + } + + semantic_version& toml_version() noexcept { + return toml_spec_.version; + } + + const semantic_version& toml_version() const noexcept { + return toml_spec_.version; + } + + spec& toml_spec() noexcept { + return toml_spec_; + } + + const spec& toml_spec() const noexcept { + return toml_spec_; + } + + void report_error(error_info err) { + this->errors_.push_back(std::move(err)); + } + + error_info pop_last_error() { + assert(!errors_.empty()); + auto e = std::move(errors_.back()); + errors_.pop_back(); + return e; + } + + private: + spec toml_spec_; + std::vector errors_; + }; + + } // namespace detail +} // namespace toml + +#if defined(TOML11_COMPILE_SOURCES) +namespace toml { + struct type_config; + struct ordered_type_config; + + namespace detail { + extern template class context<::toml::type_config>; + extern template class context<::toml::ordered_type_config>; + } // namespace detail +} // namespace toml +#endif // TOML11_COMPILE_SOURCES + +#endif // TOML11_CONTEXT_HPP +#ifndef TOML11_SCANNER_HPP +#define TOML11_SCANNER_HPP + +#ifndef TOML11_SCANNER_FWD_HPP + #define TOML11_SCANNER_FWD_HPP + + #include + #include + #include + #include + #include + #include + #include + +namespace toml { + namespace detail { + + class scanner_base { + public: + virtual ~scanner_base() = default; + virtual region scan(location& loc) const = 0; + virtual scanner_base* clone() const = 0; + + // returns expected character or set of characters or literal. + // to show the error location, it changes loc (in `sequence`, especially). + virtual std::string expected_chars(location& loc) const = 0; + virtual std::string name() const = 0; + }; + + // make `scanner*` copyable + struct scanner_storage { + template >::value, + std::nullptr_t> = nullptr> + explicit scanner_storage(Scanner&& s) + : scanner_(cxx::make_unique>( + std::forward(s))) {} + + ~scanner_storage() = default; + + scanner_storage(const scanner_storage& other); + scanner_storage& operator=(const scanner_storage& other); + scanner_storage(scanner_storage&&) = default; + scanner_storage& operator=(scanner_storage&&) = default; + + bool is_ok() const noexcept { + return static_cast(scanner_); + } + + region scan(location& loc) const; + + std::string expected_chars(location& loc) const; + + scanner_base& get() const noexcept; + + std::string name() const; + + private: + std::unique_ptr scanner_; + }; + + // ---------------------------------------------------------------------------- + + class character final : public scanner_base { + public: + using char_type = location::char_type; + + public: + explicit character(const char_type c) noexcept : value_(c) {} + + ~character() override = default; + + region scan(location& loc) const override; + + std::string expected_chars(location&) const override; + + scanner_base* clone() const override; + + std::string name() const override; + + private: + char_type value_; + }; + + // ---------------------------------------------------------------------------- + + class character_either final : public scanner_base { + public: + using char_type = location::char_type; + + public: + explicit character_either(std::initializer_list cs) noexcept + : chars_(std::move(cs)) { + assert(!this->chars_.empty()); + } + + template + explicit character_either(const char (&cs)[N]) noexcept + : chars_(N - 1, '\0') { + static_assert(N >= 1, ""); + for (std::size_t i = 0; i + 1 < N; ++i) { + chars_.at(i) = char_type(cs[i]); + } + } + + ~character_either() override = default; + + region scan(location& loc) const override; + + std::string expected_chars(location&) const override; + + scanner_base* clone() const override; + + void push_back(const char_type c); + + std::string name() const override; + + private: + std::vector chars_; + }; + + // ---------------------------------------------------------------------------- + + class character_in_range final : public scanner_base { + public: + using char_type = location::char_type; + + public: + explicit character_in_range(const char_type from, const char_type to) noexcept + : from_(from) + , to_(to) {} + + ~character_in_range() override = default; + + region scan(location& loc) const override; + + std::string expected_chars(location&) const override; + + scanner_base* clone() const override; + + std::string name() const override; + + private: + char_type from_; + char_type to_; + }; + + // ---------------------------------------------------------------------------- + + class literal final : public scanner_base { + public: + using char_type = location::char_type; + + public: + template + explicit literal(const char (&cs)[N]) noexcept + : value_(cs) + , size_(N - 1) // remove null character at the end + {} + + ~literal() override = default; + + region scan(location& loc) const override; + + std::string expected_chars(location&) const override; + + scanner_base* clone() const override; + + std::string name() const override; + + private: + const char* value_; + std::size_t size_; + }; + + // ---------------------------------------------------------------------------- + + class sequence final : public scanner_base { + public: + using char_type = location::char_type; + + public: + template + explicit sequence(Ts&&... args) { + push_back_all(std::forward(args)...); + } + + sequence(const sequence&) = default; + sequence(sequence&&) = default; + sequence& operator=(const sequence&) = default; + sequence& operator=(sequence&&) = default; + ~sequence() override = default; + + region scan(location& loc) const override; + + std::string expected_chars(location& loc) const override; + + scanner_base* clone() const override; + + template + void push_back(Scanner&& other_scanner) { + this->others_.emplace_back(std::forward(other_scanner)); + } + + std::string name() const override; + + private: + void push_back_all() { + return; + } + + template + void push_back_all(T&& head, Ts&&... args) { + others_.emplace_back(std::forward(head)); + push_back_all(std::forward(args)...); + return; + } + + private: + std::vector others_; + }; + + // ---------------------------------------------------------------------------- + + class either final : public scanner_base { + public: + using char_type = location::char_type; + + public: + template + explicit either(Ts&&... args) { + push_back_all(std::forward(args)...); + } + + either(const either&) = default; + either(either&&) = default; + either& operator=(const either&) = default; + either& operator=(either&&) = default; + ~either() override = default; + + region scan(location& loc) const override; + + std::string expected_chars(location& loc) const override; + + scanner_base* clone() const override; + + template + void push_back(Scanner&& other_scanner) { + this->others_.emplace_back(std::forward(other_scanner)); + } + + std::string name() const override; + + private: + void push_back_all() { + return; + } + + template + void push_back_all(T&& head, Ts&&... args) { + others_.emplace_back(std::forward(head)); + push_back_all(std::forward(args)...); + return; + } + + private: + std::vector others_; + }; + + // ---------------------------------------------------------------------------- + + class repeat_exact final : public scanner_base { + public: + using char_type = location::char_type; + + public: + template + repeat_exact(const std::size_t length, Scanner&& other) + : length_(length) + , other_(std::forward(other)) {} + + repeat_exact(const repeat_exact&) = default; + repeat_exact(repeat_exact&&) = default; + repeat_exact& operator=(const repeat_exact&) = default; + repeat_exact& operator=(repeat_exact&&) = default; + ~repeat_exact() override = default; + + region scan(location& loc) const override; + + std::string expected_chars(location& loc) const override; + + scanner_base* clone() const override; + + std::string name() const override; + + private: + std::size_t length_; + scanner_storage other_; + }; + + // ---------------------------------------------------------------------------- + + class repeat_at_least final : public scanner_base { + public: + using char_type = location::char_type; + + public: + template + repeat_at_least(const std::size_t length, Scanner&& s) + : length_(length) + , other_(std::forward(s)) {} + + repeat_at_least(const repeat_at_least&) = default; + repeat_at_least(repeat_at_least&&) = default; + repeat_at_least& operator=(const repeat_at_least&) = default; + repeat_at_least& operator=(repeat_at_least&&) = default; + ~repeat_at_least() override = default; + + region scan(location& loc) const override; + + std::string expected_chars(location& loc) const override; + + scanner_base* clone() const override; + + std::string name() const override; + + private: + std::size_t length_; + scanner_storage other_; + }; + + // ---------------------------------------------------------------------------- + + class maybe final : public scanner_base { + public: + using char_type = location::char_type; + + public: + template + explicit maybe(Scanner&& s) : other_(std::forward(s)) {} + + maybe(const maybe&) = default; + maybe(maybe&&) = default; + maybe& operator=(const maybe&) = default; + maybe& operator=(maybe&&) = default; + ~maybe() override = default; + + region scan(location& loc) const override; + + std::string expected_chars(location&) const override; + + scanner_base* clone() const override; + + std::string name() const override; + + private: + scanner_storage other_; + }; + + } // namespace detail +} // namespace toml +#endif // TOML11_SCANNER_FWD_HPP + +#if !defined(TOML11_COMPILE_SOURCES) + #ifndef TOML11_SCANNER_IMPL_HPP + #define TOML11_SCANNER_IMPL_HPP + +namespace toml { + namespace detail { + + TOML11_INLINE scanner_storage::scanner_storage(const scanner_storage& other) + : scanner_(nullptr) { + if (other.is_ok()) { + scanner_.reset(other.get().clone()); + } + } + + TOML11_INLINE scanner_storage& scanner_storage::operator=( + const scanner_storage& other) { + if (this == std::addressof(other)) { + return *this; + } + if (other.is_ok()) { + scanner_.reset(other.get().clone()); + } + return *this; + } + + TOML11_INLINE region scanner_storage::scan(location& loc) const { + assert(this->is_ok()); + return this->scanner_->scan(loc); + } + + TOML11_INLINE std::string scanner_storage::expected_chars(location& loc) const { + assert(this->is_ok()); + return this->scanner_->expected_chars(loc); + } + + TOML11_INLINE scanner_base& scanner_storage::get() const noexcept { + assert(this->is_ok()); + return *scanner_; + } + + TOML11_INLINE std::string scanner_storage::name() const { + assert(this->is_ok()); + return this->scanner_->name(); + } + + // ---------------------------------------------------------------------------- + + TOML11_INLINE region character::scan(location& loc) const { + if (loc.eof()) { + return region {}; + } + + if (loc.current() == this->value_) { + const auto first = loc; + loc.advance(1); + return region(first, loc); + } + return region {}; + } + + TOML11_INLINE std::string character::expected_chars(location&) const { + return show_char(value_); + } + + TOML11_INLINE scanner_base* character::clone() const { + return new character(*this); + } + + TOML11_INLINE std::string character::name() const { + return "character{" + show_char(value_) + "}"; + } + + // ---------------------------------------------------------------------------- + + TOML11_INLINE region character_either::scan(location& loc) const { + if (loc.eof()) { + return region {}; + } + + for (const auto c : this->chars_) { + if (loc.current() == c) { + const auto first = loc; + loc.advance(1); + return region(first, loc); + } + } + return region {}; + } + + TOML11_INLINE std::string character_either::expected_chars(location&) const { + assert(!chars_.empty()); + + std::string expected; + if (chars_.size() == 1) { + expected += show_char(chars_.at(0)); + } else if (chars_.size() == 2) { + expected += show_char(chars_.at(0)) + " or " + show_char(chars_.at(1)); + } else { + for (std::size_t i = 0; i < chars_.size(); ++i) { + if (i != 0) { + expected += ", "; + } + if (i + 1 == chars_.size()) { + expected += "or "; + } + expected += show_char(chars_.at(i)); + } + } + return expected; + } + + TOML11_INLINE scanner_base* character_either::clone() const { + return new character_either(*this); + } + + TOML11_INLINE void character_either::push_back(const char_type c) { + chars_.push_back(c); + } + + TOML11_INLINE std::string character_either::name() const { + std::string n("character_either{"); + for (const auto c : this->chars_) { + n += show_char(c); + n += ", "; + } + if (!this->chars_.empty()) { + n.pop_back(); + n.pop_back(); + } + n += "}"; + return n; + } + + // ---------------------------------------------------------------------------- + // character_in_range + + TOML11_INLINE region character_in_range::scan(location& loc) const { + if (loc.eof()) { + return region {}; + } + + const auto curr = loc.current(); + if (this->from_ <= curr && curr <= this->to_) { + const auto first = loc; + loc.advance(1); + return region(first, loc); + } + return region {}; + } + + TOML11_INLINE std::string character_in_range::expected_chars(location&) const { + std::string expected("from `"); + expected += show_char(from_); + expected += "` to `"; + expected += show_char(to_); + expected += "`"; + return expected; + } + + TOML11_INLINE scanner_base* character_in_range::clone() const { + return new character_in_range(*this); + } + + TOML11_INLINE std::string character_in_range::name() const { + return "character_in_range{" + show_char(from_) + "," + show_char(to_) + "}"; + } + + // ---------------------------------------------------------------------------- + // literal + + TOML11_INLINE region literal::scan(location& loc) const { + const auto first = loc; + for (std::size_t i = 0; i < size_; ++i) { + if (loc.eof() || char_type(value_[i]) != loc.current()) { + loc = first; + return region {}; + } + loc.advance(1); + } + return region(first, loc); + } + + TOML11_INLINE std::string literal::expected_chars(location&) const { + return std::string(value_); + } + + TOML11_INLINE scanner_base* literal::clone() const { + return new literal(*this); + } + + TOML11_INLINE std::string literal::name() const { + return std::string("literal{") + std::string(value_, size_) + "}"; + } + + // ---------------------------------------------------------------------------- + // sequence + + TOML11_INLINE region sequence::scan(location& loc) const { + const auto first = loc; + for (const auto& other : others_) { + const auto reg = other.scan(loc); + if (!reg.is_ok()) { + loc = first; + return region {}; + } + } + return region(first, loc); + } + + TOML11_INLINE std::string sequence::expected_chars(location& loc) const { + const auto first = loc; + for (const auto& other : others_) { + const auto reg = other.scan(loc); + if (!reg.is_ok()) { + return other.expected_chars(loc); + } + } + assert(false); + return ""; // XXX + } + + TOML11_INLINE scanner_base* sequence::clone() const { + return new sequence(*this); + } + + TOML11_INLINE std::string sequence::name() const { + std::string n("sequence{"); + for (const auto& other : others_) { + n += other.name(); + n += ", "; + } + if (!this->others_.empty()) { + n.pop_back(); + n.pop_back(); + } + n += "}"; + return n; + } + + // ---------------------------------------------------------------------------- + // either + + TOML11_INLINE region either::scan(location& loc) const { + for (const auto& other : others_) { + const auto reg = other.scan(loc); + if (reg.is_ok()) { + return reg; + } + } + return region {}; + } + + TOML11_INLINE std::string either::expected_chars(location& loc) const { + assert(!others_.empty()); + + std::string expected = others_.at(0).expected_chars(loc); + if (others_.size() == 2) { + expected += " or "; + expected += others_.at(1).expected_chars(loc); + } else { + for (std::size_t i = 1; i < others_.size(); ++i) { + expected += ", "; + if (i + 1 == others_.size()) { + expected += "or "; + } + expected += others_.at(i).expected_chars(loc); + } + } + return expected; + } + + TOML11_INLINE scanner_base* either::clone() const { + return new either(*this); + } + + TOML11_INLINE std::string either::name() const { + std::string n("either{"); + for (const auto& other : others_) { + n += other.name(); + n += ", "; + } + if (!this->others_.empty()) { + n.pop_back(); + n.pop_back(); + } + n += "}"; + return n; + } + + // ---------------------------------------------------------------------------- + // repeat_exact + + TOML11_INLINE region repeat_exact::scan(location& loc) const { + const auto first = loc; + for (std::size_t i = 0; i < length_; ++i) { + const auto reg = other_.scan(loc); + if (!reg.is_ok()) { + loc = first; + return region {}; + } + } + return region(first, loc); + } + + TOML11_INLINE std::string repeat_exact::expected_chars(location& loc) const { + for (std::size_t i = 0; i < length_; ++i) { + const auto reg = other_.scan(loc); + if (!reg.is_ok()) { + return other_.expected_chars(loc); + } + } + assert(false); + return ""; + } + + TOML11_INLINE scanner_base* repeat_exact::clone() const { + return new repeat_exact(*this); + } + + TOML11_INLINE std::string repeat_exact::name() const { + return "repeat_exact{" + std::to_string(length_) + ", " + other_.name() + "}"; + } + + // ---------------------------------------------------------------------------- + // repeat_at_least + + TOML11_INLINE region repeat_at_least::scan(location& loc) const { + const auto first = loc; + for (std::size_t i = 0; i < length_; ++i) { + const auto reg = other_.scan(loc); + if (!reg.is_ok()) { + loc = first; + return region {}; + } + } + while (!loc.eof()) { + const auto checkpoint = loc; + const auto reg = other_.scan(loc); + if (!reg.is_ok()) { + loc = checkpoint; + return region(first, loc); + } + } + return region(first, loc); + } + + TOML11_INLINE std::string repeat_at_least::expected_chars(location& loc) const { + for (std::size_t i = 0; i < length_; ++i) { + const auto reg = other_.scan(loc); + if (!reg.is_ok()) { + return other_.expected_chars(loc); + } + } + assert(false); + return ""; + } + + TOML11_INLINE scanner_base* repeat_at_least::clone() const { + return new repeat_at_least(*this); + } + + TOML11_INLINE std::string repeat_at_least::name() const { + return "repeat_at_least{" + std::to_string(length_) + ", " + + other_.name() + "}"; + } + + // ---------------------------------------------------------------------------- + // maybe + + TOML11_INLINE region maybe::scan(location& loc) const { + const auto first = loc; + const auto reg = other_.scan(loc); + if (!reg.is_ok()) { + loc = first; + } + return region(first, loc); + } + + TOML11_INLINE std::string maybe::expected_chars(location&) const { + return ""; + } + + TOML11_INLINE scanner_base* maybe::clone() const { + return new maybe(*this); + } + + TOML11_INLINE std::string maybe::name() const { + return "maybe{" + other_.name() + "}"; + } + + } // namespace detail +} // namespace toml + #endif // TOML11_SCANNER_IMPL_HPP +#endif + +#endif // TOML11_SCANNER_HPP +#ifndef TOML11_SYNTAX_HPP +#define TOML11_SYNTAX_HPP + +#ifndef TOML11_SYNTAX_FWD_HPP + #define TOML11_SYNTAX_FWD_HPP + +namespace toml { + namespace detail { + namespace syntax { + + using char_type = location::char_type; + + // =========================================================================== + // UTF-8 + + // avoid redundant representation and out-of-unicode sequence + + character_in_range utf8_1byte(const spec&); + sequence utf8_2bytes(const spec&); + sequence utf8_3bytes(const spec&); + sequence utf8_4bytes(const spec&); + + class non_ascii final : public scanner_base { + public: + using char_type = location::char_type; + + public: + explicit non_ascii(const spec& s) noexcept; + ~non_ascii() override = default; + + region scan(location& loc) const override { + return scanner_.scan(loc); + } + + std::string expected_chars(location&) const override { + return "non-ascii utf-8 bytes"; + } + + scanner_base* clone() const override { + return new non_ascii(*this); + } + + std::string name() const override { + return "non_ascii"; + } + + private: + either scanner_; + }; + + // =========================================================================== + // Whitespace + + character_either wschar(const spec&); + + repeat_at_least ws(const spec& s); + + // =========================================================================== + // Newline + + either newline(const spec&); + + // =========================================================================== + // Comments + + either allowed_comment_char(const spec& s); + + // XXX Note that it does not take newline + sequence comment(const spec& s); + + // =========================================================================== + // Boolean + + either boolean(const spec&); + + // =========================================================================== + // Integer + + class digit final : public scanner_base { + public: + using char_type = location::char_type; + + public: + explicit digit(const spec&) noexcept; + ~digit() override = default; + + region scan(location& loc) const override { + return scanner_.scan(loc); + } + + std::string expected_chars(location&) const override { + return "digit [0-9]"; + } + + scanner_base* clone() const override { + return new digit(*this); + } + + std::string name() const override { + return "digit"; + } + + private: + character_in_range scanner_; + }; + + class alpha final : public scanner_base { + public: + using char_type = location::char_type; + + public: + explicit alpha(const spec&) noexcept; + ~alpha() override = default; + + region scan(location& loc) const override { + return scanner_.scan(loc); + } + + std::string expected_chars(location&) const override { + return "alpha [a-zA-Z]"; + } + + scanner_base* clone() const override { + return new alpha(*this); + } + + std::string name() const override { + return "alpha"; + } + + private: + either scanner_; + }; + + class hexdig final : public scanner_base { + public: + using char_type = location::char_type; + + public: + explicit hexdig(const spec& s) noexcept; + ~hexdig() override = default; + + region scan(location& loc) const override { + return scanner_.scan(loc); + } + + std::string expected_chars(location&) const override { + return "hex [0-9a-fA-F]"; + } + + scanner_base* clone() const override { + return new hexdig(*this); + } + + std::string name() const override { + return "hexdig"; + } + + private: + either scanner_; + }; + + sequence num_suffix(const spec& s); + + sequence dec_int(const spec& s); + sequence hex_int(const spec& s); + sequence oct_int(const spec&); + sequence bin_int(const spec&); + either integer(const spec& s); + + // =========================================================================== + // Floating + + sequence zero_prefixable_int(const spec& s); + sequence fractional_part(const spec& s); + sequence exponent_part(const spec& s); + sequence hex_floating(const spec& s); + either floating(const spec& s); + + // =========================================================================== + // Datetime + + sequence local_date(const spec& s); + sequence local_time(const spec& s); + either time_offset(const spec& s); + sequence full_time(const spec& s); + character_either time_delim(const spec&); + sequence local_datetime(const spec& s); + sequence offset_datetime(const spec& s); + + // =========================================================================== + // String + + sequence escaped(const spec& s); + + either basic_char(const spec& s); + + sequence basic_string(const spec& s); + + // --------------------------------------------------------------------------- + // multiline string + + sequence escaped_newline(const spec& s); + sequence ml_basic_string(const spec& s); + + // --------------------------------------------------------------------------- + // literal string + + either literal_char(const spec& s); + sequence literal_string(const spec& s); + + sequence ml_literal_string(const spec& s); + + either string(const spec& s); + + // =========================================================================== + // Keys + + // to keep `expected_chars` simple + class non_ascii_key_char final : public scanner_base { + public: + using char_type = location::char_type; + + private: + using in_range = character_in_range; // make definition short + + public: + explicit non_ascii_key_char(const spec& s) noexcept; + ~non_ascii_key_char() override = default; + + region scan(location& loc) const override; + + std::string expected_chars(location&) const override { + return "bare key non-ASCII script"; + } + + scanner_base* clone() const override { + return new non_ascii_key_char(*this); + } + + std::string name() const override { + return "non-ASCII bare key"; + } + + private: + std::uint32_t read_utf8(location& loc) const; + }; + + repeat_at_least unquoted_key(const spec& s); + + either quoted_key(const spec& s); + + either simple_key(const spec& s); + + sequence dot_sep(const spec& s); + + sequence dotted_key(const spec& s); + + class key final : public scanner_base { + public: + using char_type = location::char_type; + + public: + explicit key(const spec& s) noexcept; + ~key() override = default; + + region scan(location& loc) const override { + return scanner_.scan(loc); + } + + std::string expected_chars(location&) const override { + return "basic key([a-zA-Z0-9_-]) or quoted key(\" or ')"; + } + + scanner_base* clone() const override { + return new key(*this); + } + + std::string name() const override { + return "key"; + } + + private: + either scanner_; + }; + + sequence keyval_sep(const spec& s); + + // =========================================================================== + // Table key + + sequence std_table(const spec& s); + + sequence array_table(const spec& s); + + // =========================================================================== + // extension: null + + literal null_value(const spec&); + + } // namespace syntax + } // namespace detail +} // namespace toml +#endif // TOML11_SYNTAX_FWD_HPP + +#if !defined(TOML11_COMPILE_SOURCES) + #ifndef TOML11_SYNTAX_IMPL_HPP + #define TOML11_SYNTAX_IMPL_HPP + +namespace toml { + namespace detail { + namespace syntax { + + using char_type = location::char_type; + + // =========================================================================== + // UTF-8 + + // avoid redundant representation and out-of-unicode sequence + + TOML11_INLINE character_in_range utf8_1byte(const spec&) { + return character_in_range(0x00, 0x7F); + } + + TOML11_INLINE sequence utf8_2bytes(const spec&) { + return sequence(character_in_range(0xC2, 0xDF), + character_in_range(0x80, 0xBF)); + } + + TOML11_INLINE sequence utf8_3bytes(const spec&) { + return sequence( + /*1~2 bytes = */ either( + sequence(character(0xE0), character_in_range(0xA0, 0xBF)), + sequence(character_in_range(0xE1, 0xEC), character_in_range(0x80, 0xBF)), + sequence(character(0xED), character_in_range(0x80, 0x9F)), + sequence(character_in_range(0xEE, 0xEF), + character_in_range(0x80, 0xBF))), + /*3rd byte = */ character_in_range(0x80, 0xBF)); + } + + TOML11_INLINE sequence utf8_4bytes(const spec&) { + return sequence( + /*1~2 bytes = */ either( + sequence(character(0xF0), character_in_range(0x90, 0xBF)), + sequence(character_in_range(0xF1, 0xF3), character_in_range(0x80, 0xBF)), + sequence(character(0xF4), character_in_range(0x80, 0x8F))), + character_in_range(0x80, 0xBF), + character_in_range(0x80, 0xBF)); + } + + TOML11_INLINE non_ascii::non_ascii(const spec& s) noexcept + : scanner_(utf8_2bytes(s), utf8_3bytes(s), utf8_4bytes(s)) {} + + // =========================================================================== + // Whitespace + + TOML11_INLINE character_either wschar(const spec&) { + return character_either { char_type(' '), char_type('\t') }; + } + + TOML11_INLINE repeat_at_least ws(const spec& s) { + return repeat_at_least(0, wschar(s)); + } + + // =========================================================================== + // Newline + + TOML11_INLINE either newline(const spec&) { + return either(character(char_type('\n')), literal("\r\n")); + } + + // =========================================================================== + // Comments + + TOML11_INLINE either allowed_comment_char(const spec& s) { + if (s.v1_1_0_allow_control_characters_in_comments) { + return either(character_in_range(0x01, 0x09), + character_in_range(0x0E, 0x7F), + non_ascii(s)); + } else { + return either(character(0x09), + character_in_range(0x20, 0x7E), + non_ascii(s)); + } + } + + // XXX Note that it does not take newline + TOML11_INLINE sequence comment(const spec& s) { + return sequence(character(char_type('#')), + repeat_at_least(0, allowed_comment_char(s))); + } + + // =========================================================================== + // Boolean + + TOML11_INLINE either boolean(const spec&) { + return either(literal("true"), literal("false")); + } + + // =========================================================================== + // Integer + + TOML11_INLINE digit::digit(const spec&) noexcept + : scanner_(char_type('0'), char_type('9')) {} + + TOML11_INLINE alpha::alpha(const spec&) noexcept + : scanner_(character_in_range(char_type('a'), char_type('z')), + character_in_range(char_type('A'), char_type('Z'))) {} + + TOML11_INLINE hexdig::hexdig(const spec& s) noexcept + : scanner_(digit(s), + character_in_range(char_type('a'), char_type('f')), + character_in_range(char_type('A'), char_type('F'))) {} + + // non-digit-graph = ([a-zA-Z]|unicode mb char) + // graph = ([a-zA-Z0-9]|unicode mb char) + // suffix = _ non-digit-graph (graph | _graph) + TOML11_INLINE sequence num_suffix(const spec& s) { + const auto non_digit_graph = [&s]() { + return either(alpha(s), non_ascii(s)); + }; + const auto graph = [&s]() { + return either(alpha(s), digit(s), non_ascii(s)); + }; + + return sequence( + character(char_type('_')), + non_digit_graph(), + repeat_at_least( + 0, + either(sequence(character(char_type('_')), graph()), graph()))); + } + + TOML11_INLINE sequence dec_int(const spec& s) { + const auto digit19 = []() { + return character_in_range(char_type('1'), char_type('9')); + }; + return sequence( + maybe(character_either { char_type('-'), char_type('+') }), + either(sequence(digit19(), + repeat_at_least( + 1, + either(digit(s), + sequence(character(char_type('_')), digit(s))))), + digit(s))); + } + + TOML11_INLINE sequence hex_int(const spec& s) { + return sequence( + literal("0x"), + hexdig(s), + repeat_at_least( + 0, + either(hexdig(s), sequence(character(char_type('_')), hexdig(s))))); + } + + TOML11_INLINE sequence oct_int(const spec&) { + const auto digit07 = []() { + return character_in_range(char_type('0'), char_type('7')); + }; + return sequence( + literal("0o"), + digit07(), + repeat_at_least( + 0, + either(digit07(), sequence(character(char_type('_')), digit07())))); + } + + TOML11_INLINE sequence bin_int(const spec&) { + const auto digit01 = []() { + return character_either { char_type('0'), char_type('1') }; + }; + return sequence( + literal("0b"), + digit01(), + repeat_at_least( + 0, + either(digit01(), sequence(character(char_type('_')), digit01())))); + } + + TOML11_INLINE either integer(const spec& s) { + return either(hex_int(s), oct_int(s), bin_int(s), dec_int(s)); + } + + // =========================================================================== + // Floating + + TOML11_INLINE sequence zero_prefixable_int(const spec& s) { + return sequence( + digit(s), + repeat_at_least(0, either(digit(s), sequence(character('_'), digit(s))))); + } + + TOML11_INLINE sequence fractional_part(const spec& s) { + return sequence(character('.'), zero_prefixable_int(s)); + } + + TOML11_INLINE sequence exponent_part(const spec& s) { + return sequence(character_either { char_type('e'), char_type('E') }, + maybe(character_either { char_type('+'), char_type('-') }), + zero_prefixable_int(s)); + } + + TOML11_INLINE sequence hex_floating(const spec& s) { + // C99 hexfloat (%a) + // [+-]? 0x ( [0-9a-fA-F]*\.[0-9a-fA-F]+ | [0-9a-fA-F]+\.? ) [pP] [+-]? [0-9]+ + + // - 0x(int).(frac)p[+-](int) + // - 0x(int).p[+-](int) + // - 0x.(frac)p[+-](int) + // - 0x(int)p[+-](int) + + return sequence( + maybe(character_either { char_type('+'), char_type('-') }), + character('0'), + character_either { char_type('x'), char_type('X') }, + either(sequence(repeat_at_least(0, hexdig(s)), + character('.'), + repeat_at_least(1, hexdig(s))), + sequence(repeat_at_least(1, hexdig(s)), maybe(character('.')))), + character_either { char_type('p'), char_type('P') }, + maybe(character_either { char_type('+'), char_type('-') }), + repeat_at_least(1, character_in_range('0', '9'))); + } + + TOML11_INLINE either floating(const spec& s) { + return either( + sequence(dec_int(s), + either(exponent_part(s), + sequence(fractional_part(s), maybe(exponent_part(s))))), + sequence(maybe(character_either { char_type('-'), char_type('+') }), + either(literal("inf"), literal("nan")))); + } + + // =========================================================================== + // Datetime + + TOML11_INLINE sequence local_date(const spec& s) { + return sequence(repeat_exact(4, digit(s)), + character('-'), + repeat_exact(2, digit(s)), + character('-'), + repeat_exact(2, digit(s))); + } + + TOML11_INLINE sequence local_time(const spec& s) { + auto time = sequence(repeat_exact(2, digit(s)), + character(':'), + repeat_exact(2, digit(s))); + + if (s.v1_1_0_make_seconds_optional) { + time.push_back(maybe(sequence( + character(':'), + repeat_exact(2, digit(s)), + maybe(sequence(character('.'), repeat_at_least(1, digit(s))))))); + } else { + time.push_back(character(':')); + time.push_back(repeat_exact(2, digit(s))); + time.push_back( + maybe(sequence(character('.'), repeat_at_least(1, digit(s))))); + } + + return time; + } + + TOML11_INLINE either time_offset(const spec& s) { + return either(character_either { 'Z', 'z' }, + sequence(character_either { '+', '-' }, + repeat_exact(2, digit(s)), + character(':'), + repeat_exact(2, digit(s)))); + } + + TOML11_INLINE sequence full_time(const spec& s) { + return sequence(local_time(s), time_offset(s)); + } + + TOML11_INLINE character_either time_delim(const spec&) { + return character_either { 'T', 't', ' ' }; + } + + TOML11_INLINE sequence local_datetime(const spec& s) { + return sequence(local_date(s), time_delim(s), local_time(s)); + } + + TOML11_INLINE sequence offset_datetime(const spec& s) { + return sequence(local_date(s), time_delim(s), full_time(s)); + } + + // =========================================================================== + // String + + TOML11_INLINE sequence escaped(const spec& s) { + character_either escape_char { '\"', '\\', 'b', 'f', 'n', 'r', 't' }; + if (s.v1_1_0_add_escape_sequence_e) { + escape_char.push_back(char_type('e')); + } + + either escape_seq(std::move(escape_char), + sequence(character('u'), repeat_exact(4, hexdig(s))), + sequence(character('U'), repeat_exact(8, hexdig(s)))); + + if (s.v1_1_0_add_escape_sequence_x) { + escape_seq.push_back( + sequence(character('x'), repeat_exact(2, hexdig(s)))); + } + + return sequence(character('\\'), std::move(escape_seq)); + } + + TOML11_INLINE either basic_char(const spec& s) { + const auto basic_unescaped = [&s]() { + return either(wschar(s), + character(0x21), // 22 is " + character_in_range(0x23, 0x5B), // 5C is backslash + character_in_range(0x5D, 0x7E), // 7F is DEL + non_ascii(s)); + }; + return either(basic_unescaped(), escaped(s)); + } + + TOML11_INLINE sequence basic_string(const spec& s) { + return sequence(character('"'), + repeat_at_least(0, basic_char(s)), + character('"')); + } + + // --------------------------------------------------------------------------- + // multiline string + + TOML11_INLINE sequence escaped_newline(const spec& s) { + return sequence(character('\\'), + ws(s), + newline(s), + repeat_at_least(0, either(wschar(s), newline(s)))); + } + + TOML11_INLINE sequence ml_basic_string(const spec& s) { + const auto mlb_content = [&s]() { + return either(basic_char(s), newline(s), escaped_newline(s)); + }; + const auto mlb_quotes = []() { + return either(literal("\"\""), character('\"')); + }; + + return sequence( + literal("\"\"\""), + maybe(newline(s)), + repeat_at_least(0, mlb_content()), + repeat_at_least(0, + sequence(mlb_quotes(), repeat_at_least(1, mlb_content()))), + // XXX """ and mlb_quotes are intentionally reordered to avoid + // unexpected match of mlb_quotes + literal("\"\"\""), + maybe(mlb_quotes())); + } + + // --------------------------------------------------------------------------- + // literal string + + TOML11_INLINE either literal_char(const spec& s) { + return either(character(0x09), + character_in_range(0x20, 0x26), + character_in_range(0x28, 0x7E), + non_ascii(s)); + } + + TOML11_INLINE sequence literal_string(const spec& s) { + return sequence(character('\''), + repeat_at_least(0, literal_char(s)), + character('\'')); + } + + TOML11_INLINE sequence ml_literal_string(const spec& s) { + const auto mll_quotes = []() { + return either(literal("''"), character('\'')); + }; + const auto mll_content = [&s]() { + return either(literal_char(s), newline(s)); + }; + + return sequence( + literal("'''"), + maybe(newline(s)), + repeat_at_least(0, mll_content()), + repeat_at_least(0, + sequence(mll_quotes(), repeat_at_least(1, mll_content()))), + literal("'''"), + maybe(mll_quotes()) + // XXX ''' and mll_quotes are intentionally reordered to avoid + // unexpected match of mll_quotes + ); + } + + TOML11_INLINE either string(const spec& s) { + return either(ml_basic_string(s), + ml_literal_string(s), + basic_string(s), + literal_string(s)); + } + + // =========================================================================== + // Keys + + // to keep `expected_chars` simple + TOML11_INLINE non_ascii_key_char::non_ascii_key_char(const spec& s) noexcept { + assert(s.v1_1_0_allow_non_english_in_bare_keys); + (void)s; // for NDEBUG + } + + TOML11_INLINE std::uint32_t non_ascii_key_char::read_utf8(location& loc) const { + // U+0000 ... U+0079 ; 0xxx_xxxx + // U+0080 ... U+07FF ; 110y_yyyx 10xx_xxxx; + // U+0800 ... U+FFFF ; 1110_yyyy 10yx_xxxx 10xx_xxxx + // U+010000 ... U+10FFFF; 1111_0yyy 10yy_xxxx 10xx_xxxx 10xx_xxxx + + const unsigned char b1 = loc.current(); + loc.advance(1); + if (b1 < 0x80) { + return static_cast(b1); + } else if ((b1 >> 5) == 6) // 0b110 == 6 + { + const auto b2 = loc.current(); + loc.advance(1); + + const std::uint32_t c1 = b1 & ((1 << 5) - 1); + const std::uint32_t c2 = b2 & ((1 << 6) - 1); + const std::uint32_t codep = (c1 << 6) + c2; + + if (codep < 0x80) { + return 0xFFFFFFFF; + } + return codep; + } else if ((b1 >> 4) == 14) // 0b1110 == 14 + { + const auto b2 = loc.current(); + loc.advance(1); + if (loc.eof()) { + return 0xFFFFFFFF; + } + const auto b3 = loc.current(); + loc.advance(1); + + const std::uint32_t c1 = b1 & ((1 << 4) - 1); + const std::uint32_t c2 = b2 & ((1 << 6) - 1); + const std::uint32_t c3 = b3 & ((1 << 6) - 1); + + const std::uint32_t codep = (c1 << 12) + (c2 << 6) + c3; + if (codep < 0x800) { + return 0xFFFFFFFF; + } + return codep; + } else if ((b1 >> 3) == 30) // 0b11110 == 30 + { + const auto b2 = loc.current(); + loc.advance(1); + if (loc.eof()) { + return 0xFFFFFFFF; + } + const auto b3 = loc.current(); + loc.advance(1); + if (loc.eof()) { + return 0xFFFFFFFF; + } + const auto b4 = loc.current(); + loc.advance(1); + + const std::uint32_t c1 = b1 & ((1 << 3) - 1); + const std::uint32_t c2 = b2 & ((1 << 6) - 1); + const std::uint32_t c3 = b3 & ((1 << 6) - 1); + const std::uint32_t c4 = b4 & ((1 << 6) - 1); + const std::uint32_t codep = (c1 << 18) + (c2 << 12) + (c3 << 6) + c4; + + if (codep < 0x10000) { + return 0xFFFFFFFF; + } + return codep; + } else // not a Unicode codepoint in UTF-8 + { + return 0xFFFFFFFF; + } + } + + TOML11_INLINE region non_ascii_key_char::scan(location& loc) const { + if (loc.eof()) { + return region {}; + } + + const auto first = loc; + + const auto cp = read_utf8(loc); + + if (cp == 0xFFFFFFFF) { + return region {}; + } + + // ALPHA / DIGIT / %x2D / %x5F ; a-z A-Z 0-9 - _ + // / %xB2 / %xB3 / %xB9 / %xBC-BE ; superscript digits, fractions + // / %xC0-D6 / %xD8-F6 / %xF8-37D ; non-symbol chars in Latin block + // / %x37F-1FFF ; exclude GREEK QUESTION MARK, which + // is basically a semi-colon / %x200C-200D / %x203F-2040 ; from + // General Punctuation Block, include the two tie symbols and ZWNJ, ZWJ + // / %x2070-218F / %x2460-24FF ; include super-/subscripts, + // letterlike/numberlike forms, enclosed alphanumerics / %x2C00-2FEF / + // %x3001-D7FF ; skip arrows, math, box drawing etc, skip 2FF0-3000 + // ideographic up/down markers and spaces / %xF900-FDCF / %xFDF0-FFFD ; + // skip D800-DFFF surrogate block, E000-F8FF Private Use area, FDD0-FDEF + // intended for process-internal use (unicode) / %x10000-EFFFF ; all + // chars outside BMP range, excluding Private Use planes (F0000-10FFFF) + + if (cp == 0xB2 || cp == 0xB3 || cp == 0xB9 || + (0xBC <= cp && cp <= 0xBE) || (0xC0 <= cp && cp <= 0xD6) || + (0xD8 <= cp && cp <= 0xF6) || (0xF8 <= cp && cp <= 0x37D) || + (0x37F <= cp && cp <= 0x1FFF) || (0x200C <= cp && cp <= 0x200D) || + (0x203F <= cp && cp <= 0x2040) || (0x2070 <= cp && cp <= 0x218F) || + (0x2460 <= cp && cp <= 0x24FF) || (0x2C00 <= cp && cp <= 0x2FEF) || + (0x3001 <= cp && cp <= 0xD7FF) || (0xF900 <= cp && cp <= 0xFDCF) || + (0xFDF0 <= cp && cp <= 0xFFFD) || (0x10000 <= cp && cp <= 0xEFFFF)) { + return region(first, loc); + } + loc = first; + return region {}; + } + + TOML11_INLINE repeat_at_least unquoted_key(const spec& s) { + auto keychar = either(alpha(s), + digit(s), + character { 0x2D }, + character { 0x5F }); + + if (s.v1_1_0_allow_non_english_in_bare_keys) { + keychar.push_back(non_ascii_key_char(s)); + } + + return repeat_at_least(1, std::move(keychar)); + } + + TOML11_INLINE either quoted_key(const spec& s) { + return either(basic_string(s), literal_string(s)); + } + + TOML11_INLINE either simple_key(const spec& s) { + return either(unquoted_key(s), quoted_key(s)); + } + + TOML11_INLINE sequence dot_sep(const spec& s) { + return sequence(ws(s), character('.'), ws(s)); + } + + TOML11_INLINE sequence dotted_key(const spec& s) { + return sequence(simple_key(s), + repeat_at_least(1, sequence(dot_sep(s), simple_key(s)))); + } + + TOML11_INLINE key::key(const spec& s) noexcept + : scanner_(dotted_key(s), simple_key(s)) {} + + TOML11_INLINE sequence keyval_sep(const spec& s) { + return sequence(ws(s), character('='), ws(s)); + } + + // =========================================================================== + // Table key + + TOML11_INLINE sequence std_table(const spec& s) { + return sequence(character('['), ws(s), key(s), ws(s), character(']')); + } + + TOML11_INLINE sequence array_table(const spec& s) { + return sequence(literal("[["), ws(s), key(s), ws(s), literal("]]")); + } + + // =========================================================================== + // extension: null + + TOML11_INLINE literal null_value(const spec&) { + return literal("null"); + } + + } // namespace syntax + } // namespace detail +} // namespace toml + #endif // TOML11_SYNTAX_IMPL_HPP +#endif + +#endif // TOML11_SYNTAX_HPP +#ifndef TOML11_SKIP_HPP +#define TOML11_SKIP_HPP + +#include + +namespace toml { + namespace detail { + + template + bool skip_whitespace(location& loc, const context& ctx) { + return syntax::ws(ctx.toml_spec()).scan(loc).is_ok(); + } + + template + bool skip_empty_lines(location& loc, const context& ctx) { + return repeat_at_least(1, + sequence(syntax::ws(ctx.toml_spec()), + syntax::newline(ctx.toml_spec()))) + .scan(loc) + .is_ok(); + } + + // For error recovery. + // + // In case if a comment line contains an invalid character, we need to skip + // it to advance parsing. + template + void skip_comment_block(location& loc, const context& ctx) { + while (!loc.eof()) { + skip_whitespace(loc, ctx); + if (loc.current() == '#') { + while (!loc.eof()) { + // both CRLF and LF ends with LF. + if (loc.current() == '\n') { + loc.advance(); + break; + } + } + } else if (syntax::newline(ctx.toml_spec()).scan(loc).is_ok()) { + ; // an empty line. skip this also + } else { + // the next token is neither a comment nor empty line. + return; + } + } + return; + } + + template + void skip_empty_or_comment_lines(location& loc, const context& ctx) { + const auto& spec = ctx.toml_spec(); + repeat_at_least(0, + sequence(syntax::ws(spec), + maybe(syntax::comment(spec)), + syntax::newline(spec))) + .scan(loc); + return; + } + + // For error recovery. + // + // Sometimes we need to skip a value and find a delimiter, like `,`, `]`, or `}`. + // To find delimiter, we need to skip delimiters in a string. + // Since we are skipping invalid value while error recovery, we don't need + // to check the syntax. Here we just skip string-like region until closing quote + // is found. + template + void skip_string_like(location& loc, const context&) { + // if """ is found, skip until the closing """ is found. + if (literal("\"\"\"").scan(loc).is_ok()) { + while (!loc.eof()) { + if (literal("\"\"\"").scan(loc).is_ok()) { + return; + } + loc.advance(); + } + } else if (literal("'''").scan(loc).is_ok()) { + while (!loc.eof()) { + if (literal("'''").scan(loc).is_ok()) { + return; + } + loc.advance(); + } + } + // if " is found, skip until the closing " or newline is found. + else if (loc.current() == '"') { + while (!loc.eof()) { + loc.advance(); + if (loc.current() == '"' || loc.current() == '\n') { + loc.advance(); + return; + } + } + } else if (loc.current() == '\'') { + while (!loc.eof()) { + loc.advance(); + if (loc.current() == '\'' || loc.current() == '\n') { + loc.advance(); + return; + } + } + } + return; + } + + template + void skip_value(location& loc, const context& ctx); + template + void skip_array_like(location& loc, const context& ctx); + template + void skip_inline_table_like(location& loc, const context& ctx); + template + void skip_key_value_pair(location& loc, const context& ctx); + + template + result guess_value_type(const location& loc, + const context& ctx); + + template + void skip_array_like(location& loc, const context& ctx) { + const auto& spec = ctx.toml_spec(); + assert(loc.current() == '['); + loc.advance(); + + while (!loc.eof()) { + if (loc.current() == '\"' || loc.current() == '\'') { + skip_string_like(loc, ctx); + } else if (loc.current() == '#') { + skip_comment_block(loc, ctx); + } else if (loc.current() == '{') { + skip_inline_table_like(loc, ctx); + } else if (loc.current() == '[') { + const auto checkpoint = loc; + if (syntax::std_table(spec).scan(loc).is_ok() || + syntax::array_table(spec).scan(loc).is_ok()) { + loc = checkpoint; + break; + } + // if it is not a table-definition, then it is an array. + skip_array_like(loc, ctx); + } else if (loc.current() == '=') { + // key-value pair cannot be inside the array. + // guessing the error is "missing closing bracket `]`". + // find the previous key just before `=`. + while (loc.get_location() != 0) { + loc.retrace(); + if (loc.current() == '\n') { + loc.advance(); + break; + } + } + break; + } else if (loc.current() == ']') { + break; // found closing bracket + } else { + loc.advance(); + } + } + return; + } + + template + void skip_inline_table_like(location& loc, const context& ctx) { + assert(loc.current() == '{'); + loc.advance(); + + const auto& spec = ctx.toml_spec(); + + while (!loc.eof()) { + if (loc.current() == '\n' && !spec.v1_1_0_allow_newlines_in_inline_tables) { + break; // missing closing `}`. + } else if (loc.current() == '\"' || loc.current() == '\'') { + skip_string_like(loc, ctx); + } else if (loc.current() == '#') { + skip_comment_block(loc, ctx); + if (!spec.v1_1_0_allow_newlines_in_inline_tables) { + // comment must end with newline. + break; // missing closing `}`. + } + } else if (loc.current() == '[') { + const auto checkpoint = loc; + if (syntax::std_table(spec).scan(loc).is_ok() || + syntax::array_table(spec).scan(loc).is_ok()) { + loc = checkpoint; + break; // missing closing `}`. + } + // if it is not a table-definition, then it is an array. + skip_array_like(loc, ctx); + } else if (loc.current() == '{') { + skip_inline_table_like(loc, ctx); + } else if (loc.current() == '}') { + // closing brace found. guessing the error is inside the table. + break; + } else { + // skip otherwise. + loc.advance(); + } + } + return; + } + + template + void skip_value(location& loc, const context& ctx) { + value_t ty = guess_value_type(loc, ctx).unwrap_or(value_t::empty); + if (ty == value_t::string) { + skip_string_like(loc, ctx); + } else if (ty == value_t::array) { + skip_array_like(loc, ctx); + } else if (ty == value_t::table) { + // In case of multiline tables, it may skip key-value pair but not the + // whole table. + skip_inline_table_like(loc, ctx); + } else // others are an "in-line" values. skip until the next line + { + while (!loc.eof()) { + if (loc.current() == '\n') { + break; + } else if (loc.current() == ',' || loc.current() == ']' || + loc.current() == '}') { + break; + } + loc.advance(); + } + } + return; + } + + template + void skip_key_value_pair(location& loc, const context& ctx) { + while (!loc.eof()) { + if (loc.current() == '=') { + skip_whitespace(loc, ctx); + skip_value(loc, ctx); + return; + } else if (loc.current() == '\n') { + // newline is found before finding `=`. assuming "missing `=`". + return; + } + loc.advance(); + } + return; + } + + template + void skip_until_next_table(location& loc, const context& ctx) { + const auto& spec = ctx.toml_spec(); + while (!loc.eof()) { + if (loc.current() == '\n') { + loc.advance(); + const auto line_begin = loc; + + skip_whitespace(loc, ctx); + if (syntax::std_table(spec).scan(loc).is_ok()) { + loc = line_begin; + return; + } + if (syntax::array_table(spec).scan(loc).is_ok()) { + loc = line_begin; + return; + } + } + loc.advance(); + } + } + + } // namespace detail +} // namespace toml + +#if defined(TOML11_COMPILE_SOURCES) +namespace toml { + struct type_config; + struct ordered_type_config; + + namespace detail { + extern template bool skip_whitespace(location& loc, + const context&); + extern template bool skip_empty_lines(location& loc, + const context&); + extern template void skip_comment_block( + location& loc, + const context&); + extern template void skip_empty_or_comment_lines( + location& loc, + const context&); + extern template void skip_string_like(location& loc, + const context&); + extern template void skip_array_like(location& loc, + const context&); + extern template void skip_inline_table_like( + location& loc, + const context&); + extern template void skip_value(location& loc, + const context&); + extern template void skip_key_value_pair( + location& loc, + const context&); + extern template void skip_until_next_table( + location& loc, + const context&); + + extern template bool skip_whitespace( + location& loc, + const context&); + extern template bool skip_empty_lines( + location& loc, + const context&); + extern template void skip_comment_block( + location& loc, + const context&); + extern template void skip_empty_or_comment_lines( + location& loc, + const context&); + extern template void skip_string_like( + location& loc, + const context&); + extern template void skip_array_like( + location& loc, + const context&); + extern template void skip_inline_table_like( + location& loc, + const context&); + extern template void skip_value( + location& loc, + const context&); + extern template void skip_key_value_pair( + location& loc, + const context&); + extern template void skip_until_next_table( + location& loc, + const context&); + + } // namespace detail +} // namespace toml +#endif // TOML11_COMPILE_SOURCES + +#endif // TOML11_SKIP_HPP +#ifndef TOML11_PARSER_HPP +#define TOML11_PARSER_HPP + +#include +#include +#include +#include + +#if defined(TOML11_HAS_FILESYSTEM) && TOML11_HAS_FILESYSTEM + #include +#endif + +namespace toml { + + struct syntax_error final : public ::toml::exception { + public: + syntax_error(std::string what_arg, std::vector err) + : what_(std::move(what_arg)) + , err_(std::move(err)) {} + + ~syntax_error() noexcept override = default; + + const char* what() const noexcept override { + return what_.c_str(); + } + + const std::vector& errors() const noexcept { + return err_; + } + + private: + std::string what_; + std::vector err_; + }; + + struct file_io_error final : public ::toml::exception { + public: + file_io_error(const std::string& msg, const std::string& fname) + : errno_(cxx::make_nullopt()) + , what_(msg + " \"" + fname + "\"") {} + + file_io_error(int errnum, const std::string& msg, const std::string& fname) + : errno_(errnum) + , what_(msg + " \"" + fname + "\": errno=" + std::to_string(errnum)) {} + + ~file_io_error() noexcept override = default; + + const char* what() const noexcept override { + return what_.c_str(); + } + + bool has_errno() const noexcept { + return errno_.has_value(); + } + + int get_errno() const noexcept { + return errno_.value_or(0); + } + + private: + cxx::optional errno_; + std::string what_; + }; + + namespace detail { + + /* ============================================================================ + * __ ___ _ __ _ __ ___ _ _ + * / _/ _ \ ' \| ' \/ _ \ ' \ + * \__\___/_|_|_|_|_|_\___/_||_| + */ + + template + error_info make_syntax_error(std::string title, + const S& scanner, + location loc, + std::string suffix = "") { + auto msg = std::string("expected ") + scanner.expected_chars(loc); + auto src = source_location(region(loc)); + return make_error_info(std::move(title), + std::move(src), + std::move(msg), + std::move(suffix)); + } + + /* ============================================================================ + * _ + * __ ___ _ __ _ __ ___ _ _| |_ + * / _/ _ \ ' \| ' \/ -_) ' \ _| + * \__\___/_|_|_|_|_|_\___|_||_\__| + */ + + template + result, error_info> parse_comment_line( + location& loc, + context& ctx) { + const auto& spec = ctx.toml_spec(); + const auto first = loc; + + skip_whitespace(loc, ctx); + + const auto com_reg = syntax::comment(spec).scan(loc); + if (com_reg.is_ok()) { + // once comment started, newline must follow (or reach EOF). + if (!loc.eof() && !syntax::newline(spec).scan(loc).is_ok()) { + while (!loc.eof()) // skip until newline to continue parsing + { + loc.advance(); + if (loc.current() == '\n') { /*skip LF*/ + loc.advance(); + break; + } + } + return err(make_error_info("toml::parse_comment_line: " + "newline (LF / CRLF) or EOF is expected", + source_location(region(loc)), + "but got this", + "Hint: most of the control characters are " + "not allowed in comments")); + } + return ok(cxx::optional(com_reg.as_string())); + } else { + loc = first; // rollback whitespace to parse indent + return ok(cxx::optional(cxx::make_nullopt())); + } + } + + /* ============================================================================ + * ___ _ + * | _ ) ___ ___| |___ __ _ _ _ + * | _ \/ _ \/ _ \ / -_) _` | ' \ + * |___/\___/\___/_\___\__,_|_||_| + */ + + template + result, error_info> parse_boolean(location& loc, + const context& ctx) { + const auto& spec = ctx.toml_spec(); + + // ---------------------------------------------------------------------- + // check syntax + auto reg = syntax::boolean(spec).scan(loc); + if (!reg.is_ok()) { + return err(make_syntax_error( + "toml::parse_boolean: " + "invalid boolean: boolean must be `true` or `false`, in lowercase. " + "string must be surrounded by `\"`", + syntax::boolean(spec), + loc)); + } + + // ---------------------------------------------------------------------- + // it matches. gen value + const auto str = reg.as_string(); + const auto val = [&str]() { + if (str == "true") { + return true; + } else { + assert(str == "false"); + return false; + } + }(); + + // ---------------------------------------------------------------------- + // no format info for boolean + boolean_format_info fmt; + + return ok(basic_value(val, std::move(fmt), {}, std::move(reg))); + } + + /* ============================================================================ + * ___ _ + * |_ _|_ _| |_ ___ __ _ ___ _ _ + * | || ' \ _/ -_) _` / -_) '_| + * |___|_||_\__\___\__, \___|_| + * |___/ + */ + + template + result, error_info> parse_bin_integer(location& loc, + const context& ctx) { + const auto first = loc; + const auto& spec = ctx.toml_spec(); + auto reg = syntax::bin_int(spec).scan(loc); + if (!reg.is_ok()) { + return err(make_syntax_error( + "toml::parse_bin_integer: " + "invalid integer: bin_int must be like: 0b0101, 0b1111_0000", + syntax::bin_int(spec), + loc)); + } + + auto str = reg.as_string(); + + integer_format_info fmt; + fmt.fmt = integer_format::bin; + fmt.width = str.size() - 2 - + static_cast(std::count(str.begin(), str.end(), '_')); + + const auto first_underscore = std::find(str.rbegin(), str.rend(), '_'); + if (first_underscore != str.rend()) { + fmt.spacer = static_cast( + std::distance(str.rbegin(), first_underscore)); + } + + // skip prefix `0b` and zeros and underscores at the MSB + str.erase(str.begin(), std::find(std::next(str.begin(), 2), str.end(), '1')); + + // remove all `_` before calling TC::parse_int + str.erase(std::remove(str.begin(), str.end(), '_'), str.end()); + + // 0b0000_0000 becomes empty. + if (str.empty()) { + str = "0"; + } + + const auto val = TC::parse_int(str, source_location(region(loc)), 2); + if (val.is_ok()) { + return ok(basic_value(val.as_ok(), std::move(fmt), {}, std::move(reg))); + } else { + loc = first; + return err(val.as_err()); + } + } + + // ---------------------------------------------------------------------------- + + template + result, error_info> parse_oct_integer(location& loc, + const context& ctx) { + const auto first = loc; + const auto& spec = ctx.toml_spec(); + auto reg = syntax::oct_int(spec).scan(loc); + if (!reg.is_ok()) { + return err(make_syntax_error( + "toml::parse_oct_integer: " + "invalid integer: oct_int must be like: 0o775, 0o04_44", + syntax::oct_int(spec), + loc)); + } + + auto str = reg.as_string(); + + integer_format_info fmt; + fmt.fmt = integer_format::oct; + fmt.width = str.size() - 2 - + static_cast(std::count(str.begin(), str.end(), '_')); + + const auto first_underscore = std::find(str.rbegin(), str.rend(), '_'); + if (first_underscore != str.rend()) { + fmt.spacer = static_cast( + std::distance(str.rbegin(), first_underscore)); + } + + // skip prefix `0o` and zeros and underscores at the MSB + str.erase(str.begin(), + std::find_if(std::next(str.begin(), 2), str.end(), [](const char c) { + return c != '0' && c != '_'; + })); + + // remove all `_` before calling TC::parse_int + str.erase(std::remove(str.begin(), str.end(), '_'), str.end()); + + // 0o0000_0000 becomes empty. + if (str.empty()) { + str = "0"; + } + + const auto val = TC::parse_int(str, source_location(region(loc)), 8); + if (val.is_ok()) { + return ok(basic_value(val.as_ok(), std::move(fmt), {}, std::move(reg))); + } else { + loc = first; + return err(val.as_err()); + } + } + + template + result, error_info> parse_hex_integer(location& loc, + const context& ctx) { + const auto first = loc; + const auto& spec = ctx.toml_spec(); + auto reg = syntax::hex_int(spec).scan(loc); + if (!reg.is_ok()) { + return err(make_syntax_error( + "toml::parse_hex_integer: " + "invalid integer: hex_int must be like: 0xC0FFEE, 0xdead_beef", + syntax::hex_int(spec), + loc)); + } + + auto str = reg.as_string(); + + integer_format_info fmt; + fmt.fmt = integer_format::hex; + fmt.width = str.size() - 2 - + static_cast(std::count(str.begin(), str.end(), '_')); + + const auto first_underscore = std::find(str.rbegin(), str.rend(), '_'); + if (first_underscore != str.rend()) { + fmt.spacer = static_cast( + std::distance(str.rbegin(), first_underscore)); + } + + // skip prefix `0x` and zeros and underscores at the MSB + str.erase(str.begin(), + std::find_if(std::next(str.begin(), 2), str.end(), [](const char c) { + return c != '0' && c != '_'; + })); + + // remove all `_` before calling TC::parse_int + str.erase(std::remove(str.begin(), str.end(), '_'), str.end()); + + // 0x0000_0000 becomes empty. + if (str.empty()) { + str = "0"; + } + + // prefix zero and _ is removed. check if it uses upper/lower case. + // if both upper and lower case letters are found, set upper=true. + const auto lower_not_found = std::find_if(str.begin(), str.end(), [](const char c) { + return std::islower(static_cast(c)) != 0; + }) == str.end(); + const auto upper_found = std::find_if(str.begin(), str.end(), [](const char c) { + return std::isupper(static_cast(c)) != 0; + }) != str.end(); + fmt.uppercase = lower_not_found || upper_found; + + const auto val = TC::parse_int(str, source_location(region(loc)), 16); + if (val.is_ok()) { + return ok(basic_value(val.as_ok(), std::move(fmt), {}, std::move(reg))); + } else { + loc = first; + return err(val.as_err()); + } + } + + template + result, error_info> parse_dec_integer(location& loc, + const context& ctx) { + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + // ---------------------------------------------------------------------- + // check syntax + auto reg = syntax::dec_int(spec).scan(loc); + if (!reg.is_ok()) { + return err(make_syntax_error( + "toml::parse_dec_integer: " + "invalid integer: dec_int must be like: 42, 123_456_789", + syntax::dec_int(spec), + loc)); + } + + // ---------------------------------------------------------------------- + // it matches. gen value + auto str = reg.as_string(); + + integer_format_info fmt; + fmt.fmt = integer_format::dec; + fmt.width = str.size() - static_cast( + std::count(str.begin(), str.end(), '_')); + + const auto first_underscore = std::find(str.rbegin(), str.rend(), '_'); + if (first_underscore != str.rend()) { + fmt.spacer = static_cast( + std::distance(str.rbegin(), first_underscore)); + } + + // remove all `_` before calling TC::parse_int + str.erase(std::remove(str.begin(), str.end(), '_'), str.end()); + + auto src = source_location(region(loc)); + const auto val = TC::parse_int(str, src, 10); + if (val.is_err()) { + loc = first; + return err(val.as_err()); + } + + // ---------------------------------------------------------------------- + // parse suffix (extension) + + if (spec.ext_num_suffix && loc.current() == '_') { + const auto sfx_reg = syntax::num_suffix(spec).scan(loc); + if (!sfx_reg.is_ok()) { + loc = first; + return err(make_error_info( + "toml::parse_dec_integer: " + "invalid suffix: should be `_ non-digit-graph (graph | _graph)`", + source_location(region(loc)), + "here")); + } + auto sfx = sfx_reg.as_string(); + assert(!sfx.empty() && sfx.front() == '_'); + sfx.erase(sfx.begin()); // remove the first `_` + + fmt.suffix = sfx; + } + + return ok(basic_value(val.as_ok(), std::move(fmt), {}, std::move(reg))); + } + + template + result, error_info> parse_integer(location& loc, + const context& ctx) { + const auto first = loc; + + if (!loc.eof() && (loc.current() == '+' || loc.current() == '-')) { + // skip +/- to diagnose +0xDEADBEEF or -0b0011 (invalid). + // without this, +0xDEAD_BEEF will be parsed as a decimal int and + // unexpected "xDEAD_BEEF" will appear after integer "+0". + loc.advance(); + } + + if (!loc.eof() && loc.current() == '0') { + loc.advance(); + if (loc.eof()) { + // `[+-]?0`. parse as an decimal integer. + loc = first; + return parse_dec_integer(loc, ctx); + } + + const auto prefix = loc.current(); + auto prefix_src = source_location(region(loc)); + + loc = first; + + if (prefix == 'b') { + return parse_bin_integer(loc, ctx); + } + if (prefix == 'o') { + return parse_oct_integer(loc, ctx); + } + if (prefix == 'x') { + return parse_hex_integer(loc, ctx); + } + + if (std::isdigit(prefix)) { + auto src = source_location(region(loc)); + return err( + make_error_info("toml::parse_integer: " + "leading zero in an decimal integer is not allowed", + std::move(src), + "leading zero")); + } + } + + loc = first; + return parse_dec_integer(loc, ctx); + } + + /* ============================================================================ + * ___ _ _ _ + * | __| |___ __ _| |_(_)_ _ __ _ + * | _|| / _ \/ _` | _| | ' \/ _` | + * |_| |_\___/\__,_|\__|_|_||_\__, | + * |___/ + */ + + template + result, error_info> parse_floating(location& loc, + const context& ctx) { + using floating_type = typename basic_value::floating_type; + + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + // ---------------------------------------------------------------------- + // check syntax + bool is_hex = false; + std::string str; + region reg; + if (spec.ext_hex_float && + sequence(character('0'), character('x')).scan(loc).is_ok()) { + loc = first; + is_hex = true; + + reg = syntax::hex_floating(spec).scan(loc); + if (!reg.is_ok()) { + return err(make_syntax_error( + "toml::parse_floating: " + "invalid hex floating: float must be like: 0xABCp-3f", + syntax::floating(spec), + loc)); + } + str = reg.as_string(); + } else { + reg = syntax::floating(spec).scan(loc); + if (!reg.is_ok()) { + return err(make_syntax_error( + "toml::parse_floating: " + "invalid floating: float must be like: -3.14159_26535, 6.022e+23, " + "inf, or nan (lowercase).", + syntax::floating(spec), + loc)); + } + str = reg.as_string(); + } + + // ---------------------------------------------------------------------- + // it matches. gen value + + floating_format_info fmt; + + if (is_hex) { + fmt.fmt = floating_format::hex; + } else { + // since we already checked that the string conforms the TOML standard. + if (std::find(str.begin(), str.end(), 'e') != str.end() || + std::find(str.begin(), str.end(), 'E') != str.end()) { + fmt.fmt = floating_format::scientific; // use exponent part + } else { + fmt.fmt = floating_format::fixed; // do not use exponent part + } + } + + str.erase(std::remove(str.begin(), str.end(), '_'), str.end()); + + floating_type val { 0 }; + + if (str == "inf" || str == "+inf") { + TOML11_CONSTEXPR_IF(std::numeric_limits::has_infinity) { + val = std::numeric_limits::infinity(); + } + else { + return err(make_error_info( + "toml::parse_floating: inf value found" + " but the current environment does not support inf. Please" + " make sure that the floating-point implementation conforms" + " IEEE 754/ISO 60559 international standard.", + source_location(region(loc)), + "floating_type: inf is not supported")); + } + } else if (str == "-inf") { + TOML11_CONSTEXPR_IF(std::numeric_limits::has_infinity) { + val = -std::numeric_limits::infinity(); + } + else { + return err(make_error_info( + "toml::parse_floating: inf value found" + " but the current environment does not support inf. Please" + " make sure that the floating-point implementation conforms" + " IEEE 754/ISO 60559 international standard.", + source_location(region(loc)), + "floating_type: inf is not supported")); + } + } else if (str == "nan" || str == "+nan") { + TOML11_CONSTEXPR_IF(std::numeric_limits::has_quiet_NaN) { + val = std::numeric_limits::quiet_NaN(); + } + else TOML11_CONSTEXPR_IF( + std::numeric_limits::has_signaling_NaN) { + val = std::numeric_limits::signaling_NaN(); + } + else { + return err(make_error_info( + "toml::parse_floating: NaN value found" + " but the current environment does not support NaN. Please" + " make sure that the floating-point implementation conforms" + " IEEE 754/ISO 60559 international standard.", + source_location(region(loc)), + "floating_type: NaN is not supported")); + } + } else if (str == "-nan") { + using std::copysign; + TOML11_CONSTEXPR_IF(std::numeric_limits::has_quiet_NaN) { + val = copysign(std::numeric_limits::quiet_NaN(), + floating_type(-1)); + } + else TOML11_CONSTEXPR_IF( + std::numeric_limits::has_signaling_NaN) { + val = copysign(std::numeric_limits::signaling_NaN(), + floating_type(-1)); + } + else { + return err(make_error_info( + "toml::parse_floating: NaN value found" + " but the current environment does not support NaN. Please" + " make sure that the floating-point implementation conforms" + " IEEE 754/ISO 60559 international standard.", + source_location(region(loc)), + "floating_type: NaN is not supported")); + } + } else { + // set precision + const auto has_sign = !str.empty() && + (str.front() == '+' || str.front() == '-'); + const auto decpoint = std::find(str.begin(), str.end(), '.'); + const auto exponent = std::find_if(str.begin(), str.end(), [](const char c) { + return c == 'e' || c == 'E'; + }); + if (decpoint != str.end() && exponent != str.end()) { + assert(decpoint < exponent); + } + + if (fmt.fmt == floating_format::scientific) { + // total width + fmt.prec = static_cast(std::distance(str.begin(), exponent)); + if (has_sign) { + fmt.prec -= 1; + } + if (decpoint != str.end()) { + fmt.prec -= 1; + } + } else if (fmt.fmt == floating_format::hex) { + fmt.prec = std::numeric_limits::max_digits10; + } else { + // width after decimal point + fmt.prec = static_cast( + std::distance(std::next(decpoint), exponent)); + } + + auto src = source_location(region(loc)); + const auto res = TC::parse_float(str, src, is_hex); + if (res.is_ok()) { + val = res.as_ok(); + } else { + return err(res.as_err()); + } + } + + // ---------------------------------------------------------------------- + // parse suffix (extension) + + if (spec.ext_num_suffix && loc.current() == '_') { + const auto sfx_reg = syntax::num_suffix(spec).scan(loc); + if (!sfx_reg.is_ok()) { + auto src = source_location(region(loc)); + loc = first; + return err(make_error_info( + "toml::parse_floating: " + "invalid suffix: should be `_ non-digit-graph (graph | _graph)`", + std::move(src), + "here")); + } + auto sfx = sfx_reg.as_string(); + assert(!sfx.empty() && sfx.front() == '_'); + sfx.erase(sfx.begin()); // remove the first `_` + + fmt.suffix = sfx; + } + + return ok(basic_value(val, std::move(fmt), {}, std::move(reg))); + } + + /* ============================================================================ + * ___ _ _ _ + * | \ __ _| |_ ___| |_(_)_ __ ___ + * | |) / _` | _/ -_) _| | ' \/ -_) + * |___/\__,_|\__\___|\__|_|_|_|_\___| + */ + + // all the offset_datetime, local_datetime, local_date parses date part. + template + result, error_info> + parse_local_date_only(location& loc, const context& ctx) { + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + local_date_format_info fmt; + + // ---------------------------------------------------------------------- + // check syntax + auto reg = syntax::local_date(spec).scan(loc); + if (!reg.is_ok()) { + return err(make_syntax_error( + "toml::parse_local_date: " + "invalid date: date must be like: 1234-05-06, yyyy-mm-dd.", + syntax::local_date(spec), + loc)); + } + + // ---------------------------------------------------------------------- + // it matches. gen value + const auto str = reg.as_string(); + + // 0123456789 + // yyyy-mm-dd + const auto year_r = from_string(str.substr(0, 4)); + const auto month_r = from_string(str.substr(5, 2)); + const auto day_r = from_string(str.substr(8, 2)); + + if (year_r.is_err()) { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_local_date: " + "failed to read year `" + + str.substr(0, 4) + "`", + std::move(src), + "here")); + } + if (month_r.is_err()) { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_local_date: " + "failed to read month `" + + str.substr(5, 2) + "`", + std::move(src), + "here")); + } + if (day_r.is_err()) { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_local_date: " + "failed to read day `" + + str.substr(8, 2) + "`", + std::move(src), + "here")); + } + + const auto year = year_r.unwrap(); + const auto month = month_r.unwrap(); + const auto day = day_r.unwrap(); + + { + // We briefly check whether the input date is valid or not. + // Actually, because of the historical reasons, there are several + // edge cases, such as 1582/10/5-1582/10/14 (only in several countries). + // But here, we do not care about it. + // It makes the code complicated and there is only low probability + // that such a specific date is needed in practice. If someone need to + // validate date accurately, that means that the one need a specialized + // library for their purpose in another layer. + + const bool is_leap = (year % 4 == 0) && + ((year % 100 != 0) || (year % 400 == 0)); + const auto max_day = [month, is_leap]() { + if (month == 2) { + return is_leap ? 29 : 28; + } + if (month == 4 || month == 6 || month == 9 || month == 11) { + return 30; + } + return 31; + }(); + + if ((month < 1 || 12 < month) || (day < 1 || max_day < day)) { + auto src = source_location(region(first)); + return err( + make_error_info("toml::parse_local_date: invalid date.", + std::move(src), + "month must be 01-12, day must be any of " + "01-28,29,30,31 depending on the month/year.")); + } + } + + return ok( + std::make_tuple(local_date(year, static_cast(month - 1), day), + std::move(fmt), + std::move(reg))); + } + + template + result, error_info> parse_local_date(location& loc, + const context& ctx) { + auto val_fmt_reg = parse_local_date_only(loc, ctx); + if (val_fmt_reg.is_err()) { + return err(val_fmt_reg.unwrap_err()); + } + + auto val = std::move(std::get<0>(val_fmt_reg.unwrap())); + auto fmt = std::move(std::get<1>(val_fmt_reg.unwrap())); + auto reg = std::move(std::get<2>(val_fmt_reg.unwrap())); + + return ok( + basic_value(std::move(val), std::move(fmt), {}, std::move(reg))); + } + + // all the offset_datetime, local_datetime, local_time parses date part. + template + result, error_info> + parse_local_time_only(location& loc, const context& ctx) { + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + local_time_format_info fmt; + + // ---------------------------------------------------------------------- + // check syntax + auto reg = syntax::local_time(spec).scan(loc); + if (!reg.is_ok()) { + if (spec.v1_1_0_make_seconds_optional) { + return err(make_syntax_error( + "toml::parse_local_time: " + "invalid time: time must be HH:MM(:SS.sss) (seconds are optional)", + syntax::local_time(spec), + loc)); + } else { + return err( + make_syntax_error("toml::parse_local_time: " + "invalid time: time must be HH:MM:SS(.sss) " + "(subseconds are optional)", + syntax::local_time(spec), + loc)); + } + } + + // ---------------------------------------------------------------------- + // it matches. gen value + const auto str = reg.as_string(); + + // at least we have HH:MM. + // 01234 + // HH:MM + const auto hour_r = from_string(str.substr(0, 2)); + const auto minute_r = from_string(str.substr(3, 2)); + + if (hour_r.is_err()) { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_local_time: " + "failed to read hour `" + + str.substr(0, 2) + "`", + std::move(src), + "here")); + } + if (minute_r.is_err()) { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_local_time: " + "failed to read minute `" + + str.substr(3, 2) + "`", + std::move(src), + "here")); + } + + const auto hour = hour_r.unwrap(); + const auto minute = minute_r.unwrap(); + + if ((hour < 0 || 24 <= hour) || (minute < 0 || 60 <= minute)) { + auto src = source_location(region(first)); + return err( + make_error_info("toml::parse_local_time: invalid time.", + std::move(src), + "hour must be 00-23, minute must be 00-59.")); + } + + // ----------------------------------------------------------------------- + // we have hour and minute. + // Since toml v1.1.0, second and subsecond part becomes optional. + // Check the version and return if second does not exist. + + if (str.size() == 5 && spec.v1_1_0_make_seconds_optional) { + fmt.has_seconds = false; + fmt.subsecond_precision = 0; + return ok(std::make_tuple(local_time(hour, minute, 0), + std::move(fmt), + std::move(reg))); + } + assert(str.at(5) == ':'); + + // we have at least `:SS` part. `.subseconds` are optional. + + // 0 1 + // 012345678901234 + // HH:MM:SS.subsec + const auto sec_r = from_string(str.substr(6, 2)); + if (sec_r.is_err()) { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_local_time: " + "failed to read second `" + + str.substr(6, 2) + "`", + std::move(src), + "here")); + } + const auto sec = sec_r.unwrap(); + + if (sec < 0 || 60 < sec) // :60 is allowed + { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_local_time: invalid time.", + std::move(src), + "second must be 00-60.")); + } + + if (str.size() == 8) { + fmt.has_seconds = true; + fmt.subsecond_precision = 0; + return ok(std::make_tuple(local_time(hour, minute, sec), + std::move(fmt), + std::move(reg))); + } + + assert(str.at(8) == '.'); + + auto secfrac = str.substr(9, str.size() - 9); + + fmt.has_seconds = true; + fmt.subsecond_precision = secfrac.size(); + + while (secfrac.size() < 9) { + secfrac += '0'; + } + assert(9 <= secfrac.size()); + const auto ms_r = from_string(secfrac.substr(0, 3)); + const auto us_r = from_string(secfrac.substr(3, 3)); + const auto ns_r = from_string(secfrac.substr(6, 3)); + + if (ms_r.is_err()) { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_local_time: " + "failed to read milliseconds `" + + secfrac.substr(0, 3) + "`", + std::move(src), + "here")); + } + if (us_r.is_err()) { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_local_time: " + "failed to read microseconds`" + + str.substr(3, 3) + "`", + std::move(src), + "here")); + } + if (ns_r.is_err()) { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_local_time: " + "failed to read nanoseconds`" + + str.substr(6, 3) + "`", + std::move(src), + "here")); + } + const auto ms = ms_r.unwrap(); + const auto us = us_r.unwrap(); + const auto ns = ns_r.unwrap(); + + return ok(std::make_tuple(local_time(hour, minute, sec, ms, us, ns), + std::move(fmt), + std::move(reg))); + } + + template + result, error_info> parse_local_time(location& loc, + const context& ctx) { + const auto first = loc; + + auto val_fmt_reg = parse_local_time_only(loc, ctx); + if (val_fmt_reg.is_err()) { + return err(val_fmt_reg.unwrap_err()); + } + + auto val = std::move(std::get<0>(val_fmt_reg.unwrap())); + auto fmt = std::move(std::get<1>(val_fmt_reg.unwrap())); + auto reg = std::move(std::get<2>(val_fmt_reg.unwrap())); + + return ok( + basic_value(std::move(val), std::move(fmt), {}, std::move(reg))); + } + + template + result, error_info> parse_local_datetime(location& loc, + const context& ctx) { + using char_type = location::char_type; + + const auto first = loc; + + local_datetime_format_info fmt; + + // ---------------------------------------------------------------------- + + auto date_fmt_reg = parse_local_date_only(loc, ctx); + if (date_fmt_reg.is_err()) { + return err(date_fmt_reg.unwrap_err()); + } + + if (loc.current() == char_type('T')) { + loc.advance(); + fmt.delimiter = datetime_delimiter_kind::upper_T; + } else if (loc.current() == char_type('t')) { + loc.advance(); + fmt.delimiter = datetime_delimiter_kind::lower_t; + } else if (loc.current() == char_type(' ')) { + loc.advance(); + fmt.delimiter = datetime_delimiter_kind::space; + } else { + auto src = source_location(region(loc)); + return err( + make_error_info("toml::parse_local_datetime: " + "expect date-time delimiter `T`, `t` or ` `(space).", + std::move(src), + "here")); + } + + auto time_fmt_reg = parse_local_time_only(loc, ctx); + if (time_fmt_reg.is_err()) { + return err(time_fmt_reg.unwrap_err()); + } + + fmt.has_seconds = std::get<1>(time_fmt_reg.unwrap()).has_seconds; + fmt.subsecond_precision = std::get<1>(time_fmt_reg.unwrap()).subsecond_precision; + + // ---------------------------------------------------------------------- + + region reg(first, loc); + local_datetime val(std::get<0>(date_fmt_reg.unwrap()), + std::get<0>(time_fmt_reg.unwrap())); + + return ok(basic_value(val, std::move(fmt), {}, std::move(reg))); + } + + template + result, error_info> parse_offset_datetime( + location& loc, + const context& ctx) { + using char_type = location::char_type; + + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + offset_datetime_format_info fmt; + + // ---------------------------------------------------------------------- + // date part + + auto date_fmt_reg = parse_local_date_only(loc, ctx); + if (date_fmt_reg.is_err()) { + return err(date_fmt_reg.unwrap_err()); + } + + // ---------------------------------------------------------------------- + // delimiter + + if (loc.current() == char_type('T')) { + loc.advance(); + fmt.delimiter = datetime_delimiter_kind::upper_T; + } else if (loc.current() == char_type('t')) { + loc.advance(); + fmt.delimiter = datetime_delimiter_kind::lower_t; + } else if (loc.current() == char_type(' ')) { + loc.advance(); + fmt.delimiter = datetime_delimiter_kind::space; + } else { + auto src = source_location(region(loc)); + return err( + make_error_info("toml::parse_offset_datetime: " + "expect date-time delimiter `T` or ` `(space).", + std::move(src), + "here")); + } + + // ---------------------------------------------------------------------- + // time part + + auto time_fmt_reg = parse_local_time_only(loc, ctx); + if (time_fmt_reg.is_err()) { + return err(time_fmt_reg.unwrap_err()); + } + + fmt.has_seconds = std::get<1>(time_fmt_reg.unwrap()).has_seconds; + fmt.subsecond_precision = std::get<1>(time_fmt_reg.unwrap()).subsecond_precision; + + // ---------------------------------------------------------------------- + // offset part + + const auto ofs_reg = syntax::time_offset(spec).scan(loc); + if (!ofs_reg.is_ok()) { + return err(make_syntax_error( + "toml::parse_offset_datetime: " + "invalid offset: offset must be like: Z, +01:00, or -10:00.", + syntax::time_offset(spec), + loc)); + } + + const auto ofs_str = ofs_reg.as_string(); + + time_offset offset(0, 0); + + assert(ofs_str.size() != 0); + + if (ofs_str.at(0) == char_type('+') || ofs_str.at(0) == char_type('-')) { + const auto hour_r = from_string(ofs_str.substr(1, 2)); + const auto minute_r = from_string(ofs_str.substr(4, 2)); + if (hour_r.is_err()) { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_offset_datetime: " + "Failed to read offset hour part", + std::move(src), + "here")); + } + if (minute_r.is_err()) { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_offset_datetime: " + "Failed to read offset minute part", + std::move(src), + "here")); + } + const auto hour = hour_r.unwrap(); + const auto minute = minute_r.unwrap(); + + if (ofs_str.at(0) == '+') { + offset = time_offset(hour, minute); + } else { + offset = time_offset(-hour, -minute); + } + } else { + assert(ofs_str.at(0) == char_type('Z') || ofs_str.at(0) == char_type('z')); + } + + if (offset.hour < -24 || 24 < offset.hour || offset.minute < -60 || + 60 < offset.minute) { + return err( + make_error_info("toml::parse_offset_datetime: " + "too large offset: |hour| <= 24, |minute| <= 60", + source_location(region(first, loc)), + "here")); + } + + // ---------------------------------------------------------------------- + + region reg(first, loc); + offset_datetime val(local_datetime(std::get<0>(date_fmt_reg.unwrap()), + std::get<0>(time_fmt_reg.unwrap())), + offset); + + return ok(basic_value(val, std::move(fmt), {}, std::move(reg))); + } + + /* ============================================================================ + * ___ _ _ + * / __| |_ _ _(_)_ _ __ _ + * \__ \ _| '_| | ' \/ _` | + * |___/\__|_| |_|_||_\__, | + * |___/ + */ + + template + result::string_type, error_info> parse_utf8_codepoint( + const region& reg) { + using string_type = typename basic_value::string_type; + using char_type = typename string_type::value_type; + + // assert(reg.as_lines().size() == 1); // XXX heavy check + + const auto str = reg.as_string(); + assert(!str.empty()); + assert(str.front() == 'u' || str.front() == 'U' || str.front() == 'x'); + + std::uint_least32_t codepoint; + std::istringstream iss(str.substr(1)); + iss >> std::hex >> codepoint; + + const auto to_char = [](const std::uint_least32_t i) noexcept -> char_type { + const auto uc = static_cast(i & 0xFF); + return cxx::bit_cast(uc); + }; + + string_type character; + if (codepoint < 0x80) // U+0000 ... U+0079 ; just an ASCII. + { + character += static_cast(codepoint); + } else if (codepoint < 0x800) // U+0080 ... U+07FF + { + // 110yyyyx 10xxxxxx; 0x3f == 0b0011'1111 + character += to_char(0xC0 | (codepoint >> 6)); + character += to_char(0x80 | (codepoint & 0x3F)); + } else if (codepoint < 0x10000) // U+0800...U+FFFF + { + if (0xD800 <= codepoint && codepoint <= 0xDFFF) { + auto src = source_location(reg); + return err(make_error_info("toml::parse_utf8_codepoint: " + "[0xD800, 0xDFFF] is not a valid UTF-8", + std::move(src), + "here")); + } + assert(codepoint < 0xD800 || 0xDFFF < codepoint); + // 1110yyyy 10yxxxxx 10xxxxxx + character += to_char(0xE0 | (codepoint >> 12)); + character += to_char(0x80 | ((codepoint >> 6) & 0x3F)); + character += to_char(0x80 | ((codepoint) & 0x3F)); + } else if (codepoint < 0x110000) // U+010000 ... U+10FFFF + { + // 11110yyy 10yyxxxx 10xxxxxx 10xxxxxx + character += to_char(0xF0 | (codepoint >> 18)); + character += to_char(0x80 | ((codepoint >> 12) & 0x3F)); + character += to_char(0x80 | ((codepoint >> 6) & 0x3F)); + character += to_char(0x80 | ((codepoint) & 0x3F)); + } else // out of UTF-8 region + { + auto src = source_location(reg); + return err(make_error_info("toml::parse_utf8_codepoint: " + "input codepoint is too large.", + std::move(src), + "must be in range [0x00, 0x10FFFF]")); + } + return ok(character); + } + + template + result::string_type, error_info> parse_escape_sequence( + location& loc, + const context& ctx) { + using string_type = typename basic_value::string_type; + using char_type = typename string_type::value_type; + + const auto& spec = ctx.toml_spec(); + + assert(!loc.eof()); + assert(loc.current() == '\\'); + loc.advance(); // consume the first backslash + + string_type retval; + + if (loc.current() == '\\') { + retval += char_type('\\'); + loc.advance(); + } else if (loc.current() == '"') { + retval += char_type('\"'); + loc.advance(); + } else if (loc.current() == 'b') { + retval += char_type('\b'); + loc.advance(); + } else if (loc.current() == 'f') { + retval += char_type('\f'); + loc.advance(); + } else if (loc.current() == 'n') { + retval += char_type('\n'); + loc.advance(); + } else if (loc.current() == 'r') { + retval += char_type('\r'); + loc.advance(); + } else if (loc.current() == 't') { + retval += char_type('\t'); + loc.advance(); + } else if (spec.v1_1_0_add_escape_sequence_e && loc.current() == 'e') { + retval += char_type('\x1b'); + loc.advance(); + } else if (spec.v1_1_0_add_escape_sequence_x && loc.current() == 'x') { + auto scanner = sequence(character('x'), + repeat_exact(2, syntax::hexdig(spec))); + const auto reg = scanner.scan(loc); + if (!reg.is_ok()) { + auto src = source_location(region(loc)); + return err( + make_error_info("toml::parse_escape_sequence: " + "invalid token found in UTF-8 codepoint \\xhh", + std::move(src), + "here")); + } + const auto utf8 = parse_utf8_codepoint(reg); + if (utf8.is_err()) { + return err(utf8.as_err()); + } + retval += utf8.unwrap(); + } else if (loc.current() == 'u') { + auto scanner = sequence(character('u'), + repeat_exact(4, syntax::hexdig(spec))); + const auto reg = scanner.scan(loc); + if (!reg.is_ok()) { + auto src = source_location(region(loc)); + return err( + make_error_info("toml::parse_escape_sequence: " + "invalid token found in UTF-8 codepoint \\uhhhh", + std::move(src), + "here")); + } + const auto utf8 = parse_utf8_codepoint(reg); + if (utf8.is_err()) { + return err(utf8.as_err()); + } + retval += utf8.unwrap(); + } else if (loc.current() == 'U') { + auto scanner = sequence(character('U'), + repeat_exact(8, syntax::hexdig(spec))); + const auto reg = scanner.scan(loc); + if (!reg.is_ok()) { + auto src = source_location(region(loc)); + return err(make_error_info( + "toml::parse_escape_sequence: " + "invalid token found in UTF-8 codepoint \\Uhhhhhhhh", + std::move(src), + "here")); + } + const auto utf8 = parse_utf8_codepoint(reg); + if (utf8.is_err()) { + return err(utf8.as_err()); + } + retval += utf8.unwrap(); + } else { + auto src = source_location(region(loc)); + std::string escape_seqs = + "allowed escape seqs: \\\\, \\\", \\b, \\f, \\n, \\r, \\t"; + if (spec.v1_1_0_add_escape_sequence_e) { + escape_seqs += ", \\e"; + } + if (spec.v1_1_0_add_escape_sequence_x) { + escape_seqs += ", \\xhh"; + } + escape_seqs += ", \\uhhhh, or \\Uhhhhhhhh"; + + return err(make_error_info("toml::parse_escape_sequence: " + "unknown escape sequence.", + std::move(src), + escape_seqs)); + } + return ok(retval); + } + + template + result, error_info> parse_ml_basic_string( + location& loc, + const context& ctx) { + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + string_format_info fmt; + fmt.fmt = string_format::multiline_basic; + + auto reg = syntax::ml_basic_string(spec).scan(loc); + if (!reg.is_ok()) { + return err(make_syntax_error("toml::parse_ml_basic_string: " + "invalid string format", + syntax::ml_basic_string(spec), + loc)); + } + + // ---------------------------------------------------------------------- + // it matches. gen value + + auto str = reg.as_string(); + + // we already checked that it starts with """ and ends with """. + assert(str.substr(0, 3) == "\"\"\""); + str.erase(0, 3); + + assert(str.size() >= 3); + assert(str.substr(str.size() - 3, 3) == "\"\"\""); + str.erase(str.size() - 3, 3); + + // the first newline just after """ is trimmed + if (str.size() >= 1 && str.at(0) == '\n') { + str.erase(0, 1); + fmt.start_with_newline = true; + } else if (str.size() >= 2 && str.at(0) == '\r' && str.at(1) == '\n') { + str.erase(0, 2); + fmt.start_with_newline = true; + } + + using string_type = typename basic_value::string_type; + string_type val; + { + auto iter = str.cbegin(); + while (iter != str.cend()) { + if (*iter == '\\') // remove whitespaces around escaped-newline + { + // we assume that the string is not too long to copy + auto loc2 = make_temporary_location(make_string(iter, str.cend())); + if (syntax::escaped_newline(spec).scan(loc2).is_ok()) { + std::advance(iter, + loc2.get_location()); // skip escaped newline and indent + // now iter points non-WS char + assert(iter == str.end() || (*iter != ' ' && *iter != '\t')); + } else // normal escape seq. + { + auto esc = parse_escape_sequence(loc2, ctx); + + // syntax does not check its value. the unicode codepoint may be + // invalid, e.g. out-of-bound, [0xD800, 0xDFFF] + if (esc.is_err()) { + return err(esc.unwrap_err()); + } + + val += esc.unwrap(); + std::advance(iter, loc2.get_location()); + } + } else // we already checked the syntax. we don't need to check it again. + { + val += static_cast(*iter); + ++iter; + } + } + } + + return ok( + basic_value(std::move(val), std::move(fmt), {}, std::move(reg))); + } + + template + result::string_type, region>, error_info> + parse_basic_string_only(location& loc, const context& ctx) { + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + auto reg = syntax::basic_string(spec).scan(loc); + if (!reg.is_ok()) { + return err(make_syntax_error("toml::parse_basic_string: " + "invalid string format", + syntax::basic_string(spec), + loc)); + } + + // ---------------------------------------------------------------------- + // it matches. gen value + + auto str = reg.as_string(); + + assert(str.back() == '\"'); + str.pop_back(); + assert(str.at(0) == '\"'); + str.erase(0, 1); + + using string_type = typename basic_value::string_type; + using char_type = typename string_type::value_type; + string_type val; + + { + auto iter = str.begin(); + while (iter != str.end()) { + if (*iter == '\\') { + auto loc2 = make_temporary_location(make_string(iter, str.end())); + + auto esc = parse_escape_sequence(loc2, ctx); + + // syntax does not check its value. the unicode codepoint may be + // invalid, e.g. out-of-bound, [0xD800, 0xDFFF] + if (esc.is_err()) { + return err(esc.unwrap_err()); + } + + val += esc.unwrap(); + std::advance(iter, loc2.get_location()); + } else { + val += char_type(*iter); // we already checked the syntax. + ++iter; + } + } + } + return ok(std::make_pair(val, reg)); + } + + template + result, error_info> parse_basic_string(location& loc, + const context& ctx) { + const auto first = loc; + + string_format_info fmt; + fmt.fmt = string_format::basic; + + auto val_res = parse_basic_string_only(loc, ctx); + if (val_res.is_err()) { + return err(std::move(val_res.unwrap_err())); + } + auto val = std::move(val_res.unwrap().first); + auto reg = std::move(val_res.unwrap().second); + + return ok( + basic_value(std::move(val), std::move(fmt), {}, std::move(reg))); + } + + template + result, error_info> parse_ml_literal_string( + location& loc, + const context& ctx) { + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + string_format_info fmt; + fmt.fmt = string_format::multiline_literal; + + auto reg = syntax::ml_literal_string(spec).scan(loc); + if (!reg.is_ok()) { + return err(make_syntax_error("toml::parse_ml_literal_string: " + "invalid string format", + syntax::ml_literal_string(spec), + loc)); + } + + // ---------------------------------------------------------------------- + // it matches. gen value + + auto str = reg.as_string(); + + assert(str.substr(0, 3) == "'''"); + assert(str.substr(str.size() - 3, 3) == "'''"); + str.erase(0, 3); + str.erase(str.size() - 3, 3); + + // the first newline just after """ is trimmed + if (str.size() >= 1 && str.at(0) == '\n') { + str.erase(0, 1); + fmt.start_with_newline = true; + } else if (str.size() >= 2 && str.at(0) == '\r' && str.at(1) == '\n') { + str.erase(0, 2); + fmt.start_with_newline = true; + } + + using string_type = typename basic_value::string_type; + string_type val(str.begin(), str.end()); + + return ok( + basic_value(std::move(val), std::move(fmt), {}, std::move(reg))); + } + + template + result::string_type, region>, error_info> + parse_literal_string_only(location& loc, const context& ctx) { + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + auto reg = syntax::literal_string(spec).scan(loc); + if (!reg.is_ok()) { + return err(make_syntax_error("toml::parse_literal_string: " + "invalid string format", + syntax::literal_string(spec), + loc)); + } + + // ---------------------------------------------------------------------- + // it matches. gen value + + auto str = reg.as_string(); + + assert(str.back() == '\''); + str.pop_back(); + assert(str.at(0) == '\''); + str.erase(0, 1); + + using string_type = typename basic_value::string_type; + string_type val(str.begin(), str.end()); + + return ok(std::make_pair(std::move(val), std::move(reg))); + } + + template + result, error_info> parse_literal_string(location& loc, + const context& ctx) { + const auto first = loc; + + string_format_info fmt; + fmt.fmt = string_format::literal; + + auto val_res = parse_literal_string_only(loc, ctx); + if (val_res.is_err()) { + return err(std::move(val_res.unwrap_err())); + } + auto val = std::move(val_res.unwrap().first); + auto reg = std::move(val_res.unwrap().second); + + return ok( + basic_value(std::move(val), std::move(fmt), {}, std::move(reg))); + } + + template + result, error_info> parse_string(location& loc, + const context& ctx) { + const auto first = loc; + + if (!loc.eof() && loc.current() == '"') { + if (literal("\"\"\"").scan(loc).is_ok()) { + loc = first; + return parse_ml_basic_string(loc, ctx); + } else { + loc = first; + return parse_basic_string(loc, ctx); + } + } else if (!loc.eof() && loc.current() == '\'') { + if (literal("'''").scan(loc).is_ok()) { + loc = first; + return parse_ml_literal_string(loc, ctx); + } else { + loc = first; + return parse_literal_string(loc, ctx); + } + } else { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_string: " + "not a string", + std::move(src), + "here")); + } + } + + template + result, error_info> parse_null(location& loc, + const context& ctx) { + const auto& spec = ctx.toml_spec(); + if (!spec.ext_null_value) { + return err( + make_error_info("toml::parse_null: " + "invalid spec: spec.ext_null_value must be true.", + source_location(region(loc)), + "here")); + } + + // ---------------------------------------------------------------------- + // check syntax + auto reg = syntax::null_value(spec).scan(loc); + if (!reg.is_ok()) { + return err(make_syntax_error("toml::parse_null: " + "invalid null: null must be lowercase. ", + syntax::null_value(spec), + loc)); + } + + // ---------------------------------------------------------------------- + // it matches. gen value + + // ---------------------------------------------------------------------- + // no format info for boolean + + return ok(basic_value(detail::none_t {}, std::move(reg))); + } + + /* ============================================================================ + * _ __ + * | |/ /___ _ _ + * | ' + result::key_type, error_info> parse_simple_key( + location& loc, + const context& ctx) { + using key_type = typename basic_value::key_type; + const auto& spec = ctx.toml_spec(); + + if (loc.current() == '\"') { + auto str_res = parse_basic_string_only(loc, ctx); + if (str_res.is_ok()) { + return ok(std::move(str_res.unwrap().first)); + } else { + return err(std::move(str_res.unwrap_err())); + } + } else if (loc.current() == '\'') { + auto str_res = parse_literal_string_only(loc, ctx); + if (str_res.is_ok()) { + return ok(std::move(str_res.unwrap().first)); + } else { + return err(std::move(str_res.unwrap_err())); + } + } + + // bare key. + + if (const auto bare = syntax::unquoted_key(spec).scan(loc)) { + return ok(string_conv(bare.as_string())); + } else { + std::string postfix; + if (spec.v1_1_0_allow_non_english_in_bare_keys) { + postfix = + "Hint: Not all Unicode characters are allowed as bare key.\n"; + } else { + postfix = "Hint: non-ASCII scripts are allowed in toml v1.1.0, but " + "not in v1.0.0.\n"; + } + return err(make_syntax_error( + "toml::parse_simple_key: " + "invalid key: key must be \"quoted\", 'quoted-literal', or bare key.", + syntax::unquoted_key(spec), + loc, + postfix)); + } + } + + // dotted key become vector of keys + template + result::key_type>, region>, error_info> + parse_key(location& loc, const context& ctx) { + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + using key_type = typename basic_value::key_type; + std::vector keys; + while (!loc.eof()) { + auto key = parse_simple_key(loc, ctx); + if (!key.is_ok()) { + return err(key.unwrap_err()); + } + keys.push_back(std::move(key.unwrap())); + + auto reg = syntax::dot_sep(spec).scan(loc); + if (!reg.is_ok()) { + break; + } + } + if (keys.empty()) { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_key: expected a new key, " + "but got nothing", + std::move(src), + "reached EOF")); + } + + return ok(std::make_pair(std::move(keys), region(first, loc))); + } + + // ============================================================================ + + // forward-decl to implement parse_array and parse_table + template + result, error_info> parse_value(location&, context& ctx); + + template + result::key_type>, region>, + basic_value>, + error_info> + parse_key_value_pair(location& loc, context& ctx) { + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + auto key_res = parse_key(loc, ctx); + if (key_res.is_err()) { + loc = first; + return err(key_res.unwrap_err()); + } + + if (!syntax::keyval_sep(spec).scan(loc).is_ok()) { + auto e = make_syntax_error("toml::parse_key_value_pair: " + "invalid key value separator `=`", + syntax::keyval_sep(spec), + loc); + loc = first; + return err(std::move(e)); + } + + auto v_res = parse_value(loc, ctx); + if (v_res.is_err()) { + // loc = first; + return err(v_res.unwrap_err()); + } + return ok( + std::make_pair(std::move(key_res.unwrap()), std::move(v_res.unwrap()))); + } + + /* ============================================================================ + * __ _ _ _ _ _ __ _ _ _ + * / _` | '_| '_/ _` | || | + * \__,_|_| |_| \__,_|\_, | + * |__/ + */ + + // array(and multiline inline table with `{` and `}`) has the following format. + // `[` + // (ws|newline|comment-line)? (value) (ws|newline|comment-line)? `,` + // (ws|newline|comment-line)? (value) (ws|newline|comment-line)? `,` + // ... + // (ws|newline|comment-line)? (value) (ws|newline|comment-line)? (`,`)? + // (ws|newline|comment-line)? `]` + // it skips (ws|newline|comment-line) and returns the token. + template + struct multiline_spacer { + using comment_type = typename TC::comment_type; + bool newline_found; + indent_char indent_type; + std::int32_t indent; + comment_type comments; + }; + + template + std::ostream& operator<<(std::ostream& os, const multiline_spacer& sp) { + os << "{newline=" << sp.newline_found << ", "; + os << "indent_type=" << sp.indent_type << ", "; + os << "indent=" << sp.indent << ", "; + os << "comments=" << sp.comments.size() << "}"; + return os; + } + + template + cxx::optional> skip_multiline_spacer( + location& loc, + context& ctx, + const bool newline_found = false) { + const auto& spec = ctx.toml_spec(); + + multiline_spacer spacer; + spacer.newline_found = newline_found; + spacer.indent_type = indent_char::none; + spacer.indent = 0; + spacer.comments.clear(); + + bool spacer_found = false; + while (!loc.eof()) { + if (auto comm = sequence(syntax::comment(spec), syntax::newline(spec)) + .scan(loc)) { + spacer.newline_found = true; + auto comment = comm.as_string(); + if (!comment.empty() && comment.back() == '\n') { + comment.pop_back(); + if (!comment.empty() && comment.back() == '\r') { + comment.pop_back(); + } + } + + spacer.comments.push_back(std::move(comment)); + spacer.indent_type = indent_char::none; + spacer.indent = 0; + spacer_found = true; + } else if (auto nl = syntax::newline(spec).scan(loc)) { + spacer.newline_found = true; + spacer.comments.clear(); + spacer.indent_type = indent_char::none; + spacer.indent = 0; + spacer_found = true; + } else if (auto sp = repeat_at_least( + 1, + character(cxx::bit_cast(' '))) + .scan(loc)) { + spacer.indent_type = indent_char::space; + spacer.indent = static_cast(sp.length()); + spacer_found = true; + } else if ( + auto tabs = repeat_at_least( + 1, + character(cxx::bit_cast('\t'))) + .scan(loc)) { + spacer.indent_type = indent_char::tab; + spacer.indent = static_cast(tabs.length()); + spacer_found = true; + } else { + break; // done + } + } + if (!spacer_found) { + return cxx::make_nullopt(); + } + return spacer; + } + + // not an [[array.of.tables]]. It parses ["this", "type"] + template + result, error_info> parse_array(location& loc, + context& ctx) { + const auto num_errors = ctx.errors().size(); + + const auto first = loc; + + if (loc.eof() || loc.current() != '[') { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_array: " + "The next token is not an array", + std::move(src), + "here")); + } + loc.advance(); + + typename basic_value::array_type val; + + array_format_info fmt; + fmt.fmt = array_format::oneline; + fmt.indent_type = indent_char::none; + + auto spacer = skip_multiline_spacer(loc, ctx); + if (spacer.has_value() && spacer.value().newline_found) { + fmt.fmt = array_format::multiline; + } + + bool comma_found = true; + while (!loc.eof()) { + if (loc.current() == location::char_type(']')) { + if (spacer.has_value() && spacer.value().newline_found && + spacer.value().indent_type != indent_char::none) { + fmt.indent_type = spacer.value().indent_type; + fmt.closing_indent = spacer.value().indent; + } + break; + } + + if (!comma_found) { + auto src = source_location(region(loc)); + return err( + make_error_info("toml::parse_array: " + "expected value-separator `,` or closing `]`", + std::move(src), + "here")); + } + + if (spacer.has_value() && spacer.value().newline_found && + spacer.value().indent_type != indent_char::none) { + fmt.indent_type = spacer.value().indent_type; + fmt.body_indent = spacer.value().indent; + } + + if (auto elem_res = parse_value(loc, ctx)) { + auto elem = std::move(elem_res.unwrap()); + + if (spacer.has_value()) // copy previous comments to value + { + elem.comments() = std::move(spacer.value().comments); + } + + // parse spaces between a value and a comma + // array = [ + // 42 , # the answer + // ^^^^ + // 3.14 # pi + // , 2.71 ^^^^ + // ^^ + spacer = skip_multiline_spacer(loc, ctx); + if (spacer.has_value()) { + for (std::size_t i = 0; i < spacer.value().comments.size(); ++i) { + elem.comments().push_back(std::move(spacer.value().comments.at(i))); + } + if (spacer.value().newline_found) { + fmt.fmt = array_format::multiline; + } + } + + comma_found = character(',').scan(loc).is_ok(); + + // parse comment after a comma + // array = [ + // 42 , # the answer + // ^^^^^^^^^^^^ + // 3.14 # pi + // ^^^^ + // ] + auto com_res = parse_comment_line(loc, ctx); + if (com_res.is_err()) { + ctx.report_error(com_res.unwrap_err()); + } + + const bool comment_found = com_res.is_ok() && + com_res.unwrap().has_value(); + if (comment_found) { + fmt.fmt = array_format::multiline; + elem.comments().push_back(com_res.unwrap().value()); + } + if (comma_found) { + spacer = skip_multiline_spacer(loc, ctx, comment_found); + if (spacer.has_value() && spacer.value().newline_found) { + fmt.fmt = array_format::multiline; + } + } + val.push_back(std::move(elem)); + } else { + // if err, push error to ctx and try recovery. + ctx.report_error(std::move(elem_res.unwrap_err())); + + // if it looks like some value, then skip the value. + // otherwise, it may be a new key-value pair or a new table and + // the error is "missing closing ]". stop parsing. + + const auto before_skip = loc.get_location(); + skip_value(loc, ctx); + if (before_skip == loc.get_location()) // cannot skip! break... + { + break; + } + } + } + + if (loc.current() != ']') { + auto src = source_location(region(loc)); + return err( + make_error_info("toml::parse_array: missing closing bracket `]`", + std::move(src), + "expected `]`, reached EOF")); + } else { + loc.advance(); + } + // any error reported from this function + if (num_errors != ctx.errors().size()) { + assert(ctx.has_error()); // already reported + return err(ctx.errors().back()); + } + + return ok( + basic_value(std::move(val), std::move(fmt), {}, region(first, loc))); + } + + /* ============================================================================ + * _ _ _ _ _ _ + * (_)_ _ | (_)_ _ ___ | |_ __ _| |__| |___ + * | | ' \| | | ' \/ -_) | _/ _` | '_ \ / -_) + * |_|_||_|_|_|_||_\___| \__\__,_|_.__/_\___| + */ + + // ---------------------------------------------------------------------------- + // insert_value is the most complicated part of the toml spec. + // + // To parse a toml file correctly, we sometimes need to check an exising + // value is appendable or not. + // + // For example while parsing an inline array of tables, + // + // ```toml + // aot = [ + // {a = "foo"}, + // {a = "bar", b = "baz"}, + // ] + // ``` + // + // this `aot` is appendable until parser reaches to `]`. After that, it + // becomes non-appendable. + // + // On the other hand, a normal array of tables, such as + // + // ```toml + // [[aot]] + // a = "foo" + // + // [[aot]] + // a = "bar" + // b = "baz" + // ``` + // This `[[aot]]` is appendable until the parser reaches to the EOF. + // + // + // It becomes a bit more difficult in case of dotted keys. + // In TOML, it is allowed to append a key-value pair to a table that is + // *implicitly* defined by a subtable definitino. + // + // ```toml + // [x.y.z] + // w = 123 + // + // [x] + // a = "foo" # OK. x is defined implicitly by `[x.y.z]`. + // ``` + // + // But if the table is defined by a dotted keys, it is not appendable. + // + // ```toml + // [x] + // y.z.w = 123 + // + // [x.y] + // # ERROR. x.y is already defined by a dotted table in the previous table. + // ``` + // + // Also, reopening a table using dotted keys is invalid. + // + // ```toml + // [x.y.z] + // w = 123 + // + // [x] + // y.z.v = 42 # ERROR. [x.y.z] is already defined. + // ``` + // + // + // ```toml + // [a] + // b.c = "foo" + // b.d = "bar" + // ``` + // + // + // ```toml + // a.b = "foo" + // [a] + // c = "bar" # ERROR + // ``` + // + // In summary, + // - a table must be defined only once. + // - assignment to an exising table is possible only when: + // - defining a subtable [x.y] to an existing table [x]. + // - defining supertable [x] explicitly after [x.y]. + // - adding dotted keys in the same table. + + enum class inserting_value_kind : std::uint8_t { + std_table, // insert [standard.table] + array_table, // insert [[array.of.tables]] + dotted_keys // insert a.b.c = "this" + }; + + template + result*, error_info> insert_value( + const inserting_value_kind kind, + typename basic_value::table_type* current_table_ptr, + const std::vector::key_type>& keys, + region key_reg, + basic_value val) { + using value_type = basic_value; + using array_type = typename basic_value::array_type; + using table_type = typename basic_value::table_type; + + auto key_loc = source_location(key_reg); + + assert(!keys.empty()); + + // dotted key can insert to dotted key tables defined at the same level. + // dotted key can NOT reopen a table even if it is implcitly-defined one. + // + // [x.y.z] # define x and x.y implicitly. + // a = 42 + // + // [x] # reopening implcitly defined table + // r.s.t = 3.14 # VALID r and r.s are new tables. + // r.s.u = 2.71 # VALID r and r.s are dotted-key tables. valid. + // + // y.z.b = "foo" # INVALID x.y.z are multiline table, not a dotted key. + // y.c = "bar" # INVALID x.y is implicit multiline table, not a dotted key. + + // a table cannot reopen dotted-key tables. + // + // [t1] + // t2.t3.v = 0 + // [t1.t2] # INVALID t1.t2 is defined as a dotted-key table. + + for (std::size_t i = 0; i < keys.size(); ++i) { + const auto& key = keys.at(i); + table_type& current_table = *current_table_ptr; + + if (i + 1 < keys.size()) // there are more keys. go down recursively... + { + const auto found = current_table.find(key); + if (found == current_table.end()) // not found. add new table + { + table_format_info fmt; + fmt.indent_type = indent_char::none; + if (kind == inserting_value_kind::dotted_keys) { + fmt.fmt = table_format::dotted; + } else // table / array of tables + { + fmt.fmt = table_format::implicit; + } + current_table.emplace( + key, + value_type(table_type {}, fmt, std::vector {}, key_reg)); + + assert(current_table.at(key).is_table()); + current_table_ptr = std::addressof(current_table.at(key).as_table()); + } else if (found->second.is_table()) { + const auto fmt = found->second.as_table_fmt().fmt; + if (fmt == table_format::oneline || + fmt == table_format::multiline_oneline) { + // foo = {bar = "baz"} or foo = { \n bar = "baz" \n } + return err(make_error_info( + "toml::insert_value: " + "failed to insert a value: inline table is immutable", + key_loc, + "inserting this", + found->second.location(), + "to this table")); + } + // dotted key cannot reopen a table. + if (kind == inserting_value_kind::dotted_keys && + fmt != table_format::dotted) { + return err(make_error_info("toml::insert_value: " + "reopening a table using dotted keys", + key_loc, + "dotted key cannot reopen a table", + found->second.location(), + "this table is already closed")); + } + assert(found->second.is_table()); + current_table_ptr = std::addressof(found->second.as_table()); + } else if (found->second.is_array_of_tables()) { + // aot = [{this = "type", of = "aot"}] # cannot be reopened + if (found->second.as_array_fmt().fmt != array_format::array_of_tables) { + return err(make_error_info("toml::insert_value:" + "inline array of tables are immutable", + key_loc, + "inserting this", + found->second.location(), + "inline array of tables")); + } + // appending to [[aot]] + + if (kind == inserting_value_kind::dotted_keys) { + // [[array.of.tables]] + // [array.of] # reopening supertable is okay + // tables.x = "foo" # appending `x` to the first table + return err( + make_error_info("toml::insert_value:" + "dotted key cannot reopen an array-of-tables", + key_loc, + "inserting this", + found->second.location(), + "to this array-of-tables.")); + } + + // insert_value_by_dotkeys::std_table + // [[array.of.tables]] + // [array.of.tables.subtable] # appending to the last aot + // + // insert_value_by_dotkeys::array_table + // [[array.of.tables]] + // [[array.of.tables.subtable]] # appending to the last aot + auto& current_array_table = found->second.as_array().back(); + + assert(current_array_table.is_table()); + current_table_ptr = std::addressof(current_array_table.as_table()); + } else { + return err( + make_error_info("toml::insert_value: " + "failed to insert a value, value already exists", + key_loc, + "while inserting this", + found->second.location(), + "non-table value already exists")); + } + } else // this is the last key. insert a new value. + { + switch (kind) { + case inserting_value_kind::dotted_keys: { + if (current_table.find(key) != current_table.end()) { + return err(make_error_info( + "toml::insert_value: " + "failed to insert a value, value already exists", + key_loc, + "inserting this", + current_table.at(key).location(), + "but value already exists")); + } + current_table.emplace(key, std::move(val)); + return ok(std::addressof(current_table.at(key))); + } + case inserting_value_kind::std_table: { + // defining a new table or reopening supertable + auto found = current_table.find(key); + if (found == current_table.end()) // define a new aot + { + current_table.emplace(key, std::move(val)); + return ok(std::addressof(current_table.at(key))); + } else // the table is already defined, reopen it + { + // assigning a [std.table]. it must be an implicit table. + auto& target = found->second; + if (!target.is_table() || // could be an array-of-tables + target.as_table_fmt().fmt != table_format::implicit) { + return err(make_error_info( + "toml::insert_value: " + "failed to insert a table, table already defined", + key_loc, + "inserting this", + target.location(), + "this table is explicitly defined")); + } + + // merge table + for (const auto& kv : val.as_table()) { + if (target.contains(kv.first)) { + // [x.y.z] + // w = "foo" + // [x] + // y = "bar" + return err( + make_error_info("toml::insert_value: " + "failed to insert a table, table keys " + "conflict to each other", + key_loc, + "inserting this table", + kv.second.location(), + "having this value", + target.at(kv.first).location(), + "already defined here")); + } else { + target[kv.first] = kv.second; + } + } + // change implicit -> explicit + target.as_table_fmt().fmt = table_format::multiline; + // change definition region + change_region_of_value(target, val); + + return ok(std::addressof(current_table.at(key))); + } + } + case inserting_value_kind::array_table: { + auto found = current_table.find(key); + if (found == current_table.end()) // define a new aot + { + array_format_info fmt; + fmt.fmt = array_format::array_of_tables; + fmt.indent_type = indent_char::none; + + current_table.emplace(key, + value_type(array_type { std::move(val) }, + std::move(fmt), + std::vector {}, + std::move(key_reg))); + + assert(!current_table.at(key).as_array().empty()); + return ok(std::addressof(current_table.at(key).as_array().back())); + } else // the array is already defined, append to it + { + if (!found->second.is_array_of_tables()) { + return err(make_error_info( + "toml::insert_value: " + "failed to insert an array of tables, value already exists", + key_loc, + "while inserting this", + found->second.location(), + "non-table value already exists")); + } + if (found->second.as_array_fmt().fmt != + array_format::array_of_tables) { + return err(make_error_info("toml::insert_value: " + "failed to insert a table, inline " + "array of tables is immutable", + key_loc, + "while inserting this", + found->second.location(), + "this is inline array-of-tables")); + } + found->second.as_array().push_back(std::move(val)); + assert(!current_table.at(key).as_array().empty()); + return ok(std::addressof(current_table.at(key).as_array().back())); + } + } + default: { + assert(false); + } + } + } + } + return err(make_error_info("toml::insert_key: no keys found", + std::move(key_loc), + "here")); + } + + // ---------------------------------------------------------------------------- + + template + result, error_info> parse_inline_table(location& loc, + context& ctx) { + using table_type = typename basic_value::table_type; + + const auto num_errors = ctx.errors().size(); + + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + if (loc.eof() || loc.current() != '{') { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_inline_table: " + "The next token is not an inline table", + std::move(src), + "here")); + } + loc.advance(); + + table_type table; + table_format_info fmt; + fmt.fmt = table_format::oneline; + fmt.indent_type = indent_char::none; + + cxx::optional> spacer(cxx::make_nullopt()); + + if (spec.v1_1_0_allow_newlines_in_inline_tables) { + spacer = skip_multiline_spacer(loc, ctx); + if (spacer.has_value() && spacer.value().newline_found) { + fmt.fmt = table_format::multiline_oneline; + } + } else { + skip_whitespace(loc, ctx); + } + + bool still_empty = true; + bool comma_found = false; + while (!loc.eof()) { + // closing! + if (loc.current() == '}') { + if (comma_found && !spec.v1_1_0_allow_trailing_comma_in_inline_tables) { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_inline_table: trailing " + "comma is not allowed in TOML-v1.0.0)", + std::move(src), + "here")); + } + + if (spec.v1_1_0_allow_newlines_in_inline_tables) { + if (spacer.has_value() && spacer.value().newline_found && + spacer.value().indent_type != indent_char::none) { + fmt.indent_type = spacer.value().indent_type; + fmt.closing_indent = spacer.value().indent; + } + } + break; + } + + // if we already found a value and didn't found `,` nor `}`, error. + if (!comma_found && !still_empty) { + auto src = source_location(region(loc)); + return err( + make_error_info("toml::parse_inline_table: " + "expected value-separator `,` or closing `}`", + std::move(src), + "here")); + } + + // parse indent. + if (spacer.has_value() && spacer.value().newline_found && + spacer.value().indent_type != indent_char::none) { + fmt.indent_type = spacer.value().indent_type; + fmt.body_indent = spacer.value().indent; + } + + still_empty = false; // parsing a value... + if (auto kv_res = parse_key_value_pair(loc, ctx)) { + auto keys = std::move(kv_res.unwrap().first.first); + auto key_reg = std::move(kv_res.unwrap().first.second); + auto val = std::move(kv_res.unwrap().second); + + auto ins_res = insert_value(inserting_value_kind::dotted_keys, + std::addressof(table), + keys, + std::move(key_reg), + std::move(val)); + if (ins_res.is_err()) { + ctx.report_error(std::move(ins_res.unwrap_err())); + // we need to skip until the next value (or end of the table) + // because we don't have valid kv pair. + while (!loc.eof()) { + const auto c = loc.current(); + if (c == ',' || c == '\n' || c == '}') { + comma_found = (c == ','); + break; + } + loc.advance(); + } + continue; + } + + // if comment line follows immediately(without newline) after `,`, then + // the comment is for the elem. we need to check if comment follows `,`. + // + // (key) = (val) (ws|newline|comment-line)? `,` (ws)? (comment)? + + if (spec.v1_1_0_allow_newlines_in_inline_tables) { + if (spacer.has_value()) // copy previous comments to value + { + for (std::size_t i = 0; i < spacer.value().comments.size(); ++i) { + ins_res.unwrap()->comments().push_back( + spacer.value().comments.at(i)); + } + } + spacer = skip_multiline_spacer(loc, ctx); + if (spacer.has_value()) { + for (std::size_t i = 0; i < spacer.value().comments.size(); ++i) { + ins_res.unwrap()->comments().push_back( + spacer.value().comments.at(i)); + } + if (spacer.value().newline_found) { + fmt.fmt = table_format::multiline_oneline; + if (spacer.value().indent_type != indent_char::none) { + fmt.indent_type = spacer.value().indent_type; + fmt.body_indent = spacer.value().indent; + } + } + } + } else { + skip_whitespace(loc, ctx); + } + + comma_found = character(',').scan(loc).is_ok(); + + if (spec.v1_1_0_allow_newlines_in_inline_tables) { + auto com_res = parse_comment_line(loc, ctx); + if (com_res.is_err()) { + ctx.report_error(com_res.unwrap_err()); + } + const bool comment_found = com_res.is_ok() && + com_res.unwrap().has_value(); + if (comment_found) { + fmt.fmt = table_format::multiline_oneline; + ins_res.unwrap()->comments().push_back(com_res.unwrap().value()); + } + if (comma_found) { + spacer = skip_multiline_spacer(loc, ctx, comment_found); + if (spacer.has_value() && spacer.value().newline_found) { + fmt.fmt = table_format::multiline_oneline; + } + } + } else { + skip_whitespace(loc, ctx); + } + } else { + ctx.report_error(std::move(kv_res.unwrap_err())); + while (!loc.eof()) { + if (loc.current() == '}') { + break; + } + if (!spec.v1_1_0_allow_newlines_in_inline_tables && + loc.current() == '\n') { + break; + } + loc.advance(); + } + break; + } + } + + if (loc.current() != '}') { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_inline_table: " + "missing closing bracket `}`", + std::move(src), + "expected `}`, reached line end")); + } else { + loc.advance(); // skip } + } + + // any error reported from this function + if (num_errors < ctx.errors().size()) { + assert(ctx.has_error()); // already reported + return err(ctx.pop_last_error()); + } + + basic_value retval(std::move(table), std::move(fmt), {}, region(first, loc)); + + return ok(std::move(retval)); + } + + /* ============================================================================ + * _ + * __ ____ _| |_ _ ___ + * \ V / _` | | || / -_) + * \_/\__,_|_|\_,_\___| + */ + + template + result guess_number_type(const location& first, + const context& ctx) { + const auto& spec = ctx.toml_spec(); + location loc = first; + + if (syntax::offset_datetime(spec).scan(loc).is_ok()) { + return ok(value_t::offset_datetime); + } + loc = first; + + if (syntax::local_datetime(spec).scan(loc).is_ok()) { + const auto curr = loc.current(); + // if offset_datetime contains bad offset, it syntax::offset_datetime + // fails to scan it. + if (curr == '+' || curr == '-') { + return err( + make_syntax_error("bad offset: must be [+-]HH:MM or Z", + syntax::time_offset(spec), + loc, + std::string("Hint: valid : +09:00, -05:30\n" + "Hint: invalid: +9:00, -5:30\n"))); + } + return ok(value_t::local_datetime); + } + loc = first; + + if (syntax::local_date(spec).scan(loc).is_ok()) { + // bad time may appear after this. + + if (!loc.eof()) { + const auto c = loc.current(); + if (c == 'T' || c == 't') { + loc.advance(); + + return err(make_syntax_error( + "bad time: must be HH:MM:SS.subsec", + syntax::local_time(spec), + loc, + std::string( + "Hint: valid : 1979-05-27T07:32:00, 1979-05-27 " + "07:32:00.999999\n" + "Hint: invalid: 1979-05-27T7:32:00, 1979-05-27 17:32\n"))); + } + if (c == ' ') { + // A space is allowed as a delimiter between local time. + // But there is a case where bad time follows a space. + // - invalid: 2019-06-16 7:00:00 + // - valid : 2019-06-16 07:00:00 + loc.advance(); + if (!loc.eof() && ('0' <= loc.current() && loc.current() <= '9')) { + return err(make_syntax_error( + "bad time: must be HH:MM:SS.subsec", + syntax::local_time(spec), + loc, + std::string( + "Hint: valid : 1979-05-27T07:32:00, 1979-05-27 " + "07:32:00.999999\n" + "Hint: invalid: 1979-05-27T7:32:00, 1979-05-27 17:32\n"))); + } + } + if ('0' <= c && c <= '9') { + return err(make_syntax_error( + "bad datetime: missing T or space", + character_either { 'T', 't', ' ' }, + loc, + std::string( + "Hint: valid : 1979-05-27T07:32:00, 1979-05-27 " + "07:32:00.999999\n" + "Hint: invalid: 1979-05-27T7:32:00, 1979-05-27 17:32\n"))); + } + } + return ok(value_t::local_date); + } + loc = first; + + if (syntax::local_time(spec).scan(loc).is_ok()) { + return ok(value_t::local_time); + } + loc = first; + + if (syntax::floating(spec).scan(loc).is_ok()) { + if (!loc.eof() && loc.current() == '_') { + if (spec.ext_num_suffix && syntax::num_suffix(spec).scan(loc).is_ok()) { + return ok(value_t::floating); + } + auto src = source_location(region(loc)); + return err(make_error_info( + "bad float: `_` must be surrounded by digits", + std::move(src), + "invalid underscore", + "Hint: valid : +1.0, -2e-2, 3.141_592_653_589, inf, nan\n" + "Hint: invalid: .0, 1., _1.0, 1.0_, 1_.0, 1.0__0\n")); + } + return ok(value_t::floating); + } + loc = first; + + if (spec.ext_hex_float) { + if (syntax::hex_floating(spec).scan(loc).is_ok()) { + if (!loc.eof() && loc.current() == '_') { + if (spec.ext_num_suffix && syntax::num_suffix(spec).scan(loc).is_ok()) { + return ok(value_t::floating); + } + auto src = source_location(region(loc)); + return err(make_error_info( + "bad float: `_` must be surrounded by digits", + std::move(src), + "invalid underscore", + "Hint: valid : +1.0, -2e-2, 3.141_592_653_589, inf, nan\n" + "Hint: invalid: .0, 1., _1.0, 1.0_, 1_.0, 1.0__0\n")); + } + return ok(value_t::floating); + } + loc = first; + } + + if (auto int_reg = syntax::integer(spec).scan(loc)) { + if (!loc.eof()) { + const auto c = loc.current(); + if (c == '_') { + if (spec.ext_num_suffix && syntax::num_suffix(spec).scan(loc).is_ok()) { + return ok(value_t::integer); + } + + if (int_reg.length() <= 2 && + (int_reg.as_string() == "0" || int_reg.as_string() == "-0" || + int_reg.as_string() == "+0")) { + auto src = source_location(region(loc)); + return err(make_error_info( + "bad integer: leading zero is not allowed in decimal int", + std::move(src), + "leading zero", + "Hint: valid : -42, 1_000, 1_2_3_4_5, 0xC0FFEE, 0b0010, " + "0o755\n" + "Hint: invalid: _42, 1__000, 0123\n")); + } else { + auto src = source_location(region(loc)); + return err( + make_error_info("bad integer: `_` must be surrounded by digits", + std::move(src), + "invalid underscore", + "Hint: valid : -42, 1_000, 1_2_3_4_5, " + "0xC0FFEE, 0b0010, 0o755\n" + "Hint: invalid: _42, 1__000, 0123\n")); + } + } + if ('0' <= c && c <= '9') { + if (loc.current() == '0') { + loc.retrace(); + return err(make_error_info( + "bad integer: leading zero", + source_location(region(loc)), + "leading zero is not allowed", + std::string("Hint: valid : -42, 1_000, 1_2_3_4_5, 0xC0FFEE, " + "0b0010, 0o755\n" + "Hint: invalid: _42, 1__000, 0123\n"))); + } else // invalid digits, especially in oct/bin ints. + { + return err(make_error_info( + "bad integer: invalid digit after an integer", + source_location(region(loc)), + "this digit is not allowed", + std::string("Hint: valid : -42, 1_000, 1_2_3_4_5, 0xC0FFEE, " + "0b0010, 0o755\n" + "Hint: invalid: _42, 1__000, 0123\n"))); + } + } + if (c == ':' || c == '-') { + auto src = source_location(region(loc)); + return err(make_error_info( + "bad datetime: invalid format", + std::move(src), + "here", + std::string("Hint: valid : 1979-05-27T07:32:00-07:00, " + "1979-05-27 07:32:00.999999Z\n" + "Hint: invalid: 1979-05-27T7:32:00-7:00, 1979-05-27 " + "7:32-00:30"))); + } + if (c == '.' || c == 'e' || c == 'E') { + auto src = source_location(region(loc)); + return err(make_error_info( + "bad float: invalid format", + std::move(src), + "here", + std::string( + "Hint: valid : +1.0, -2e-2, 3.141_592_653_589, inf, nan\n" + "Hint: invalid: .0, 1., _1.0, 1.0_, 1_.0, 1.0__0\n"))); + } + } + return ok(value_t::integer); + } + if (!loc.eof() && loc.current() == '.') { + auto src = source_location(region(loc)); + return err(make_error_info( + "bad float: integer part is required before decimal point", + std::move(src), + "missing integer part", + std::string( + "Hint: valid : +1.0, -2e-2, 3.141_592_653_589, inf, nan\n" + "Hint: invalid: .0, 1., _1.0, 1.0_, 1_.0, 1.0__0\n"))); + } + if (!loc.eof() && loc.current() == '_') { + auto src = source_location(region(loc)); + return err(make_error_info( + "bad number: `_` must be surrounded by digits", + std::move(src), + "digits required before `_`", + std::string( + "Hint: valid : -42, 1_000, 1_2_3_4_5, 0xC0FFEE, 0b0010, 0o755\n" + "Hint: invalid: _42, 1__000, 0123\n"))); + } + + auto src = source_location(region(loc)); + return err(make_error_info("bad format: unknown value appeared", + std::move(src), + "here")); + } + + template + result guess_value_type(const location& loc, + const context& ctx) { + const auto& sp = ctx.toml_spec(); + location inner(loc); + + switch (loc.current()) { + case '"': { + return ok(value_t::string); + } + case '\'': { + return ok(value_t::string); + } + case '[': { + return ok(value_t::array); + } + case '{': { + return ok(value_t::table); + } + case 't': { + return ok(value_t::boolean); + } + case 'f': { + return ok(value_t::boolean); + } + case 'T': // invalid boolean. + { + return err(make_syntax_error("toml::parse_value: " + "`true` must be in lowercase. " + "A string must be surrounded by quotes.", + syntax::boolean(sp), + inner)); + } + case 'F': { + return err(make_syntax_error("toml::parse_value: " + "`false` must be in lowercase. " + "A string must be surrounded by quotes.", + syntax::boolean(sp), + inner)); + } + case 'i': // inf or string without quotes(syntax error). + { + if (literal("inf").scan(inner).is_ok()) { + return ok(value_t::floating); + } else { + return err( + make_syntax_error("toml::parse_value: " + "`inf` must be in lowercase. " + "A string must be surrounded by quotes.", + syntax::floating(sp), + inner)); + } + } + case 'I': // Inf or string without quotes(syntax error). + { + return err(make_syntax_error("toml::parse_value: " + "`inf` must be in lowercase. " + "A string must be surrounded by quotes.", + syntax::floating(sp), + inner)); + } + case 'n': // nan or null-extension + { + if (sp.ext_null_value) { + if (literal("nan").scan(inner).is_ok()) { + return ok(value_t::floating); + } else if (literal("null").scan(inner).is_ok()) { + return ok(value_t::empty); + } else { + return err( + make_syntax_error("toml::parse_value: " + "Both `nan` and `null` must be in lowercase. " + "A string must be surrounded by quotes.", + syntax::floating(sp), + inner)); + } + } else // must be nan. + { + if (literal("nan").scan(inner).is_ok()) { + return ok(value_t::floating); + } else { + return err( + make_syntax_error("toml::parse_value: " + "`nan` must be in lowercase. " + "A string must be surrounded by quotes.", + syntax::floating(sp), + inner)); + } + } + } + case 'N': // nan or null-extension + { + if (sp.ext_null_value) { + return err( + make_syntax_error("toml::parse_value: " + "Both `nan` and `null` must be in lowercase. " + "A string must be surrounded by quotes.", + syntax::floating(sp), + inner)); + } else { + return err( + make_syntax_error("toml::parse_value: " + "`nan` must be in lowercase. " + "A string must be surrounded by quotes.", + syntax::floating(sp), + inner)); + } + } + default: { + return guess_number_type(loc, ctx); + } + } + } + + template + result, error_info> parse_value(location& loc, + context& ctx) { + const auto ty_res = guess_value_type(loc, ctx); + if (ty_res.is_err()) { + return err(ty_res.unwrap_err()); + } + + switch (ty_res.unwrap()) { + case value_t::empty: { + if (ctx.toml_spec().ext_null_value) { + return parse_null(loc, ctx); + } else { + auto src = source_location(region(loc)); + return err( + make_error_info("toml::parse_value: unknown value appeared", + std::move(src), + "here")); + } + } + case value_t::boolean: { + return parse_boolean(loc, ctx); + } + case value_t::integer: { + return parse_integer(loc, ctx); + } + case value_t::floating: { + return parse_floating(loc, ctx); + } + case value_t::string: { + return parse_string(loc, ctx); + } + case value_t::offset_datetime: { + return parse_offset_datetime(loc, ctx); + } + case value_t::local_datetime: { + return parse_local_datetime(loc, ctx); + } + case value_t::local_date: { + return parse_local_date(loc, ctx); + } + case value_t::local_time: { + return parse_local_time(loc, ctx); + } + case value_t::array: { + return parse_array(loc, ctx); + } + case value_t::table: { + return parse_inline_table(loc, ctx); + } + default: { + auto src = source_location(region(loc)); + return err( + make_error_info("toml::parse_value: unknown value appeared", + std::move(src), + "here")); + } + } + } + + /* ============================================================================ + * _____ _ _ + * |_ _|_ _| |__| |___ + * | |/ _` | '_ \ / -_) + * |_|\__,_|_.__/_\___| + */ + + template + result::key_type>, region>, error_info> + parse_table_key(location& loc, context& ctx) { + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + auto reg = syntax::std_table(spec).scan(loc); + if (!reg.is_ok()) { + return err(make_syntax_error("toml::parse_table_key: invalid table key", + syntax::std_table(spec), + loc)); + } + + loc = first; + loc.advance(); // skip [ + skip_whitespace(loc, ctx); + + auto keys_res = parse_key(loc, ctx); + if (keys_res.is_err()) { + return err(std::move(keys_res.unwrap_err())); + } + + skip_whitespace(loc, ctx); + loc.advance(); // ] + + return ok(std::make_pair(std::move(keys_res.unwrap().first), std::move(reg))); + } + + template + result::key_type>, region>, error_info> + parse_array_table_key(location& loc, context& ctx) { + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + auto reg = syntax::array_table(spec).scan(loc); + if (!reg.is_ok()) { + return err(make_syntax_error( + "toml::parse_array_table_key: invalid array-of-tables key", + syntax::array_table(spec), + loc)); + } + + loc = first; + loc.advance(); // [ + loc.advance(); // [ + skip_whitespace(loc, ctx); + + auto keys_res = parse_key(loc, ctx); + if (keys_res.is_err()) { + return err(std::move(keys_res.unwrap_err())); + } + + skip_whitespace(loc, ctx); + loc.advance(); // ] + loc.advance(); // ] + + return ok(std::make_pair(std::move(keys_res.unwrap().first), std::move(reg))); + } + + // called after reading [table.keys] and comments around it. + // Since table may already contain a subtable ([x.y.z] can be defined before + // [x]), the table that is being parsed is passed as an argument. + template + result parse_table(location& loc, + context& ctx, + basic_value& table) { + assert(table.is_table()); + + const auto num_errors = ctx.errors().size(); + const auto& spec = ctx.toml_spec(); + + // clear indent info + table.as_table_fmt().indent_type = indent_char::none; + + bool newline_found = true; + while (!loc.eof()) { + const auto start = loc; + + auto sp = skip_multiline_spacer(loc, ctx, newline_found); + + // if reached to EOF, the table ends here. return. + if (loc.eof()) { + break; + } + // if next table is comming, return. + if (sequence(syntax::ws(spec), character('[')).scan(loc).is_ok()) { + loc = start; + break; + } + // otherwise, it should be a key-value pair. + newline_found = newline_found || + (sp.has_value() && sp.value().newline_found); + if (!newline_found) { + return err(make_error_info("toml::parse_table: " + "newline (LF / CRLF) or EOF is expected", + source_location(region(loc)), + "here")); + } + if (sp.has_value() && sp.value().indent_type != indent_char::none) { + table.as_table_fmt().indent_type = sp.value().indent_type; + table.as_table_fmt().body_indent = sp.value().indent; + } + + newline_found = false; // reset + if (auto kv_res = parse_key_value_pair(loc, ctx)) { + auto keys = std::move(kv_res.unwrap().first.first); + auto key_reg = std::move(kv_res.unwrap().first.second); + auto val = std::move(kv_res.unwrap().second); + + if (sp.has_value()) { + for (const auto& com : sp.value().comments) { + val.comments().push_back(com); + } + } + + if (auto com_res = parse_comment_line(loc, ctx)) { + if (auto com_opt = com_res.unwrap()) { + val.comments().push_back(com_opt.value()); + newline_found = true; // comment includes newline at the end + } + } else { + ctx.report_error(std::move(com_res.unwrap_err())); + } + + auto ins_res = insert_value(inserting_value_kind::dotted_keys, + std::addressof(table.as_table()), + keys, + std::move(key_reg), + std::move(val)); + if (ins_res.is_err()) { + ctx.report_error(std::move(ins_res.unwrap_err())); + } + } else { + ctx.report_error(std::move(kv_res.unwrap_err())); + skip_key_value_pair(loc, ctx); + } + } + + if (num_errors < ctx.errors().size()) { + assert(ctx.has_error()); // already reported + return err(ctx.pop_last_error()); + } + return ok(); + } + + template + result, std::vector> parse_file(location& loc, + context& ctx) { + using value_type = basic_value; + using table_type = typename value_type::table_type; + + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + if (loc.eof()) { + return ok(value_type(table_type(), table_format_info {}, {}, region(loc))); + } + + value_type root(table_type(), table_format_info {}, {}, region(loc)); + root.as_table_fmt().fmt = table_format::multiline; + root.as_table_fmt().indent_type = indent_char::none; + + // parse top comment. + // + // ```toml + // # this is a comment for the top-level table. + // + // key = "the first value" + // ``` + // + // ```toml + // # this is a comment for "the first value". + // key = "the first value" + // ``` + while (!loc.eof()) { + if (auto com_res = parse_comment_line(loc, ctx)) { + if (auto com_opt = com_res.unwrap()) { + root.comments().push_back(std::move(com_opt.value())); + } else // no comment found. + { + // if it is not an empty line, clear the root comment. + if (!sequence(syntax::ws(spec), syntax::newline(spec)).scan(loc).is_ok()) { + loc = first; + root.comments().clear(); + } + break; + } + } else { + ctx.report_error(std::move(com_res.unwrap_err())); + skip_comment_block(loc, ctx); + } + } + + // parse root table + { + const auto res = parse_table(loc, ctx, root); + if (res.is_err()) { + ctx.report_error(std::move(res.unwrap_err())); + skip_until_next_table(loc, ctx); + } + } + + // parse tables + + while (!loc.eof()) { + auto sp = skip_multiline_spacer(loc, ctx, /*newline_found=*/true); + + if (auto key_res = parse_array_table_key(loc, ctx)) { + auto key = std::move(std::get<0>(key_res.unwrap())); + auto reg = std::move(std::get<1>(key_res.unwrap())); + + std::vector com; + if (sp.has_value()) { + for (std::size_t i = 0; i < sp.value().comments.size(); ++i) { + com.push_back(std::move(sp.value().comments.at(i))); + } + } + + // [table.def] must be followed by one of + // - a comment line + // - whitespace + newline + // - EOF + if (auto com_res = parse_comment_line(loc, ctx)) { + if (auto com_opt = com_res.unwrap()) { + com.push_back(com_opt.value()); + } else // if there is no comment, ws+newline must exist (or EOF) + { + skip_whitespace(loc, ctx); + if (!loc.eof() && + !syntax::newline(ctx.toml_spec()).scan(loc).is_ok()) { + ctx.report_error(make_syntax_error("toml::parse_file: " + "newline (or EOF) expected", + syntax::newline(ctx.toml_spec()), + loc)); + skip_until_next_table(loc, ctx); + continue; + } + } + } else // comment syntax error (rare) + { + ctx.report_error(com_res.unwrap_err()); + skip_until_next_table(loc, ctx); + continue; + } + + table_format_info fmt; + fmt.fmt = table_format::multiline; + fmt.indent_type = indent_char::none; + auto tab = value_type(table_type {}, std::move(fmt), std::move(com), reg); + + auto inserted = insert_value(inserting_value_kind::array_table, + std::addressof(root.as_table()), + key, + std::move(reg), + std::move(tab)); + + if (inserted.is_err()) { + ctx.report_error(inserted.unwrap_err()); + + // check errors in the table + auto tmp = basic_value(table_type()); + auto res = parse_table(loc, ctx, tmp); + if (res.is_err()) { + ctx.report_error(res.unwrap_err()); + skip_until_next_table(loc, ctx); + } + continue; + } + + auto tab_ptr = inserted.unwrap(); + assert(tab_ptr); + + const auto tab_res = parse_table(loc, ctx, *tab_ptr); + if (tab_res.is_err()) { + ctx.report_error(tab_res.unwrap_err()); + skip_until_next_table(loc, ctx); + } + + // parse_table first clears `indent_type`. + // to keep header indent info, we must store it later. + if (sp.has_value() && sp.value().indent_type != indent_char::none) { + tab_ptr->as_table_fmt().indent_type = sp.value().indent_type; + tab_ptr->as_table_fmt().name_indent = sp.value().indent; + } + continue; + } + if (auto key_res = parse_table_key(loc, ctx)) { + auto key = std::move(std::get<0>(key_res.unwrap())); + auto reg = std::move(std::get<1>(key_res.unwrap())); + + std::vector com; + if (sp.has_value()) { + for (std::size_t i = 0; i < sp.value().comments.size(); ++i) { + com.push_back(std::move(sp.value().comments.at(i))); + } + } + + // [table.def] must be followed by one of + // - a comment line + // - whitespace + newline + // - EOF + if (auto com_res = parse_comment_line(loc, ctx)) { + if (auto com_opt = com_res.unwrap()) { + com.push_back(com_opt.value()); + } else // if there is no comment, ws+newline must exist (or EOF) + { + skip_whitespace(loc, ctx); + if (!loc.eof() && + !syntax::newline(ctx.toml_spec()).scan(loc).is_ok()) { + ctx.report_error(make_syntax_error("toml::parse_file: " + "newline (or EOF) expected", + syntax::newline(ctx.toml_spec()), + loc)); + skip_until_next_table(loc, ctx); + continue; + } + } + } else // comment syntax error (rare) + { + ctx.report_error(com_res.unwrap_err()); + skip_until_next_table(loc, ctx); + continue; + } + + table_format_info fmt; + fmt.fmt = table_format::multiline; + fmt.indent_type = indent_char::none; + auto tab = value_type(table_type {}, std::move(fmt), std::move(com), reg); + + auto inserted = insert_value(inserting_value_kind::std_table, + std::addressof(root.as_table()), + key, + std::move(reg), + std::move(tab)); + + if (inserted.is_err()) { + ctx.report_error(inserted.unwrap_err()); + + // check errors in the table + auto tmp = basic_value(table_type()); + auto res = parse_table(loc, ctx, tmp); + if (res.is_err()) { + ctx.report_error(res.unwrap_err()); + skip_until_next_table(loc, ctx); + } + continue; + } + + auto tab_ptr = inserted.unwrap(); + assert(tab_ptr); + + const auto tab_res = parse_table(loc, ctx, *tab_ptr); + if (tab_res.is_err()) { + ctx.report_error(tab_res.unwrap_err()); + skip_until_next_table(loc, ctx); + } + if (sp.has_value() && sp.value().indent_type != indent_char::none) { + tab_ptr->as_table_fmt().indent_type = sp.value().indent_type; + tab_ptr->as_table_fmt().name_indent = sp.value().indent; + } + continue; + } + + // does not match array_table nor std_table. report an error. + const auto keytop = loc; + const auto maybe_array_of_tables = literal("[[").scan(loc).is_ok(); + loc = keytop; + + if (maybe_array_of_tables) { + ctx.report_error( + make_syntax_error("toml::parse_file: invalid array-table key", + syntax::array_table(spec), + loc)); + } else { + ctx.report_error( + make_syntax_error("toml::parse_file: invalid table key", + syntax::std_table(spec), + loc)); + } + skip_until_next_table(loc, ctx); + } + + if (!ctx.errors().empty()) { + return err(std::move(ctx.errors())); + } + return ok(std::move(root)); + } + + template + result, std::vector> parse_impl( + std::vector cs, + std::string fname, + const spec& s) { + using value_type = basic_value; + using table_type = typename value_type::table_type; + + // an empty file is a valid toml file. + if (cs.empty()) { + auto src = std::make_shared>( + std::move(cs)); + location loc(std::move(src), std::move(fname)); + return ok(value_type(table_type(), + table_format_info {}, + std::vector {}, + region(loc))); + } + + // to simplify parser, add newline at the end if there is no LF. + // But, if it has raw CR, the file is invalid (in TOML, CR is not a valid + // newline char). if it ends with CR, do not add LF and report it. + if (cs.back() != '\n' && cs.back() != '\r') { + cs.push_back('\n'); + } + + auto src = std::make_shared>(std::move(cs)); + + location loc(std::move(src), std::move(fname)); + + // skip BOM if found + if (loc.source()->size() >= 3) { + auto first = loc.get_location(); + + const auto c0 = loc.current(); + loc.advance(); + const auto c1 = loc.current(); + loc.advance(); + const auto c2 = loc.current(); + loc.advance(); + + const auto bom_found = (c0 == 0xEF) && (c1 == 0xBB) && (c2 == 0xBF); + if (!bom_found) { + loc.set_location(first); + } + } + + context ctx(s); + + return parse_file(loc, ctx); + } + + } // namespace detail + + // ----------------------------------------------------------------------------- + // parse(byte array) + + template + result, std::vector> try_parse( + std::vector content, + std::string filename, + spec s = spec::default_version()) { + return detail::parse_impl(std::move(content), + std::move(filename), + std::move(s)); + } + + template + basic_value parse(std::vector content, + std::string filename, + spec s = spec::default_version()) { + auto res = try_parse(std::move(content), std::move(filename), std::move(s)); + if (res.is_ok()) { + return res.unwrap(); + } else { + std::string msg; + for (const auto& err : res.unwrap_err()) { + msg += format_error(err); + } + throw syntax_error(std::move(msg), std::move(res.unwrap_err())); + } + } + + // ----------------------------------------------------------------------------- + // parse(istream) + + template + result, std::vector> try_parse( + std::istream& is, + std::string fname = "unknown file", + spec s = spec::default_version()) { + const auto beg = is.tellg(); + is.seekg(0, std::ios::end); + const auto end = is.tellg(); + const auto fsize = end - beg; + is.seekg(beg); + + // read whole file as a sequence of char + assert(fsize >= 0); + std::vector letters(static_cast(fsize), + '\0'); + is.read(reinterpret_cast(letters.data()), + static_cast(fsize)); + + return detail::parse_impl(std::move(letters), + std::move(fname), + std::move(s)); + } + + template + basic_value parse(std::istream& is, + std::string fname = "unknown file", + spec s = spec::default_version()) { + auto res = try_parse(is, std::move(fname), std::move(s)); + if (res.is_ok()) { + return res.unwrap(); + } else { + std::string msg; + for (const auto& err : res.unwrap_err()) { + msg += format_error(err); + } + throw syntax_error(std::move(msg), std::move(res.unwrap_err())); + } + } + + // ----------------------------------------------------------------------------- + // parse(filename) + + template + result, std::vector> try_parse( + std::string fname, + spec s = spec::default_version()) { + std::ifstream ifs(fname, std::ios_base::binary); + if (!ifs.good()) { + std::vector e; + e.push_back( + error_info("toml::parse: Error opening file \"" + fname + "\"", {})); + return err(std::move(e)); + } + ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit); + + return try_parse(ifs, std::move(fname), std::move(s)); + } + + template + basic_value parse(std::string fname, spec s = spec::default_version()) { + std::ifstream ifs(fname, std::ios_base::binary); + if (!ifs.good()) { + throw file_io_error("toml::parse: error opening file", fname); + } + ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit); + + return parse(ifs, std::move(fname), std::move(s)); + } + + template + result, std::vector> try_parse( + const char (&fname)[N], + spec s = spec::default_version()) { + return try_parse(std::string(fname), std::move(s)); + } + + template + basic_value parse(const char (&fname)[N], spec s = spec::default_version()) { + return parse(std::string(fname), std::move(s)); + } + + // ---------------------------------------------------------------------------- + // parse_str + + template + result, std::vector> try_parse_str( + std::string content, + spec s = spec::default_version(), + cxx::source_location loc = cxx::source_location::current()) { + std::istringstream iss(std::move(content)); + std::string name("internal string" + cxx::to_string(loc)); + return try_parse(iss, std::move(name), std::move(s)); + } + + template + basic_value parse_str( + std::string content, + spec s = spec::default_version(), + cxx::source_location loc = cxx::source_location::current()) { + auto res = try_parse_str(std::move(content), std::move(s), std::move(loc)); + if (res.is_ok()) { + return res.unwrap(); + } else { + std::string msg; + for (const auto& err : res.unwrap_err()) { + msg += format_error(err); + } + throw syntax_error(std::move(msg), std::move(res.unwrap_err())); + } + } + + // ---------------------------------------------------------------------------- + // filesystem + +#if defined(TOML11_HAS_FILESYSTEM) + + template + cxx::enable_if_t::value, + result, std::vector>> + try_parse(const FSPATH& fpath, spec s = spec::default_version()) { + std::ifstream ifs(fpath, std::ios_base::binary); + if (!ifs.good()) { + std::vector e; + e.push_back( + error_info("toml::parse: Error opening file \"" + fpath.string() + "\"", + {})); + return err(std::move(e)); + } + ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit); + + return try_parse(ifs, fpath.string(), std::move(s)); + } + + template + cxx::enable_if_t::value, basic_value> + parse(const FSPATH& fpath, spec s = spec::default_version()) { + std::ifstream ifs(fpath, std::ios_base::binary); + if (!ifs.good()) { + throw file_io_error("toml::parse: error opening file", fpath.string()); + } + ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit); + + return parse(ifs, fpath.string(), std::move(s)); + } +#endif + + // ----------------------------------------------------------------------------- + // FILE* + + template + result, std::vector> try_parse( + FILE* fp, + std::string filename, + spec s = spec::default_version()) { + const long beg = std::ftell(fp); + if (beg == -1L) { + return err(std::vector { + error_info(std::string("Failed to access: \"") + filename + + "\", errno = " + std::to_string(errno), + {}) }); + } + + const int res_seekend = std::fseek(fp, 0, SEEK_END); + if (res_seekend != 0) { + return err(std::vector { + error_info(std::string("Failed to seek: \"") + filename + + "\", errno = " + std::to_string(errno), + {}) }); + } + + const long end = std::ftell(fp); + if (end == -1L) { + return err(std::vector { + error_info(std::string("Failed to access: \"") + filename + + "\", errno = " + std::to_string(errno), + {}) }); + } + + const auto fsize = end - beg; + + const auto res_seekbeg = std::fseek(fp, beg, SEEK_SET); + if (res_seekbeg != 0) { + return err(std::vector { + error_info(std::string("Failed to seek: \"") + filename + + "\", errno = " + std::to_string(errno), + {}) }); + } + + // read whole file as a sequence of char + assert(fsize >= 0); + std::vector letters( + static_cast(fsize)); + const auto actual = std::fread(letters.data(), + sizeof(char), + static_cast(fsize), + fp); + if (actual != static_cast(fsize)) { + return err(std::vector { + error_info(std::string("File size changed: \"") + filename + + std::string("\" make sure that FILE* is in binary mode " + "to avoid LF <-> CRLF conversion"), + {}) }); + } + + return detail::parse_impl(std::move(letters), + std::move(filename), + std::move(s)); + } + + template + basic_value parse(FILE* fp, + std::string filename, + spec s = spec::default_version()) { + const long beg = std::ftell(fp); + if (beg == -1L) { + throw file_io_error(errno, "Failed to access", filename); + } + + const int res_seekend = std::fseek(fp, 0, SEEK_END); + if (res_seekend != 0) { + throw file_io_error(errno, "Failed to seek", filename); + } + + const long end = std::ftell(fp); + if (end == -1L) { + throw file_io_error(errno, "Failed to access", filename); + } + + const auto fsize = end - beg; + + const auto res_seekbeg = std::fseek(fp, beg, SEEK_SET); + if (res_seekbeg != 0) { + throw file_io_error(errno, "Failed to seek", filename); + } + + // read whole file as a sequence of char + assert(fsize >= 0); + std::vector letters( + static_cast(fsize)); + const auto actual = std::fread(letters.data(), + sizeof(char), + static_cast(fsize), + fp); + if (actual != static_cast(fsize)) { + throw file_io_error( + errno, + "File size changed; make sure that " + "FILE* is in binary mode to avoid LF <-> CRLF conversion", + filename); + } + + auto res = detail::parse_impl(std::move(letters), + std::move(filename), + std::move(s)); + if (res.is_ok()) { + return res.unwrap(); + } else { + std::string msg; + for (const auto& err : res.unwrap_err()) { + msg += format_error(err); + } + throw syntax_error(std::move(msg), std::move(res.unwrap_err())); + } + } + +} // namespace toml + +#if defined(TOML11_COMPILE_SOURCES) +namespace toml { + struct type_config; + struct ordered_type_config; + + extern template result, std::vector> + try_parse(std::vector, std::string, spec); + extern template result, std::vector> + try_parse(std::istream&, std::string, spec); + extern template result, std::vector> + try_parse(std::string, spec); + extern template result, std::vector> + try_parse(FILE*, std::string, spec); + extern template result, std::vector> + try_parse_str(std::string, spec, cxx::source_location); + + extern template basic_value parse( + std::vector, + std::string, + spec); + extern template basic_value parse(std::istream&, + std::string, + spec); + extern template basic_value parse(std::string, spec); + extern template basic_value parse(FILE*, + std::string, + spec); + extern template basic_value parse_str( + std::string, + spec, + cxx::source_location); + + extern template result, std::vector> + try_parse(std::vector, std::string, spec); + extern template result, std::vector> + try_parse(std::istream&, std::string, spec); + extern template result, std::vector> + try_parse(std::string, spec); + extern template result, std::vector> + try_parse(FILE*, std::string, spec); + extern template result, std::vector> + try_parse_str(std::string, spec, cxx::source_location); + + extern template basic_value parse( + std::vector, + std::string, + spec); + extern template basic_value parse( + std::istream&, + std::string, + spec); + extern template basic_value parse( + std::string, + spec); + extern template basic_value parse( + FILE*, + std::string, + spec); + extern template basic_value parse_str( + std::string, + spec, + cxx::source_location); + + #if defined(TOML11_HAS_FILESYSTEM) + extern template cxx::enable_if_t< + std::is_same::value, + result, std::vector>> + try_parse(const std::filesystem::path&, + spec); + extern template cxx::enable_if_t< + std::is_same::value, + result, std::vector>> + try_parse( + const std::filesystem::path&, + spec); + extern template cxx::enable_if_t< + std::is_same::value, + basic_value> + parse(const std::filesystem::path&, spec); + extern template cxx::enable_if_t< + std::is_same::value, + basic_value> + parse(const std::filesystem::path&, + spec); + #endif // filesystem + +} // namespace toml +#endif // TOML11_COMPILE_SOURCES + +#endif // TOML11_PARSER_HPP +#ifndef TOML11_LITERAL_HPP +#define TOML11_LITERAL_HPP + +#ifndef TOML11_LITERAL_FWD_HPP + #define TOML11_LITERAL_FWD_HPP + +namespace toml { + + namespace detail { + // implementation + ::toml::value literal_internal_impl(location loc); + } // namespace detail + + inline namespace literals { + inline namespace toml_literals { + + ::toml::value operator"" _toml(const char* str, std::size_t len); + + #if defined(TOML11_HAS_CHAR8_T) + // value of u8"" literal has been changed from char to char8_t and char8_t + // is NOT compatible to char + ::toml::value operator"" _toml(const char8_t* str, std::size_t len); + #endif + + } // namespace toml_literals + } // namespace literals +} // namespace toml +#endif // TOML11_LITERAL_FWD_HPP + +#if !defined(TOML11_COMPILE_SOURCES) + #ifndef TOML11_LITERAL_IMPL_HPP + #define TOML11_LITERAL_IMPL_HPP + +namespace toml { + + namespace detail { + // implementation + TOML11_INLINE ::toml::value literal_internal_impl(location loc) { + const auto s = ::toml::spec::default_version(); + context ctx(s); + + const auto front = loc; + + // ------------------------------------------------------------------------ + // check if it is a raw value. + + // skip empty lines and comment lines + auto sp = skip_multiline_spacer(loc, ctx); + if (loc.eof()) { + ::toml::value val; + if (sp.has_value()) { + for (std::size_t i = 0; i < sp.value().comments.size(); ++i) { + val.comments().push_back(std::move(sp.value().comments.at(i))); + } + } + return val; + } + + // to distinguish arrays and tables, first check it is a table or not. + // + // "[1,2,3]"_toml; // json: [1, 2, 3] + // "[table]"_toml; // json: {"table": {}} + // "[[1,2,3]]"_toml; // json: [[1, 2, 3]] + // "[[table]]"_toml; // json: {"table": [{}]} + // + // "[[1]]"_toml; // json: {"1": [{}]} + // "1 = [{}]"_toml; // json: {"1": [{}]} + // "[[1,]]"_toml; // json: [[1]] + // "[[1],]"_toml; // json: [[1]] + const auto val_start = loc; + + const bool is_table_key = syntax::std_table(s).scan(loc).is_ok(); + loc = val_start; + const bool is_aots_key = syntax::array_table(s).scan(loc).is_ok(); + loc = val_start; + + // If it is neither a table-key or a array-of-table-key, it may be a value. + if (!is_table_key && !is_aots_key) { + auto data = parse_value(loc, ctx); + if (data.is_ok()) { + auto val = std::move(data.unwrap()); + if (sp.has_value()) { + for (std::size_t i = 0; i < sp.value().comments.size(); ++i) { + val.comments().push_back(std::move(sp.value().comments.at(i))); + } + } + auto com_res = parse_comment_line(loc, ctx); + if (com_res.is_ok() && com_res.unwrap().has_value()) { + val.comments().push_back(com_res.unwrap().value()); + } + return val; + } + } + + // ------------------------------------------------------------------------- + // Note that still it can be a table, because the literal might be + // something like the following. + // ```cpp + // // c++11 raw-string literal + // const auto val = R"( + // key = "value" + // int = 42 + // )"_toml; + // ``` + // It is a valid toml file. + // It should be parsed as if we parse a file with this content. + + loc = front; + auto data = parse_file(loc, ctx); + if (data.is_ok()) { + return data.unwrap(); + } else // not a value && not a file. error. + { + std::string msg; + for (const auto& err : data.unwrap_err()) { + msg += format_error(err); + } + throw ::toml::syntax_error(std::move(msg), std::move(data.unwrap_err())); + } + } + + } // namespace detail + + inline namespace literals { + inline namespace toml_literals { + + TOML11_INLINE ::toml::value operator"" _toml(const char* str, + std::size_t len) { + if (len == 0) { + return ::toml::value {}; + } + + ::toml::detail::location::container_type c(len); + std::copy( + reinterpret_cast(str), + reinterpret_cast(str + len), + c.begin()); + if (!c.empty() && c.back()) { + c.push_back('\n'); // to make it easy to parse comment, we add newline + } + + return literal_internal_impl(::toml::detail::location( + std::make_shared( + std::move(c)), + "TOML literal encoded in a C++ code")); + } + + #if defined(__cpp_char8_t) + #if __cpp_char8_t >= 201811L + #define TOML11_HAS_CHAR8_T 1 + #endif + #endif + + #if defined(TOML11_HAS_CHAR8_T) + // value of u8"" literal has been changed from char to char8_t and char8_t + // is NOT compatible to char + TOML11_INLINE ::toml::value operator"" _toml(const char8_t* str, + std::size_t len) { + if (len == 0) { + return ::toml::value {}; + } + + ::toml::detail::location::container_type c(len); + std::copy( + reinterpret_cast(str), + reinterpret_cast(str + len), + c.begin()); + if (!c.empty() && c.back()) { + c.push_back('\n'); // to make it easy to parse comment, we add newline + } + + return literal_internal_impl(::toml::detail::location( + std::make_shared( + std::move(c)), + "TOML literal encoded in a C++ code")); + } + #endif + + } // namespace toml_literals + } // namespace literals +} // namespace toml + #endif // TOML11_LITERAL_IMPL_HPP +#endif + +#endif // TOML11_LITERAL_HPP +#ifndef TOML11_SERIALIZER_HPP +#define TOML11_SERIALIZER_HPP + +#include +#include +#include +#include +#include + +namespace toml { + + struct serialization_error final : public ::toml::exception { + public: + explicit serialization_error(std::string what_arg, source_location loc) + : what_(std::move(what_arg)) + , loc_(std::move(loc)) {} + + ~serialization_error() noexcept override = default; + + const char* what() const noexcept override { + return what_.c_str(); + } + + const source_location& location() const noexcept { + return loc_; + } + + private: + std::string what_; + source_location loc_; + }; + + namespace detail { + template + class serializer { + public: + using value_type = basic_value; + + using key_type = typename value_type::key_type; + using comment_type = typename value_type::comment_type; + using boolean_type = typename value_type::boolean_type; + using integer_type = typename value_type::integer_type; + using floating_type = typename value_type::floating_type; + using string_type = typename value_type::string_type; + using local_time_type = typename value_type::local_time_type; + using local_date_type = typename value_type::local_date_type; + using local_datetime_type = typename value_type::local_datetime_type; + using offset_datetime_type = typename value_type::offset_datetime_type; + using array_type = typename value_type::array_type; + using table_type = typename value_type::table_type; + + using char_type = typename string_type::value_type; + + public: + explicit serializer(const spec& sp) + : spec_(sp) + , force_inline_(false) + , current_indent_(0) {} + + string_type operator()(const std::vector& ks, const value_type& v) { + for (const auto& k : ks) { + this->keys_.push_back(k); + } + return (*this)(v); + } + + string_type operator()(const key_type& k, const value_type& v) { + this->keys_.push_back(k); + return (*this)(v); + } + + string_type operator()(const value_type& v) { + switch (v.type()) { + case value_t::boolean: { + return (*this)(v.as_boolean(), v.as_boolean_fmt(), v.location()); + } + case value_t::integer: { + return (*this)(v.as_integer(), v.as_integer_fmt(), v.location()); + } + case value_t::floating: { + return (*this)(v.as_floating(), v.as_floating_fmt(), v.location()); + } + case value_t::string: { + return (*this)(v.as_string(), v.as_string_fmt(), v.location()); + } + case value_t::offset_datetime: { + return (*this)(v.as_offset_datetime(), + v.as_offset_datetime_fmt(), + v.location()); + } + case value_t::local_datetime: { + return (*this)(v.as_local_datetime(), + v.as_local_datetime_fmt(), + v.location()); + } + case value_t::local_date: { + return (*this)(v.as_local_date(), v.as_local_date_fmt(), v.location()); + } + case value_t::local_time: { + return (*this)(v.as_local_time(), v.as_local_time_fmt(), v.location()); + } + case value_t::array: { + return ( + *this)(v.as_array(), v.as_array_fmt(), v.comments(), v.location()); + } + case value_t::table: { + string_type retval; + if (this->keys_.empty()) // it might be the root table. emit comments here. + { + retval += format_comments(v.comments(), v.as_table_fmt().indent_type); + } + if (!retval.empty()) // we have comment. + { + retval += char_type('\n'); + } + + retval += (*this)(v.as_table(), + v.as_table_fmt(), + v.comments(), + v.location()); + return retval; + } + case value_t::empty: { + if (this->spec_.ext_null_value) { + return string_conv("null"); + } + break; + } + default: { + break; + } + } + throw serialization_error( + format_error("[error] toml::serializer: toml::basic_value " + "does not have any valid type.", + v.location(), + "here"), + v.location()); + } + + private: + string_type operator()(const boolean_type& b, + const boolean_format_info&, + const source_location&) // {{{ + { + if (b) { + return string_conv("true"); + } else { + return string_conv("false"); + } + } // }}} + + string_type operator()(const integer_type i, + const integer_format_info& fmt, + const source_location& loc) // {{{ + { + std::ostringstream oss; + this->set_locale(oss); + + const auto insert_spacer = [&fmt](std::string s) -> std::string { + if (fmt.spacer == 0) { + return s; + } + + std::string sign; + if (!s.empty() && (s.at(0) == '+' || s.at(0) == '-')) { + sign += s.at(0); + s.erase(s.begin()); + } + + std::string spaced; + std::size_t counter = 0; + for (auto iter = s.rbegin(); iter != s.rend(); ++iter) { + if (counter != 0 && counter % fmt.spacer == 0) { + spaced += '_'; + } + spaced += *iter; + counter += 1; + } + if (!spaced.empty() && spaced.back() == '_') { + spaced.pop_back(); + } + + s.clear(); + std::copy(spaced.rbegin(), spaced.rend(), std::back_inserter(s)); + return sign + s; + }; + + std::string retval; + if (fmt.fmt == integer_format::dec) { + oss << std::setw(static_cast(fmt.width)) << std::dec << i; + retval = insert_spacer(oss.str()); + + if (this->spec_.ext_num_suffix && !fmt.suffix.empty()) { + retval += '_'; + retval += fmt.suffix; + } + } else { + if (i < 0) { + throw serialization_error( + format_error("binary, octal, hexadecimal " + "integer does not allow negative value", + loc, + "here"), + loc); + } + switch (fmt.fmt) { + case integer_format::hex: { + oss << std::noshowbase << std::setw(static_cast(fmt.width)) + << std::setfill('0') << std::hex; + if (fmt.uppercase) { + oss << std::uppercase; + } else { + oss << std::nouppercase; + } + oss << i; + retval = std::string("0x") + insert_spacer(oss.str()); + break; + } + case integer_format::oct: { + oss << std::setw(static_cast(fmt.width)) << std::setfill('0') + << std::oct << i; + retval = std::string("0o") + insert_spacer(oss.str()); + break; + } + case integer_format::bin: { + integer_type x { i }; + std::string tmp; + std::size_t bits(0); + while (x != 0) { + if (fmt.spacer != 0) { + if (bits != 0 && (bits % fmt.spacer) == 0) { + tmp += '_'; + } + } + if (x % 2 == 1) { + tmp += '1'; + } else { + tmp += '0'; + } + x >>= 1; + bits += 1; + } + for (; bits < fmt.width; ++bits) { + if (fmt.spacer != 0) { + if (bits != 0 && (bits % fmt.spacer) == 0) { + tmp += '_'; + } + } + tmp += '0'; + } + for (auto iter = tmp.rbegin(); iter != tmp.rend(); ++iter) { + oss << *iter; + } + retval = std::string("0b") + oss.str(); + break; + } + default: { + throw serialization_error( + format_error("none of dec, hex, oct, bin: " + to_string(fmt.fmt), + loc, + "here"), + loc); + } + } + } + return string_conv(retval); + } // }}} + + string_type operator()(const floating_type f, + const floating_format_info& fmt, + const source_location&) // {{{ + { + using std::isinf; + using std::isnan; + using std::signbit; + + std::ostringstream oss; + this->set_locale(oss); + + if (isnan(f)) { + if (signbit(f)) { + oss << '-'; + } + oss << "nan"; + if (this->spec_.ext_num_suffix && !fmt.suffix.empty()) { + oss << '_'; + oss << fmt.suffix; + } + return string_conv(oss.str()); + } + + if (isinf(f)) { + if (signbit(f)) { + oss << '-'; + } + oss << "inf"; + if (this->spec_.ext_num_suffix && !fmt.suffix.empty()) { + oss << '_'; + oss << fmt.suffix; + } + return string_conv(oss.str()); + } + + switch (fmt.fmt) { + case floating_format::defaultfloat: { + if (fmt.prec != 0) { + oss << std::setprecision(static_cast(fmt.prec)); + } + oss << f; + // since defaultfloat may omit point, we need to add it + std::string s = oss.str(); + if (s.find('.') == std::string::npos && + s.find('e') == std::string::npos && + s.find('E') == std::string::npos) { + s += ".0"; + } + if (this->spec_.ext_num_suffix && !fmt.suffix.empty()) { + s += '_'; + s += fmt.suffix; + } + return string_conv(s); + } + case floating_format::fixed: { + if (fmt.prec != 0) { + oss << std::setprecision(static_cast(fmt.prec)); + } + oss << std::fixed << f; + if (this->spec_.ext_num_suffix && !fmt.suffix.empty()) { + oss << '_' << fmt.suffix; + } + return string_conv(oss.str()); + } + case floating_format::scientific: { + if (fmt.prec != 0) { + oss << std::setprecision(static_cast(fmt.prec)); + } + oss << std::scientific << f; + if (this->spec_.ext_num_suffix && !fmt.suffix.empty()) { + oss << '_' << fmt.suffix; + } + return string_conv(oss.str()); + } + case floating_format::hex: { + if (this->spec_.ext_hex_float) { + oss << std::hexfloat << f; + // suffix is only for decimal numbers. + return string_conv(oss.str()); + } else // no hex allowed. output with max precision. + { + oss << std::setprecision( + std::numeric_limits::max_digits10) + << std::scientific << f; + // suffix is only for decimal numbers. + return string_conv(oss.str()); + } + } + default: { + if (this->spec_.ext_num_suffix && !fmt.suffix.empty()) { + oss << '_' << fmt.suffix; + } + return string_conv(oss.str()); + } + } + } // }}} + + string_type operator()(string_type s, + const string_format_info& fmt, + const source_location& loc) // {{{ + { + string_type retval; + switch (fmt.fmt) { + case string_format::basic: { + retval += char_type('"'); + retval += this->escape_basic_string(s); + retval += char_type('"'); + return retval; + } + case string_format::literal: { + if (std::find(s.begin(), s.end(), char_type('\n')) != s.end()) { + throw serialization_error( + format_error( + "toml::serializer: " + "(non-multiline) literal string cannot have a newline", + loc, + "here"), + loc); + } + retval += char_type('\''); + retval += s; + retval += char_type('\''); + return retval; + } + case string_format::multiline_basic: { + retval += string_conv("\"\"\""); + if (fmt.start_with_newline) { + retval += char_type('\n'); + } + + retval += this->escape_ml_basic_string(s); + + retval += string_conv("\"\"\""); + return retval; + } + case string_format::multiline_literal: { + retval += string_conv("'''"); + if (fmt.start_with_newline) { + retval += char_type('\n'); + } + retval += s; + retval += string_conv("'''"); + return retval; + } + default: { + throw serialization_error( + format_error("[error] toml::serializer::operator()(string): " + "invalid string_format value", + loc, + "here"), + loc); + } + } + } // }}} + + string_type operator()(const local_date_type& d, + const local_date_format_info&, + const source_location&) // {{{ + { + std::ostringstream oss; + oss << d; + return string_conv(oss.str()); + } // }}} + + string_type operator()(const local_time_type& t, + const local_time_format_info& fmt, + const source_location&) // {{{ + { + return this->format_local_time(t, fmt.has_seconds, fmt.subsecond_precision); + } // }}} + + string_type operator()(const local_datetime_type& dt, + const local_datetime_format_info& fmt, + const source_location&) // {{{ + { + std::ostringstream oss; + oss << dt.date; + switch (fmt.delimiter) { + case datetime_delimiter_kind::upper_T: { + oss << 'T'; + break; + } + case datetime_delimiter_kind::lower_t: { + oss << 't'; + break; + } + case datetime_delimiter_kind::space: { + oss << ' '; + break; + } + default: { + oss << 'T'; + break; + } + } + return string_conv(oss.str()) + + this->format_local_time(dt.time, + fmt.has_seconds, + fmt.subsecond_precision); + } // }}} + + string_type operator()(const offset_datetime_type& odt, + const offset_datetime_format_info& fmt, + const source_location&) // {{{ + { + std::ostringstream oss; + oss << odt.date; + switch (fmt.delimiter) { + case datetime_delimiter_kind::upper_T: { + oss << 'T'; + break; + } + case datetime_delimiter_kind::lower_t: { + oss << 't'; + break; + } + case datetime_delimiter_kind::space: { + oss << ' '; + break; + } + default: { + oss << 'T'; + break; + } + } + oss << string_conv( + this->format_local_time(odt.time, fmt.has_seconds, fmt.subsecond_precision)); + oss << odt.offset; + return string_conv(oss.str()); + } // }}} + + string_type operator()(const array_type& a, + const array_format_info& fmt, + const comment_type& com, + const source_location& loc) // {{{ + { + array_format f = fmt.fmt; + if (fmt.fmt == array_format::default_format) { + // [[in.this.form]], you cannot add a comment to the array itself + // (but you can add a comment to each table). + // To keep comments, we need to avoid multiline array-of-tables + // if array itself has a comment. + if (!this->keys_.empty() && !a.empty() && com.empty() && + std::all_of(a.begin(), a.end(), [](const value_type& e) { + return e.is_table(); + })) { + f = array_format::array_of_tables; + } else { + f = array_format::oneline; + + // check if it becomes long + std::size_t approx_len = 0; + for (const auto& e : a) { + // have a comment. cannot be inlined + if (!e.comments().empty()) { + f = array_format::multiline; + break; + } + // possibly long types ... + if (e.is_array() || e.is_table() || e.is_offset_datetime() || + e.is_local_datetime()) { + f = array_format::multiline; + break; + } else if (e.is_boolean()) { + approx_len += + (*this)(e.as_boolean(), e.as_boolean_fmt(), e.location()).size(); + } else if (e.is_integer()) { + approx_len += + (*this)(e.as_integer(), e.as_integer_fmt(), e.location()).size(); + } else if (e.is_floating()) { + approx_len += + (*this)(e.as_floating(), e.as_floating_fmt(), e.location()).size(); + } else if (e.is_string()) { + if (e.as_string_fmt().fmt == string_format::multiline_basic || + e.as_string_fmt().fmt == string_format::multiline_literal) { + f = array_format::multiline; + break; + } + approx_len += + 2 + + (*this)(e.as_string(), e.as_string_fmt(), e.location()).size(); + } else if (e.is_local_date()) { + approx_len += 10; // 1234-56-78 + } else if (e.is_local_time()) { + approx_len += 15; // 12:34:56.789012 + } + + if (approx_len > 60) // key, ` = `, `[...]` < 80 + { + f = array_format::multiline; + break; + } + approx_len += 2; // `, ` + } + } + } + if (this->force_inline_ && f == array_format::array_of_tables) { + f = array_format::multiline; + } + if (a.empty() && f == array_format::array_of_tables) { + f = array_format::oneline; + } + + // -------------------------------------------------------------------- + + if (f == array_format::array_of_tables) { + if (this->keys_.empty()) { + throw serialization_error("array of table must have its key. " + "use format(key, v)", + loc); + } + string_type retval; + for (const auto& e : a) { + assert(e.is_table()); + + this->current_indent_ += e.as_table_fmt().name_indent; + retval += this->format_comments(e.comments(), + e.as_table_fmt().indent_type); + retval += this->format_indent(e.as_table_fmt().indent_type); + this->current_indent_ -= e.as_table_fmt().name_indent; + + retval += string_conv("[["); + retval += this->format_keys(this->keys_).value(); + retval += string_conv("]]\n"); + + retval += this->format_ml_table(e.as_table(), e.as_table_fmt()); + } + return retval; + } else if (f == array_format::oneline) { + // ignore comments. we cannot emit comments + string_type retval; + retval += char_type('['); + for (const auto& e : a) { + this->force_inline_ = true; + retval += (*this)(e); + retval += string_conv(", "); + } + if (!a.empty()) { + retval.pop_back(); // ` ` + retval.pop_back(); // `,` + } + retval += char_type(']'); + this->force_inline_ = false; + return retval; + } else { + assert(f == array_format::multiline); + + string_type retval; + retval += string_conv("[\n"); + + for (const auto& e : a) { + this->current_indent_ += fmt.body_indent; + retval += this->format_comments(e.comments(), fmt.indent_type); + retval += this->format_indent(fmt.indent_type); + this->current_indent_ -= fmt.body_indent; + + this->force_inline_ = true; + retval += (*this)(e); + retval += string_conv(",\n"); + } + this->force_inline_ = false; + + this->current_indent_ += fmt.closing_indent; + retval += this->format_indent(fmt.indent_type); + this->current_indent_ -= fmt.closing_indent; + + retval += char_type(']'); + return retval; + } + } // }}} + + string_type operator()(const table_type& t, + const table_format_info& fmt, + const comment_type& com, + const source_location& loc) // {{{ + { + if (this->force_inline_) { + if (fmt.fmt == table_format::multiline_oneline) { + return this->format_ml_inline_table(t, fmt); + } else { + return this->format_inline_table(t, fmt); + } + } else { + if (fmt.fmt == table_format::multiline) { + string_type retval; + // comment is emitted inside format_ml_table + if (auto k = this->format_keys(this->keys_)) { + this->current_indent_ += fmt.name_indent; + retval += this->format_comments(com, fmt.indent_type); + retval += this->format_indent(fmt.indent_type); + this->current_indent_ -= fmt.name_indent; + retval += char_type('['); + retval += k.value(); + retval += string_conv("]\n"); + } + // otherwise, its the root. + + retval += this->format_ml_table(t, fmt); + return retval; + } else if (fmt.fmt == table_format::oneline) { + return this->format_inline_table(t, fmt); + } else if (fmt.fmt == table_format::multiline_oneline) { + return this->format_ml_inline_table(t, fmt); + } else if (fmt.fmt == table_format::dotted) { + std::vector keys; + if (this->keys_.empty()) { + throw serialization_error( + format_error( + "toml::serializer: " + "dotted table must have its key. use format(key, v)", + loc, + "here"), + loc); + } + keys.push_back(this->keys_.back()); + + const auto retval = this->format_dotted_table(t, fmt, loc, keys); + keys.pop_back(); + return retval; + } else { + assert(fmt.fmt == table_format::implicit); + + string_type retval; + for (const auto& kv : t) { + const auto& k = kv.first; + const auto& v = kv.second; + + if (!v.is_table() && !v.is_array_of_tables()) { + throw serialization_error( + format_error("toml::serializer: " + "an implicit table cannot have non-table value.", + v.location(), + "here"), + v.location()); + } + if (v.is_table()) { + if (v.as_table_fmt().fmt != table_format::multiline && + v.as_table_fmt().fmt != table_format::implicit) { + throw serialization_error( + format_error( + "toml::serializer: " + "an implicit table cannot have non-multiline table", + v.location(), + "here"), + v.location()); + } + } else { + assert(v.is_array()); + for (const auto& e : v.as_array()) { + if (e.as_table_fmt().fmt != table_format::multiline && + v.as_table_fmt().fmt != table_format::implicit) { + throw serialization_error( + format_error( + "toml::serializer: " + "an implicit table cannot have non-multiline table", + e.location(), + "here"), + e.location()); + } + } + } + + keys_.push_back(k); + retval += (*this)(v); + keys_.pop_back(); + } + return retval; + } + } + } // }}} + + private: + string_type escape_basic_string(const string_type& s) const // {{{ + { + string_type retval; + for (const char_type c : s) { + switch (c) { + case char_type('\\'): { + retval += string_conv("\\\\"); + break; + } + case char_type('\"'): { + retval += string_conv("\\\""); + break; + } + case char_type('\b'): { + retval += string_conv("\\b"); + break; + } + case char_type('\t'): { + retval += string_conv("\\t"); + break; + } + case char_type('\f'): { + retval += string_conv("\\f"); + break; + } + case char_type('\n'): { + retval += string_conv("\\n"); + break; + } + case char_type('\r'): { + retval += string_conv("\\r"); + break; + } + default: { + if (c == char_type(0x1B) && spec_.v1_1_0_add_escape_sequence_e) { + retval += string_conv("\\e"); + } else if ((char_type(0x00) <= c && c <= char_type(0x08)) || + (char_type(0x0A) <= c && c <= char_type(0x1F)) || + c == char_type(0x7F)) { + if (spec_.v1_1_0_add_escape_sequence_x) { + retval += string_conv("\\x"); + } else { + retval += string_conv("\\u00"); + } + const auto c1 = c / 16; + const auto c2 = c % 16; + retval += static_cast('0' + c1); + if (c2 < 10) { + retval += static_cast('0' + c2); + } else // 10 <= c2 + { + retval += static_cast('A' + (c2 - 10)); + } + } else { + retval += c; + } + } + } + } + return retval; + } // }}} + + string_type escape_ml_basic_string(const string_type& s) // {{{ + { + string_type retval; + for (const char_type c : s) { + switch (c) { + case char_type('\\'): { + retval += string_conv("\\\\"); + break; + } + case char_type('\b'): { + retval += string_conv("\\b"); + break; + } + case char_type('\t'): { + retval += string_conv("\\t"); + break; + } + case char_type('\f'): { + retval += string_conv("\\f"); + break; + } + case char_type('\n'): { + retval += string_conv("\n"); + break; + } + case char_type('\r'): { + retval += string_conv("\\r"); + break; + } + default: { + if (c == char_type(0x1B) && spec_.v1_1_0_add_escape_sequence_e) { + retval += string_conv("\\e"); + } else if ((char_type(0x00) <= c && c <= char_type(0x08)) || + (char_type(0x0A) <= c && c <= char_type(0x1F)) || + c == char_type(0x7F)) { + if (spec_.v1_1_0_add_escape_sequence_x) { + retval += string_conv("\\x"); + } else { + retval += string_conv("\\u00"); + } + const auto c1 = c / 16; + const auto c2 = c % 16; + retval += static_cast('0' + c1); + if (c2 < 10) { + retval += static_cast('0' + c2); + } else // 10 <= c2 + { + retval += static_cast('A' + (c2 - 10)); + } + } else { + retval += c; + } + } + } + } + // Only 1 or 2 consecutive `"`s are allowed in multiline basic string. + // 3 consecutive `"`s are considered as a closing delimiter. + // We need to check if there are 3 or more consecutive `"`s and insert + // backslash to break them down into several short `"`s like the `str6` + // in the following example. + // ```toml + // str4 = """Here are two quotation marks: "". Simple enough.""" + // # str5 = """Here are three quotation marks: """.""" # INVALID + // str5 = """Here are three quotation marks: ""\".""" + // str6 = """Here are fifteen quotation marks: ""\"""\"""\"""\"""\".""" + // ``` + auto found_3_quotes = retval.find(string_conv("\"\"\"")); + while (found_3_quotes != string_type::npos) { + retval.replace(found_3_quotes, 3, string_conv("\"\"\\\"")); + found_3_quotes = retval.find(string_conv("\"\"\"")); + } + return retval; + } // }}} + + string_type format_local_time(const local_time_type& t, + const bool has_seconds, + const std::size_t subsec_prec) // {{{ + { + std::ostringstream oss; + oss << std::setfill('0') << std::setw(2) << static_cast(t.hour); + oss << ':'; + oss << std::setfill('0') << std::setw(2) << static_cast(t.minute); + if (has_seconds) { + oss << ':'; + oss << std::setfill('0') << std::setw(2) << static_cast(t.second); + if (subsec_prec != 0) { + std::ostringstream subsec; + subsec << std::setfill('0') << std::setw(3) + << static_cast(t.millisecond); + subsec << std::setfill('0') << std::setw(3) + << static_cast(t.microsecond); + subsec << std::setfill('0') << std::setw(3) + << static_cast(t.nanosecond); + std::string subsec_str = subsec.str(); + oss << '.' << subsec_str.substr(0, subsec_prec); + } + } + return string_conv(oss.str()); + } // }}} + + string_type format_ml_table(const table_type& t, + const table_format_info& fmt) // {{{ + { + const auto format_later = [](const value_type& v) -> bool { + const bool is_ml_table = v.is_table() && + v.as_table_fmt().fmt != table_format::oneline && + v.as_table_fmt().fmt != + table_format::multiline_oneline && + v.as_table_fmt().fmt != table_format::dotted; + + const bool is_ml_array_table = v.is_array_of_tables() && + v.as_array_fmt().fmt != + array_format::oneline && + v.as_array_fmt().fmt != + array_format::multiline; + + return is_ml_table || is_ml_array_table; + }; + + string_type retval; + this->current_indent_ += fmt.body_indent; + for (const auto& kv : t) { + const auto& key = kv.first; + const auto& val = kv.second; + if (format_later(val)) { + continue; + } + this->keys_.push_back(key); + + retval += format_comments(val.comments(), fmt.indent_type); + retval += format_indent(fmt.indent_type); + if (val.is_table() && val.as_table_fmt().fmt == table_format::dotted) { + retval += (*this)(val); + } else { + retval += format_key(key); + retval += string_conv(" = "); + retval += (*this)(val); + retval += char_type('\n'); + } + this->keys_.pop_back(); + } + this->current_indent_ -= fmt.body_indent; + + if (!retval.empty()) { + retval += char_type('\n'); // for readability, add empty line between tables + } + for (const auto& kv : t) { + if (!format_later(kv.second)) { + continue; + } + // must be a [multiline.table] or [[multiline.array.of.tables]]. + // comments will be generated inside it. + this->keys_.push_back(kv.first); + retval += (*this)(kv.second); + this->keys_.pop_back(); + } + return retval; + } // }}} + + string_type format_inline_table(const table_type& t, + const table_format_info&) // {{{ + { + // comments are ignored because we cannot write without newline + string_type retval; + retval += char_type('{'); + for (const auto& kv : t) { + this->force_inline_ = true; + retval += this->format_key(kv.first); + retval += string_conv(" = "); + retval += (*this)(kv.second); + retval += string_conv(", "); + } + if (!t.empty()) { + retval.pop_back(); // ' ' + retval.pop_back(); // ',' + } + retval += char_type('}'); + this->force_inline_ = false; + return retval; + } // }}} + + string_type format_ml_inline_table(const table_type& t, + const table_format_info& fmt) // {{{ + { + string_type retval; + retval += string_conv("{\n"); + this->current_indent_ += fmt.body_indent; + for (const auto& kv : t) { + this->force_inline_ = true; + retval += format_comments(kv.second.comments(), fmt.indent_type); + retval += format_indent(fmt.indent_type); + retval += kv.first; + retval += string_conv(" = "); + + this->force_inline_ = true; + retval += (*this)(kv.second); + + retval += string_conv(",\n"); + } + if (!t.empty()) { + retval.pop_back(); // '\n' + retval.pop_back(); // ',' + } + this->current_indent_ -= fmt.body_indent; + this->force_inline_ = false; + + this->current_indent_ += fmt.closing_indent; + retval += format_indent(fmt.indent_type); + this->current_indent_ -= fmt.closing_indent; + + retval += char_type('}'); + return retval; + } // }}} + + string_type format_dotted_table(const table_type& t, + const table_format_info& fmt, // {{{ + const source_location&, + std::vector& keys) { + // lets say we have: `{"a": {"b": {"c": {"d": "foo", "e": "bar"} } }` + // and `a` and `b` are `dotted`. + // + // - in case if `c` is `oneline`: + // ```toml + // a.b.c = {d = "foo", e = "bar"} + // ``` + // + // - in case if and `c` is `dotted`: + // ```toml + // a.b.c.d = "foo" + // a.b.c.e = "bar" + // ``` + + string_type retval; + + for (const auto& kv : t) { + const auto& key = kv.first; + const auto& val = kv.second; + + keys.push_back(key); + + // format recursive dotted table? + if (val.is_table() && val.as_table_fmt().fmt != table_format::oneline && + val.as_table_fmt().fmt != table_format::multiline_oneline) { + retval += this->format_dotted_table(val.as_table(), + val.as_table_fmt(), + val.location(), + keys); + } else // non-table or inline tables. format normally + { + retval += format_comments(val.comments(), fmt.indent_type); + retval += format_indent(fmt.indent_type); + retval += format_keys(keys).value(); + retval += string_conv(" = "); + this->force_inline_ = true; // sub-table must be inlined + retval += (*this)(val); + retval += char_type('\n'); + this->force_inline_ = false; + } + keys.pop_back(); + } + return retval; + } // }}} + + string_type format_key(const key_type& key) // {{{ + { + if (key.empty()) { + return string_conv("\"\""); + } + + // check the key can be a bare (unquoted) key + auto loc = detail::make_temporary_location(string_conv(key)); + auto reg = detail::syntax::unquoted_key(this->spec_).scan(loc); + if (reg.is_ok() && loc.eof()) { + return key; + } + + // if it includes special characters, then format it in a "quoted" key. + string_type formatted = string_conv("\""); + for (const char_type c : key) { + switch (c) { + case char_type('\\'): { + formatted += string_conv("\\\\"); + break; + } + case char_type('\"'): { + formatted += string_conv("\\\""); + break; + } + case char_type('\b'): { + formatted += string_conv("\\b"); + break; + } + case char_type('\t'): { + formatted += string_conv("\\t"); + break; + } + case char_type('\f'): { + formatted += string_conv("\\f"); + break; + } + case char_type('\n'): { + formatted += string_conv("\\n"); + break; + } + case char_type('\r'): { + formatted += string_conv("\\r"); + break; + } + default: { + // ASCII ctrl char + if ((char_type(0x00) <= c && c <= char_type(0x08)) || + (char_type(0x0A) <= c && c <= char_type(0x1F)) || + c == char_type(0x7F)) { + if (spec_.v1_1_0_add_escape_sequence_x) { + formatted += string_conv("\\x"); + } else { + formatted += string_conv("\\u00"); + } + const auto c1 = c / 16; + const auto c2 = c % 16; + formatted += static_cast('0' + c1); + if (c2 < 10) { + formatted += static_cast('0' + c2); + } else // 10 <= c2 + { + formatted += static_cast('A' + (c2 - 10)); + } + } else { + formatted += c; + } + break; + } + } + } + formatted += string_conv("\""); + return formatted; + } // }}} + + cxx::optional format_keys(const std::vector& keys) // {{{ + { + if (keys.empty()) { + return cxx::make_nullopt(); + } + + string_type formatted; + for (const auto& ky : keys) { + formatted += format_key(ky); + formatted += char_type('.'); + } + formatted.pop_back(); // remove the last dot '.' + return formatted; + } // }}} + + string_type format_comments(const discard_comments&, + const indent_char) const // {{{ + { + return string_conv(""); + } // }}} + + string_type format_comments(const preserve_comments& comments, + const indent_char indent_type) const // {{{ + { + string_type retval; + for (const auto& c : comments) { + if (c.empty()) { + continue; + } + retval += format_indent(indent_type); + if (c.front() != '#') { + retval += char_type('#'); + } + retval += string_conv(c); + if (c.back() != '\n') { + retval += char_type('\n'); + } + } + return retval; + } // }}} + + string_type format_indent(const indent_char indent_type) const // {{{ + { + const auto indent = static_cast( + (std::max)(0, this->current_indent_)); + if (indent_type == indent_char::space) { + return string_conv(make_string(indent, ' ')); + } else if (indent_type == indent_char::tab) { + return string_conv(make_string(indent, '\t')); + } else { + return string_type {}; + } + } // }}} + + std::locale set_locale(std::ostream& os) const { + return os.imbue(std::locale::classic()); + } + + private: + spec spec_; + bool force_inline_; // table inside an array without fmt specification + std::int32_t current_indent_; + std::vector keys_; + }; + } // namespace detail + + template + typename basic_value::string_type format( + const basic_value& v, + const spec s = spec::default_version()) { + detail::serializer ser(s); + return ser(v); + } + + template + typename basic_value::string_type format( + const typename basic_value::key_type& k, + const basic_value& v, + const spec s = spec::default_version()) { + detail::serializer ser(s); + return ser(k, v); + } + + template + typename basic_value::string_type format( + const std::vector::key_type>& ks, + const basic_value& v, + const spec s = spec::default_version()) { + detail::serializer ser(s); + return ser(ks, v); + } + + template + std::ostream& operator<<(std::ostream& os, const basic_value& v) { + os << format(v); + return os; + } + +} // namespace toml + +#if defined(TOML11_COMPILE_SOURCES) +namespace toml { + struct type_config; + struct ordered_type_config; + + extern template typename basic_value::string_type format( + const basic_value&, + const spec); + + extern template typename basic_value::string_type format( + const typename basic_value::key_type& k, + const basic_value& v, + const spec); + + extern template typename basic_value::string_type format( + const std::vector::key_type>& ks, + const basic_value& v, + const spec s); + + extern template typename basic_value::string_type + format(const basic_value&, + const spec); + + extern template typename basic_value::string_type format( + const typename basic_value::key_type& k, + const basic_value& v, + const spec); + + extern template typename basic_value::string_type format( + const std::vector::key_type>& ks, + const basic_value& v, + const spec s); + + namespace detail { + extern template class serializer<::toml::type_config>; + extern template class serializer<::toml::ordered_type_config>; + } // namespace detail +} // namespace toml +#endif // TOML11_COMPILE_SOURCES + +#endif // TOML11_SERIALIZER_HPP +#ifndef TOML11_TOML_HPP +#define TOML11_TOML_HPP + +// The MIT License (MIT) +// +// Copyright (c) 2017-now Toru Niina +// +// 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. + +// IWYU pragma: begin_exports +// IWYU pragma: end_exports + +#endif // TOML11_TOML_HPP diff --git a/src/global/utils/tools.h b/src/global/utils/tools.h index 451c24264..30687d01b 100644 --- a/src/global/utils/tools.h +++ b/src/global/utils/tools.h @@ -2,20 +2,24 @@ * @file utils/tools.h * @brief Helper functions for general use * @implements + * - tools::ArrayImbalance -> unsigned short * - tools::TensorProduct<> -> boundaries_t * - tools::decompose1D -> std::vector * - tools::divideInProportions2D -> std::tuple * - tools::divideInProportions3D -> std::tuple * - tools::Decompose -> std::vector> + * - tools::Tracker * @namespaces: * - tools:: */ -#ifndef UTILS_HELPERS_H -#define UTILS_HELPERS_H +#ifndef UTILS_TOOLS_H +#define UTILS_TOOLS_H #include "global.h" +#include "arch/kokkos_aliases.h" +#include "utils/comparators.h" #include "utils/error.h" #include "utils/numeric.h" @@ -26,6 +30,30 @@ namespace tools { + /** + * @brief Compute the imbalance of a list of nonnegative values + * @param values List of values + * @return Imbalance of the list (0...100) + */ + template + auto ArrayImbalance(const std::vector& values) -> unsigned short { + raise::ErrorIf(values.empty(), "Disbalance error: value array is empty", HERE); + const auto mean = static_cast(std::accumulate(values.begin(), + values.end(), + static_cast(0))) / + static_cast(values.size()); + const auto sq_sum = static_cast(std::inner_product(values.begin(), + values.end(), + values.begin(), + static_cast(0))); + if (cmp::AlmostZero_host(sq_sum) || cmp::AlmostZero_host(mean)) { + return 0; + } + const auto cv = std::sqrt( + sq_sum / static_cast(values.size()) / mean - 1.0); + return static_cast(100.0 / (1.0 + math::exp(-cv))); + } + /** * @brief Compute a tensor product of a list of vectors * @param list List of vectors @@ -227,7 +255,7 @@ namespace tools { raise::ErrorIf(ndomains % n1 != 0, "Decomposition error: does not divide evenly", HERE); - std::tie(n2, + std::tie(n2, n3) = divideInProportions2D(ndomains / n1, ncells[1], ncells[2]); } else if (decomposition[0] < 0 && decomposition[1] < 0 && decomposition[2] < 0) { @@ -245,6 +273,54 @@ namespace tools { } } + class Tracker { + bool m_initialized { false }; + + std::string m_type; + std::size_t m_interval; + long double m_interval_time; + bool m_use_time; + + long double m_last_output_time { -1.0 }; + + public: + Tracker() = default; + + Tracker(const std::string& type, std::size_t interval, long double interval_time) + : m_initialized { true } + , m_type { type } + , m_interval { interval } + , m_interval_time { interval_time } + , m_use_time { interval_time > 0.0 } {} + + ~Tracker() = default; + + void init(const std::string& type, + std::size_t interval, + long double interval_time) { + m_type = type; + m_interval = interval; + m_interval_time = interval_time; + m_use_time = interval_time > 0.0; + m_initialized = true; + } + + auto shouldWrite(std::size_t step, long double time) -> bool { + raise::ErrorIf(!m_initialized, "Tracker not initialized", HERE); + if (m_use_time) { + if ((m_last_output_time < 0) or + (time - m_last_output_time >= m_interval_time)) { + m_last_output_time = time; + return true; + } else { + return false; + } + } else { + return step % m_interval == 0; + } + } + }; + } // namespace tools -#endif // UTILS_HELPERS_H +#endif // UTILS_TOOLS_H diff --git a/src/output/CMakeLists.txt b/src/output/CMakeLists.txt index b262d7771..2c25631ec 100644 --- a/src/output/CMakeLists.txt +++ b/src/output/CMakeLists.txt @@ -17,7 +17,6 @@ set(SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}) set(SOURCES ${SRC_DIR}/writer.cpp - ${SRC_DIR}/write_attrs.cpp ${SRC_DIR}/fields.cpp ${SRC_DIR}/utils/interpret_prompt.cpp ) diff --git a/src/output/tests/writer-mpi.cpp b/src/output/tests/writer-mpi.cpp index 72cf46e35..6b810fa22 100644 --- a/src/output/tests/writer-mpi.cpp +++ b/src/output/tests/writer-mpi.cpp @@ -1,6 +1,7 @@ #include "enums.h" #include "global.h" +#include "arch/mpi_aliases.h" #include "utils/formatting.h" #include "output/fields.h" @@ -19,8 +20,8 @@ void cleanup() { namespace fs = std::filesystem; - // fs::path tempfile_path { "test.h5" }; - // fs::remove(tempfile_path); + fs::path tempfile_path { "test.h5" }; + fs::remove(tempfile_path); } auto main(int argc, char* argv[]) -> int { @@ -32,7 +33,10 @@ auto main(int argc, char* argv[]) -> int { try { using namespace ntt; - auto writer = out::Writer("hdf5"); + + adios2::ADIOS adios { MPI_COMM_WORLD }; + auto writer = out::Writer(); + writer.init(&adios, "hdf5"); writer.defineMeshLayout({ static_cast(size) * 10 }, { static_cast(rank) * 10 }, { 10 }, @@ -65,7 +69,9 @@ auto main(int argc, char* argv[]) -> int { } catch (std::exception& e) { std::cerr << e.what() << std::endl; - cleanup(); + CallOnce([]() { + cleanup(); + }); MPI_Finalize(); Kokkos::finalize(); return 1; diff --git a/src/output/tests/writer-nompi.cpp b/src/output/tests/writer-nompi.cpp index a2a116e65..25a9a2c51 100644 --- a/src/output/tests/writer-nompi.cpp +++ b/src/output/tests/writer-nompi.cpp @@ -26,10 +26,16 @@ auto main(int argc, char* argv[]) -> int { Kokkos::initialize(argc, argv); try { + adios2::ADIOS adios; using namespace ntt; - auto writer = out::Writer("hdf5"); - writer.defineMeshLayout({ 10, 10, 10 }, { 0, 0, 0 }, { 10, 10, 10 }, false, Coord::Cart); + auto writer = out::Writer(); + writer.init(&adios, "hdf5", "test"); + writer.defineMeshLayout({ 10, 10, 10 }, + { 0, 0, 0 }, + { 10, 10, 10 }, + false, + Coord::Cart); writer.defineFieldOutputs(SimEngine::SRPIC, { "E", "B", "Rho_1_3", "N_2" }); ndfield_t field { "fld", @@ -51,11 +57,11 @@ auto main(int argc, char* argv[]) -> int { names.push_back(writer.fieldWriters()[0].name(i)); addresses.push_back(i); } - writer.beginWriting("test", 0, 0.0); + writer.beginWriting(0, 0.0); writer.writeField(names, field, addresses); writer.endWriting(); - writer.beginWriting("test", 1, 0.1); + writer.beginWriting(1, 0.1); writer.writeField(names, field, addresses); writer.endWriting(); diff --git a/src/output/write_attrs.cpp b/src/output/write_attrs.cpp deleted file mode 100644 index e1e70f467..000000000 --- a/src/output/write_attrs.cpp +++ /dev/null @@ -1,136 +0,0 @@ -#include "enums.h" -#include "global.h" - -#include "output/writer.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace out { - - template - struct has_to_string : std::false_type {}; - - template - struct has_to_string().to_string())>> - : std::true_type {}; - - template - auto write(adios2::IO& io, const std::string& name, T var) -> - typename std::enable_if::value, void>::type { - io.DefineAttribute(name, std::string(var.to_string())); - } - - template - auto write(adios2::IO& io, const std::string& name, T var) - -> decltype(void(T()), void()) { - io.DefineAttribute(name, var); - } - - template <> - void write(adios2::IO& io, const std::string& name, bool var) { - io.DefineAttribute(name, var ? 1 : 0); - } - - template <> - void write(adios2::IO& io, const std::string& name, Dimension var) { - io.DefineAttribute(name, (unsigned short)var); - } - - template - auto write_vec(adios2::IO& io, const std::string& name, std::vector var) -> - typename std::enable_if::value, void>::type { - std::vector var_str; - for (const auto& v : var) { - var_str.push_back(v.to_string()); - } - io.DefineAttribute(name, var_str.data(), var_str.size()); - } - - template - auto write_vec(adios2::IO& io, const std::string& name, std::vector var) - -> decltype(void(T()), void()) { - io.DefineAttribute(name, var.data(), var.size()); - } - - std::map> - write_functions; - - template - void register_write_function() { - write_functions[std::type_index(typeid(T))] = - [](adios2::IO& io, const std::string& name, std::any a) { - write(io, name, std::any_cast(a)); - }; - } - - template - void register_write_function_for_vector() { - write_functions[std::type_index(typeid(std::vector))] = - [](adios2::IO& io, const std::string& name, std::any a) { - write_vec(io, name, std::any_cast>(a)); - }; - } - - void write_any(adios2::IO& io, const std::string& name, std::any a) { - auto it = write_functions.find(a.type()); - if (it != write_functions.end()) { - it->second(io, name, a); - } else { - throw std::runtime_error("No write function registered for this type"); - } - } - - void Writer::writeAttrs(const prm::Parameters& params) { - register_write_function(); - register_write_function(); - register_write_function(); - register_write_function(); - register_write_function(); - register_write_function(); - register_write_function(); - register_write_function(); - register_write_function(); - register_write_function(); - register_write_function(); - register_write_function(); - register_write_function(); - register_write_function(); - register_write_function(); - register_write_function(); - register_write_function(); - register_write_function(); - register_write_function(); - register_write_function_for_vector(); - register_write_function_for_vector(); - register_write_function_for_vector(); - register_write_function_for_vector(); - register_write_function_for_vector(); - register_write_function_for_vector(); - register_write_function_for_vector(); - register_write_function_for_vector(); - register_write_function_for_vector(); - register_write_function_for_vector(); - register_write_function_for_vector(); - register_write_function_for_vector(); - register_write_function_for_vector(); - register_write_function_for_vector(); - register_write_function_for_vector(); - register_write_function_for_vector(); - register_write_function_for_vector(); - - for (auto& [key, value] : params.allVars()) { - try { - write_any(m_io, key, value); - } catch (const std::exception& e) { - continue; - } - } - } -} // namespace out \ No newline at end of file diff --git a/src/output/writer.cpp b/src/output/writer.cpp index 182705efa..3d526b306 100644 --- a/src/output/writer.cpp +++ b/src/output/writer.cpp @@ -4,12 +4,13 @@ #include "arch/kokkos_aliases.h" #include "utils/error.h" +#include "utils/formatting.h" #include "utils/param_container.h" +#include "utils/tools.h" #include - -#include -#include +#include +#include #if defined(MPI_ENABLED) #include "arch/mpi_aliases.h" @@ -17,21 +18,31 @@ #include #endif +#include +#include + namespace out { - Writer::Writer(const std::string& engine) : m_engine { engine } { - m_io = m_adios.DeclareIO("Entity::ADIOS2"); + void Writer::init(adios2::ADIOS* ptr_adios, + const std::string& engine, + const std::string& title) { + m_engine = engine; + p_adios = ptr_adios; + + raise::ErrorIf(p_adios == nullptr, "ADIOS pointer is null", HERE); + + m_io = p_adios->DeclareIO("Entity::Output"); m_io.SetEngine(engine); m_io.DefineVariable("Step"); m_io.DefineVariable("Time"); + m_fname = title + (m_engine == "hdf5" ? ".h5" : ".bp"); } void Writer::addTracker(const std::string& type, std::size_t interval, long double interval_time) { - m_trackers.insert(std::pair( - { type, Tracker(type, interval, interval_time) })); + m_trackers.insert({ type, tools::Tracker(type, interval, interval_time) }); } auto Writer::shouldWrite(const std::string& type, @@ -40,11 +51,15 @@ namespace out { if (m_trackers.find(type) != m_trackers.end()) { return m_trackers.at(type).shouldWrite(step, time); } else { - raise::Error("Tracker type not found", HERE); + raise::Error(fmt::format("Tracker type %s not found", type.c_str()), HERE); return false; } } + void Writer::setMode(adios2::Mode mode) { + m_mode = mode; + } + void Writer::defineMeshLayout(const std::vector& glob_shape, const std::vector& loc_corner, const std::vector& loc_shape, @@ -130,17 +145,20 @@ namespace out { for (const auto& prtl : m_prtl_writers) { for (auto d { 0u }; d < dim; ++d) { m_io.DefineVariable(prtl.name("X", d + 1), - {}, - {}, + { adios2::UnknownDim }, + { adios2::UnknownDim }, { adios2::UnknownDim }); } for (auto d { 0u }; d < Dim::_3D; ++d) { m_io.DefineVariable(prtl.name("U", d + 1), - {}, - {}, + { adios2::UnknownDim }, + { adios2::UnknownDim }, { adios2::UnknownDim }); } - m_io.DefineVariable(prtl.name("W", 0), {}, {}, { adios2::UnknownDim }); + m_io.DefineVariable(prtl.name("W", 0), + { adios2::UnknownDim }, + { adios2::UnknownDim }, + { adios2::UnknownDim }); } } @@ -155,6 +173,10 @@ namespace out { } } + void Writer::writeAttrs(const prm::Parameters& params) { + params.write(m_io); + } + template void WriteField(adios2::IO& io, adios2::Engine& writer, @@ -216,9 +238,13 @@ namespace out { } void Writer::writeParticleQuantity(const array_t& array, + std::size_t glob_total, + std::size_t loc_offset, const std::string& varname) { auto var = m_io.InquireVariable(varname); - var.SetSelection(adios2::Box({}, { array.extent(0) })); + var.SetShape({ glob_total }); + var.SetSelection( + adios2::Box({ loc_offset }, { array.extent(0) })); auto array_h = Kokkos::create_mirror_view(array); Kokkos::deep_copy(array_h, array); m_writer.Put(var, array_h); @@ -281,12 +307,15 @@ namespace out { m_writer.Put(vare, xe_h); } - void Writer::beginWriting(const std::string& fname, - std::size_t tstep, - long double time) { - m_adios.ExitComputationBlock(); + void Writer::beginWriting(std::size_t tstep, long double time) { + raise::ErrorIf(p_adios == nullptr, "ADIOS pointer is null", HERE); + p_adios->ExitComputationBlock(); + if (m_writing_mode) { + raise::Fatal("Already writing", HERE); + } + m_writing_mode = true; try { - m_writer = m_io.Open(fname + (m_engine == "hdf5" ? ".h5" : ".bp"), m_mode); + m_writer = m_io.Open(m_fname, m_mode); } catch (std::exception& e) { raise::Fatal(e.what(), HERE); } @@ -297,9 +326,14 @@ namespace out { } void Writer::endWriting() { + raise::ErrorIf(p_adios == nullptr, "ADIOS pointer is null", HERE); + if (!m_writing_mode) { + raise::Fatal("Not writing", HERE); + } + m_writing_mode = false; m_writer.EndStep(); m_writer.Close(); - m_adios.EnterComputationBlock(); + p_adios->EnterComputationBlock(); } template void Writer::writeField(const std::vector&, diff --git a/src/output/writer.h b/src/output/writer.h index d6a089095..ba24a3d65 100644 --- a/src/output/writer.h +++ b/src/output/writer.h @@ -11,6 +11,7 @@ #include "arch/kokkos_aliases.h" #include "utils/param_container.h" +#include "utils/tools.h" #include "output/fields.h" #include "output/particles.h" @@ -28,74 +29,46 @@ namespace out { - class Tracker { - const std::string m_type; - const std::size_t m_interval; - const long double m_interval_time; - const bool m_use_time; - - long double m_last_output_time { -1.0 }; - - public: - Tracker(const std::string& type, std::size_t interval, long double interval_time) - : m_type { type } - , m_interval { interval } - , m_interval_time { interval_time } - , m_use_time { interval_time > 0.0 } {} - - ~Tracker() = default; - - auto shouldWrite(std::size_t step, long double time) -> bool { - if (m_use_time) { - if (time - m_last_output_time >= m_interval_time) { - m_last_output_time = time; - return true; - } else { - return false; - } - } else { - return step % m_interval == 0; - } - } - }; - class Writer { -#if !defined(MPI_ENABLED) - adios2::ADIOS m_adios; -#else // MPI_ENABLED - adios2::ADIOS m_adios { MPI_COMM_WORLD }; -#endif + adios2::ADIOS* p_adios { nullptr }; + adios2::IO m_io; adios2::Engine m_writer; adios2::Mode m_mode { adios2::Mode::Write }; // global shape of the fields array to output - adios2::Dims m_flds_g_shape; + adios2::Dims m_flds_g_shape; // local corner of the fields array to output - adios2::Dims m_flds_l_corner; + adios2::Dims m_flds_l_corner; // local shape of the fields array to output - adios2::Dims m_flds_l_shape; - bool m_flds_ghosts; - const std::string m_engine; + adios2::Dims m_flds_l_shape; + bool m_flds_ghosts; + std::string m_engine; + std::string m_fname; - std::map m_trackers; + std::map m_trackers; std::vector m_flds_writers; std::vector m_prtl_writers; std::vector m_spectra_writers; + bool m_writing_mode { false }; + public: - Writer() : m_engine { "disabled" } {} + Writer() {} - Writer(const std::string& engine); ~Writer() = default; Writer(Writer&&) = default; + void init(adios2::ADIOS*, const std::string&, const std::string&); + + void setMode(adios2::Mode); + void addTracker(const std::string&, std::size_t, long double); auto shouldWrite(const std::string&, std::size_t, long double) -> bool; - void writeAttrs(const prm::Parameters& params); + void writeAttrs(const prm::Parameters&); void defineMeshLayout(const std::vector&, const std::vector&, @@ -114,14 +87,21 @@ namespace out { const ndfield_t&, const std::vector&); - void writeParticleQuantity(const array_t&, const std::string&); + void writeParticleQuantity(const array_t&, + std::size_t, + std::size_t, + const std::string&); void writeSpectrum(const array_t&, const std::string&); void writeSpectrumBins(const array_t&, const std::string&); - void beginWriting(const std::string&, std::size_t, long double); + void beginWriting(std::size_t, long double); void endWriting(); /* getters -------------------------------------------------------------- */ + auto fname() const -> const std::string& { + return m_fname; + } + auto fieldWriters() const -> const std::vector& { return m_flds_writers; }