diff --git a/implementations/Crafter.Build-Test.cpp b/implementations/Crafter.Build-Test.cpp index 8ad80fb..23ef72d 100644 --- a/implementations/Crafter.Build-Test.cpp +++ b/implementations/Crafter.Build-Test.cpp @@ -17,6 +17,12 @@ License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +module; +// toml++ is consumed as a translation-unit-private dependency in the GMF: the +// parser is only needed for test.toml discovery here, so keeping it out of +// `import std`-using module purviews avoids dragging it through the module +// graph (and through every PCM consumer of Crafter.Build). +#include "../lib/toml.hpp" export module Crafter.Build:Test_impl; import std; import :Test; @@ -103,15 +109,11 @@ namespace { #endif } - std::string JoinAndQuoteArgs(std::span args, TestRunner::Shell shell) { + std::string JoinAndQuoteArgs(std::span args) { std::string out; for (const auto& a : args) { if (!out.empty()) out.push_back(' '); - switch (shell) { - case TestRunner::Shell::Sh: out += ShellQuoteSh(a); break; - case TestRunner::Shell::Cmd: out += ShellQuoteCmd(a); break; - case TestRunner::Shell::Host: out += ShellQuoteHost(a); break; - } + out += ShellQuoteHost(a); } return out; } @@ -235,69 +237,12 @@ Configuration* Crafter::ParentLib(std::string_view name) { TestRunner TestRunner::Local() { TestRunner r; r.name = "local"; - r.argsShell = Shell::Host; - return r; -} - -TestRunner TestRunner::Ssh(std::string host, std::string remoteDir) { - TestRunner r; - r.name = std::format("ssh:{}", host); - r.remoteDir = std::move(remoteDir); - r.argsShell = Shell::Sh; - // Outer "..." (not '...') so the wrapper survives both sh (Linux host) - // and cmd (Windows host). cmd doesn't honor single quotes, which would - // cause it to split on the inner '&&'. Args inside use POSIX quoting - // because they reach a remote bash regardless of which host issued them. - r.copy = std::format("ssh -q {0} \"mkdir -p {{remote_bundle}}\" && scp -r -q {{bundle}}/. {0}:{{remote_bundle}}/", host); - r.exec = std::format("ssh -q {} \"cd {{remote_bundle}} && ./{{bin_name}} {{args}}\"", host); - r.cleanup = std::format("ssh -q {} \"rm -rf {{remote_bundle}}\"", host); - // RunCommandChecked captures stdout+stderr internally — no shell redirect - // needed in the probe spec, which means it works the same way under cmd - // (Windows host) and sh (Linux host) since `> /dev/null` doesn't translate. - r.probe = std::format("ssh -q -o BatchMode=yes -o ConnectTimeout=5 {} true", host); - return r; -} - -TestRunner TestRunner::SshWin(std::string host, std::string remoteDir) { - TestRunner r; - r.name = std::format("sshwin:{}", host); - r.remoteDir = std::move(remoteDir); - r.argsShell = Shell::Cmd; - // cmd.exe-friendly templates. Use backslash variants of remote paths because - // cmd's mkdir won't auto-create intermediate directories with forward - // slashes. scp tolerates either, so we keep forward slashes for the scp - // dest (which `{remote_bundle}` provides). 2>nul + `& rem ok` swallows - // mkdir's "already exists" error and forces exit code 0. - r.copy = std::format("ssh -q {0} \"mkdir {{remote_bundle_win}} 2>nul & rem ok\" && scp -r -q {{bundle}}/. {0}:{{remote_bundle}}/", host); - r.exec = std::format("ssh -q {} \"{{bin_win}} {{args}}\"", host); - r.cleanup = std::format("ssh -q {} \"rd /s /q {{remote_bundle_win}} 2>nul & rem ok\"", host); - // No shell redirect in the probe — see TestRunner::Ssh for rationale. - r.probe = std::format("ssh -q -o BatchMode=yes -o ConnectTimeout=5 {} \"ver\"", host); - return r; -} - -TestRunner TestRunner::Wsl(std::string remoteDir) { - TestRunner r; - r.name = "wsl"; - r.argsShell = Shell::Sh; - r.remoteDir = std::move(remoteDir); - // Transport runner: stage the test bundle into WSL's native filesystem - // (faster than executing in-place from /mnt/c) then run via bash. {bundle_wsl} - // is the local Windows path translated to /mnt//... form so wsl cp - // can read it. Args are POSIX-quoted because they reach a Linux shell. - r.copy = "wsl mkdir -p {remote_bundle} && wsl cp -r {bundle_wsl}/. {remote_bundle}/"; - r.exec = "wsl bash -c \"cd {remote_bundle} && ./{bin_name} {args}\""; - r.cleanup = "wsl rm -rf {remote_bundle}"; - // `where wsl` matches the host-shell convention; on a non-Windows host - // it fails (no `where` command), which correctly skips this runner. - r.probe = "where wsl"; return r; } TestRunner TestRunner::Cmd(std::string command) { TestRunner r; r.name = std::format("cmd:{}", command); - r.argsShell = Shell::Host; r.exec = std::format("{} {{bin}} {{args}}", command); #ifdef _WIN32 r.probe = std::format("where {}", command); @@ -307,6 +252,68 @@ TestRunner TestRunner::Cmd(std::string command) { return r; } +TestRunner TestRunner::Wine() { + TestRunner r; + r.name = "wine"; + r.exec = "wine {bin} {args}"; +#ifdef _WIN32 + r.probe = "where wine"; +#else + r.probe = "which wine"; +#endif + return r; +} + +TestRunner TestRunner::ForTarget(const Configuration& cfg) { + const std::string& target = cfg.target; + + // Same triple as the host → run the binary directly. Covers the common + // case (cfg.target defaulted to HostTarget()) without any wrapper. + if (target == HostTarget()) return Local(); + + // Windows targets: native on a Windows host, Wine on Linux. We don't + // distinguish mingw vs msvc here — the produced .exe runs the same way. + if (TargetIsWindows(target)) { + return TargetIsWindows(HostTarget()) ? Local() : Wine(); + } + + // WASI: a .wasm file isn't directly executable; wasmtime is the canonical + // runtime. wasi-cli also works but the upstream Bytecode Alliance name is + // wasmtime, so we standardize on that. + if (target.starts_with("wasm32-wasi") || target.starts_with("wasm64-wasi")) { + return Cmd("wasmtime"); + } + + // Non-host Linux triple: extract the architecture and route through + // qemu-user. Triple is --- (or sometimes 3 parts); + // qemu-user's binary names mostly follow the arch field, with two known + // mismatches handled below. cfg.sysroot, when set, becomes QEMU_LD_PREFIX + // so the target's dynamic linker / shared libs are reachable — without + // it qemu-user crashes on dynamic ELFs with "could not open /lib/ld...". + if (target.find("-linux-") != std::string::npos) { + auto dash = target.find('-'); + std::string arch = target.substr(0, dash); + // i686-linux-gnu → qemu-i386; arm-* already matches qemu-arm; aarch64, + // riscv64, ppc64le, mips, mips64, s390x all match their qemu names. + if (arch == "i686") arch = "i386"; + TestRunner r = Cmd(std::format("qemu-{}", arch)); + if (!cfg.sysroot.empty()) { + // Use `env VAR=value cmd` rather than the shell's `VAR=value cmd` + // prefix syntax: RunCommandWithTimeout pipes through GNU `timeout`, + // which execvp's its argument list directly without going through a + // shell. A bare VAR=value would be exec'd as a command path and + // fail with "No such file or directory". + r.exec = std::format("env QEMU_LD_PREFIX={} {}", cfg.sysroot, r.exec); + } + return r; + } + + // Unknown / bare-metal / freestanding targets: fall back to Local. The + // caller's runner-availability probe (or absence of the binary) surfaces + // the problem rather than us inventing a wrong wrapper here. + return Local(); +} + namespace { std::string NormalizeTriple(std::string_view target) { std::string out(target); @@ -316,56 +323,18 @@ namespace { return out; } + // Spec grammar: "local" | "cmd:". Used by CRAFTER_BUILD_RUNNER_* + // env vars and --runner=. The set used to include ssh/sshwin/wsl variants; + // those were removed when transport-style runners were dropped (issue #8). std::optional ParseRunnerSpec(std::string_view spec) { if (spec.empty()) return std::nullopt; - std::vector parts; - for (auto piece : std::views::split(spec, ':')) { - parts.emplace_back(std::string_view(piece.begin(), piece.end())); - } - if (parts.empty()) return std::nullopt; - if (parts[0] == "local") return TestRunner::Local(); - if (parts[0] == "cmd" && parts.size() == 2) return TestRunner::Cmd(parts[1]); - if (parts[0] == "ssh" && parts.size() == 2) return TestRunner::Ssh(parts[1]); - if (parts[0] == "ssh" && parts.size() >= 3) { - std::string remote; - for (std::size_t i = 2; i < parts.size(); ++i) { - if (i > 2) remote.push_back(':'); - remote += parts[i]; - } - return TestRunner::Ssh(parts[1], remote); - } - if (parts[0] == "sshwin" && parts.size() == 2) return TestRunner::SshWin(parts[1]); - if (parts[0] == "sshwin" && parts.size() >= 3) { - std::string remote; - for (std::size_t i = 2; i < parts.size(); ++i) { - if (i > 2) remote.push_back(':'); - remote += parts[i]; - } - return TestRunner::SshWin(parts[1], remote); - } - if (parts[0] == "wsl" && parts.size() == 1) return TestRunner::Wsl(); - if (parts[0] == "wsl" && parts.size() >= 2) { - std::string remote; - for (std::size_t i = 1; i < parts.size(); ++i) { - if (i > 1) remote.push_back(':'); - remote += parts[i]; - } - return TestRunner::Wsl(remote); + if (spec == "local") return TestRunner::Local(); + if (spec.starts_with("cmd:") && spec.size() > 4) { + return TestRunner::Cmd(std::string(spec.substr(4))); } throw std::runtime_error(std::format( - "TestRunner::FromSpec: unrecognized runner spec '{}'", spec)); - } - - // C:\Users\jorij -> /mnt/c/Users/jorij. Idempotent on paths that don't - // start with a drive letter, so callers can compute it unconditionally. - std::string WindowsPathToWsl(std::string_view p) { - if (p.size() < 2 || p[1] != ':') return std::string(p); - char drive = static_cast(std::tolower(static_cast(p[0]))); - std::string out = std::format("/mnt/{}", drive); - for (std::size_t i = 2; i < p.size(); ++i) { - out.push_back(p[i] == '\\' ? '/' : p[i]); - } - return out; + "TestRunner::FromSpec: unrecognized runner spec '{}' " + "(expected 'local' or 'cmd:')", spec)); } } @@ -382,60 +351,24 @@ TestRunner TestRunner::FromEnv(std::string_view target, TestRunner fallback) { } TestResult Crafter::RunSingleTest(const Test& test, const fs::path& binary, std::chrono::seconds timeout) { - using namespace std::chrono_literals; TestResult result; result.name = test.config.name; std::map ph; - ph["{args}"] = JoinAndQuoteArgs(test.args, test.runner.argsShell); - ph["{bin_name}"] = binary.filename().string(); - ph["{bundle}"] = binary.parent_path().string(); + ph["{args}"] = JoinAndQuoteArgs(test.args); auto start = std::chrono::steady_clock::now(); CommandResult r; if (test.runner.exec.empty()) { - // Pure-local runner: spawn the binary directly through the host shell. + // Local runner: spawn the binary directly through the host shell. std::string cmd = std::format("{} {}", ShellQuoteHost(binary.string()), ph["{args}"]); r = RunCommandWithTimeout(cmd, timeout); - } else if (test.runner.copy.empty()) { - // Prefix runner (qemu-user, wsl, ...): templated exec wraps a local binary. + } else { + // Prefix runner (qemu-user, wasmtime, wine, ...): templated exec + // wraps the local binary. ph["{bin}"] = binary.string(); r = RunCommandWithTimeout(Substitute(test.runner.exec, ph), timeout); - } else { - // Transport runner (ssh, ...): copy bundle → exec remotely → cleanup. - std::string unique = std::format("{}-{}-{}", - test.config.name, - std::hash{}(std::this_thread::get_id()), - std::chrono::steady_clock::now().time_since_epoch().count()); - ph["{remote_bundle}"] = std::format("{}/{}", test.runner.remoteDir, unique); - ph["{bin}"] = std::format("{}/{}", ph["{remote_bundle}"], ph["{bin_name}"]); - // Backslash variants for cmd.exe-shell remotes (cmd's mkdir won't - // auto-create intermediate directories when path uses forward slashes). - ph["{remote_bundle_win}"] = ph["{remote_bundle}"]; - std::replace(ph["{remote_bundle_win}"].begin(), ph["{remote_bundle_win}"].end(), '/', '\\'); - ph["{bin_win}"] = ph["{bin}"]; - std::replace(ph["{bin_win}"].begin(), ph["{bin_win}"].end(), '/', '\\'); - // WSL form of the local bundle path: C:\foo -> /mnt/c/foo. Used by - // the Wsl runner's `wsl cp -r` step to read the test bundle out of - // the Windows host's filesystem. - ph["{bundle_wsl}"] = WindowsPathToWsl(ph["{bundle}"]); - - CommandResult cp = RunCommandWithTimeout(Substitute(test.runner.copy, ph), 5min); - if (cp.exitCode != 0) { - auto end = std::chrono::steady_clock::now(); - result.duration = std::chrono::duration_cast(end - start); - result.outcome = TestOutcome::Fail; - result.exitCode = cp.exitCode; - result.output = std::format("copy step failed (exit {}):\n{}", cp.exitCode, cp.output); - return result; - } - - r = RunCommandWithTimeout(Substitute(test.runner.exec, ph), timeout); - - if (!test.runner.cleanup.empty()) { - std::system(Substitute(test.runner.cleanup, ph).c_str()); - } } auto end = std::chrono::steady_clock::now(); @@ -453,20 +386,165 @@ TestResult Crafter::RunSingleTest(const Test& test, const fs::path& binary, std: return result; } +namespace { + // Declarative test metadata loaded from tests//test.toml. Lets a test + // ship just main.cpp + a few lines of config instead of a whole project.cpp + // when its needs are "pick a target, gate on prerequisites, run with these + // args". project.cpp stays the escape hatch for outer-driver tests that + // call Build() / inspect intermediate state. + struct TestManifest { + std::optional target; + std::optional march; + std::optional mtune; + std::optional sysroot; + std::vector requires_; + std::optional timeoutSeconds; + std::vector args; + std::vector> defines; + }; + + TestManifest ParseTestManifest(const fs::path& path) { + // toml++ builds with exceptions enabled by default; parse_file throws + // toml::parse_error on malformed input. Rethrow with the path attached + // so the discovery loop's catch can surface "where the error came from" + // alongside toml++'s "what was wrong". + toml::table t; + try { + t = toml::parse_file(path.string()); + } catch (const toml::parse_error& e) { + throw std::runtime_error(std::format( + "test.toml parse error in {}: {}", + path.string(), + std::string_view(e.description()))); + } + TestManifest m; + if (auto v = t["target"].value()) m.target = *v; + if (auto v = t["march"].value()) m.march = *v; + if (auto v = t["mtune"].value()) m.mtune = *v; + if (auto v = t["sysroot"].value()) m.sysroot = *v; + if (auto v = t["timeout"].value()) m.timeoutSeconds = static_cast(*v); + if (auto arr = t["requires"].as_array()) { + for (auto& el : *arr) { + if (auto s = el.value()) m.requires_.push_back(*s); + } + } + if (auto arr = t["args"].as_array()) { + for (auto& el : *arr) { + if (auto s = el.value()) m.args.push_back(*s); + } + } + if (auto tbl = t["defines"].as_table()) { + for (auto&& [k, v] : *tbl) { + if (auto s = v.value()) { + m.defines.emplace_back(std::string(k.str()), *s); + } + } + } + return m; + } + + // Apply manifest overlay onto a Configuration synthesized from the test + // folder. Target overrides come last so a manifest's `target = "..."` + // wins over the synth default (= run's targetFilter). Defines accumulate; + // they don't replace pre-existing ones. + void ApplyManifest(Configuration& cfg, const TestManifest& m) { + if (m.target) cfg.target = *m.target; + if (m.march) cfg.march = *m.march; + if (m.mtune) cfg.mtune = *m.mtune; + if (m.sysroot) cfg.sysroot = *m.sysroot; + for (auto& [k, v] : m.defines) cfg.defines.push_back({k, v}); + } + + bool ToolOnPath(std::string_view name) { +#ifdef _WIN32 + std::string cmd = std::format("where {} > nul 2>&1", name); +#else + std::string cmd = std::format("which {} > /dev/null 2>&1", name); +#endif + return std::system(cmd.c_str()) == 0; + } + + struct RequireResult { + bool ok; + std::string reason; // human-readable when !ok + }; + + // Evaluate each `:` precondition. Returns the first failure + // (short-circuit; reporting one missing dep at a time is enough to act on + // and keeps the test log uncluttered). + RequireResult EvaluateRequires(std::span reqs) { + for (const auto& r : reqs) { + auto sep = r.find(':'); + if (sep == std::string::npos || sep == 0 || sep == r.size() - 1) { + return {false, std::format("malformed require '{}' (expected kind:arg)", r)}; + } + std::string_view kind(r.data(), sep); + std::string_view arg(r.data() + sep + 1, r.size() - sep - 1); + if (kind == "tool") { + if (!ToolOnPath(arg)) { + return {false, std::format("tool '{}' not on PATH", arg)}; + } + } else if (kind == "file") { + if (!fs::exists(std::string(arg))) { + return {false, std::format("file '{}' missing", arg)}; + } + } else if (kind == "env") { + const char* v = std::getenv(std::string(arg).c_str()); + if (!v || !*v) { + return {false, std::format("env '{}' unset", arg)}; + } + } else { + return {false, std::format( + "unknown require kind '{}' (expected tool/file/env)", kind)}; + } + } + return {true, ""}; + } + + // Match a runner's tool dependency against the test's declared + // requirements. Used to decide between Skip (declared, may legitimately + // be missing) and Fail (runner unavailable but test didn't declare it — + // a silent skip would mask broken cross-arch CI configuration). + bool RequiresMentionsTool(std::span reqs, std::string_view tool) { + std::string needle = std::format("tool:{}", tool); + return std::ranges::any_of(reqs, [&](const std::string& s) { return s == needle; }); + } + + // Best-effort extraction of the runner-tool name from a TestRunner so the + // hard-fail-unless-declared check can match it against `requires`. For + // Cmd("foo"), the name is "cmd:foo"; for Wine, it's "wine". Anything else + // (Local, transport runners) returns empty — those don't trigger the + // declared/undeclared gate. + std::string RunnerToolName(const TestRunner& runner) { + if (runner.name == "wine") return "wine"; + if (runner.name.starts_with("cmd:")) { + std::string tool = runner.name.substr(4); + // QEMU_LD_PREFIX prefix may be glued onto exec but the runner's + // `name` field already isolates the command, so no extra parsing. + return tool; + } + return ""; + } +} + namespace { // Synthesize a Configuration for tests// folders that don't contain // a project.cpp. Convention: cfg.path = the folder, cfg.name/outputName = - // folder basename, cfg.target = the run's targetFilter, cfg.type = exe. - // Sources: top-level *.cpp (excluding project.cpp) become implementations, - // interfaces/*.cppm become module interfaces (matching the layout used - // elsewhere in this codebase). Tests with deeper layouts, defines, or - // dependencies still need an explicit project.cpp. - Configuration SynthesizeTest(const fs::path& dir, std::string_view target) { + // folder basename, cfg.target = host (overridable via test.toml `target`), + // cfg.type = exe. Sources: top-level *.cpp (excluding project.cpp) become + // implementations, interfaces/*.cppm become module interfaces. Tests with + // deeper layouts or dependencies still need an explicit project.cpp. + // + // Why host-default instead of targetFilter-default: under the multi-target + // sweep, an arch-agnostic test (no test.toml target) should run at the + // host iteration only — not get rebuilt against every cross-target the + // suite happens to declare. Cross-targeting is an opt-in via test.toml. + Configuration SynthesizeTest(const fs::path& dir) { Configuration cfg; cfg.path = dir; cfg.name = dir.filename().string(); cfg.outputName = cfg.name; - cfg.target = std::string(target); + cfg.target = HostTarget(); cfg.type = ConfigurationType::Executable; std::vector impls; @@ -502,6 +580,61 @@ namespace { } TestSummary Crafter::RunTests(Configuration& projectCfg, const RunTestsOptions& opts, std::span projectArgs) { + // Multi-target sweep: when no --target= was given, the run covers every + // distinct target a test.toml declares plus the host target. Lets a bare + // `crafter-build test` exercise cross-arch tests without the user having + // to know which targets exist in this project. An explicit --target=X + // bypasses the sweep and runs that target only. + if (opts.targetFilter.empty()) { + std::set sweep; + sweep.insert(HostTarget()); + fs::path testsDir = fs::current_path() / "tests"; + if (fs::exists(testsDir) && fs::is_directory(testsDir)) { + for (auto& e : fs::directory_iterator(testsDir)) { + if (!e.is_directory()) continue; + auto stem = e.path().filename().string(); + if (stem.empty() || stem[0] == '_' || stem[0] == '.') continue; + fs::path tomlPath = e.path() / "test.toml"; + if (!fs::exists(tomlPath)) continue; + try { + TestManifest m = ParseTestManifest(tomlPath); + if (m.target) sweep.insert(*m.target); + } catch (...) { + // Parse failures surface as discovery failures during the + // actual run; the sweep phase just collects targets. + } + } + } + TestSummary aggregate; + // Inline tests pushed by the caller (fixture-driven inner RunTests + // calls, e.g. RunnerClassification) must survive each sweep + // iteration. Configuration isn't copyable so we can't snapshot+restore; + // instead, remember the inline count and erase only the entries + // appended by the previous iteration's auto-discovery. + size_t inlineCount = projectCfg.tests.size(); + for (const auto& target : sweep) { + RunTestsOptions perTarget = opts; + perTarget.targetFilter = target; + if (projectCfg.tests.size() > inlineCount) { + projectCfg.tests.erase( + projectCfg.tests.begin() + inlineCount, + projectCfg.tests.end()); + } + if (sweep.size() > 1) { + Progress::Clear(); + std::println("\n=== target: {} ===", target); + } + TestSummary s = RunTests(projectCfg, perTarget, projectArgs); + aggregate.passed += s.passed; + aggregate.failed += s.failed; + aggregate.crashed += s.crashed; + aggregate.timedOut += s.timedOut; + aggregate.skipped += s.skipped; + for (auto& r : s.results) aggregate.results.push_back(std::move(r)); + } + return aggregate; + } + TestSummary summary; std::vector discoveryFailures; @@ -517,7 +650,16 @@ TestSummary Crafter::RunTests(Configuration& projectCfg, const RunTestsOptions& // when projectCfg.path points at a subdirectory like "./src/" or "./lib/". fs::path testsDir = fs::current_path() / "tests"; if (fs::exists(testsDir) && fs::is_directory(testsDir)) { - struct TestEntry { fs::path dir; fs::path pcpp; }; // pcpp empty = synth + // A discovered fixture is one of: + // - project.cpp present → outer-driver test (LoadProject) + // - test.toml present → declarative synth + manifest + // - neither, just *.cpp → bare synth (host-target) + // - both project.cpp and test.toml → XOR violation, discovery Fail + struct TestEntry { + fs::path dir; + fs::path pcpp; // outer-driver path + std::optional manifest; + }; std::vector entries; for (auto& entry : fs::directory_iterator(testsDir)) { if (!entry.is_directory()) continue; @@ -526,7 +668,34 @@ TestSummary Crafter::RunTests(Configuration& projectCfg, const RunTestsOptions& TestEntry te; te.dir = entry.path(); auto pcpp = te.dir / "project.cpp"; - if (fs::exists(pcpp)) te.pcpp = pcpp; + auto tomlPath = te.dir / "test.toml"; + bool hasPcpp = fs::exists(pcpp); + bool hasToml = fs::exists(tomlPath); + if (hasPcpp && hasToml) { + TestResult r; + r.name = stem; + r.outcome = TestOutcome::Fail; + r.exitCode = -1; + r.output = "both project.cpp and test.toml present — they're " + "mutually exclusive (delete one to disambiguate " + "outer-driver vs declarative test)"; + discoveryFailures.push_back(std::move(r)); + continue; + } + if (hasPcpp) te.pcpp = pcpp; + if (hasToml) { + try { + te.manifest = ParseTestManifest(tomlPath); + } catch (const std::exception& e) { + TestResult r; + r.name = stem; + r.outcome = TestOutcome::Fail; + r.exitCode = -1; + r.output = e.what(); + discoveryFailures.push_back(std::move(r)); + continue; + } + } entries.push_back(std::move(te)); } std::ranges::sort(entries, [](auto& a, auto& b) { return a.dir < b.dir; }); @@ -548,7 +717,15 @@ TestSummary Crafter::RunTests(Configuration& projectCfg, const RunTestsOptions& if (!te.pcpp.empty()) { t.config = LoadProject(te.pcpp, fixtureArgs); } else { - t.config = SynthesizeTest(te.dir, opts.targetFilter); + t.config = SynthesizeTest(te.dir); + if (te.manifest) { + ApplyManifest(t.config, *te.manifest); + if (te.manifest->timeoutSeconds) { + t.timeout = std::chrono::seconds(*te.manifest->timeoutSeconds); + } + t.args = te.manifest->args; + t.requires_ = te.manifest->requires_; + } } } catch (const std::exception& e) { // A broken fixture shouldn't kill the whole run. Surface as a @@ -564,7 +741,7 @@ TestSummary Crafter::RunTests(Configuration& projectCfg, const RunTestsOptions& continue; } if (t.config.target != opts.targetFilter) continue; - t.runner = TestRunner::FromEnv(t.config.target, TestRunner::Local()); + t.runner = TestRunner::FromEnv(t.config.target, TestRunner::ForTarget(t.config)); if (opts.runnerOverride) { if (auto r = TestRunner::FromSpec(*opts.runnerOverride)) { t.runner = std::move(*r); @@ -639,9 +816,42 @@ TestSummary Crafter::RunTests(Configuration& projectCfg, const RunTestsOptions& TestResult r; r.name = t.config.name; - if (!runnerAvailable(t.runner)) { + // Declarative preconditions (test.toml requires = [...] or Test.requires_ + // set in project.cpp). Evaluated before the build so a missing tool/file/env + // turns into a Skip without paying the compile cost. Reports the first + // failure only — once one precondition is unmet the test couldn't run + // anyway, and a wall of "also missing X, also missing Y" buries the + // actionable root cause. + if (auto req = EvaluateRequires(t.requires_); !req.ok) { r.outcome = TestOutcome::Skipped; - r.output = std::format("runner '{}' not available", t.runner.name); + r.output = req.reason; + { + std::lock_guard lk(printMutex); + PrintResult(r, t.runner.name); + } + results[i] = std::move(r); + continue; + } + + if (!runnerAvailable(t.runner)) { + // Hard-fail-unless-declared: if the runner depends on a tool + // (qemu-aarch64, wasmtime, wine, ...) and the test didn't say + // "tool:" in requires, the missing runner is a Fail. The + // intent is to surface broken cross-arch CI configuration + // instead of letting it masquerade as a Skip; tests that + // legitimately may run without their runner have to opt in. + std::string tool = RunnerToolName(t.runner); + if (!tool.empty() && !RequiresMentionsTool(t.requires_, tool)) { + r.outcome = TestOutcome::Fail; + r.exitCode = -1; + r.output = std::format( + "runner '{}' unavailable and not declared in requires " + "(add 'tool:{}' to test.toml requires to permit skipping)", + t.runner.name, tool); + } else { + r.outcome = TestOutcome::Skipped; + r.output = std::format("runner '{}' not available", t.runner.name); + } { std::lock_guard lk(printMutex); PrintResult(r, t.runner.name); diff --git a/interfaces/Crafter.Build-Clang.cppm b/interfaces/Crafter.Build-Clang.cppm index 2fa60bd..45e8809 100644 --- a/interfaces/Crafter.Build-Clang.cppm +++ b/interfaces/Crafter.Build-Clang.cppm @@ -53,32 +53,43 @@ export namespace Crafter { struct Test; struct TestRunner { - // Quoting style for {args} when this runner is used. Args reach - // whichever shell the runner ultimately delivers them to: Host for - // Local + Cmd-prefix (host shell parses), Sh for Ssh (remote bash), - // Cmd for SshWin (remote cmd.exe). - enum class Shell { Host, Sh, Cmd }; - - std::string copy; + // Command template the harness executes to run the test binary. + // Local runners leave this empty; prefix runners (Cmd, Wine) set it to + // a template like "wine {bin} {args}" or "qemu-aarch64 {bin} {args}". std::string exec; - std::string cleanup; - std::string remoteDir; + // Display name; also the cache key for the availability probe. std::string name; // Runs once per RunTests invocation (cached by `name`). Exit 0 = runner // is available; non-zero = skip every Test using this runner with a // "runner unavailable" message. Empty = always available (e.g., Local). std::string probe; - Shell argsShell = Shell::Host; bool IsLocal() const { return exec.empty(); } static CRAFTER_API TestRunner Local(); - static CRAFTER_API TestRunner Ssh(std::string host, std::string remoteDir = "/tmp/crafter-tests"); - static CRAFTER_API TestRunner SshWin(std::string host, std::string remoteDir = "C:/temp/crafter-tests"); - static CRAFTER_API TestRunner Wsl(std::string remoteDir = "/tmp/crafter-tests-wsl"); + // Prefix runner: wraps the local binary in ` {bin} {args}`. + // Used for qemu-user, wasmtime, and similar single-binary wrappers. static CRAFTER_API TestRunner Cmd(std::string command); + // Run a Windows .exe through Wine. Probes `wine` on PATH; on a Windows + // host the wine wrapper is pointless, so callers should route to Local + // before reaching here. + static CRAFTER_API TestRunner Wine(); + // Parse a `[:]` spec used by CRAFTER_BUILD_RUNNER_ + // and --runner=. Supported: "local", "cmd:". Returns nullopt + // for an empty string; throws on a non-empty unrecognized spec. static CRAFTER_API std::optional FromSpec(std::string_view spec); + // Honor CRAFTER_BUILD_RUNNER_ (power-user override). Triple + // dashes/dots become underscores so they're valid in env-var names. + // Returns `fallback` when the env var is unset. static CRAFTER_API TestRunner FromEnv(std::string_view target, TestRunner fallback = Local()); + // Derive a runner from a Configuration's target triple + sysroot. + // Returns Local() when target equals the host, Wine() for Windows + // targets on a non-Windows host, `qemu-` (with QEMU_LD_PREFIX + // set when cfg.sysroot is non-empty) for non-host -linux- triples, + // `wasmtime` for wasm32-wasi/wasm64-wasi, and Local() as a last + // resort. CRAFTER_BUILD_RUNNER_ still wins as an override + // upstream of this — see FromEnv. + static CRAFTER_API TestRunner ForTarget(const struct Configuration& cfg); }; enum class TestOutcome { Pass, Fail, Crash, Timeout, Skipped }; @@ -185,6 +196,15 @@ export namespace Crafter { TestRunner runner; std::chrono::seconds timeout{60}; std::vector args; + // Declarative preconditions. Each entry is "tool:", + // "file:", or "env:". Evaluated before the test runs; any + // unmet require turns the test into a Skip with a derived reason. + // Also doubles as the "I know this runner might not be here" opt-in: + // when the test's derived runner needs a tool (e.g. qemu-aarch64, + // wasmtime, wine) and the matching tool: entry isn't present, an + // unavailable runner becomes a Fail instead of a silent Skip — the + // dependency has to be declared to be allowed to be missing. + std::vector requires_; }; CRAFTER_API BuildResult Build(Configuration& config, std::unordered_map>& depResults, std::mutex& depMutex); diff --git a/interfaces/Crafter.Build-Test.cppm b/interfaces/Crafter.Build-Test.cppm index 5e630ba..b2a340c 100644 --- a/interfaces/Crafter.Build-Test.cppm +++ b/interfaces/Crafter.Build-Test.cppm @@ -29,15 +29,13 @@ export namespace Crafter { int jobs = 0; std::optional timeoutOverride; bool listOnly = false; - // Only tests whose Configuration::target equals targetFilter are run. - // Set from --target=... (host triple if unspecified). Tests for other - // targets are silently excluded so e.g. `--target=mingw` doesn't drag - // in host-only outer-driver tests. -#ifdef _WIN32 - std::string targetFilter = "x86_64-pc-windows-msvc"; -#else - std::string targetFilter = "x86_64-pc-linux-gnu"; -#endif + // Single-target run: only tests whose Configuration::target matches + // are included. Empty (default) = run every distinct target declared + // across discovered tests, plus the host target. Set from --target=... + // (when omitted, the harness sweeps all declared targets so cross-arch + // tests run by default without the user having to know which targets + // exist). + std::string targetFilter; // CLI override for --runner=: applies to every test in the run. // Target scoping is unnecessary because targetFilter ensures the run // contains only one target's tests. diff --git a/lib/toml.hpp b/lib/toml.hpp new file mode 100644 index 0000000..caf87c4 --- /dev/null +++ b/lib/toml.hpp @@ -0,0 +1,17899 @@ +//---------------------------------------------------------------------------------------------------------------------- +// +// toml++ v3.4.0 +// https://github.com/marzer/tomlplusplus +// SPDX-License-Identifier: MIT +// +//---------------------------------------------------------------------------------------------------------------------- +// +// - THIS FILE WAS ASSEMBLED FROM MULTIPLE HEADER FILES BY A SCRIPT - PLEASE DON'T EDIT IT DIRECTLY - +// +// If you wish to submit a contribution to toml++, hooray and thanks! Before you crack on, please be aware that this +// file was assembled from a number of smaller files by a python script, and code contributions should not be made +// against it directly. You should instead make your changes in the relevant source file(s). The file names of the files +// that contributed to this header can be found at the beginnings and ends of the corresponding sections of this file. +// +//---------------------------------------------------------------------------------------------------------------------- +// +// TOML Language Specifications: +// latest: https://github.com/toml-lang/toml/blob/master/README.md +// v1.0.0: https://toml.io/en/v1.0.0 +// v0.5.0: https://toml.io/en/v0.5.0 +// changelog: https://github.com/toml-lang/toml/blob/master/CHANGELOG.md +// +//---------------------------------------------------------------------------------------------------------------------- +// +// MIT License +// +// Copyright (c) Mark Gillard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +// Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +//---------------------------------------------------------------------------------------------------------------------- +#ifndef TOMLPLUSPLUS_HPP +#define TOMLPLUSPLUS_HPP + +#define INCLUDE_TOMLPLUSPLUS_H // old guard name used pre-v3 +#define TOMLPLUSPLUS_H // guard name used in the legacy toml.h + +//******** impl/preprocessor.hpp ************************************************************************************* + +#ifndef __cplusplus +#error toml++ is a C++ library. +#endif + +#ifndef TOML_CPP +#ifdef _MSVC_LANG +#if _MSVC_LANG > __cplusplus +#define TOML_CPP _MSVC_LANG +#endif +#endif +#ifndef TOML_CPP +#define TOML_CPP __cplusplus +#endif +#if TOML_CPP >= 202900L +#undef TOML_CPP +#define TOML_CPP 29 +#elif TOML_CPP >= 202600L +#undef TOML_CPP +#define TOML_CPP 26 +#elif TOML_CPP >= 202302L +#undef TOML_CPP +#define TOML_CPP 23 +#elif TOML_CPP >= 202002L +#undef TOML_CPP +#define TOML_CPP 20 +#elif TOML_CPP >= 201703L +#undef TOML_CPP +#define TOML_CPP 17 +#elif TOML_CPP >= 201402L +#undef TOML_CPP +#define TOML_CPP 14 +#elif TOML_CPP >= 201103L +#undef TOML_CPP +#define TOML_CPP 11 +#else +#undef TOML_CPP +#define TOML_CPP 0 +#endif +#endif + +#if !TOML_CPP +#error toml++ requires C++17 or higher. For a pre-C++11 TOML library see https://github.com/ToruNiina/Boost.toml +#elif TOML_CPP < 17 +#error toml++ requires C++17 or higher. For a C++11 TOML library see https://github.com/ToruNiina/toml11 +#endif + +#ifndef TOML_MAKE_VERSION +#define TOML_MAKE_VERSION(major, minor, patch) (((major)*10000) + ((minor)*100) + ((patch))) +#endif + +#ifndef TOML_INTELLISENSE +#ifdef __INTELLISENSE__ +#define TOML_INTELLISENSE 1 +#else +#define TOML_INTELLISENSE 0 +#endif +#endif + +#ifndef TOML_DOXYGEN +#if defined(DOXYGEN) || defined(__DOXYGEN) || defined(__DOXYGEN__) || defined(__doxygen__) || defined(__POXY__) \ + || defined(__poxy__) +#define TOML_DOXYGEN 1 +#else +#define TOML_DOXYGEN 0 +#endif +#endif + +#ifndef TOML_CLANG +#ifdef __clang__ +#define TOML_CLANG __clang_major__ +#else +#define TOML_CLANG 0 +#endif + +// special handling for apple clang; see: +// - https://github.com/marzer/tomlplusplus/issues/189 +// - https://en.wikipedia.org/wiki/Xcode +// - +// https://stackoverflow.com/questions/19387043/how-can-i-reliably-detect-the-version-of-clang-at-preprocessing-time +#if TOML_CLANG && defined(__apple_build_version__) +#undef TOML_CLANG +#define TOML_CLANG_VERSION TOML_MAKE_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__) +#if TOML_CLANG_VERSION >= TOML_MAKE_VERSION(15, 0, 0) +#define TOML_CLANG 16 +#elif TOML_CLANG_VERSION >= TOML_MAKE_VERSION(14, 3, 0) +#define TOML_CLANG 15 +#elif TOML_CLANG_VERSION >= TOML_MAKE_VERSION(14, 0, 0) +#define TOML_CLANG 14 +#elif TOML_CLANG_VERSION >= TOML_MAKE_VERSION(13, 1, 6) +#define TOML_CLANG 13 +#elif TOML_CLANG_VERSION >= TOML_MAKE_VERSION(13, 0, 0) +#define TOML_CLANG 12 +#elif TOML_CLANG_VERSION >= TOML_MAKE_VERSION(12, 0, 5) +#define TOML_CLANG 11 +#elif TOML_CLANG_VERSION >= TOML_MAKE_VERSION(12, 0, 0) +#define TOML_CLANG 10 +#elif TOML_CLANG_VERSION >= TOML_MAKE_VERSION(11, 0, 3) +#define TOML_CLANG 9 +#elif TOML_CLANG_VERSION >= TOML_MAKE_VERSION(11, 0, 0) +#define TOML_CLANG 8 +#elif TOML_CLANG_VERSION >= TOML_MAKE_VERSION(10, 0, 1) +#define TOML_CLANG 7 +#else +#define TOML_CLANG 6 // not strictly correct but doesn't matter below this +#endif +#undef TOML_CLANG_VERSION +#endif +#endif + +#ifndef TOML_ICC +#ifdef __INTEL_COMPILER +#define TOML_ICC __INTEL_COMPILER +#ifdef __ICL +#define TOML_ICC_CL TOML_ICC +#else +#define TOML_ICC_CL 0 +#endif +#else +#define TOML_ICC 0 +#define TOML_ICC_CL 0 +#endif +#endif + +#ifndef TOML_MSVC_LIKE +#ifdef _MSC_VER +#define TOML_MSVC_LIKE _MSC_VER +#else +#define TOML_MSVC_LIKE 0 +#endif +#endif + +#ifndef TOML_MSVC +#if TOML_MSVC_LIKE && !TOML_CLANG && !TOML_ICC +#define TOML_MSVC TOML_MSVC_LIKE +#else +#define TOML_MSVC 0 +#endif +#endif + +#ifndef TOML_GCC_LIKE +#ifdef __GNUC__ +#define TOML_GCC_LIKE __GNUC__ +#else +#define TOML_GCC_LIKE 0 +#endif +#endif + +#ifndef TOML_GCC +#if TOML_GCC_LIKE && !TOML_CLANG && !TOML_ICC +#define TOML_GCC TOML_GCC_LIKE +#else +#define TOML_GCC 0 +#endif +#endif + +#ifndef TOML_CUDA +#if defined(__CUDACC__) || defined(__CUDA_ARCH__) || defined(__CUDA_LIBDEVICE__) +#define TOML_CUDA 1 +#else +#define TOML_CUDA 0 +#endif +#endif + +#ifndef TOML_NVCC +#ifdef __NVCOMPILER_MAJOR__ +#define TOML_NVCC __NVCOMPILER_MAJOR__ +#else +#define TOML_NVCC 0 +#endif +#endif + +#ifndef TOML_ARCH_ITANIUM +#if defined(__ia64__) || defined(__ia64) || defined(_IA64) || defined(__IA64__) || defined(_M_IA64) +#define TOML_ARCH_ITANIUM 1 +#define TOML_ARCH_BITNESS 64 +#else +#define TOML_ARCH_ITANIUM 0 +#endif +#endif + +#ifndef TOML_ARCH_AMD64 +#if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_AMD64) +#define TOML_ARCH_AMD64 1 +#define TOML_ARCH_BITNESS 64 +#else +#define TOML_ARCH_AMD64 0 +#endif +#endif + +#ifndef TOML_ARCH_X86 +#if defined(__i386__) || defined(_M_IX86) +#define TOML_ARCH_X86 1 +#define TOML_ARCH_BITNESS 32 +#else +#define TOML_ARCH_X86 0 +#endif +#endif + +#ifndef TOML_ARCH_ARM +#if defined(__aarch64__) || defined(__ARM_ARCH_ISA_A64) || defined(_M_ARM64) || defined(__ARM_64BIT_STATE) \ + || defined(_M_ARM64EC) +#define TOML_ARCH_ARM32 0 +#define TOML_ARCH_ARM64 1 +#define TOML_ARCH_ARM 1 +#define TOML_ARCH_BITNESS 64 +#elif defined(__arm__) || defined(_M_ARM) || defined(__ARM_32BIT_STATE) +#define TOML_ARCH_ARM32 1 +#define TOML_ARCH_ARM64 0 +#define TOML_ARCH_ARM 1 +#define TOML_ARCH_BITNESS 32 +#else +#define TOML_ARCH_ARM32 0 +#define TOML_ARCH_ARM64 0 +#define TOML_ARCH_ARM 0 +#endif +#endif + +#ifndef TOML_ARCH_BITNESS +#define TOML_ARCH_BITNESS 0 +#endif + +#ifndef TOML_ARCH_X64 +#if TOML_ARCH_BITNESS == 64 +#define TOML_ARCH_X64 1 +#else +#define TOML_ARCH_X64 0 +#endif +#endif + +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || defined(__CYGWIN__) +#define TOML_WINDOWS 1 +#else +#define TOML_WINDOWS 0 +#endif + +#ifdef __unix__ +#define TOML_UNIX 1 +#else +#define TOML_UNIX 0 +#endif + +#ifdef __linux__ +#define TOML_LINUX 1 +#else +#define TOML_LINUX 0 +#endif + +// TOML_HAS_INCLUDE +#ifndef TOML_HAS_INCLUDE +#ifdef __has_include +#define TOML_HAS_INCLUDE(header) __has_include(header) +#else +#define TOML_HAS_INCLUDE(header) 0 +#endif +#endif + +// TOML_HAS_BUILTIN +#ifndef TOML_HAS_BUILTIN +#ifdef __has_builtin +#define TOML_HAS_BUILTIN(name) __has_builtin(name) +#else +#define TOML_HAS_BUILTIN(name) 0 +#endif +#endif + +// TOML_HAS_FEATURE +#ifndef TOML_HAS_FEATURE +#ifdef __has_feature +#define TOML_HAS_FEATURE(name) __has_feature(name) +#else +#define TOML_HAS_FEATURE(name) 0 +#endif +#endif + +// TOML_HAS_ATTR +#ifndef TOML_HAS_ATTR +#ifdef __has_attribute +#define TOML_HAS_ATTR(attr) __has_attribute(attr) +#else +#define TOML_HAS_ATTR(attr) 0 +#endif +#endif + +// TOML_HAS_CPP_ATTR +#ifndef TOML_HAS_CPP_ATTR +#ifdef __has_cpp_attribute +#define TOML_HAS_CPP_ATTR(attr) __has_cpp_attribute(attr) +#else +#define TOML_HAS_CPP_ATTR(attr) 0 +#endif +#endif + +// TOML_ATTR (gnu attributes) +#ifndef TOML_ATTR +#if TOML_CLANG || TOML_GCC_LIKE +#define TOML_ATTR(...) __attribute__((__VA_ARGS__)) +#else +#define TOML_ATTR(...) +#endif +#endif + +// TOML_DECLSPEC (msvc attributes) +#ifndef TOML_DECLSPEC +#if TOML_MSVC_LIKE +#define TOML_DECLSPEC(...) __declspec(__VA_ARGS__) +#else +#define TOML_DECLSPEC(...) +#endif +#endif + +// TOML_COMPILER_HAS_EXCEPTIONS +#ifndef TOML_COMPILER_HAS_EXCEPTIONS +#if defined(__EXCEPTIONS) || defined(_CPPUNWIND) || defined(__cpp_exceptions) +#define TOML_COMPILER_HAS_EXCEPTIONS 1 +#else +#define TOML_COMPILER_HAS_EXCEPTIONS 0 +#endif +#endif + +// TOML_COMPILER_HAS_RTTI +#ifndef TOML_COMPILER_HAS_RTTI +#if defined(_CPPRTTI) || defined(__GXX_RTTI) || TOML_HAS_FEATURE(cxx_rtti) +#define TOML_COMPILER_HAS_RTTI 1 +#else +#define TOML_COMPILER_HAS_RTTI 0 +#endif +#endif + +// TOML_CONCAT +#define TOML_CONCAT_1(x, y) x##y +#define TOML_CONCAT(x, y) TOML_CONCAT_1(x, y) + +// TOML_MAKE_STRING +#define TOML_MAKE_STRING_1(s) #s +#define TOML_MAKE_STRING(s) TOML_MAKE_STRING_1(s) + +// TOML_PRAGMA_XXXX (compiler-specific pragmas) +#if TOML_CLANG +#define TOML_PRAGMA_CLANG(decl) _Pragma(TOML_MAKE_STRING(clang decl)) +#else +#define TOML_PRAGMA_CLANG(decl) +#endif +#if TOML_CLANG >= 8 +#define TOML_PRAGMA_CLANG_GE_8(decl) TOML_PRAGMA_CLANG(decl) +#else +#define TOML_PRAGMA_CLANG_GE_8(decl) +#endif +#if TOML_CLANG >= 9 +#define TOML_PRAGMA_CLANG_GE_9(decl) TOML_PRAGMA_CLANG(decl) +#else +#define TOML_PRAGMA_CLANG_GE_9(decl) +#endif +#if TOML_CLANG >= 10 +#define TOML_PRAGMA_CLANG_GE_10(decl) TOML_PRAGMA_CLANG(decl) +#else +#define TOML_PRAGMA_CLANG_GE_10(decl) +#endif +#if TOML_CLANG >= 11 +#define TOML_PRAGMA_CLANG_GE_11(decl) TOML_PRAGMA_CLANG(decl) +#else +#define TOML_PRAGMA_CLANG_GE_11(decl) +#endif +#if TOML_GCC +#define TOML_PRAGMA_GCC(decl) _Pragma(TOML_MAKE_STRING(GCC decl)) +#else +#define TOML_PRAGMA_GCC(decl) +#endif +#if TOML_MSVC +#define TOML_PRAGMA_MSVC(...) __pragma(__VA_ARGS__) +#else +#define TOML_PRAGMA_MSVC(...) +#endif +#if TOML_ICC +#define TOML_PRAGMA_ICC(...) __pragma(__VA_ARGS__) +#else +#define TOML_PRAGMA_ICC(...) +#endif + +// TOML_ALWAYS_INLINE +#ifndef TOML_ALWAYS_INLINE +#ifdef _MSC_VER +#define TOML_ALWAYS_INLINE __forceinline +#elif TOML_GCC || TOML_CLANG || TOML_HAS_ATTR(__always_inline__) +#define TOML_ALWAYS_INLINE \ + TOML_ATTR(__always_inline__) \ + inline +#else +#define TOML_ALWAYS_INLINE inline +#endif +#endif + +// TOML_NEVER_INLINE +#ifndef TOML_NEVER_INLINE +#ifdef _MSC_VER +#define TOML_NEVER_INLINE TOML_DECLSPEC(noinline) +#elif TOML_CUDA // https://gitlab.gnome.org/GNOME/glib/-/issues/2555 +#define TOML_NEVER_INLINE TOML_ATTR(noinline) +#else +#if TOML_GCC || TOML_CLANG || TOML_HAS_ATTR(__noinline__) +#define TOML_NEVER_INLINE TOML_ATTR(__noinline__) +#endif +#endif +#ifndef TOML_NEVER_INLINE +#define TOML_NEVER_INLINE +#endif +#endif + +// MSVC attributes +#ifndef TOML_ABSTRACT_INTERFACE +#define TOML_ABSTRACT_INTERFACE TOML_DECLSPEC(novtable) +#endif +#ifndef TOML_EMPTY_BASES +#define TOML_EMPTY_BASES TOML_DECLSPEC(empty_bases) +#endif + +// TOML_TRIVIAL_ABI +#ifndef TOML_TRIVIAL_ABI +#if TOML_CLANG || TOML_HAS_ATTR(__trivial_abi__) +#define TOML_TRIVIAL_ABI TOML_ATTR(__trivial_abi__) +#else +#define TOML_TRIVIAL_ABI +#endif +#endif + +// TOML_NODISCARD +#ifndef TOML_NODISCARD +#if TOML_CPP >= 17 && TOML_HAS_CPP_ATTR(nodiscard) >= 201603 +#define TOML_NODISCARD [[nodiscard]] +#elif TOML_CLANG || TOML_GCC || TOML_HAS_ATTR(__warn_unused_result__) +#define TOML_NODISCARD TOML_ATTR(__warn_unused_result__) +#else +#define TOML_NODISCARD +#endif +#endif + +// TOML_NODISCARD_CTOR +#ifndef TOML_NODISCARD_CTOR +#if TOML_CPP >= 17 && TOML_HAS_CPP_ATTR(nodiscard) >= 201907 +#define TOML_NODISCARD_CTOR [[nodiscard]] +#else +#define TOML_NODISCARD_CTOR +#endif +#endif + +// pure + const +#ifndef TOML_PURE +#ifdef NDEBUG +#define TOML_PURE \ + TOML_DECLSPEC(noalias) \ + TOML_ATTR(pure) +#else +#define TOML_PURE +#endif +#endif +#ifndef TOML_CONST +#ifdef NDEBUG +#define TOML_CONST \ + TOML_DECLSPEC(noalias) \ + TOML_ATTR(const) +#else +#define TOML_CONST +#endif +#endif +#ifndef TOML_INLINE_GETTER +#define TOML_INLINE_GETTER \ + TOML_NODISCARD \ + TOML_ALWAYS_INLINE +#endif +#ifndef TOML_PURE_GETTER +#define TOML_PURE_GETTER \ + TOML_NODISCARD \ + TOML_PURE +#endif +#ifndef TOML_PURE_INLINE_GETTER +#define TOML_PURE_INLINE_GETTER \ + TOML_NODISCARD \ + TOML_ALWAYS_INLINE \ + TOML_PURE +#endif +#ifndef TOML_CONST_GETTER +#define TOML_CONST_GETTER \ + TOML_NODISCARD \ + TOML_CONST +#endif +#ifndef TOML_CONST_INLINE_GETTER +#define TOML_CONST_INLINE_GETTER \ + TOML_NODISCARD \ + TOML_ALWAYS_INLINE \ + TOML_CONST +#endif + +// TOML_ASSUME +#ifndef TOML_ASSUME +#ifdef _MSC_VER +#define TOML_ASSUME(expr) __assume(expr) +#elif TOML_ICC || TOML_CLANG || TOML_HAS_BUILTIN(__builtin_assume) +#define TOML_ASSUME(expr) __builtin_assume(expr) +#elif TOML_HAS_CPP_ATTR(assume) >= 202207 +#define TOML_ASSUME(expr) [[assume(expr)]] +#elif TOML_HAS_ATTR(__assume__) +#define TOML_ASSUME(expr) __attribute__((__assume__(expr))) +#else +#define TOML_ASSUME(expr) static_cast(0) +#endif +#endif + +// TOML_UNREACHABLE +#ifndef TOML_UNREACHABLE +#ifdef _MSC_VER +#define TOML_UNREACHABLE __assume(0) +#elif TOML_ICC || TOML_CLANG || TOML_GCC || TOML_HAS_BUILTIN(__builtin_unreachable) +#define TOML_UNREACHABLE __builtin_unreachable() +#else +#define TOML_UNREACHABLE static_cast(0) +#endif +#endif + +// TOML_LIKELY +#if TOML_CPP >= 20 && TOML_HAS_CPP_ATTR(likely) >= 201803 +#define TOML_LIKELY(...) (__VA_ARGS__) [[likely]] +#define TOML_LIKELY_CASE [[likely]] +#elif TOML_GCC || TOML_CLANG || TOML_HAS_BUILTIN(__builtin_expect) +#define TOML_LIKELY(...) (__builtin_expect(!!(__VA_ARGS__), 1)) +#else +#define TOML_LIKELY(...) (__VA_ARGS__) +#endif +#ifndef TOML_LIKELY_CASE +#define TOML_LIKELY_CASE +#endif + +// TOML_UNLIKELY +#if TOML_CPP >= 20 && TOML_HAS_CPP_ATTR(unlikely) >= 201803 +#define TOML_UNLIKELY(...) (__VA_ARGS__) [[unlikely]] +#define TOML_UNLIKELY_CASE [[unlikely]] +#elif TOML_GCC || TOML_CLANG || TOML_HAS_BUILTIN(__builtin_expect) +#define TOML_UNLIKELY(...) (__builtin_expect(!!(__VA_ARGS__), 0)) +#else +#define TOML_UNLIKELY(...) (__VA_ARGS__) +#endif +#ifndef TOML_UNLIKELY_CASE +#define TOML_UNLIKELY_CASE +#endif + +// TOML_FLAGS_ENUM +#if TOML_CLANG || TOML_HAS_ATTR(flag_enum) +#define TOML_FLAGS_ENUM __attribute__((flag_enum)) +#else +#define TOML_FLAGS_ENUM +#endif + +// TOML_OPEN_ENUM + TOML_CLOSED_ENUM +#if TOML_CLANG || TOML_HAS_ATTR(enum_extensibility) +#define TOML_OPEN_ENUM __attribute__((enum_extensibility(open))) +#define TOML_CLOSED_ENUM __attribute__((enum_extensibility(closed))) +#else +#define TOML_OPEN_ENUM +#define TOML_CLOSED_ENUM +#endif + +// TOML_OPEN_FLAGS_ENUM + TOML_CLOSED_FLAGS_ENUM +#define TOML_OPEN_FLAGS_ENUM TOML_OPEN_ENUM TOML_FLAGS_ENUM +#define TOML_CLOSED_FLAGS_ENUM TOML_CLOSED_ENUM TOML_FLAGS_ENUM + +// TOML_MAKE_FLAGS +#define TOML_MAKE_FLAGS_2(T, op, linkage) \ + TOML_CONST_INLINE_GETTER \ + linkage constexpr T operator op(T lhs, T rhs) noexcept \ + { \ + using under = std::underlying_type_t; \ + return static_cast(static_cast(lhs) op static_cast(rhs)); \ + } \ + \ + linkage constexpr T& operator TOML_CONCAT(op, =)(T & lhs, T rhs) noexcept \ + { \ + return lhs = (lhs op rhs); \ + } \ + \ + static_assert(true) +#define TOML_MAKE_FLAGS_1(T, linkage) \ + static_assert(std::is_enum_v); \ + \ + TOML_MAKE_FLAGS_2(T, &, linkage); \ + TOML_MAKE_FLAGS_2(T, |, linkage); \ + TOML_MAKE_FLAGS_2(T, ^, linkage); \ + \ + TOML_CONST_INLINE_GETTER \ + linkage constexpr T operator~(T val) noexcept \ + { \ + using under = std::underlying_type_t; \ + return static_cast(~static_cast(val)); \ + } \ + \ + TOML_CONST_INLINE_GETTER \ + linkage constexpr bool operator!(T val) noexcept \ + { \ + using under = std::underlying_type_t; \ + return !static_cast(val); \ + } \ + \ + static_assert(true) +#define TOML_MAKE_FLAGS(T) TOML_MAKE_FLAGS_1(T, ) + +#define TOML_UNUSED(...) static_cast(__VA_ARGS__) + +#define TOML_DELETE_DEFAULTS(T) \ + T(const T&) = delete; \ + T(T&&) = delete; \ + T& operator=(const T&) = delete; \ + T& operator=(T&&) = delete + +#define TOML_ASYMMETRICAL_EQUALITY_OPS(LHS, RHS, ...) \ + __VA_ARGS__ TOML_NODISCARD \ + friend bool operator==(RHS rhs, LHS lhs) noexcept \ + { \ + return lhs == rhs; \ + } \ + __VA_ARGS__ TOML_NODISCARD \ + friend bool operator!=(LHS lhs, RHS rhs) noexcept \ + { \ + return !(lhs == rhs); \ + } \ + __VA_ARGS__ TOML_NODISCARD \ + friend bool operator!=(RHS rhs, LHS lhs) noexcept \ + { \ + return !(lhs == rhs); \ + } \ + static_assert(true) + +#define TOML_EVAL_BOOL_1(T, F) T +#define TOML_EVAL_BOOL_0(T, F) F + +#if !defined(__POXY__) && !defined(POXY_IMPLEMENTATION_DETAIL) +#define POXY_IMPLEMENTATION_DETAIL(...) __VA_ARGS__ +#endif + +// COMPILER-SPECIFIC WARNING MANAGEMENT + +#if TOML_CLANG + +#define TOML_PUSH_WARNINGS \ + TOML_PRAGMA_CLANG(diagnostic push) \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wunknown-warning-option") \ + static_assert(true) + +#define TOML_DISABLE_SWITCH_WARNINGS \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wswitch") \ + static_assert(true) + +#define TOML_DISABLE_ARITHMETIC_WARNINGS \ + TOML_PRAGMA_CLANG_GE_10(diagnostic ignored "-Wimplicit-int-float-conversion") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wfloat-equal") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wdouble-promotion") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wchar-subscripts") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wshift-sign-overflow") \ + static_assert(true) + +#define TOML_DISABLE_SPAM_WARNINGS \ + TOML_PRAGMA_CLANG_GE_8(diagnostic ignored "-Wdefaulted-function-deleted") \ + TOML_PRAGMA_CLANG_GE_9(diagnostic ignored "-Wctad-maybe-unsupported") \ + TOML_PRAGMA_CLANG_GE_10(diagnostic ignored "-Wzero-as-null-pointer-constant") \ + TOML_PRAGMA_CLANG_GE_11(diagnostic ignored "-Wsuggest-destructor-override") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wweak-vtables") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wweak-template-vtables") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wdouble-promotion") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wchar-subscripts") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wmissing-field-initializers") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wpadded") \ + static_assert(true) + +#define TOML_POP_WARNINGS \ + TOML_PRAGMA_CLANG(diagnostic pop) \ + static_assert(true) + +#define TOML_DISABLE_WARNINGS \ + TOML_PRAGMA_CLANG(diagnostic push) \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Weverything") \ + static_assert(true, "") + +#define TOML_ENABLE_WARNINGS \ + TOML_PRAGMA_CLANG(diagnostic pop) \ + static_assert(true) + +#define TOML_SIMPLE_STATIC_ASSERT_MESSAGES 1 + +#elif TOML_MSVC + +#define TOML_PUSH_WARNINGS \ + __pragma(warning(push)) \ + static_assert(true) + +#if TOML_HAS_INCLUDE() +#pragma warning(push, 0) +#include +#pragma warning(pop) +#define TOML_DISABLE_CODE_ANALYSIS_WARNINGS \ + __pragma(warning(disable : ALL_CODE_ANALYSIS_WARNINGS)) \ + static_assert(true) +#else +#define TOML_DISABLE_CODE_ANALYSIS_WARNINGS static_assert(true) +#endif + +#define TOML_DISABLE_SWITCH_WARNINGS \ + __pragma(warning(disable : 4061)) \ + __pragma(warning(disable : 4062)) \ + __pragma(warning(disable : 4063)) \ + __pragma(warning(disable : 5262)) /* switch-case implicit fallthrough (false-positive) */ \ + __pragma(warning(disable : 26819)) /* cg: unannotated fallthrough */ \ + static_assert(true) + +#define TOML_DISABLE_SPAM_WARNINGS \ + __pragma(warning(disable : 4127)) /* conditional expr is constant */ \ + __pragma(warning(disable : 4324)) /* structure was padded due to alignment specifier */ \ + __pragma(warning(disable : 4348)) \ + __pragma(warning(disable : 4464)) /* relative include path contains '..' */ \ + __pragma(warning(disable : 4505)) /* unreferenced local function removed */ \ + __pragma(warning(disable : 4514)) /* unreferenced inline function has been removed */ \ + __pragma(warning(disable : 4582)) /* constructor is not implicitly called */ \ + __pragma(warning(disable : 4619)) /* there is no warning number 'XXXX' */ \ + __pragma(warning(disable : 4623)) /* default constructor was implicitly defined as deleted */ \ + __pragma(warning(disable : 4625)) /* copy constructor was implicitly defined as deleted */ \ + __pragma(warning(disable : 4626)) /* assignment operator was implicitly defined as deleted */ \ + __pragma(warning(disable : 4710)) /* function not inlined */ \ + __pragma(warning(disable : 4711)) /* function selected for automatic expansion */ \ + __pragma(warning(disable : 4820)) /* N bytes padding added */ \ + __pragma(warning(disable : 5026)) /* move constructor was implicitly defined as deleted */ \ + __pragma(warning(disable : 5027)) /* move assignment operator was implicitly defined as deleted */ \ + __pragma(warning(disable : 5039)) /* potentially throwing function passed to 'extern "C"' function */ \ + __pragma(warning(disable : 5045)) /* Compiler will insert Spectre mitigation */ \ + __pragma(warning(disable : 5264)) /* const variable is not used (false-positive) */ \ + __pragma(warning(disable : 26451)) \ + __pragma(warning(disable : 26490)) \ + __pragma(warning(disable : 26495)) \ + __pragma(warning(disable : 26812)) \ + __pragma(warning(disable : 26819)) \ + static_assert(true) + +#define TOML_DISABLE_ARITHMETIC_WARNINGS \ + __pragma(warning(disable : 4365)) /* argument signed/unsigned mismatch */ \ + __pragma(warning(disable : 4738)) /* storing 32-bit float result in memory */ \ + __pragma(warning(disable : 5219)) /* implicit conversion from integral to float */ \ + static_assert(true) + +#define TOML_POP_WARNINGS \ + __pragma(warning(pop)) \ + static_assert(true) + +#define TOML_DISABLE_WARNINGS \ + __pragma(warning(push, 0)) \ + __pragma(warning(disable : 4348)) \ + __pragma(warning(disable : 4668)) \ + __pragma(warning(disable : 5105)) \ + __pragma(warning(disable : 5264)) \ + TOML_DISABLE_CODE_ANALYSIS_WARNINGS; \ + TOML_DISABLE_SWITCH_WARNINGS; \ + TOML_DISABLE_SPAM_WARNINGS; \ + TOML_DISABLE_ARITHMETIC_WARNINGS; \ + static_assert(true) + +#define TOML_ENABLE_WARNINGS TOML_POP_WARNINGS + +#elif TOML_ICC + +#define TOML_PUSH_WARNINGS \ + __pragma(warning(push)) \ + static_assert(true) + +#define TOML_DISABLE_SPAM_WARNINGS \ + __pragma(warning(disable : 82)) /* storage class is not first */ \ + __pragma(warning(disable : 111)) /* statement unreachable (false-positive) */ \ + __pragma(warning(disable : 869)) /* unreferenced parameter */ \ + __pragma(warning(disable : 1011)) /* missing return (false-positive) */ \ + __pragma(warning(disable : 2261)) /* assume expr side-effects discarded */ \ + static_assert(true) + +#define TOML_POP_WARNINGS \ + __pragma(warning(pop)) \ + static_assert(true) + +#define TOML_DISABLE_WARNINGS \ + __pragma(warning(push, 0)) \ + TOML_DISABLE_SPAM_WARNINGS + +#define TOML_ENABLE_WARNINGS \ + __pragma(warning(pop)) \ + static_assert(true) + +#elif TOML_GCC + +#define TOML_PUSH_WARNINGS \ + TOML_PRAGMA_GCC(diagnostic push) \ + static_assert(true) + +#define TOML_DISABLE_SWITCH_WARNINGS \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wswitch") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wswitch-enum") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wswitch-default") \ + static_assert(true) + +#define TOML_DISABLE_ARITHMETIC_WARNINGS \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wfloat-equal") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wsign-conversion") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wchar-subscripts") \ + static_assert(true) + +#define TOML_DISABLE_SUGGEST_ATTR_WARNINGS \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wsuggest-attribute=const") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wsuggest-attribute=pure") \ + static_assert(true) + +#define TOML_DISABLE_SPAM_WARNINGS \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wpadded") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wcast-align") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wcomment") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wtype-limits") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wuseless-cast") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wchar-subscripts") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wsubobject-linkage") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wmissing-field-initializers") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wmaybe-uninitialized") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wnoexcept") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wnull-dereference") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wduplicated-branches") \ + static_assert(true) + +#define TOML_POP_WARNINGS \ + TOML_PRAGMA_GCC(diagnostic pop) \ + static_assert(true) + +#define TOML_DISABLE_WARNINGS \ + TOML_PRAGMA_GCC(diagnostic push) \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wall") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wextra") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wpedantic") \ + TOML_DISABLE_SWITCH_WARNINGS; \ + TOML_DISABLE_ARITHMETIC_WARNINGS; \ + TOML_DISABLE_SUGGEST_ATTR_WARNINGS; \ + TOML_DISABLE_SPAM_WARNINGS; \ + static_assert(true) + +#define TOML_ENABLE_WARNINGS \ + TOML_PRAGMA_GCC(diagnostic pop) \ + static_assert(true) + +#endif + +#ifndef TOML_PUSH_WARNINGS +#define TOML_PUSH_WARNINGS static_assert(true) +#endif +#ifndef TOML_DISABLE_CODE_ANALYSIS_WARNINGS +#define TOML_DISABLE_CODE_ANALYSIS_WARNINGS static_assert(true) +#endif +#ifndef TOML_DISABLE_SWITCH_WARNINGS +#define TOML_DISABLE_SWITCH_WARNINGS static_assert(true) +#endif +#ifndef TOML_DISABLE_SUGGEST_ATTR_WARNINGS +#define TOML_DISABLE_SUGGEST_ATTR_WARNINGS static_assert(true) +#endif +#ifndef TOML_DISABLE_SPAM_WARNINGS +#define TOML_DISABLE_SPAM_WARNINGS static_assert(true) +#endif +#ifndef TOML_DISABLE_ARITHMETIC_WARNINGS +#define TOML_DISABLE_ARITHMETIC_WARNINGS static_assert(true) +#endif +#ifndef TOML_POP_WARNINGS +#define TOML_POP_WARNINGS static_assert(true) +#endif +#ifndef TOML_DISABLE_WARNINGS +#define TOML_DISABLE_WARNINGS static_assert(true) +#endif +#ifndef TOML_ENABLE_WARNINGS +#define TOML_ENABLE_WARNINGS static_assert(true) +#endif +#ifndef TOML_SIMPLE_STATIC_ASSERT_MESSAGES +#define TOML_SIMPLE_STATIC_ASSERT_MESSAGES 0 +#endif + +#ifdef TOML_CONFIG_HEADER +#include TOML_CONFIG_HEADER +#endif + +// is the library being built as a shared lib/dll using meson and friends? +#ifndef TOML_SHARED_LIB +#define TOML_SHARED_LIB 0 +#endif + +// header-only mode +#if !defined(TOML_HEADER_ONLY) && defined(TOML_ALL_INLINE) // was TOML_ALL_INLINE pre-2.0 +#define TOML_HEADER_ONLY TOML_ALL_INLINE +#endif +#if !defined(TOML_HEADER_ONLY) || (defined(TOML_HEADER_ONLY) && TOML_HEADER_ONLY) || TOML_INTELLISENSE +#undef TOML_HEADER_ONLY +#define TOML_HEADER_ONLY 1 +#endif +#if TOML_DOXYGEN || TOML_SHARED_LIB +#undef TOML_HEADER_ONLY +#define TOML_HEADER_ONLY 0 +#endif + +// internal implementation switch +#if defined(TOML_IMPLEMENTATION) || TOML_HEADER_ONLY +#undef TOML_IMPLEMENTATION +#define TOML_IMPLEMENTATION 1 +#else +#define TOML_IMPLEMENTATION 0 +#endif + +// dll/shared lib function exports (legacy - TOML_API was the old name for this setting) +#if !defined(TOML_EXPORTED_MEMBER_FUNCTION) && !defined(TOML_EXPORTED_STATIC_FUNCTION) \ + && !defined(TOML_EXPORTED_FREE_FUNCTION) && !defined(TOML_EXPORTED_CLASS) && defined(TOML_API) +#define TOML_EXPORTED_MEMBER_FUNCTION TOML_API +#define TOML_EXPORTED_STATIC_FUNCTION TOML_API +#define TOML_EXPORTED_FREE_FUNCTION TOML_API +#endif + +// dll/shared lib exports +#if TOML_SHARED_LIB +#undef TOML_API +#undef TOML_EXPORTED_CLASS +#undef TOML_EXPORTED_MEMBER_FUNCTION +#undef TOML_EXPORTED_STATIC_FUNCTION +#undef TOML_EXPORTED_FREE_FUNCTION +#if TOML_WINDOWS +#if TOML_IMPLEMENTATION +#define TOML_EXPORTED_CLASS __declspec(dllexport) +#define TOML_EXPORTED_FREE_FUNCTION __declspec(dllexport) +#else +#define TOML_EXPORTED_CLASS __declspec(dllimport) +#define TOML_EXPORTED_FREE_FUNCTION __declspec(dllimport) +#endif +#ifndef TOML_CALLCONV +#define TOML_CALLCONV __cdecl +#endif +#elif defined(__GNUC__) && __GNUC__ >= 4 +#define TOML_EXPORTED_CLASS __attribute__((visibility("default"))) +#define TOML_EXPORTED_MEMBER_FUNCTION __attribute__((visibility("default"))) +#define TOML_EXPORTED_STATIC_FUNCTION __attribute__((visibility("default"))) +#define TOML_EXPORTED_FREE_FUNCTION __attribute__((visibility("default"))) +#endif +#endif +#ifndef TOML_EXPORTED_CLASS +#define TOML_EXPORTED_CLASS +#endif +#ifndef TOML_EXPORTED_MEMBER_FUNCTION +#define TOML_EXPORTED_MEMBER_FUNCTION +#endif +#ifndef TOML_EXPORTED_STATIC_FUNCTION +#define TOML_EXPORTED_STATIC_FUNCTION +#endif +#ifndef TOML_EXPORTED_FREE_FUNCTION +#define TOML_EXPORTED_FREE_FUNCTION +#endif + +// experimental language features +#if !defined(TOML_ENABLE_UNRELEASED_FEATURES) && defined(TOML_UNRELEASED_FEATURES) // was TOML_UNRELEASED_FEATURES + // pre-3.0 +#define TOML_ENABLE_UNRELEASED_FEATURES TOML_UNRELEASED_FEATURES +#endif +#if (defined(TOML_ENABLE_UNRELEASED_FEATURES) && TOML_ENABLE_UNRELEASED_FEATURES) || TOML_INTELLISENSE +#undef TOML_ENABLE_UNRELEASED_FEATURES +#define TOML_ENABLE_UNRELEASED_FEATURES 1 +#endif +#ifndef TOML_ENABLE_UNRELEASED_FEATURES +#define TOML_ENABLE_UNRELEASED_FEATURES 0 +#endif + +// parser +#if !defined(TOML_ENABLE_PARSER) && defined(TOML_PARSER) // was TOML_PARSER pre-3.0 +#define TOML_ENABLE_PARSER TOML_PARSER +#endif +#if !defined(TOML_ENABLE_PARSER) || (defined(TOML_ENABLE_PARSER) && TOML_ENABLE_PARSER) || TOML_INTELLISENSE +#undef TOML_ENABLE_PARSER +#define TOML_ENABLE_PARSER 1 +#endif + +// formatters +#if !defined(TOML_ENABLE_FORMATTERS) || (defined(TOML_ENABLE_FORMATTERS) && TOML_ENABLE_FORMATTERS) || TOML_INTELLISENSE +#undef TOML_ENABLE_FORMATTERS +#define TOML_ENABLE_FORMATTERS 1 +#endif + +// SIMD +#if !defined(TOML_ENABLE_SIMD) || (defined(TOML_ENABLE_SIMD) && TOML_ENABLE_SIMD) || TOML_INTELLISENSE +#undef TOML_ENABLE_SIMD +#define TOML_ENABLE_SIMD 1 +#endif + +// windows compat +#if !defined(TOML_ENABLE_WINDOWS_COMPAT) && defined(TOML_WINDOWS_COMPAT) // was TOML_WINDOWS_COMPAT pre-3.0 +#define TOML_ENABLE_WINDOWS_COMPAT TOML_WINDOWS_COMPAT +#endif +#if !defined(TOML_ENABLE_WINDOWS_COMPAT) || (defined(TOML_ENABLE_WINDOWS_COMPAT) && TOML_ENABLE_WINDOWS_COMPAT) \ + || TOML_INTELLISENSE +#undef TOML_ENABLE_WINDOWS_COMPAT +#define TOML_ENABLE_WINDOWS_COMPAT 1 +#endif + +#if !TOML_WINDOWS +#undef TOML_ENABLE_WINDOWS_COMPAT +#define TOML_ENABLE_WINDOWS_COMPAT 0 +#endif + +#ifndef TOML_INCLUDE_WINDOWS_H +#define TOML_INCLUDE_WINDOWS_H 0 +#endif + +// custom optional +#ifdef TOML_OPTIONAL_TYPE +#define TOML_HAS_CUSTOM_OPTIONAL_TYPE 1 +#else +#define TOML_HAS_CUSTOM_OPTIONAL_TYPE 0 +#endif + +// exceptions (library use) +#if TOML_COMPILER_HAS_EXCEPTIONS +#if !defined(TOML_EXCEPTIONS) || (defined(TOML_EXCEPTIONS) && TOML_EXCEPTIONS) +#undef TOML_EXCEPTIONS +#define TOML_EXCEPTIONS 1 +#endif +#else +#if defined(TOML_EXCEPTIONS) && TOML_EXCEPTIONS +#error TOML_EXCEPTIONS was explicitly enabled but exceptions are disabled/unsupported by the compiler. +#endif +#undef TOML_EXCEPTIONS +#define TOML_EXCEPTIONS 0 +#endif + +// calling convention for static/free/friend functions +#ifndef TOML_CALLCONV +#define TOML_CALLCONV +#endif + +#ifndef TOML_UNDEF_MACROS +#define TOML_UNDEF_MACROS 1 +#endif + +#ifndef TOML_MAX_NESTED_VALUES +#define TOML_MAX_NESTED_VALUES 128 +// this refers to the depth of nested values, e.g. inline tables and arrays. +// 128 is very generous; real TOML files rarely exceed single-digit nesting. +// keep this value low enough to avoid stack overflows in sanitizer-instrumented builds +// where each recursion cycle may consume ~3KB of stack. +#endif + +#ifndef TOML_MAX_DOTTED_KEYS_DEPTH +#define TOML_MAX_DOTTED_KEYS_DEPTH 1024 +#endif + +#ifdef TOML_CHAR_8_STRINGS +#if TOML_CHAR_8_STRINGS +#error TOML_CHAR_8_STRINGS was removed in toml++ 2.0.0; all value setters and getters now work with char8_t strings implicitly. +#endif +#endif + +#ifdef TOML_LARGE_FILES +#if !TOML_LARGE_FILES +#error Support for !TOML_LARGE_FILES (i.e. 'small files') was removed in toml++ 3.0.0. +#endif +#endif + +#ifndef TOML_LIFETIME_HOOKS +#define TOML_LIFETIME_HOOKS 0 +#endif + +#ifdef NDEBUG +#undef TOML_ASSERT +#define TOML_ASSERT(expr) static_assert(true) +#endif +#ifndef TOML_ASSERT +#ifndef assert +TOML_DISABLE_WARNINGS; +#include +TOML_ENABLE_WARNINGS; +#endif +#define TOML_ASSERT(expr) assert(expr) +#endif +#ifdef NDEBUG +#define TOML_ASSERT_ASSUME(expr) TOML_ASSUME(expr) +#else +#define TOML_ASSERT_ASSUME(expr) TOML_ASSERT(expr) +#endif + +#ifndef TOML_ENABLE_FLOAT16 +#define TOML_ENABLE_FLOAT16 0 +#endif + +#ifndef TOML_DISABLE_CONDITIONAL_NOEXCEPT_LAMBDA +#define TOML_DISABLE_CONDITIONAL_NOEXCEPT_LAMBDA 0 +#endif + +#ifndef TOML_DISABLE_NOEXCEPT_NOEXCEPT +#define TOML_DISABLE_NOEXCEPT_NOEXCEPT 0 + #ifdef _MSC_VER + #if _MSC_VER <= 1943 // Up to Visual Studio 2022 Version 17.13.6 + #undef TOML_DISABLE_NOEXCEPT_NOEXCEPT + #define TOML_DISABLE_NOEXCEPT_NOEXCEPT 1 + #endif + #endif +#endif + +#if !defined(TOML_FLOAT_CHARCONV) && (TOML_GCC || TOML_CLANG || (TOML_ICC && !TOML_ICC_CL)) +// not supported by any version of GCC or Clang as of 26/11/2020 +// not supported by any version of ICC on Linux as of 11/01/2021 +#define TOML_FLOAT_CHARCONV 0 +#endif +#if !defined(TOML_INT_CHARCONV) && (defined(__EMSCRIPTEN__) || defined(__APPLE__)) +// causes link errors on emscripten +// causes Mac OS SDK version errors on some versions of Apple Clang +#define TOML_INT_CHARCONV 0 +#endif +#ifndef TOML_INT_CHARCONV +#define TOML_INT_CHARCONV 1 +#endif +#ifndef TOML_FLOAT_CHARCONV +#define TOML_FLOAT_CHARCONV 1 +#endif +#if (TOML_INT_CHARCONV || TOML_FLOAT_CHARCONV) && !TOML_HAS_INCLUDE() +#undef TOML_INT_CHARCONV +#undef TOML_FLOAT_CHARCONV +#define TOML_INT_CHARCONV 0 +#define TOML_FLOAT_CHARCONV 0 +#endif + +#if defined(__cpp_concepts) && __cpp_concepts >= 201907 +#define TOML_REQUIRES(...) requires(__VA_ARGS__) +#else +#define TOML_REQUIRES(...) +#endif +#define TOML_ENABLE_IF(...) , typename std::enable_if<(__VA_ARGS__), int>::type = 0 +#define TOML_CONSTRAINED_TEMPLATE(condition, ...) \ + template <__VA_ARGS__ TOML_ENABLE_IF(condition)> \ + TOML_REQUIRES(condition) +#define TOML_HIDDEN_CONSTRAINT(condition, ...) TOML_CONSTRAINED_TEMPLATE(condition, __VA_ARGS__) + +#if defined(__SIZEOF_FLOAT128__) && defined(__FLT128_MANT_DIG__) && defined(__LDBL_MANT_DIG__) \ + && __FLT128_MANT_DIG__ > __LDBL_MANT_DIG__ +#define TOML_FLOAT128 __float128 +#endif + +#ifdef __SIZEOF_INT128__ +#define TOML_INT128 __int128_t +#define TOML_UINT128 __uint128_t +#endif + +// clang-format off + +//******** impl/version.hpp ****************************************************************************************** + +#define TOML_LIB_MAJOR 3 +#define TOML_LIB_MINOR 4 +#define TOML_LIB_PATCH 0 + +#define TOML_LANG_MAJOR 1 +#define TOML_LANG_MINOR 0 +#define TOML_LANG_PATCH 0 + +//******** impl/preprocessor.hpp ************************************************************************************* + +#define TOML_LIB_SINGLE_HEADER 1 + +#if TOML_ENABLE_UNRELEASED_FEATURES + #define TOML_LANG_EFFECTIVE_VERSION \ + TOML_MAKE_VERSION(TOML_LANG_MAJOR, TOML_LANG_MINOR, TOML_LANG_PATCH+1) +#else + #define TOML_LANG_EFFECTIVE_VERSION \ + TOML_MAKE_VERSION(TOML_LANG_MAJOR, TOML_LANG_MINOR, TOML_LANG_PATCH) +#endif + +#define TOML_LANG_HIGHER_THAN(major, minor, patch) \ + (TOML_LANG_EFFECTIVE_VERSION > TOML_MAKE_VERSION(major, minor, patch)) + +#define TOML_LANG_AT_LEAST(major, minor, patch) \ + (TOML_LANG_EFFECTIVE_VERSION >= TOML_MAKE_VERSION(major, minor, patch)) + +#define TOML_LANG_UNRELEASED \ + TOML_LANG_HIGHER_THAN(TOML_LANG_MAJOR, TOML_LANG_MINOR, TOML_LANG_PATCH) + +#ifndef TOML_ABI_NAMESPACES + #if TOML_DOXYGEN + #define TOML_ABI_NAMESPACES 0 + #else + #define TOML_ABI_NAMESPACES 1 + #endif +#endif +#if TOML_ABI_NAMESPACES + #define TOML_NAMESPACE_START namespace toml { inline namespace TOML_CONCAT(v, TOML_LIB_MAJOR) + #define TOML_NAMESPACE_END } static_assert(true) + #define TOML_NAMESPACE ::toml::TOML_CONCAT(v, TOML_LIB_MAJOR) + #define TOML_ABI_NAMESPACE_START(name) inline namespace name { static_assert(true) + #define TOML_ABI_NAMESPACE_BOOL(cond, T, F) TOML_ABI_NAMESPACE_START(TOML_CONCAT(TOML_EVAL_BOOL_, cond)(T, F)) + #define TOML_ABI_NAMESPACE_END } static_assert(true) +#else + #define TOML_NAMESPACE_START namespace toml + #define TOML_NAMESPACE_END static_assert(true) + #define TOML_NAMESPACE toml + #define TOML_ABI_NAMESPACE_START(...) static_assert(true) + #define TOML_ABI_NAMESPACE_BOOL(...) static_assert(true) + #define TOML_ABI_NAMESPACE_END static_assert(true) +#endif +#define TOML_IMPL_NAMESPACE_START TOML_NAMESPACE_START { namespace impl +#define TOML_IMPL_NAMESPACE_END } TOML_NAMESPACE_END +#if TOML_HEADER_ONLY + #define TOML_ANON_NAMESPACE_START static_assert(TOML_IMPLEMENTATION); TOML_IMPL_NAMESPACE_START + #define TOML_ANON_NAMESPACE_END TOML_IMPL_NAMESPACE_END + #define TOML_ANON_NAMESPACE TOML_NAMESPACE::impl + #define TOML_EXTERNAL_LINKAGE inline + #define TOML_INTERNAL_LINKAGE inline +#else + #define TOML_ANON_NAMESPACE_START static_assert(TOML_IMPLEMENTATION); \ + using namespace toml; \ + namespace + #define TOML_ANON_NAMESPACE_END static_assert(true) + #define TOML_ANON_NAMESPACE + #define TOML_EXTERNAL_LINKAGE + #define TOML_INTERNAL_LINKAGE static +#endif + +// clang-format on + +// clang-format off + +#if TOML_SIMPLE_STATIC_ASSERT_MESSAGES + + #define TOML_SA_NEWLINE " " + #define TOML_SA_LIST_SEP ", " + #define TOML_SA_LIST_BEG " (" + #define TOML_SA_LIST_END ")" + #define TOML_SA_LIST_NEW " " + #define TOML_SA_LIST_NXT ", " + +#else + + #define TOML_SA_NEWLINE "\n| " + #define TOML_SA_LIST_SEP TOML_SA_NEWLINE " - " + #define TOML_SA_LIST_BEG TOML_SA_LIST_SEP + #define TOML_SA_LIST_END + #define TOML_SA_LIST_NEW TOML_SA_NEWLINE TOML_SA_NEWLINE + #define TOML_SA_LIST_NXT TOML_SA_LIST_NEW + +#endif + +#define TOML_SA_NATIVE_VALUE_TYPE_LIST \ + TOML_SA_LIST_BEG "std::string" \ + TOML_SA_LIST_SEP "int64_t" \ + TOML_SA_LIST_SEP "double" \ + TOML_SA_LIST_SEP "bool" \ + TOML_SA_LIST_SEP "toml::date" \ + TOML_SA_LIST_SEP "toml::time" \ + TOML_SA_LIST_SEP "toml::date_time" \ + TOML_SA_LIST_END + +#define TOML_SA_NODE_TYPE_LIST \ + TOML_SA_LIST_BEG "toml::table" \ + TOML_SA_LIST_SEP "toml::array" \ + TOML_SA_LIST_SEP "toml::value" \ + TOML_SA_LIST_SEP "toml::value" \ + TOML_SA_LIST_SEP "toml::value" \ + TOML_SA_LIST_SEP "toml::value" \ + TOML_SA_LIST_SEP "toml::value" \ + TOML_SA_LIST_SEP "toml::value" \ + TOML_SA_LIST_SEP "toml::value" \ + TOML_SA_LIST_END + +#define TOML_SA_UNWRAPPED_NODE_TYPE_LIST \ + TOML_SA_LIST_NEW "A native TOML value type" \ + TOML_SA_NATIVE_VALUE_TYPE_LIST \ + \ + TOML_SA_LIST_NXT "A TOML node type" \ + TOML_SA_NODE_TYPE_LIST + +// clang-format on + +TOML_PUSH_WARNINGS; +TOML_DISABLE_SPAM_WARNINGS; +TOML_DISABLE_SWITCH_WARNINGS; +TOML_DISABLE_SUGGEST_ATTR_WARNINGS; + +// misc warning false-positives +#if TOML_MSVC +#pragma warning(disable : 5031) // #pragma warning(pop): likely mismatch +#if TOML_SHARED_LIB +#pragma warning(disable : 4251) // dll exports for std lib types +#endif +#elif TOML_CLANG +TOML_PRAGMA_CLANG(diagnostic ignored "-Wheader-hygiene") +#if TOML_CLANG >= 12 +TOML_PRAGMA_CLANG(diagnostic ignored "-Wc++20-extensions") +#endif +#if TOML_CLANG == 13 +TOML_PRAGMA_CLANG(diagnostic ignored "-Wreserved-identifier") +#endif +#endif + +//******** impl/std_new.hpp ****************************************************************************************** + +TOML_DISABLE_WARNINGS; +#include +TOML_ENABLE_WARNINGS; + +#if (!defined(__apple_build_version__) && TOML_CLANG >= 8) || TOML_GCC >= 7 || TOML_ICC >= 1910 || TOML_MSVC >= 1914 +#define TOML_LAUNDER(x) __builtin_launder(x) +#elif defined(__cpp_lib_launder) && __cpp_lib_launder >= 201606 +#define TOML_LAUNDER(x) std::launder(x) +#else +#define TOML_LAUNDER(x) x +#endif + +//******** impl/std_string.hpp *************************************************************************************** + +TOML_DISABLE_WARNINGS; +#include +#include +TOML_ENABLE_WARNINGS; + +#if TOML_DOXYGEN \ + || (defined(__cpp_char8_t) && __cpp_char8_t >= 201811 && defined(__cpp_lib_char8_t) \ + && __cpp_lib_char8_t >= 201907) +#define TOML_HAS_CHAR8 1 +#else +#define TOML_HAS_CHAR8 0 +#endif + +namespace toml // non-abi namespace; this is not an error +{ + using namespace std::string_literals; + using namespace std::string_view_literals; +} + +#if TOML_ENABLE_WINDOWS_COMPAT + +TOML_IMPL_NAMESPACE_START +{ + TOML_NODISCARD + TOML_EXPORTED_FREE_FUNCTION + std::string narrow(std::wstring_view); + + TOML_NODISCARD + TOML_EXPORTED_FREE_FUNCTION + std::wstring widen(std::string_view); + +#if TOML_HAS_CHAR8 + + TOML_NODISCARD + TOML_EXPORTED_FREE_FUNCTION + std::wstring widen(std::u8string_view); + +#endif +} +TOML_IMPL_NAMESPACE_END; + +#endif // TOML_ENABLE_WINDOWS_COMPAT + +//******** impl/std_optional.hpp ************************************************************************************* + +TOML_DISABLE_WARNINGS; +#if !TOML_HAS_CUSTOM_OPTIONAL_TYPE +#include +#endif +TOML_ENABLE_WARNINGS; + +TOML_NAMESPACE_START +{ +#if TOML_HAS_CUSTOM_OPTIONAL_TYPE + + template + using optional = TOML_OPTIONAL_TYPE; + +#else + + template + using optional = std::optional; + +#endif +} +TOML_NAMESPACE_END; + +//******** impl/forward_declarations.hpp ***************************************************************************** + +TOML_DISABLE_WARNINGS; +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +TOML_ENABLE_WARNINGS; +TOML_PUSH_WARNINGS; +#ifdef _MSC_VER +#ifndef __clang__ +#pragma inline_recursion(on) +#endif +#pragma push_macro("min") +#pragma push_macro("max") +#undef min +#undef max +#endif + +#ifndef TOML_DISABLE_ENVIRONMENT_CHECKS +#define TOML_ENV_MESSAGE \ + "If you're seeing this error it's because you're building toml++ for an environment that doesn't conform to " \ + "one of the 'ground truths' assumed by the library. Essentially this just means that I don't have the " \ + "resources to test on more platforms, but I wish I did! You can try disabling the checks by defining " \ + "TOML_DISABLE_ENVIRONMENT_CHECKS, but your mileage may vary. Please consider filing an issue at " \ + "https://github.com/marzer/tomlplusplus/issues to help me improve support for your target environment. " \ + "Thanks!" + +static_assert(CHAR_BIT == 8, TOML_ENV_MESSAGE); +#ifdef FLT_RADIX +static_assert(FLT_RADIX == 2, TOML_ENV_MESSAGE); +#endif +static_assert('A' == 65, TOML_ENV_MESSAGE); +static_assert(sizeof(double) == 8, TOML_ENV_MESSAGE); +static_assert(std::numeric_limits::is_iec559, TOML_ENV_MESSAGE); +static_assert(std::numeric_limits::digits == 53, TOML_ENV_MESSAGE); +static_assert(std::numeric_limits::digits10 == 15, TOML_ENV_MESSAGE); +static_assert(std::numeric_limits::radix == 2, TOML_ENV_MESSAGE); + +#undef TOML_ENV_MESSAGE +#endif // !TOML_DISABLE_ENVIRONMENT_CHECKS + +// undocumented forward declarations are hidden from doxygen because they fuck it up =/ + +namespace toml // non-abi namespace; this is not an error +{ + using ::std::size_t; + using ::std::intptr_t; + using ::std::uintptr_t; + using ::std::ptrdiff_t; + using ::std::nullptr_t; + using ::std::int8_t; + using ::std::int16_t; + using ::std::int32_t; + using ::std::int64_t; + using ::std::uint8_t; + using ::std::uint16_t; + using ::std::uint32_t; + using ::std::uint64_t; + using ::std::uint_least32_t; + using ::std::uint_least64_t; +} + +TOML_NAMESPACE_START +{ + struct date; + struct time; + struct time_offset; + + TOML_ABI_NAMESPACE_BOOL(TOML_HAS_CUSTOM_OPTIONAL_TYPE, custopt, stdopt); + struct date_time; + TOML_ABI_NAMESPACE_END; + + struct source_position; + struct source_region; + + class node; + template + class node_view; + + class key; + class array; + class table; + template + class value; + + class path; + + class toml_formatter; + class json_formatter; + class yaml_formatter; + + TOML_ABI_NAMESPACE_BOOL(TOML_EXCEPTIONS, ex, noex); +#if TOML_EXCEPTIONS + using parse_result = table; +#else + class parse_result; +#endif + TOML_ABI_NAMESPACE_END; // TOML_EXCEPTIONS +} +TOML_NAMESPACE_END; + +TOML_IMPL_NAMESPACE_START +{ + using node_ptr = std::unique_ptr; + + TOML_ABI_NAMESPACE_BOOL(TOML_EXCEPTIONS, impl_ex, impl_noex); + class parser; + TOML_ABI_NAMESPACE_END; // TOML_EXCEPTIONS + + // clang-format off + + inline constexpr std::string_view control_char_escapes[] = + { + "\\u0000"sv, + "\\u0001"sv, + "\\u0002"sv, + "\\u0003"sv, + "\\u0004"sv, + "\\u0005"sv, + "\\u0006"sv, + "\\u0007"sv, + "\\b"sv, + "\\t"sv, + "\\n"sv, + "\\u000B"sv, + "\\f"sv, + "\\r"sv, + "\\u000E"sv, + "\\u000F"sv, + "\\u0010"sv, + "\\u0011"sv, + "\\u0012"sv, + "\\u0013"sv, + "\\u0014"sv, + "\\u0015"sv, + "\\u0016"sv, + "\\u0017"sv, + "\\u0018"sv, + "\\u0019"sv, + "\\u001A"sv, + "\\u001B"sv, + "\\u001C"sv, + "\\u001D"sv, + "\\u001E"sv, + "\\u001F"sv, + }; + + inline constexpr std::string_view node_type_friendly_names[] = + { + "none"sv, + "table"sv, + "array"sv, + "string"sv, + "integer"sv, + "floating-point"sv, + "boolean"sv, + "date"sv, + "time"sv, + "date-time"sv + }; + + // clang-format on +} +TOML_IMPL_NAMESPACE_END; + +#if TOML_ABI_NAMESPACES +#if TOML_EXCEPTIONS +#define TOML_PARSER_TYPENAME TOML_NAMESPACE::impl::impl_ex::parser +#else +#define TOML_PARSER_TYPENAME TOML_NAMESPACE::impl::impl_noex::parser +#endif +#else +#define TOML_PARSER_TYPENAME TOML_NAMESPACE::impl::parser +#endif + +namespace toml +{ +} + +TOML_NAMESPACE_START // abi namespace +{ + inline namespace literals + { + } + + enum class TOML_CLOSED_ENUM node_type : uint8_t + { + none, + table, + array, + string, + integer, + floating_point, + boolean, + date, + time, + date_time + }; + + template + inline std::basic_ostream& operator<<(std::basic_ostream& lhs, node_type rhs) + { + const auto str = impl::node_type_friendly_names[static_cast>(rhs)]; + using str_char_t = decltype(str)::value_type; + if constexpr (std::is_same_v) + return lhs << str; + else + { + if constexpr (sizeof(Char) == sizeof(str_char_t)) + return lhs << std::basic_string_view{ reinterpret_cast(str.data()), str.length() }; + else + return lhs << str.data(); + } + } + + enum class TOML_OPEN_FLAGS_ENUM value_flags : uint16_t // being an "OPEN" flags enum is not an error + { + none, + format_as_binary = 1, + format_as_octal = 2, + format_as_hexadecimal = 3, + }; + TOML_MAKE_FLAGS(value_flags); + + inline constexpr value_flags preserve_source_value_flags = + POXY_IMPLEMENTATION_DETAIL(value_flags{ static_cast>(-1) }); + + enum class TOML_CLOSED_FLAGS_ENUM format_flags : uint64_t + { + none, + quote_dates_and_times = (1ull << 0), + quote_infinities_and_nans = (1ull << 1), + allow_literal_strings = (1ull << 2), + allow_multi_line_strings = (1ull << 3), + allow_real_tabs_in_strings = (1ull << 4), + allow_unicode_strings = (1ull << 5), + allow_binary_integers = (1ull << 6), + allow_octal_integers = (1ull << 7), + allow_hexadecimal_integers = (1ull << 8), + indent_sub_tables = (1ull << 9), + indent_array_elements = (1ull << 10), + indentation = indent_sub_tables | indent_array_elements, + relaxed_float_precision = (1ull << 11), + terse_key_value_pairs = (1ull << 12), + force_multiline_arrays = (1ull << 13), + }; + TOML_MAKE_FLAGS(format_flags); + + template + struct TOML_TRIVIAL_ABI inserter + { + static_assert(std::is_reference_v); + + T value; + }; + template + inserter(T&&) -> inserter; + template + inserter(T&) -> inserter; + + using default_formatter = toml_formatter; +} +TOML_NAMESPACE_END; + +TOML_IMPL_NAMESPACE_START +{ + template + using remove_cvref = std::remove_cv_t>; + + template + using common_signed_type = std::common_type_t...>; + + template + inline constexpr bool is_one_of = (false || ... || std::is_same_v); + + template + inline constexpr bool all_integral = (std::is_integral_v && ...); + + template + inline constexpr bool is_cvref = std::is_reference_v || std::is_const_v || std::is_volatile_v; + + template + inline constexpr bool is_wide_string = + is_one_of, const wchar_t*, wchar_t*, std::wstring_view, std::wstring>; + + template + inline constexpr bool value_retrieval_is_nothrow = !std::is_same_v, std::string> +#if TOML_HAS_CHAR8 + && !std::is_same_v, std::u8string> +#endif + + && !is_wide_string; + + template + struct copy_ref_; + template + using copy_ref = typename copy_ref_::type; + + template + struct copy_ref_ + { + using type = Dest; + }; + + template + struct copy_ref_ + { + using type = std::add_lvalue_reference_t; + }; + + template + struct copy_ref_ + { + using type = std::add_rvalue_reference_t; + }; + + template + struct copy_cv_; + template + using copy_cv = typename copy_cv_::type; + + template + struct copy_cv_ + { + using type = Dest; + }; + + template + struct copy_cv_ + { + using type = std::add_const_t; + }; + + template + struct copy_cv_ + { + using type = std::add_volatile_t; + }; + + template + struct copy_cv_ + { + using type = std::add_cv_t; + }; + + template + using copy_cvref = + copy_ref, std::remove_reference_t>, Dest>, Src>; + + template + inline constexpr bool always_false = false; + + template + inline constexpr bool first_is_same = false; + template + inline constexpr bool first_is_same = true; + + template > + struct underlying_type_ + { + using type = std::underlying_type_t; + }; + template + struct underlying_type_ + { + using type = T; + }; + template + using underlying_type = typename underlying_type_::type; + + // general value traits + // (as they relate to their equivalent native TOML type) + struct default_value_traits + { + using native_type = void; + static constexpr bool is_native = false; + static constexpr bool is_losslessly_convertible_to_native = false; + static constexpr bool can_represent_native = false; + static constexpr bool can_partially_represent_native = false; + static constexpr auto type = node_type::none; + }; + + template + struct value_traits; + + template > + struct value_traits_base_selector + { + static_assert(!is_cvref); + + using type = default_value_traits; + }; + template + struct value_traits_base_selector + { + static_assert(!is_cvref); + + using type = value_traits>; + }; + + template + struct value_traits : value_traits_base_selector::type + {}; + template + struct value_traits : value_traits + {}; + template + struct value_traits : value_traits + {}; + template + struct value_traits : value_traits + {}; + template + struct value_traits : value_traits + {}; + template + struct value_traits : value_traits + {}; + + // integer value_traits specializations - standard types + template + struct integer_limits + { + static constexpr T min = T{ (std::numeric_limits>::min)() }; + static constexpr T max = T{ (std::numeric_limits>::max)() }; + }; + template + struct integer_traits_base : integer_limits + { + using native_type = int64_t; + static constexpr bool is_native = std::is_same_v, native_type>; + static constexpr bool is_signed = static_cast>(-1) < underlying_type{}; + static constexpr auto type = node_type::integer; + static constexpr bool can_partially_represent_native = true; + }; + template + struct unsigned_integer_traits : integer_traits_base + { + static constexpr bool is_losslessly_convertible_to_native = + integer_limits>::max <= 9223372036854775807ULL; + static constexpr bool can_represent_native = false; + }; + template + struct signed_integer_traits : integer_traits_base + { + using native_type = int64_t; + static constexpr bool is_losslessly_convertible_to_native = + integer_limits>::min >= (-9223372036854775807LL - 1LL) + && integer_limits>::max <= 9223372036854775807LL; + static constexpr bool can_represent_native = + integer_limits>::min <= (-9223372036854775807LL - 1LL) + && integer_limits>::max >= 9223372036854775807LL; + }; + template ::is_signed> + struct integer_traits : signed_integer_traits + {}; + template + struct integer_traits : unsigned_integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + static_assert(value_traits::is_native); + static_assert(value_traits::is_signed); + static_assert(value_traits::is_losslessly_convertible_to_native); + static_assert(value_traits::can_represent_native); + static_assert(value_traits::can_partially_represent_native); + + // integer value_traits specializations - non-standard types +#ifdef TOML_INT128 + template <> + struct integer_limits + { + static constexpr TOML_INT128 max = + static_cast((TOML_UINT128{ 1u } << ((__SIZEOF_INT128__ * CHAR_BIT) - 1)) - 1); + static constexpr TOML_INT128 min = -max - TOML_INT128{ 1 }; + }; + template <> + struct integer_limits + { + static constexpr TOML_UINT128 min = TOML_UINT128{}; + static constexpr TOML_UINT128 max = (2u * static_cast(integer_limits::max)) + 1u; + }; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; +#endif +#ifdef TOML_SMALL_INT_TYPE + template <> + struct value_traits : signed_integer_traits + {}; +#endif + + // floating-point traits base + template + struct float_traits_base + { + static constexpr auto type = node_type::floating_point; + using native_type = double; + static constexpr bool is_native = std::is_same_v; + static constexpr bool is_signed = true; + + static constexpr int bits = static_cast(sizeof(T) * CHAR_BIT); + static constexpr int digits = MantissaDigits; + static constexpr int digits10 = DecimalDigits; + + static constexpr bool is_losslessly_convertible_to_native = bits <= 64 // + && digits <= 53 // DBL_MANT_DIG + && digits10 <= 15; // DBL_DIG + + static constexpr bool can_represent_native = digits >= 53 // DBL_MANT_DIG + && digits10 >= 15; // DBL_DIG + + static constexpr bool can_partially_represent_native = digits > 0 && digits10 > 0; + }; + template + struct float_traits : float_traits_base::digits, std::numeric_limits::digits10> + {}; +#if TOML_ENABLE_FLOAT16 + template <> + struct float_traits<_Float16> : float_traits_base<_Float16, __FLT16_MANT_DIG__, __FLT16_DIG__> + {}; +#endif +#ifdef TOML_FLOAT128 + template <> + struct float_traits : float_traits_base + {}; +#endif + + // floating-point traits + template <> + struct value_traits : float_traits + {}; + template <> + struct value_traits : float_traits + {}; + template <> + struct value_traits : float_traits + {}; +#if TOML_ENABLE_FLOAT16 + template <> + struct value_traits<_Float16> : float_traits<_Float16> + {}; +#endif +#ifdef TOML_FLOAT128 + template <> + struct value_traits : float_traits + {}; +#endif +#ifdef TOML_SMALL_FLOAT_TYPE + template <> + struct value_traits : float_traits + {}; +#endif + static_assert(value_traits::is_native); + static_assert(value_traits::is_losslessly_convertible_to_native); + static_assert(value_traits::can_represent_native); + static_assert(value_traits::can_partially_represent_native); + + // string value_traits specializations - char-based strings + template + struct string_traits + { + using native_type = std::string; + static constexpr bool is_native = std::is_same_v; + static constexpr bool is_losslessly_convertible_to_native = true; + static constexpr bool can_represent_native = + !std::is_array_v && (!std::is_pointer_v || std::is_const_v>); + static constexpr bool can_partially_represent_native = can_represent_native; + static constexpr auto type = node_type::string; + }; + template <> + struct value_traits : string_traits + {}; + template <> + struct value_traits : string_traits + {}; + template <> + struct value_traits : string_traits + {}; + template + struct value_traits : string_traits + {}; + template <> + struct value_traits : string_traits + {}; + template + struct value_traits : string_traits + {}; + + // string value_traits specializations - char8_t-based strings +#if TOML_HAS_CHAR8 + template <> + struct value_traits : string_traits + {}; + template <> + struct value_traits : string_traits + {}; + template <> + struct value_traits : string_traits + {}; + template + struct value_traits : string_traits + {}; + template <> + struct value_traits : string_traits + {}; + template + struct value_traits : string_traits + {}; +#endif + + // string value_traits specializations - wchar_t-based strings on Windows +#if TOML_ENABLE_WINDOWS_COMPAT + template + struct wstring_traits + { + using native_type = std::string; + static constexpr bool is_native = false; + static constexpr bool is_losslessly_convertible_to_native = true; // narrow + static constexpr bool can_represent_native = std::is_same_v; // widen + static constexpr bool can_partially_represent_native = can_represent_native; + static constexpr auto type = node_type::string; + }; + template <> + struct value_traits : wstring_traits + {}; + template <> + struct value_traits : wstring_traits + {}; + template <> + struct value_traits : wstring_traits + {}; + template + struct value_traits : wstring_traits + {}; + template <> + struct value_traits : wstring_traits + {}; + template + struct value_traits : wstring_traits + {}; +#endif + + // other 'native' value_traits specializations + template + struct native_value_traits + { + using native_type = T; + static constexpr bool is_native = true; + static constexpr bool is_losslessly_convertible_to_native = true; + static constexpr bool can_represent_native = true; + static constexpr bool can_partially_represent_native = true; + static constexpr auto type = NodeType; + }; + template <> + struct value_traits : native_value_traits + {}; + template <> + struct value_traits : native_value_traits + {}; + template <> + struct value_traits