From 9494963ea54a3deb6dbf1261864033a4cee9be76 Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Tue, 28 Feb 2023 19:07:26 -0500 Subject: [PATCH 01/14] c++17 changes and starting changes to aproj binaries --- CMakeLists.txt | 4 +- aproj/aproj-init.cpp | 141 ------ aproj/aproj.cpp | 487 +++++++-------------- aproj/init.hpp | 32 ++ project/CMakeLists.txt | 6 +- project/antler/project/project.hpp | 7 +- project/antler/project/semver.hpp | 19 +- project/antler/project/version.hpp | 17 +- project/antler/project/version_compare.hpp | 3 +- project/antler/system/exec.hpp | 2 +- project/antler/system/version.hpp.in | 14 + project/src/location.cpp | 39 +- project/src/project-print.cpp | 10 +- project/src/project.cpp | 4 +- project/src/semver.cpp | 67 ++- project/src/version.cpp | 40 +- project/src/version_compare.cpp | 30 +- project/src/version_constraint.cpp | 59 ++- 18 files changed, 387 insertions(+), 594 deletions(-) delete mode 100644 aproj/aproj-init.cpp create mode 100644 aproj/init.hpp create mode 100644 project/antler/system/version.hpp.in diff --git a/CMakeLists.txt b/CMakeLists.txt index c5524d0..029f488 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.11) # If ryml >= 0.5.0 is included via FetchContent or submodule, this minimum is 3.12. -project("ANTLER Project Tools ") +project("ANTLER Project Tools " VERSION 1.0.0) include( ./common.cmake REQUIRED ) @@ -12,7 +12,7 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") endif() elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "10") - message(FATAL_ERROR "Insufficient gcc version") + #message(FATAL_ERROR "Insufficient gcc version") endif() else() message(WARNING "This build requires a C++20 compliant compiler.") diff --git a/aproj/aproj-init.cpp b/aproj/aproj-init.cpp deleted file mode 100644 index 128715f..0000000 --- a/aproj/aproj-init.cpp +++ /dev/null @@ -1,141 +0,0 @@ -/// @copyright See `LICENSE` in the root directory of this project. - -#include -#include -#include -#include - -#include - -#include - - -int main(int argc, char** argv) { - - std::filesystem::path cli_path; - std::string cli_name; - antler::project::version ver("0.0.0"); - bool interactive=false; - - - // CLI setup - - // Description needs a bit more info than our standard brief string contains; still, we cannot break brief_str. - const std::string brief_str = "Initialize a new projet creating the directory tree and a `project.yaml` file."; - - CLI::App cli{}; - common_init(cli, argv[0], brief_str); - - const std::string desc=brief_str + "\n" - + "`project.yaml` is created in PATH if PATH is an empty directory AND the filename matches PROJECT_NAME;\n" - + "otherwise, a directory matching PROJECT_NAME is created at PATH to contain `project.yaml`.\n"; - cli.description(desc); - - // Positional arguments: - cli.add_option("path", cli_path, "This is the root path to create the project in.")->required(); - cli.add_option("project_name", cli_name, "The name of the project."); - cli.add_option("version", cli_name, "The version to store in the project file."); // add default info? - - // Option flag - cli.add_flag("--interactive", interactive, "Force interactive mode."); - - // Parse - CLI11_PARSE(cli,argc,argv); - - - // Begin doing the work. (This may move into `antler::project::project` at a later date.) - - // `name` will contain the project name. It needs to be separate from cli_name because we might use both in the logic below. - std::string name; - - // Sanity check potential project directory. - std::error_code sec; - if (!std::filesystem::exists(cli_path, sec)) - name = cli_path.filename().string(); - else { - // It might be okay if it exists, but only if it's a directory AND it's empty. - if (!std::filesystem::is_directory(cli_path, sec)) - return cli.exit( CLI::Error("path", cli_path.string() + " already exists.") ); - if (!std::filesystem::is_empty(cli_path, sec)) { - if (std::filesystem::exists(cli_path / "project.yaml")) - return cli.exit( CLI::Error("path", "not initializing where a `project.yaml` file already exists.") ); - } - else if (!cli_path.has_extension()) { - name = cli_path.filename().string(); - } - } - - // Maybe copy name from cli. - if(!cli_name.empty()) - name = cli_name; - - // Resolve the path to the project root. This may be overwritten in interactive mode. - auto project_root = cli_path; - if (cli_path.filename() != name) - project_root /= name; - - // Test for interactive mode. - interactive |= cli_name.empty(); - for (const auto loop=interactive; loop;) { - // Loop until user is satisfied. - if (!name.empty()) { - // Resolve the path to the project root. - project_root = cli_path; - if (cli_path.filename() != name) - project_root /= name; - - std::cout - << '\n' - << "Path: " << project_root << '\n' - << "Project name: " << name << '\n' - << "Version: " << ver << '\n' - << '\n'; - if(!ver.is_semver()) - std::cout << "Warning: Version is NOT a SemVer.\n\n"; - - if (is_this_correct()) - break; - } - - get_name("project name",name); - get_version("project version", ver); - } - - - // Sanity check. - - if (!validate_name(name)) - return cli.exit( CLI::Error("name", std::string{"name \""} + name + "\" contains invalid chars. Expecting [0-9a-zA-Z_].") ); - - - // Do initialization here: - - // Create the root directory. - std::filesystem::create_directories(project_root, sec); - if (sec) - return cli.exit( CLI::Error("path", std::string{project_root} + " could not be created: " + sec.message()) ); - - - if (!std::filesystem::is_empty(project_root, sec)) - return cli.exit( CLI::Error("path", std::string{project_root} + " is NOT empty!") ); - - // Create the directory structure. - { - const std::vector files = { "apps", "include", "ricardian", "libs", "tests" }; - for (const auto& fn : files) { - std::filesystem::create_directory(project_root / fn, sec); - if (sec) - return cli.exit( CLI::Error("path", std::string{project_root / fn} + " could not be created: " + sec.message()) ); - } - } - - // Create an empty project and populate it. - antler::project::project proj; - proj.path(project_root / "project.yaml"); - proj.name(name); - proj.version(ver); - proj.sync(); - - - return -1; -} diff --git a/aproj/aproj.cpp b/aproj/aproj.cpp index 32c2b81..ae06084 100644 --- a/aproj/aproj.cpp +++ b/aproj/aproj.cpp @@ -1,319 +1,168 @@ -/// @copyright See `LICENSE` in the root directory of this project. - -#include -#include -#include -#include -#include -#include -#include - -#include // boost::split() -#include // boost::dll::program_location() - -#include - - -#include - - -namespace { // anonymous - -// The executable name as derived from argv[0] for display in --help command. -std::string exe_name; - -/// Structure to store contents of subcommands/flags -struct app_entry { - std::filesystem::path path; ///< path to the executable subcommand. - std::string raw_flags; ///< Raw string representation of args (e.g. "-q, --quiet"). This is used to calculate pretty column widths. - std::vector flags; ///< flags to select this subcomand (e.g. {"-q", "--quiet"}). - std::string brief; ///< Brief description adequate for displaying what the subcommand does when `--help` is called. - - /// Test to see if this structure has a given command (cmd). - /// @param cmd The command to search for. - /// @return true if cmd was found, false otherwise. - bool has_command(std::string_view cmd) const noexcept { - for (const auto& flag : flags) { - if (flag == cmd) - return true; - } - return false; - } - -#if _DEBUG - void print(std::ostream& os) const noexcept { - os << path << " flags: ["; - for(size_t i=0; i < flags.size(); ++i) { - if(i) - os << ","; - os << flags[i]; - } - os << "] raw: [" << raw_flags << "] brief: " << brief << "\n"; - } -#endif -}; -// Storage for subcommands. -std::vector apps; - - -/// Print usage information to std::cout and return 0 or, optionally - if err is not-empty() - print to std::cerr and return -1. -/// @param err An error string to print. If empty, print to std::cout and return 0; otherwise std::cerr and return -1. -/// @return 0 if err.empty(); otherwise -1. This value is suitable for using at exit. -int usage(std::string_view err = "") { - - constexpr std::string_view help_arg{ "--help" }; - - // Determine how wide the flags "column" should be. - size_t width = help_arg.size(); - for (const auto& a : apps) { - width = std::max(width, a.raw_flags.size()); - } - width += 3; - - // Sort the subcommands based on flag value. - std::sort(apps.begin(), apps.end(), [](const app_entry& l, const app_entry& r) { return l.raw_flags < r.raw_flags; }); - - // Determine target ostream. - std::ostream& os = (err.empty() ? std::cout : std::cerr); - - os << exe_name << ": COMMAND [options]\n" - << "\n" - << " Commands:\n"; - - // Print the flags followed by brief for each subcommand. - for (const auto& a : apps) { - // Make sure to pad for a nice column. - std::string pad(width - a.raw_flags.size(), ' '); - os << " " << a.raw_flags << pad << a.brief << '\n'; - } - - // add --help arg: - { - std::string pad(width - help_arg.size(), ' '); - os << " " << help_arg << pad << "Show this help and exit.\n"; - } - - os << '\n' - << " Options vary by command and may be viewed with --help.\n"; - - // Return success(0) or print the error and return failure(-1). - if (err.empty()) - return 0; - os << "Error: " << err << "\n"; - return -1; -} - - -/// Helper function to limit boilerplate cut and paste when calling subcommands. -/// This function calls a subcommand and passes a string for use in the subcommands `usage()` function. -/// @tparam iterator_type Nominally std::vector::iterator, but could be const char* -/// @param exe The path for the subcommand. -/// @param cmd The flag/command used to indicate which exe to call. -/// @param begin Iterator to the beginning of a list of arguments that will be passed. -/// @param end Iterator to the end of a list of arguments that will be passed. -/// @return The result of calling exe with the given arguments. -template -int exec_helper(const std::filesystem::path& exe, std::string_view cmd, iterator_type begin, iterator_type end) { - - std::stringstream ss; - ss << exe; - // Pass all the arguments. - for (auto i = begin; i < end; ++i) - ss << " " << *i; - // Add the string for use in the subcomand's usage() function. - ss << " --indirect=\"" << exe_name << ' ' << cmd << '"'; - - // Call the subcommand. - return system(ss.str().c_str()); -} - - -/// Search the directory containing this executable for its subcommands and return them to the caller. -/// @return A vector containing the absolute paths to the subcommands. -std::vector get_aproj_subcommands() { - - std::vector rv; // return value. - - // As of this writting, program location returns a boost::dll::path type that must be explicityly converted to a std::filesystem::path. - const std::filesystem::path bin_path = boost::dll::program_location().parent_path().string(); // This could throw, but really shouldn't. - - // Iterate over the collocated executables, if they match our pattern store them in the return value list. - for (auto const& entry : std::filesystem::directory_iterator{ bin_path }) { - const auto& path = entry.path(); - if (path.stem().string().starts_with(project_prefix)) // project_prefix should be "aproj-". - rv.push_back(path); - } - - return rv; -} - - -/// Populate app list -/// Given an executable path, call it with the `--brief` falg and store the results in the app list. -/// @param path Path to an `aproj-` subcommand executable. -/// @post The `app` global is updated with the results from calling `path` with the command `--brief`. -void populate_apps(const std::filesystem::path& path) noexcept { - - // Example output from `aproj-init --brief`: "--init Initialize a new projet creating the directory tree and a `project.yaml` file." - // path: "/usr/local/bin/aproj-init" - // flags: "--init" - // brief: "Initialize a new projet creating the directory tree and a `project.yaml` file." - // Note: flags are parsed from the beginiing to the first space, so if the output was "-i,--init Initialize..." flags would have been "-i","--init" - - - // Get the brief description from the subcommand executable: - auto result = antler::system::exec(path.string() + " --brief"); - - // On error: print the error and return. - if (!result) { - std::cerr << exe_name << ": `--brief` capture failed for " << path << '\n'; - return; - } - - // Search for the spc to break the string into flags and brief. - auto spc = result.output.find_first_of(' '); - - // If the space is missing report the error and return. - if (spc == std::string::npos) { - std::cerr << exe_name << ": `--brief` parse failed for " << path << '\n'; - return; - } - - // Build an app entry from the path and results of capturing the brief output. - app_entry ae; - ae.path = path; - ae.raw_flags = result.output.substr(0, spc); - // Split the raw flags into a vector of strings. e.g. "-i,--init" becomes {"-i","--init"} - boost::split(ae.flags, ae.raw_flags, boost::is_any_of(",")); - // Store the help text, then trim any new line characters. - ae.brief = result.output.substr(spc + 1); - while (ae.brief.back() == '\n') - ae.brief.pop_back(); - - // Finally, store the app_entry into the global list. - apps.push_back(ae); -} - - -/// Search for an app entry that has either cmd or --cmd in it's flags. -/// @note if cmd is not found and does not begin with '-', "--" is appended and the search is rerun. -/// @param cmd The command to search for. -/// @return A std::option that is populated with the app_entry ONLY if it is found. -[[nodiscard]] std::optional find_app_entry(const std::string& cmd) noexcept { - - // sanity check. - if (cmd.empty()) - return {}; - - // Look for the command in the apps. - for (const auto& a : apps) { - if (a.has_command(cmd)) - return a; - } - - // If cmd starts with '-', we return. If not, we will add "--" to command and recheck. - if (cmd[0] == '-') - return {}; - - // Look for the dashed command in the apps. - auto dashed_cmd = std::string("--") + cmd; - for (const auto& a : apps) { - if (a.has_command(dashed_cmd)) - return a; - } - - // No app was found. Return expty. - return {}; -} - - -/// Parse command. Attempt to convert argv values into a command and options. -/// @param begin At entry, this should be the first argument (argv[1]) at exit this is the first option after the parsed command. -/// @param end End iterator. -/// @param cmd As long as begin != end, this is updated with the parsed command. -/// @param ae If command was found -/// @return true if a command was found with an associated ae and begin, command, and ae are updated. -template -[[nodiscard]] bool parse_command(iterator_type& begin, const iterator_type end, std::string& cmd, app_entry& ae) noexcept { - - if (begin == end) - return false; - - // Get the command. - cmd = *begin; - ++begin; - - // See if the command was found, if it is, copy ae and return true. - if (auto ae_opt = find_app_entry(cmd); ae_opt) { - ae = ae_opt.value(); - return true; - } - - // We didn't find the command, maybe it's special? If it's definately NOT, return false. - if (begin == end) - return false; - - // If this is the add command it needs a prefix. - if (cmd == "add") { - - // Update command to add the prefix. eg "add" becomes "add app" or "add lib" or etc. - // We don't expect to find "add X" in apps, but we should find "--add-X", so make a special alias search command. - cmd += std::string(" ") + std::string(*begin); - auto alias_cmd = std::string("--add-") + *begin; - ++begin; - - // See if we can find the alias command. - if (auto ae_opt = find_app_entry(alias_cmd); ae_opt) { - ae = ae_opt.value(); - return true; - } - - // We didn't find the alias_cmd, return false. - return false; - } - - // If we got here, then no app_entry was found. Return false. - return false; -} - - -} // anonymous namespace - - - -int main(int argc, char** argv) { - - // Get the executable name, this is used for populating `--help` / usage() output. - exe_name = std::filesystem::path(argv[0]).filename().string(); - - // Get all the subcomands. - auto subcommand_path_list = get_aproj_subcommands(); - - // Populate the apps global list for each subcommand. - for(const auto& subcommand_path : subcommand_path_list) - populate_apps(subcommand_path); - - - // Try try to find the command. - auto span = std::span(argv+1, argc-1); - auto args_begin = span.begin(); - auto args_end = span.end(); - std::string cmd; - app_entry ae; - if (parse_command(args_begin, args_end, cmd, ae)) { - // We found the command, so let's call it! - // Note: args_begin was updated by parse_command. - return exec_helper(ae.path, cmd, args_begin, args_end); - } - - // If cmd is empty, then no command was given, so provide the usage to stderr and exit. - if (cmd.empty()) - return usage("No command supplied."); - - // The command did not result in finding an app_entry. If the command was `--help`, call usage. - if (cmd == "--help") - return usage(); - - // The command was populated and NOT --help, call usage with the error. - return usage(cmd + " is not a valid command."); -} +/// @copyright See `LICENSE` in the root directory of this project. + +#include +#include +#include +#include +#include +#include + +#include + +#include // boost::split() +#include // boost::dll::program_location() + +#include + + +#include + +#include "init.hpp" + +int main(int argc, char** argv) { + // using this as we will alias antler-proj to cdt-proj for the + // next release of CDT. + const auto app_name = std::filesystem::path(argv[0]).filename().string(); + CLI::App app{app_name}; + + init_project init = {app}; + + CLI11_PARSE(app, argc, argv); + + if (init.subcommand) { + return init.exec(); + } + + std::cout << "Param " << init.path << std::endl; + return 0; +} + +/* +std::filesystem::path cli_path; + std::string cli_name; + antler::project::version ver("0.0.0"); + bool interactive=false; + + + // CLI setup + + // Description needs a bit more info than our standard brief string contains; still, we cannot break brief_str. + const std::string brief_str = "Initialize a new projet creating the directory tree and a `project.yaml` file."; + + CLI::App cli{}; + common_init(cli, argv[0], brief_str); + + const std::string desc=brief_str + "\n" + + "`project.yaml` is created in PATH if PATH is an empty directory AND the filename matches PROJECT_NAME;\n" + + "otherwise, a directory matching PROJECT_NAME is created at PATH to contain `project.yaml`.\n"; + cli.description(desc); + + // Positional arguments: + cli.add_option("path", cli_path, "This is the root path to create the project in.")->required(); + cli.add_option("project_name", cli_name, "The name of the project."); + cli.add_option("version", cli_name, "The version to store in the project file."); // add default info? + + // Option flag + cli.add_flag("--interactive", interactive, "Force interactive mode."); + + // Parse + CLI11_PARSE(cli,argc,argv); + + + // Begin doing the work. (This may move into `antler::project::project` at a later date.) + + // `name` will contain the project name. It needs to be separate from cli_name because we might use both in the logic below. + std::string name; + + // Sanity check potential project directory. + std::error_code sec; + if (!std::filesystem::exists(cli_path, sec)) + name = cli_path.filename().string(); + else { + // It might be okay if it exists, but only if it's a directory AND it's empty. + if (!std::filesystem::is_directory(cli_path, sec)) + return cli.exit( CLI::Error("path", cli_path.string() + " already exists.") ); + if (!std::filesystem::is_empty(cli_path, sec)) { + if (std::filesystem::exists(cli_path / "project.yaml")) + return cli.exit( CLI::Error("path", "not initializing where a `project.yaml` file already exists.") ); + } + else if (!cli_path.has_extension()) { + name = cli_path.filename().string(); + } + } + + // Maybe copy name from cli. + if(!cli_name.empty()) + name = cli_name; + + // Resolve the path to the project root. This may be overwritten in interactive mode. + auto project_root = cli_path; + if (cli_path.filename() != name) + project_root /= name; + + // Test for interactive mode. + interactive |= cli_name.empty(); + for (const auto loop=interactive; loop;) { + // Loop until user is satisfied. + if (!name.empty()) { + // Resolve the path to the project root. + project_root = cli_path; + if (cli_path.filename() != name) + project_root /= name; + + std::cout + << '\n' + << "Path: " << project_root << '\n' + << "Project name: " << name << '\n' + << "Version: " << ver << '\n' + << '\n'; + if(!ver.is_semver()) + std::cout << "Warning: Version is NOT a SemVer.\n\n"; + + if (is_this_correct()) + break; + } + + get_name("project name",name); + get_version("project version", ver); + } + + + // Sanity check. + + if (!validate_name(name)) + return cli.exit( CLI::Error("name", std::string{"name \""} + name + "\" contains invalid chars. Expecting [0-9a-zA-Z_].") ); + + + // Do initialization here: + + // Create the root directory. + std::filesystem::create_directories(project_root, sec); + if (sec) + return cli.exit( CLI::Error("path", std::string{project_root} + " could not be created: " + sec.message()) ); + + + if (!std::filesystem::is_empty(project_root, sec)) + return cli.exit( CLI::Error("path", std::string{project_root} + " is NOT empty!") ); + + // Create the directory structure. + { + const std::vector files = { "apps", "include", "ricardian", "libs", "tests" }; + for (const auto& fn : files) { + std::filesystem::create_directory(project_root / fn, sec); + if (sec) + return cli.exit( CLI::Error("path", std::string{project_root / fn} + " could not be created: " + sec.message()) ); + } + } + + // Create an empty project and populate it. + antler::project::project proj; + proj.path(project_root / "project.yaml"); + proj.name(name); + proj.version(ver); + proj.sync(); + + + return -1; + } + */ \ No newline at end of file diff --git a/aproj/init.hpp b/aproj/init.hpp new file mode 100644 index 0000000..4248ac6 --- /dev/null +++ b/aproj/init.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +#include + +#include + +struct init_project { + inline init_project(CLI::App& app) { + subcommand = app.add_subcommand("init", "Initialize a new project creating the directory tree and a `project.yaml` file."); + subcommand->add_option("path", path, "This is the root path to create the project in.")->required(); + subcommand->add_option("project_name", name, "The name of the project.")->required(); + subcommand->add_option("version", version_raw, "The version to store in the project file."); + } + + int32_t exec() { + antler::project::project proj = {path, name, version_raw}; + + if (!proj.init_dirs(path)) + return -1; + + proj.sync(); + return 0; + } + + CLI::App* subcommand; + std::string path; + std::string name; + std::string version_raw; +}; \ No newline at end of file diff --git a/project/CMakeLists.txt b/project/CMakeLists.txt index 8b41ddf..575c5a0 100644 --- a/project/CMakeLists.txt +++ b/project/CMakeLists.txt @@ -10,8 +10,11 @@ file(GLOB_RECURSE src_cpp ./src/*.cpp) file(GLOB_RECURSE src_h ./src/*.h ./src/*.hpp) file(GLOB inc_h ./antler/project/*.h ./antler/project/*.hpp ./antler/project/*.ipp) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/antler/system/version.hpp.in + ${CMAKE_CURRENT_BINARY_DIR}/antler/system/version.hpp) + add_library(${target_name} ${src_cpp} ${src_h} ${inc_h}) -set_property(TARGET ${target_name} PROPERTY CXX_STANDARD 20) +set_property(TARGET ${target_name} PROPERTY CXX_STANDARD 17) target_compile_definitions(${target_name} PRIVATE BUILDING_AP_PROJ ) @@ -23,6 +26,7 @@ target_include_directories(${target_name} SYSTEM PRIVATE ) target_include_directories(${target_name} PUBLIC . + ${CMAKE_CURRENT_BINARY_DIR} ) # Install commands diff --git a/project/antler/project/project.hpp b/project/antler/project/project.hpp index 692e4bc..0a4a6ff 100644 --- a/project/antler/project/project.hpp +++ b/project/antler/project/project.hpp @@ -25,9 +25,12 @@ class project { public: // parse a project from a project.yml + constexpr static inline std::string_view manifest_name = "project.yml"; + // constructors - project(); - //project(const std::filesystem::path& filename); + project() = default; + project(std::string_view path, std::string_view name, std::string_view version_raw) : + m_path(path), m_name(name), m_ver(version_raw) {} /// Get the project name. /// @return The name of the project. diff --git a/project/antler/project/semver.hpp b/project/antler/project/semver.hpp index cf6e64d..b31cbd9 100644 --- a/project/antler/project/semver.hpp +++ b/project/antler/project/semver.hpp @@ -27,19 +27,24 @@ class semver { /// @param build The build value. semver(value_type major = 0, value_type minor = 0, value_type patch = 0, std::string_view pre_release = "", std::string_view build = "") noexcept; + [[nodiscard]] int64_t compare(const semver& rhs) const noexcept; + /// comparison operator /// @param rhs The right hand side semver to compare against. /// @return Follows standard rules. - //[[nodiscard]] std::strong_ordering operator<=>(const self& rhs) const; - [[nodiscard]] std::strong_ordering operator<=>(const self& rhs) const noexcept; + [[nodiscard]] inline bool operator<(const semver& rhs) const noexcept { return compare(rhs) == -1; } + [[nodiscard]] inline bool operator<=(const semver& rhs) const noexcept { return compare(rhs) != 1; } + [[nodiscard]] inline bool operator>(const semver& rhs) const noexcept { return compare(rhs) == 1; } + [[nodiscard]] inline bool operator>=(const semver& rhs) const noexcept { return compare(rhs) != -1; } + /// comparison operator /// @param rhs The right hand side semver to compare against. /// @return true if this object and rhs are equivalent. - [[nodiscard]] bool operator==(const self& rhs) const noexcept; + [[nodiscard]] inline bool operator==(const semver& rhs) const noexcept { return compare(rhs) == 0;} /// comparison operator /// @param rhs The right hand side semver to compare against. /// @return true if this object and rhs are different (aka NOT equivalent). - [[nodiscard]] bool operator!=(const self& rhs) const noexcept; + [[nodiscard]] inline bool operator!=(const semver& rhs) const noexcept { return compare(rhs) != 0; } /// Clear the version. void clear() noexcept; @@ -61,14 +66,14 @@ class semver { private: /// compare prerelease according to rule 12. lhs and rhs must both be the pre-release portion of the version and /// @TODO Initial development erroneously used 2.0.0-rc.1; this function needs to be reevaluated for correctness and renamed. - [[nodiscard]] static std::strong_ordering compare_p_rule12(std::string_view lhs, std::string_view rhs) noexcept; + [[nodiscard]] static int64_t compare_p_rule12(std::string_view lhs, std::string_view rhs) noexcept; /// compare build according to rule 12. lhs and rhs must both be the build portion of the version. /// @TODO Initial development erroneously used 2.0.0-rc.1; this function needs to be reevaluated for correctness and renamed. - [[nodiscard]] static std::strong_ordering compare_b_rule12(std::string_view lhs, std::string_view rhs) noexcept; + [[nodiscard]] static int64_t compare_b_rule12(std::string_view lhs, std::string_view rhs) noexcept; /// @return a comparison of lhs and rhs according to semver rule 12. lhs and rhs must both be EITHER pre-release or build and /// both must be populated. /// @TODO Initial development erroneously used 2.0.0-rc.1; this function needs to be reevaluated for correctness and renamed. - [[nodiscard]] static std::strong_ordering compare_pb_rule12(std::string_view lhs, std::string_view rhs) noexcept; + [[nodiscard]] static int64_t compare_pb_rule12(std::string_view lhs, std::string_view rhs) noexcept; /// @return true indivcates valid pre-release or build string; false otherwise. /// @TODO Initial development erroneously used 2.0.0-rc.1; this function needs to be reevaluated for correctness and renamed. [[nodiscard]] static bool validate_pb_rule10or11(std::string_view s) noexcept; diff --git a/project/antler/project/version.hpp b/project/antler/project/version.hpp index beea8b1..f4d898f 100644 --- a/project/antler/project/version.hpp +++ b/project/antler/project/version.hpp @@ -9,7 +9,6 @@ #include #include #include -#include namespace antler::project { @@ -20,7 +19,7 @@ class version { using self = version; ///< Alias for self type. /// Default constructor. - version(); + version() = default; /// @parm ver A string to create this version with. ver is evaluated to see if it might be a semver. version(std::string_view ver); /// @param sv A semver version to create this version with. @@ -40,10 +39,16 @@ class version { /// @param rhs The right hand side semver to compare against. /// @return Follows standard rules. //[[nodiscard]] std::strong_ordering operator<=>(const self& rhs) const; - [[nodiscard]] std::strong_ordering operator<=>(const self& rhs) const noexcept; + [[nodiscard]] inline bool operator<(const self& rhs) const noexcept { return compare(rhs) == -1; } + [[nodiscard]] inline bool operator<=(const self& rhs) const noexcept{ return compare(rhs) != 1; } + [[nodiscard]] inline bool operator>(const self& rhs) const noexcept { return compare(rhs) == 1; } + [[nodiscard]] inline bool operator>=(const self& rhs) const noexcept { return compare(rhs) != -1; } + // comparison operators: - [[nodiscard]] bool operator==(const self& rhs) const noexcept; - [[nodiscard]] bool operator!=(const self& rhs) const noexcept; + [[nodiscard]] inline bool operator==(const self& rhs) const noexcept { return compare(rhs) == 0; } + [[nodiscard]] inline bool operator!=(const self& rhs) const noexcept { return compare(rhs) != 0; } + + [[nodiscard]] int64_t compare(const self& rhs) const noexcept; /// Clear any values. void clear() noexcept; @@ -62,7 +67,7 @@ class version { /// compare the string value of this to rhs. Attempt to use semver rules. /// @param rhs The version to compare to. /// @return the result of the comparison: eq, lt, gt. - [[nodiscard]] static std::strong_ordering raw_compare(std::string_view l_in, std::string_view r_in) noexcept; + [[nodiscard]] static int64_t raw_compare(std::string_view l_in, std::string_view r_in) noexcept; /// Load this version from a string. Attempts to parse and store as semver in the process. Either way, s is stored as m_raw. /// @param s The string to store. diff --git a/project/antler/project/version_compare.hpp b/project/antler/project/version_compare.hpp index 69645c3..02bd595 100644 --- a/project/antler/project/version_compare.hpp +++ b/project/antler/project/version_compare.hpp @@ -5,13 +5,12 @@ #include #include -#include namespace antler::project { /// compare lhs and rhs by splitting on '.' and comparing results. This is a simple compare and does not consider semver or any other precedence. /// @return Follows standard comparison. -std::strong_ordering raw_compare(std::string_view lhs, std::string_view rhs) noexcept; +int64_t raw_compare(std::string_view lhs, std::string_view rhs) noexcept; } // namespace antler::project diff --git a/project/antler/system/exec.hpp b/project/antler/system/exec.hpp index 3340164..5760545 100644 --- a/project/antler/system/exec.hpp +++ b/project/antler/system/exec.hpp @@ -31,4 +31,4 @@ struct result { /// @return The result struct containing the integer result and captured stdout and stderr streams. result exec(std::string_view cmd) noexcept; -} // namespace antler::system +} // namespace antler::system \ No newline at end of file diff --git a/project/antler/system/version.hpp.in b/project/antler/system/version.hpp.in new file mode 100644 index 0000000..06bc7af --- /dev/null +++ b/project/antler/system/version.hpp.in @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +namespace antler::system { + struct version { + constexpr static inline uint32_t major() { return @PROJECT_VERSION_MAJOR@; } + constexpr static inline uint32_t minor() { return @PROJECT_VERSION_MINOR@; } + constexpr static inline uint32_t patch() { return @PROJECT_VERSION_PATCH@; } + constexpr static inline std::string_view tweak() { return "@PROJECT_VERSION_tweak@"; } + constexpr static inline std::string_view full() { return "@PROJECT_VERSION@"; } + }; +} \ No newline at end of file diff --git a/project/src/location.cpp b/project/src/location.cpp index 23f9603..218af46 100644 --- a/project/src/location.cpp +++ b/project/src/location.cpp @@ -11,36 +11,51 @@ namespace antler::project::location { +// TODO replace later with string_view.ends_with() and string_view.starts_with() +static inline bool ends_with(std::string_view src, std::string_view comp) { + const std::size_t sz = comp.size(); + if (src.size() < sz) + return false; + + return memcmp(&src[0] + src.size()-sz, &comp[0], sz) == 0; +} + +static inline bool starts_with(std::string_view src, std::string_view comp) { + const std::size_t sz = comp.size(); + if (src.size() < sz) + return false; + + return memcmp(&src[0], &comp[0], sz) == 0; +} + bool is_archive(std::string_view s) { - return - s.ends_with(".tar.gz") - || s.ends_with(".tgz") - || s.ends_with(".tar.bz2") - || s.ends_with(".tar.xz") - || s.ends_with(".tar.zst") - ; + return ends_with(s, ".tar.gz") || + ends_with(s, ".tgz") || + ends_with(s, ".tar.bz2") || + ends_with(s, ".tar.xz") || + ends_with(s, ".tar.zst"); } bool is_github_archive(std::string_view s) { - return s.starts_with("https://github.com") && is_archive(s); + return starts_with(s, "https://github.com") && is_archive(s); } bool is_github_repo(std::string_view s) { - return s.starts_with("https://github.com") && !is_archive(s); + return starts_with(s, "https://github.com") && !is_archive(s); } bool is_local_file(std::string_view s) { - if (s.starts_with("https://github.com")) + if (starts_with(s, "https://github.com")) return false; - if (s.starts_with("file://")) + if (starts_with(s, "file://")) s = s.substr(7); std::error_code sec; @@ -50,7 +65,7 @@ bool is_local_file(std::string_view s) { bool is_org_repo_shorthand(std::string_view s) { - std::vector splits; + std::vector splits; boost::split(splits, s, [](const char c) { return c == '/'; }); if (splits.size() != 2) return false; diff --git a/project/src/project-print.cpp b/project/src/project-print.cpp index e50999e..13827b6 100644 --- a/project/src/project-print.cpp +++ b/project/src/project-print.cpp @@ -1,6 +1,7 @@ /// @copyright See `LICENSE` in the root directory of this project. #include +#include #include #pragma GCC diagnostic push @@ -179,17 +180,16 @@ void project::print(std::ostream& os) const noexcept { // Add a header. os << "# `" << (!m_name.empty() ? m_name : "UNNAMED") << "` project file.\n" - << "# generated by antler-pack v" - << "TBD" - << "\n"; + << "# generated by antler-proj v" + << system::version::full() << std::endl; // If we know the source file name, then add it to the header. if (!m_path.empty()) - os << "# source file: " << m_path.string() << "\n"; + os << "# source: " << m_path.string() << std::endl; os << "#\n" - << "# This file was auto-generated. Be aware antler-pack may discard added comments.\n" + << "# This file was auto-generated. Be aware antler-proj may discard added comments.\n" << "\n\n"; os << tree; } diff --git a/project/src/project.cpp b/project/src/project.cpp index 0aadf73..d05f7b2 100644 --- a/project/src/project.cpp +++ b/project/src/project.cpp @@ -12,7 +12,6 @@ namespace antler::project { //--- constructors/destrructor ------------------------------------------------------------------------------------------ -project::project() = default; /* project::project(const char* filename) { parse(filename); @@ -191,7 +190,8 @@ bool project::sync(std::ostream& es) noexcept { try { // Open the file. - std::ofstream out(m_path); + + std::ofstream out(m_path / project::manifest_name); if (!out.is_open()) { es << "Problem opening " << m_path << "\n"; return false; diff --git a/project/src/semver.cpp b/project/src/semver.cpp index 1a5599d..4749445 100644 --- a/project/src/semver.cpp +++ b/project/src/semver.cpp @@ -25,31 +25,20 @@ semver::semver(value_type x, value_type y, value_type z, std::string_view pre_re //--- operators --------------------------------------------------------------------------------------------------------- - -std::strong_ordering semver::operator<=>(const self& rhs) const noexcept { - //auto semver::operator<=>(const self& rhs) const noexcept { - // Compare x.y.z - for (size_t i = 0; i < m_xyz.size(); ++i) { - if (auto cmp = m_xyz[i] <=> rhs.m_xyz[i] ; cmp != 0) - return cmp; +int64_t semver::compare(const semver& rhs) const noexcept { + for (size_t i = 0; i < m_xyz.size(); ++i ) { + if (m_xyz[i] < rhs.m_xyz[i]) + return -1; + else if (m_xyz[i] > rhs.m_xyz[i]) + return 1; } - // x.y.z are the same, so compare pre-release and build. - if (auto cmp = compare_p_rule12(m_pre, rhs.m_pre) ; cmp != 0) - return cmp; + if (auto cmp = compare_p_rule12(m_pre, rhs.m_pre); cmp !=0 ) + return cmp; + return compare_b_rule12(m_build, rhs.m_build); } - -bool semver::operator==(const self& rhs) const noexcept { - return operator<=>(rhs) == 0; -} - - -bool semver::operator!=(const self& rhs) const noexcept { - return operator<=>(rhs) != 0; -} - //--- alphabetic -------------------------------------------------------------------------------------------------------- void semver::clear() noexcept { @@ -59,31 +48,31 @@ void semver::clear() noexcept { } -std::strong_ordering semver::compare_b_rule12(std::string_view lhs, std::string_view rhs) noexcept { +int64_t semver::compare_b_rule12(std::string_view lhs, std::string_view rhs) noexcept { // If either is empty, this is a quick comparison. if (lhs.empty()) - return (rhs.empty() ? std::strong_ordering::equal : std::strong_ordering::less); + return (rhs.empty() ? 0 : -1); if (rhs.empty()) - return std::strong_ordering::greater; + return 1; return compare_pb_rule12(lhs, rhs); } -std::strong_ordering semver::compare_p_rule12(std::string_view lhs, std::string_view rhs) noexcept { +int64_t semver::compare_p_rule12(std::string_view lhs, std::string_view rhs) noexcept { // If either is empty, this is a quick comparison. if (lhs.empty()) - return (rhs.empty() ? std::strong_ordering::equal : std::strong_ordering::greater); + return (rhs.empty() ? 0 : 1); if (rhs.empty()) - return std::strong_ordering::less; + return -1; return compare_pb_rule12(lhs, rhs); } -std::strong_ordering semver::compare_pb_rule12(std::string_view lhs, std::string_view rhs) noexcept { +int64_t semver::compare_pb_rule12(std::string_view lhs, std::string_view rhs) noexcept { // Requirements here: // https://semver.org/spec/v2.0.0-rc.1.html#spec-item-12 @@ -91,10 +80,14 @@ std::strong_ordering semver::compare_pb_rule12(std::string_view lhs, std::string // https://github.com/semver/semver/blob/v2.0.0/semver.md // Split on '.' - std::vector l; + // std::vector is a limitation of boost, + // TODO in next release of CDT/ANTLER change this back to std::vector with a sufficient version of boost + std::vector l; boost::split(l, lhs, boost::is_any_of(".")); - std::vector r; + // std::vector is a limitation of boost, + // TODO in next release of CDT/ANTLER change this back to std::vector with a sufficient version of boost + std::vector r; boost::split(r, rhs, boost::is_any_of(".")); // Compare the splits. @@ -109,24 +102,26 @@ std::strong_ordering semver::compare_pb_rule12(std::string_view lhs, std::string if (left != std::string_view::npos) { // Left is NOT numbers only. if (right != std::string_view::npos) { // Also Right is NOT numbers only. // Simple string compare works here. - return l[i] <=> r[i]; + return l[i] < r[i] ? -1 : l[i] > r[i] ? 1 : 0; } // Left has letters, right is numbers. So right is of higer magnitude. - return std::strong_ordering::less; + return -1; } // Left is a number, if right is NOT a number, then left has greater magnitude. if (right != std::string_view::npos) - return std::strong_ordering::greater; + return 1; // Both are numbers, convert and compare. int lnum = 0; [[maybe_unused]] auto discard1 = string::from(l[i], lnum); // ignore return code. If it's a failure, we will just use the zero value. int rnum = 0; [[maybe_unused]] auto discard2 = string::from(r[i], rnum); - if (auto cmp = lnum <=> rnum; cmp != 0) - return cmp; + if ( lnum < rnum ) + return -1; + else if ( lnum > rnum ) + return 1; } // So far, all the splits are equal. Compare the split size. - return l.size() <=> r.size(); + return l.size() < r.size() ? -1 : l.size() > r.size() ? 1 : 0; } @@ -165,7 +160,7 @@ std::optional semver::parse(std::string_view s) noexcept { // Split x.y.z apart, validate it as well. - std::vector splits; + std::vector splits; boost::split(splits, s, boost::is_any_of(".")); if (splits.empty() || (splits.size() > 3)) return {}; diff --git a/project/src/version.cpp b/project/src/version.cpp index 23898f3..b59e31f 100644 --- a/project/src/version.cpp +++ b/project/src/version.cpp @@ -14,9 +14,6 @@ namespace antler::project { //--- constructors/destruct ------------------------------------------------------------------------------------------ -version::version() = default; - - version::version(std::string_view ver) { load(ver); } @@ -59,23 +56,12 @@ version& version::operator=(const self& rhs) { } -std::strong_ordering version::operator<=>(const self& rhs) const noexcept { +int64_t version::compare(const version& rhs) const noexcept { if (is_semver() && rhs.is_semver()) - return *m_semver <=> *rhs.m_semver; + return m_semver->compare(*rhs.m_semver); return raw_compare(m_raw, rhs.m_raw); } - -bool version::operator==(const self& rhs) const noexcept { - return operator<=>(rhs) == 0; -} - - -bool version::operator!=(const self& rhs) const noexcept { - return operator<=>(rhs) != 0; -} - - version::operator semver() const noexcept { if (m_semver) return *m_semver; @@ -127,14 +113,14 @@ std::string_view version::raw() const noexcept { } -std::strong_ordering version::raw_compare(std::string_view l_in, std::string_view r_in) noexcept { +int64_t version::raw_compare(std::string_view l_in, std::string_view r_in) noexcept { if (l_in == r_in) - return std::strong_ordering::equal; + return 0; - std::vector l; + std::vector l; boost::split(l, l_in, boost::is_any_of(".,-+;")); - std::vector r; + std::vector r; boost::split(r, r_in, boost::is_any_of(".,-+;")); for (size_t i = 0; i < std::min(l.size(), r.size()); ++i) { @@ -172,25 +158,27 @@ std::strong_ordering version::raw_compare(std::string_view l_in, std::string_vie } // If the numbers differ, return the difference between them. - if (auto cmp = lnum <=> rnum; cmp != 0) - return cmp; + if (lnum < rnum) + return -1; + else if (lnum > rnum) + return 1; // Otherwise, return the difference in the remaining values. - return lremain <=> rremain; + return lremain.compare(rremain); } // Convert into ints and compare (or do a simple string compare). if (int lnum = 0, rnum = 0; string::from(l[i], lnum) && string::from(r[i], rnum)) { - return lnum <=> rnum; + return lnum < rnum ? -1 : lnum > rnum ? 1 : 0; } // Nope, STILL can't convert to JUST a number. Just do a raw string compare. - return l[i] <=> r[i]; + return l[i].compare(r[i]); } // Thus far, all the splits are equal, so just compare the number of splits. // Example: `a.b.c <=> a.b.c.d` finds the first 3 splits equal, so we just compare the split count: `3 <=> 4`. - return l.size() <=> r.size(); + return l.size() < r.size() ? -1 : l.size() > r.size() ? 1 : 0; } diff --git a/project/src/version_compare.cpp b/project/src/version_compare.cpp index 7bc1b56..11475a0 100644 --- a/project/src/version_compare.cpp +++ b/project/src/version_compare.cpp @@ -8,15 +8,15 @@ namespace antler::project { -std::strong_ordering raw_compare(std::string_view lhs, std::string_view rhs) noexcept { +int64_t raw_compare(std::string_view lhs, std::string_view rhs) noexcept { if (lhs == rhs) - return std::strong_ordering::equivalent; + return 0; - std::vector l; + std::vector l; boost::split(l, lhs, boost::is_any_of(".,-;+")); - std::vector r; + std::vector r; boost::split(r, rhs, boost::is_any_of(".,-;+")); for (size_t i = 0; i < std::min(l.size(), r.size()); ++i) { @@ -55,33 +55,33 @@ std::strong_ordering raw_compare(std::string_view lhs, std::string_view rhs) noe if (lnum != rnum) { if (lnum < rnum) - return std::strong_ordering::less; - return std::strong_ordering::greater; + return -1; + return 1; } auto temp = lremain.compare(rremain); if (temp < 0) - return std::strong_ordering::less; - return std::strong_ordering::greater; + return -1; + return 1; } if (!string::from(l[i], lnum) || !string::from(r[i], rnum)) { // Nope, STILL can't convert to JUST a number. Just do a raw string compare. auto temp = l[i].compare(r[i]); if (temp < 0) - return std::strong_ordering::less; - return std::strong_ordering::greater; + return -1; + return 1; } if (lnum < rnum) - return std::strong_ordering::less; - return std::strong_ordering::greater; + return -1; + return 1; } if (l.size() < r.size()) - return std::strong_ordering::less; + return -1; if (l.size() > r.size()) - return std::strong_ordering::greater; - return std::strong_ordering::equal; + return 1; + return 0; } diff --git a/project/src/version_constraint.cpp b/project/src/version_constraint.cpp index f31e5cc..0b7aa19 100644 --- a/project/src/version_constraint.cpp +++ b/project/src/version_constraint.cpp @@ -50,7 +50,7 @@ inline std::string_view trim(std::string_view s) { auto last = std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {return !std::isspace(ch);}); // Convert the reverse iter last to the forward iterator containing end using base(). // See: https://en.cppreference.com/w/cpp/iterator/reverse_iterator/base - return std::string_view{first, last.base()}; + return std::string_view{first, s.size() - 1 - (last - s.rbegin())}; } @@ -105,16 +105,16 @@ void version_constraint::load(std::string_view sin, std::ostream& os) { return; // Start by splitting on '|' - std::vector splits; + std::vector splits; boost::split(splits, m_raw, boost::is_any_of("|;")); for (auto split : splits) { // Now split on ',' - std::vector element; + std::vector element; boost::split(element, split, boost::is_any_of(",")); if (element.size() == 1) { // If there's only one constraint, we need to decide if it's an upper bound, a lower bound, or unique. auto trimmed_el = trim(element[0]); - std::vector el_list; + std::vector el_list; boost::split(el_list, trimmed_el, boost::is_any_of(" ")); if (el_list.size() == 1) { @@ -138,7 +138,11 @@ void version_constraint::load(std::string_view sin, std::ostream& os) { else if (op_str == ">=") m_constraints.emplace_back(constraint{ version(ver_str), max_version, bounds_inclusivity::both }); else { - os << __FILE__ << ":" << __LINE__ << " Failed to decode version_constraint: \"" << sin << "\" Bad op: \"" << el_list << "\"\n"; + os << __FILE__ << ":" << __LINE__ << " Failed to decode version_constraint: \"" << sin << "\" Bad op: \""; + for (const auto& el : el_list) { + os << el; + } + os << std::endl; clear(); return; } @@ -146,20 +150,28 @@ void version_constraint::load(std::string_view sin, std::ostream& os) { continue; } - os << __FILE__ << ":" << __LINE__ << " Failed to decode version_constraint: \"" << sin << "\" Too many or too few elements in: \"" << el_list << "\"\n"; + os << __FILE__ << ":" << __LINE__ << " Failed to decode version_constraint: \"" << sin << "\" Too many or too few elements in: \""; + for (const auto& el : el_list) { + os << el; + } + os << std::endl; clear(); return; } if (element.size() == 2) { - std::vector lower_list; + std::vector lower_list; boost::split(lower_list, trim(element[0]), boost::is_any_of(" ")); - std::vector upper_list; + std::vector upper_list; boost::split(upper_list, trim(element[1]), boost::is_any_of(" ")); if (lower_list.size() != 2 || upper_list.size() != 2) { os << __FILE__ << ":" << __LINE__ << " Failed to decode version_constraint: \"" << sin - << "\" Too many or too few elements in: \"" << element << "\"\n"; + << "\" Too many or too few elements in: \""; + for (const auto& e : element) { + os << e; + } + os << std::endl; clear(); return; } @@ -178,8 +190,11 @@ void version_constraint::load(std::string_view sin, std::ostream& os) { continue; } - os << __FILE__ << ":" << __LINE__ << " Failed to decode version_constraint: \"" << sin << "\" Bad upper limit operator in: \"" - << element << "\"\n"; + os << __FILE__ << ":" << __LINE__ << " Failed to decode version_constraint: \"" << sin << "\" Bad upper limit operator in: \""; + for (const auto& e : element) { + os << e; + } + os << std::endl; clear(); return; } @@ -194,20 +209,30 @@ void version_constraint::load(std::string_view sin, std::ostream& os) { continue; } - os << __FILE__ << ":" << __LINE__ << " Failed to decode version_constraint: \"" << sin << "\" Bad upper limit operator in: \"" - << element << "\"\n"; + os << __FILE__ << ":" << __LINE__ << " Failed to decode version_constraint: \"" << sin << "\" Bad upper limit operator in: \""; + for (const auto& e : element) { + os << e; + } + os << std::endl; clear(); return; } - os << __FILE__ << ":" << __LINE__ << " Failed to decode version_constraint: \"" << sin << "\" Bad lower limit operator in: \"" - << element << "\"\n"; + os << __FILE__ << ":" << __LINE__ << " Failed to decode version_constraint: \"" << sin << "\" Bad lower limit operator in: \""; + for (const auto& e : element) { + os << e; + } + os << std::endl; clear(); return; } - os << __FILE__ << ":" << __LINE__ << " Failed to decode version_constraint: \"" << sin << "\" Too many elements in: \"" << element - << "\"\n"; + os << __FILE__ << ":" << __LINE__ << " Failed to decode version_constraint: \"" << sin << "\" Too many elements in: \""; + + for (const auto& e : element) { + os << e; + } + os << std::endl; clear(); return; } From 4531e78e808f7449e6b6c5609b5c90fb93fb5725 Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Tue, 28 Feb 2023 23:42:40 -0500 Subject: [PATCH 02/14] some more fixes --- aproj/CMakeLists.txt | 5 +- aproj/add_to.hpp | 108 +++++++++++++++++++++++++++++ aproj/aproj-common.hpp | 3 +- aproj/aproj.cpp | 43 ++++++++++-- project/CMakeLists.txt | 7 ++ project/antler/project/project.hpp | 1 + project/src/location.cpp | 64 ++++++++++------- project/src/project.cpp | 12 +--- 8 files changed, 200 insertions(+), 43 deletions(-) create mode 100644 aproj/add_to.hpp diff --git a/aproj/CMakeLists.txt b/aproj/CMakeLists.txt index a0be976..4200e76 100644 --- a/aproj/CMakeLists.txt +++ b/aproj/CMakeLists.txt @@ -1,8 +1,10 @@ # @copyright See `LICENSE` in the root directory of this project. +include(FindPkgConfig) # Boost find_package( Boost REQUIRED filesystem CONFIG) +pkg_check_modules(CURL libcurl REQUIRED) # # These files are generally built in a common way, so here are some functions to simplify the process. @@ -17,9 +19,10 @@ function(add_aproj target_name files) add_executable(${target_name} ${files}) set_property(TARGET ${target_name} PROPERTY CXX_STANDARD 20) - target_link_libraries(${target_name} PUBLIC antler-project ${extra_libs}) + target_link_libraries(${target_name} PUBLIC antler-project ${extra_libs} ${CURL_LIBRARIES}) target_include_directories(${target_name} PRIVATE . ) target_include_directories(${target_name} SYSTEM PRIVATE ../3p_include ) + target_include_directories(${target_name} PRIVATE ${CURL_INCLUDE_DIRS}) install(FILES $ COMPONENT Runtime DESTINATION .) endfunction() diff --git a/aproj/add_to.hpp b/aproj/add_to.hpp new file mode 100644 index 0000000..ab61c0b --- /dev/null +++ b/aproj/add_to.hpp @@ -0,0 +1,108 @@ +#pragma once + +#include +#include + +#include + +#include + +enum class add_to_ty { + app, + dep, + lib, + test +}; + +struct add_to_project { + bool add_dependency(antler::project::project& proj) { + if (!obj_name.empty() && !dep_name.empty() && antler::project::dependency::validate_location(location, tag, release, hash)) { + // Get the object to operate on. + auto obj_vec = proj.object(obj_name); + + // If it doesn't exist, none of the existing values can be correct, so alert and jump straigt to individual queries. + if (obj_vec.empty()) { + std::cerr << obj_name << " does not exist in project.\n"; + return false; + } + else { + // We have values, so query the user if they want to apply. + std::cout + << '\n' + << "Object name (to update): " << obj_name << '\n' + << "Dependency name: " << dep_name << '\n' + << "Dependency location: " << location << '\n' + << "tag/commit hash: " << tag << '\n' + << "release version: " << release << '\n' + << "SHA256 hash: " << hash << '\n' + << std::endl; + + // Get object here and warn user if dep_name already exists. + auto obj = obj_vec[0]; + if (!dep_name.empty() && obj.dependency_exists(dep_name)) { + std::cerr << dep_name << " already exists for " << obj_name << " in project.\n"; + return false; + } + } + + antler::project::dependency dep; + dep.set(dep_name, location, tag, release, hash); + obj_vec[0].upsert_dependency(std::move(dep)); + proj.upsert(std::move(obj_vec[0])); + } + + proj.sync(); + return true; + } + + inline add_to_project(CLI::App& app) { + path = std::filesystem::current_path().string(); + subcommand = app.add_subcommand("add", "Add an app, dependency, library or test to your project."); + subcommand->add_option("-p, --path", path, "This is the root path to create the project in."); + + dep_subcommand = subcommand->add_subcommand("dep", "Add a new dependency to the project."); + dep_subcommand->add_option("-o, obj", obj_name, "The name of the object to attach dependency to.")->required(); + dep_subcommand->add_option("-d, dep", dep_name, "The name of the dependency.")->required(); + dep_subcommand->add_option("-l, loc", location, "Location of the dependency.")->required(); + } + + int32_t exec() { + try { + auto p = std::filesystem::canonical(std::filesystem::path(path)); + if (!antler::project::project::update_path(p)) { + std::cerr << "path either did not exist or no `project.yaml` file could be found." << std::endl; + return -1; + } + std::optional proj = antler::project::project::parse(p); + + if (!proj) { + return -1; + } + + if (*dep_subcommand) { + add_dependency(*proj); + } else { + std::cerr << "Need to supply either dep/app/lib/test after `add`" << std::endl; + return -1; + } + + proj->sync(); + + } catch (...) { + std::cerr << "Path <" << path << "> does not exist" << std::endl; + } + return 0; + } + + CLI::App* subcommand; + CLI::App* app_subcommand; + CLI::App* dep_subcommand; + add_to_ty ty; + std::string path; + std::string obj_name; + std::string dep_name; + std::string location; + std::string tag; + std::string release; + std::string hash; +}; \ No newline at end of file diff --git a/aproj/aproj-common.hpp b/aproj/aproj-common.hpp index b86fb37..80058f3 100644 --- a/aproj/aproj-common.hpp +++ b/aproj/aproj-common.hpp @@ -191,7 +191,8 @@ inline void dump_obj_deps(const T& obj_list, std::ostream& os = std::cout) { /// @param str Reference to the value to set. Comes in as default value. /// @param validator A function to validate str with. /// @param allow_empty Set to true to allow the user to clear the value. -inline void get_valid_string(std::string_view friendly_name, std::string& str, std::function validator, bool allow_empty = false) noexcept { +template +inline void get_valid_string(std::string_view friendly_name, std::string& str, F&& validator, bool allow_empty = false) noexcept { // Loop until return. for (;;) { // Start with the text to display to the user. diff --git a/aproj/aproj.cpp b/aproj/aproj.cpp index ae06084..2c84df8 100644 --- a/aproj/aproj.cpp +++ b/aproj/aproj.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include @@ -17,23 +18,55 @@ #include +#include "add_to.hpp" #include "init.hpp" +template +struct run_on { + run_on(CLI::App& app) + : tup(Ts{app}...) { + } + + template + constexpr inline int exec() { + if constexpr (I == sizeof...(Ts)) { + return -1; + } else { + auto& v = std::get(tup); + if (*v.subcommand) { + return v.exec(); + } else { + return exec(); + } + } + + } + std::tuple tup; +}; + int main(int argc, char** argv) { // using this as we will alias antler-proj to cdt-proj for the // next release of CDT. const auto app_name = std::filesystem::path(argv[0]).filename().string(); CLI::App app{app_name}; - init_project init = {app}; + + //run_on runner{app}; + + add_to_project add_to = {app}; CLI11_PARSE(app, argc, argv); - if (init.subcommand) { - return init.exec(); - } + if (*add_to.subcommand) return add_to.exec(); + //return runner.exec(); + + //if (*init.subcommand) { + // return init.exec(); + //} else if (*add_to.subcommand) { + // return add_to.exec(); + //} - std::cout << "Param " << init.path << std::endl; + //std::cout << "Param " << init.path << std::endl; return 0; } diff --git a/project/CMakeLists.txt b/project/CMakeLists.txt index 575c5a0..75bdfba 100644 --- a/project/CMakeLists.txt +++ b/project/CMakeLists.txt @@ -2,8 +2,11 @@ set( target_name antler-project ) +include(FindPkgConfig) + # Boost find_package( Boost REQUIRED ) +pkg_check_modules( CURL libcurl REQUIRED ) # I know. But it's appropriate to glob for now. file(GLOB_RECURSE src_cpp ./src/*.cpp) @@ -27,6 +30,10 @@ target_include_directories(${target_name} SYSTEM PRIVATE target_include_directories(${target_name} PUBLIC . ${CMAKE_CURRENT_BINARY_DIR} + ${CURL_INCLUDE_DIRS} +) +target_link_libraries(${target_name} PUBLIC + ${CURL_LIBRARIES} ) # Install commands diff --git a/project/antler/project/project.hpp b/project/antler/project/project.hpp index 0a4a6ff..d0927a2 100644 --- a/project/antler/project/project.hpp +++ b/project/antler/project/project.hpp @@ -29,6 +29,7 @@ class project { // constructors project() = default; + project(const std::filesystem::path&); project(std::string_view path, std::string_view name, std::string_view version_raw) : m_path(path), m_name(name), m_ver(version_raw) {} diff --git a/project/src/location.cpp b/project/src/location.cpp index 218af46..267c32d 100644 --- a/project/src/location.cpp +++ b/project/src/location.cpp @@ -4,13 +4,46 @@ #include #include +#include #include // boost::split() #include +#include + namespace antler::project::location { +// object to encapsulate github api +struct github { + static size_t write_data(void* ptr, size_t sz, size_t nm, void* s) { + reinterpret_cast(s)->append(reinterpret_cast(ptr), sz*nm); + return sz*nm; + } + inline github() { + CURL* curl; + curl_global_init(CURL_GLOBAL_ALL); + curl = curl_easy_init(); + if (curl) { + curl_easy_setopt(curl, CURLOPT_URL, "https://ghcr.io/token?service=registry.docker.io&scope=repository:AntelopeIO/experimental-binaries:pull"); + //curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &github::write_data); + std::string buff; + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buff); + auto res = curl_easy_perform(curl); + if (res) { + std::cerr << curl_easy_strerror(res) << std::endl; + throw std::runtime_error("internal curl failure"); + } + curl_easy_cleanup(curl); + bearer_token = buff.substr(buff.find(":\"")+2); + bearer_token = bearer_token.substr(0, bearer_token.size()-3); + } + } + + std::string bearer_token; +}; + // TODO replace later with string_view.ends_with() and string_view.starts_with() static inline bool ends_with(std::string_view src, std::string_view comp) { const std::size_t sz = comp.size(); @@ -29,7 +62,6 @@ static inline bool starts_with(std::string_view src, std::string_view comp) { } bool is_archive(std::string_view s) { - return ends_with(s, ".tar.gz") || ends_with(s, ".tgz") || ends_with(s, ".tar.bz2") || @@ -37,22 +69,15 @@ bool is_archive(std::string_view s) { ends_with(s, ".tar.zst"); } +inline bool is_github(std::string_view s) { return starts_with(s, "https://github.com"); } -bool is_github_archive(std::string_view s) { - - return starts_with(s, "https://github.com") && is_archive(s); -} - - -bool is_github_repo(std::string_view s) { - - return starts_with(s, "https://github.com") && !is_archive(s); -} +bool is_github_archive(std::string_view s) { return is_github(s) && is_archive(s); } +bool is_github_repo(std::string_view s) { return is_github(s) && !is_archive(s); } bool is_local_file(std::string_view s) { - if (starts_with(s, "https://github.com")) + if (is_github(s)) return false; if (starts_with(s, "file://")) @@ -72,21 +97,10 @@ bool is_org_repo_shorthand(std::string_view s) { // Set ss to the command string. std::ostringstream ss; - ss << "gh repo list " << splits[0] << " --json name"; + ss << "gh repo view " << s; // Call the command, test the result. - const auto result = system::exec(ss.str()); - if (!result) { - std::cerr << result.output << "\n"; - return false; - } - - // Reset/clear the stream and write the search string into it. - ss.str(""); // Reset the stream. - ss << "\"name\":\"" << splits[1] << "\""; - - // Search for the repo. - return result.output.find(ss.str()) != std::string::npos; + return system::exec(ss.str()).return_code != 0; } diff --git a/project/src/project.cpp b/project/src/project.cpp index d05f7b2..221f51f 100644 --- a/project/src/project.cpp +++ b/project/src/project.cpp @@ -12,16 +12,6 @@ namespace antler::project { //--- constructors/destrructor ------------------------------------------------------------------------------------------ -/* -project::project(const char* filename) { - parse(filename); -} - - -project::project(const std::filesystem::path& filename) { - parse(filename); -} -*/ //--- alphabetic -------------------------------------------------------------------------------------------------------- @@ -191,7 +181,7 @@ bool project::sync(std::ostream& es) noexcept { // Open the file. - std::ofstream out(m_path / project::manifest_name); + std::ofstream out(m_path); if (!out.is_open()) { es << "Problem opening " << m_path << "\n"; return false; From ae4006c681ed4393a2dbc0ca015c48821a0b4caa Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Wed, 1 Mar 2023 15:18:14 -0500 Subject: [PATCH 03/14] some more updates --- CMakeLists.txt | 13 -- aproj/CMakeLists.txt | 29 ++-- aproj/add_to.hpp | 229 +++++++++++++++++++---------- aproj/aproj-add-app.cpp | 87 ----------- aproj/aproj-add-dep.cpp | 155 ------------------- aproj/aproj-add-lib.cpp | 91 ------------ aproj/aproj-add-test.cpp | 90 ------------ aproj/aproj-common.hpp | 8 +- aproj/aproj-populate.cpp | 43 ------ aproj/aproj-prefix.hpp | 6 - aproj/aproj-rm-dep.cpp | 218 --------------------------- aproj/aproj.cpp | 174 +++------------------- aproj/common.hpp | 18 +++ aproj/init.hpp | 42 +++--- aproj/populate.hpp | 35 +++++ aproj/remove_from.hpp | 126 ++++++++++++++++ project/antler/project/object.hpp | 15 +- project/antler/project/project.hpp | 5 +- project/src/object.cpp | 9 +- project/src/project-parse.cpp | 6 +- project/src/project-print.cpp | 7 +- project/src/project.cpp | 28 ++-- 22 files changed, 431 insertions(+), 1003 deletions(-) delete mode 100644 aproj/aproj-add-app.cpp delete mode 100644 aproj/aproj-add-dep.cpp delete mode 100644 aproj/aproj-add-lib.cpp delete mode 100644 aproj/aproj-add-test.cpp delete mode 100644 aproj/aproj-populate.cpp delete mode 100644 aproj/aproj-prefix.hpp delete mode 100644 aproj/aproj-rm-dep.cpp create mode 100644 aproj/common.hpp create mode 100644 aproj/populate.hpp create mode 100644 aproj/remove_from.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 029f488..81da7f3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,19 +6,6 @@ project("ANTLER Project Tools " VERSION 1.0.0) include( ./common.cmake REQUIRED ) -if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "11") - message(FATAL_ERROR "Insufficient clang version") - endif() -elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "10") - #message(FATAL_ERROR "Insufficient gcc version") - endif() -else() - message(WARNING "This build requires a C++20 compliant compiler.") -endif() - - add_subdirectory( project ) add_subdirectory( aproj ) diff --git a/aproj/CMakeLists.txt b/aproj/CMakeLists.txt index 4200e76..eb29c09 100644 --- a/aproj/CMakeLists.txt +++ b/aproj/CMakeLists.txt @@ -18,7 +18,7 @@ function(add_aproj target_name files) endif() add_executable(${target_name} ${files}) - set_property(TARGET ${target_name} PROPERTY CXX_STANDARD 20) + set_property(TARGET ${target_name} PROPERTY CXX_STANDARD 17) target_link_libraries(${target_name} PUBLIC antler-project ${extra_libs} ${CURL_LIBRARIES}) target_include_directories(${target_name} PRIVATE . ) target_include_directories(${target_name} SYSTEM PRIVATE ../3p_include ) @@ -29,13 +29,20 @@ endfunction() file(GLOB src_cpp ./*.cpp) -foreach(file_name IN ITEMS ${src_cpp}) - if(${CMAKE_VERSION} VERSION_LESS "3.20") - get_filename_component(fn ${file_name} NAME) - else() - cmake_path(GET file_name FILENAME fn) - endif() - - string(REPLACE ".cpp" "" name ${fn}) - add_aproj("${name}" "${fn}") -endforeach() +#foreach(file_name IN ITEMS ${src_cpp}) +# if(${CMAKE_VERSION} VERSION_LESS "3.20") +# get_filename_component(fn ${file_name} NAME) +# else() +# cmake_path(GET file_name FILENAME fn) +# endif() +# +# string(REPLACE ".cpp" "" name ${fn}) +# add_aproj("${name}" "${fn}") +#endforeach() + +add_executable(antler-proj aproj.cpp) +set_property(TARGET antler-proj PROPERTY CXX_STANDARD 17) +target_link_libraries(antler-proj PUBLIC antler-project ${extra_libs} ${CURL_LIBRARIES}) +target_include_directories(antler-proj PRIVATE . ) +target_include_directories(antler-proj SYSTEM PRIVATE ../3p_include ) +target_include_directories(antler-proj PRIVATE ${CURL_INCLUDE_DIRS}) \ No newline at end of file diff --git a/aproj/add_to.hpp b/aproj/add_to.hpp index ab61c0b..3eefb3c 100644 --- a/aproj/add_to.hpp +++ b/aproj/add_to.hpp @@ -7,102 +7,169 @@ #include -enum class add_to_ty { - app, - dep, - lib, - test -}; - -struct add_to_project { - bool add_dependency(antler::project::project& proj) { - if (!obj_name.empty() && !dep_name.empty() && antler::project::dependency::validate_location(location, tag, release, hash)) { - // Get the object to operate on. - auto obj_vec = proj.object(obj_name); - - // If it doesn't exist, none of the existing values can be correct, so alert and jump straigt to individual queries. - if (obj_vec.empty()) { - std::cerr << obj_name << " does not exist in project.\n"; - return false; - } - else { - // We have values, so query the user if they want to apply. +#include "common.hpp" + +namespace antler { + struct add_to_project { + + template + bool add_obj(antler::project::project& proj) { + if (!obj_name.empty()) { std::cout << '\n' - << "Object name (to update): " << obj_name << '\n' - << "Dependency name: " << dep_name << '\n' - << "Dependency location: " << location << '\n' - << "tag/commit hash: " << tag << '\n' - << "release version: " << release << '\n' - << "SHA256 hash: " << hash << '\n' + << "name: " << obj_name << '\n' + << "language: " << lang << '\n' + << "options: " << options << '\n' << std::endl; - // Get object here and warn user if dep_name already exists. - auto obj = obj_vec[0]; - if (!dep_name.empty() && obj.dependency_exists(dep_name)) { - std::cerr << dep_name << " already exists for " << obj_name << " in project.\n"; + if (proj.object_exists(obj_name, Ty)) { + std::cerr << "Application " << obj_name << " already exists in project. Can't add.\n\n"; return false; } } - antler::project::dependency dep; - dep.set(dep_name, location, tag, release, hash); - obj_vec[0].upsert_dependency(std::move(dep)); - proj.upsert(std::move(obj_vec[0])); + auto obj = antler::project::object(Ty, obj_name, lang, options); + proj.upsert_app(std::move(obj)); + return true; } - proj.sync(); - return true; - } + inline bool add_app(antler::project::project& proj) { return add_obj(proj); } + inline bool add_lib(antler::project::project& proj) { return add_obj(proj); } - inline add_to_project(CLI::App& app) { - path = std::filesystem::current_path().string(); - subcommand = app.add_subcommand("add", "Add an app, dependency, library or test to your project."); - subcommand->add_option("-p, --path", path, "This is the root path to create the project in."); - - dep_subcommand = subcommand->add_subcommand("dep", "Add a new dependency to the project."); - dep_subcommand->add_option("-o, obj", obj_name, "The name of the object to attach dependency to.")->required(); - dep_subcommand->add_option("-d, dep", dep_name, "The name of the dependency.")->required(); - dep_subcommand->add_option("-l, loc", location, "Location of the dependency.")->required(); - } - - int32_t exec() { - try { - auto p = std::filesystem::canonical(std::filesystem::path(path)); - if (!antler::project::project::update_path(p)) { - std::cerr << "path either did not exist or no `project.yaml` file could be found." << std::endl; - return -1; - } - std::optional proj = antler::project::project::parse(p); + bool add_test(antler::project::project& proj) { + if (!obj_name.empty()) { + + // Print values. + std::cout + << '\n' + << "test name: " << obj_name << '\n' + << "command: " << cmd << '\n' + << '\n'; - if (!proj) { - return -1; + // Check to see if name is a TEST duplicate. + if (proj.object_exists(obj_name, antler::project::object::type_t::test)) { + // Enform user of the duplicate. + std::cerr << "Test " << obj_name << " already exists in project. Can't add.\n\n"; + } else { + // Check to see if name is otherwise a duplicate, warn if so. + if (proj.object_exists(obj_name)) + std::cerr << "WARNING: " << obj_name << " already exists in project as app and/or lib.\n\n"; + } + return true; } + return false; + } + + bool add_dependency(antler::project::project& proj) { + if (!obj_name.empty() && !dep_name.empty() && antler::project::dependency::validate_location(location, tag, release, hash)) { + // Get the object to operate on. + try { + auto& obj = proj.object(obj_name); + + // We have values, so query the user if they want to apply. + std::cout + << '\n' + << "Object name (to update): " << obj_name << '\n' + << "Dependency name: " << dep_name << '\n' + << "Dependency location: " << location << '\n' + << "tag/commit hash: " << tag << '\n' + << "release version: " << release << '\n' + << "SHA256 hash: " << hash << '\n' + << std::endl; - if (*dep_subcommand) { - add_dependency(*proj); - } else { - std::cerr << "Need to supply either dep/app/lib/test after `add`" << std::endl; - return -1; + // Get object here and warn user if dep_name already exists. + if (!dep_name.empty() && obj.dependency_exists(dep_name)) { + std::cerr << dep_name << " already exists for " << obj_name << " in project.\n"; + return false; + } + + antler::project::dependency dep; + dep.set(dep_name, location, tag, release, hash); + obj.upsert_dependency(std::move(dep)); + //proj.upsert(std::move(obj_vec[0])); + + } catch(...) { + std::cerr << "Object: " << obj_name << " does not exist." << std::endl; + } } + + return true; + } + + add_to_project(CLI::App& app) { + path = std::filesystem::current_path().string(); + subcommand = app.add_subcommand("add", "Add an app, dependency, library or test to your project."); + subcommand->add_option("-p, path", path, "This is the root path to create the project in."); + + app_subcommand = subcommand->add_subcommand("app", "Add a new app to your project."); + app_subcommand->add_option("-n, name", obj_name, "The name of the app to add.")->required(); + app_subcommand->add_option("-l, lang", lang, "Language this app will use.")->required(); + app_subcommand->add_option("-o, opts", options, "Options for the compiler for this app."); + + lib_subcommand = subcommand->add_subcommand("lib", "Add a new library to your project."); + lib_subcommand->add_option("-n, name", obj_name, "The name of the library to add.")->required(); + lib_subcommand->add_option("-l, lang", lang, "Language this library will use.")->required(); + lib_subcommand->add_option("-o, opts", options, "Options for the compiler for this library."); + + dep_subcommand = subcommand->add_subcommand("dep", "Add a new dependency to the project."); + dep_subcommand->add_option("-o, obj", obj_name, "The name of the object to attach dependency to.")->required(); + dep_subcommand->add_option("-d, dep", dep_name, "The name of the dependency.")->required(); + dep_subcommand->add_option("-l, loc", location, "Location of the dependency.")->required(); + dep_subcommand->add_option("-t, tag", tag, "Tag associated with the dependency."); + dep_subcommand->add_option("-r, release", release, "Release version of the depedency."); + dep_subcommand->add_option("--digest, hash", hash, "Hash of the dependency."); - proj->sync(); + /* TODO Add back after this release when we have the testing framework finished + test_subcommand = subcommand->add_subcommand("test", "Add a new test to the project."); + test_subcommand->add_option("-n, name", obj_name, "The name of the test to add.")->required(); + test_subcommand->add_option("-c, command", cmd, "The test command to execute."); + */ + } - } catch (...) { - std::cerr << "Path <" << path << "> does not exist" << std::endl; + int32_t exec() { + try { + auto proj = load_project(path); + + if (!proj) { + return -1; + } + + if (*app_subcommand) { + add_app(*proj); + } else if (*lib_subcommand) { + add_lib(*proj); + } else if (*dep_subcommand) { + add_dependency(*proj); + /* TODO Add back after this release when we have the testing framework finished + } else if (*test_subcommand) { + add_test(*proj); + */ + } else { + std::cerr << "Need to supply either dep/app/lib/test after `add`" << std::endl; + return -1; + } + + proj->sync(); + } catch (...) { + std::cerr << "Path <" << path << "> does not exist" << std::endl; + } + return 0; } - return 0; - } - - CLI::App* subcommand; - CLI::App* app_subcommand; - CLI::App* dep_subcommand; - add_to_ty ty; - std::string path; - std::string obj_name; - std::string dep_name; - std::string location; - std::string tag; - std::string release; - std::string hash; -}; \ No newline at end of file + + CLI::App* subcommand; + CLI::App* app_subcommand; + CLI::App* dep_subcommand; + CLI::App* lib_subcommand; + CLI::App* test_subcommand; + std::string path; + std::string obj_name; + std::string dep_name; + std::string location; + std::string tag; + std::string release; + std::string hash; + std::string lang; + std::string options; + std::string cmd; + }; +} // namespace antler \ No newline at end of file diff --git a/aproj/aproj-add-app.cpp b/aproj/aproj-add-app.cpp deleted file mode 100644 index d5d5671..0000000 --- a/aproj/aproj-add-app.cpp +++ /dev/null @@ -1,87 +0,0 @@ -/// @copyright See `LICENSE` in the root directory of this project. - -// aproj-add-app.cpp and aproj-add-lib.cpp are nearly identical while aproj-add-test.cpp has a few differences. If you change any -// of the three, make sure to keep the others similarly updated. - - -#include -#include -#include -#include - -#include - -#include - -int main(int argc, char** argv) { - - std::filesystem::path path; - std::string name; - antler::project::language lang = antler::project::language::none; - std::string opts; - bool interactive=false; - - CLI::App cli{}; - common_init(cli, argv[0], "Add an application entry to the project."); - - // Positional arguments: - cli.add_option("path", path, "This must be the path to `project.yaml` or the path containing it.")->required(); - cli.add_option("app_name", name, "The name of the application to add."); - cli.add_option("app_lang", lang, "The application's language (e.g. `cpp`, `java`, `c`, etc.)."); - cli.add_option("app_options", opts, "Options to pass to the compiler."); - - // Option flag - cli.add_flag("--interactive", interactive, "Force interactive mode."); - - // Parse - CLI11_PARSE(cli,argc,argv); - - - // Load the project or exit. - auto proj = load_project_or_exit(cli, path); - - - // Interactive mode? - interactive |= (lang == antler::project::language::none); - for (const auto loop=interactive; loop;) { - // Loop until the user says values are correct. - // Only query for correct if all the info is updated. - if (!name.empty() && lang != antler::project::language::none) { - - std::cout - << '\n' - << "app name: " << name << '\n' - << "language: " << lang << '\n' - << "options: " << opts << '\n' - << '\n'; - - if (proj.object_exists(name, antler::project::object::type_t::app)) { - std::cerr << "Application " << name << " already exists in project. Can't add.\n\n"; - } - else { - if (proj.object_exists(name)) - std::cerr << "WARNING: " << name << " already exists in project as lib and/or test.\n\n"; - - if (is_this_correct()) - break; - } - } - - get_name("application name", name); - get_language("project language", lang); - get_string("application options", opts, true); - } - - - // Sanity check and apply. - if (!name.empty() && proj.object_exists(name, antler::project::object::type_t::app)) - return cli.exit( CLI::Error("name", "app_name already exists in project.") ); - if (lang == antler::project::language::none) - return cli.exit( CLI::Error("name", "invalid language.") ); - - auto obj = antler::project::object(antler::project::object::app, name, lang, opts); - proj.upsert_app(std::move(obj)); - proj.sync(); - - return 0; -} diff --git a/aproj/aproj-add-dep.cpp b/aproj/aproj-add-dep.cpp deleted file mode 100644 index 6ed4f0e..0000000 --- a/aproj/aproj-add-dep.cpp +++ /dev/null @@ -1,155 +0,0 @@ -/// @copyright See `LICENSE` in the root directory of this project. - -#include -#include -#include -#include - -#include - -#include - - -int main(int argc, char** argv) { - - std::filesystem::path path; - std::string obj_name; - std::string dep_name; - std::string dep_loc; - std::string dep_tag; - std::string dep_rel; - std::string dep_hash; - bool interactive=false; - - - // CLI setup. - - CLI::App cli; - common_init(cli, argv[0], "Add a dependency."); - - // Positional arguments: - cli.add_option("path", path, "This must be the path to `project.yaml` or the path containing it.")->required(); - cli.add_option("obj_name", obj_name, "The name of the object to receive the dependency `dep_name`."); - cli.add_option("dep_name", dep_name, "The name of the dependency to add to `obj_name`."); - cli.add_option("location", dep_loc, "Dependency location url, path, or github organization/repo shortcut."); - - // Optional arguments: - auto tag_opt = cli.add_option("--tag", dep_tag, "The github tag or commit hash; only valid when LOCATION is a github repository."); - //auto commit_opt = cli.add_option("--commit", dep_commit, "The github tag or commit hash; only valid when LOCATION is a github repository."); - //auto branch_opt = cli.add_option("--branch", dep_branch, "The github tag or commit hash; only valid when LOCATION is a github repository."); - cli.add_option("--rel,--release", dep_rel, "A github release version.")->excludes(tag_opt); - cli.add_option("--hash", dep_hash, "SHA256 hash; only valid when LOCATION gets an archive (i.e. *.tar.gz or similar)."); - // Option flag - cli.add_flag("--interactive", interactive, "Force interactive mode."); - - // Parse - CLI11_PARSE(cli,argc,argv); - - - // Load the project or exit. - auto proj = load_project_or_exit(cli,path); - - - // Done with CLI, evaluate interactive mode. Assuming interactive mode if dependency name was empty. - interactive |= dep_name.empty(); // interactive mode should be const from this point forward. - for (const auto loop=interactive; loop;) { - - if (!obj_name.empty() && !dep_name.empty() && antler::project::dependency::validate_location(dep_loc, dep_tag, dep_rel, dep_hash)) { - // Get the object to operate on. - auto obj_vec = proj.object(obj_name); - - // If it doesn't exist, none of the existing values can be correct, so alert and jump straigt to individual queries. - if (obj_vec.empty()) - std::cerr << obj_name << " does not exist in project.\n"; - else { - - // We have values, so query the user if they want to apply. - std::cout - << '\n' - << "Object name (to update): " << obj_name << '\n' - << "Dependency name: " << dep_name << '\n' - << "Dependency location: " << dep_loc << '\n' - << "tag/commit hash: " << dep_tag << '\n' - << "release version: " << dep_rel << '\n' - << "SHA256 hash: " << dep_hash << '\n' - << '\n'; - - // Get object here and warn user if dep_name already exists. - auto obj = obj_vec[0]; - if (!dep_name.empty() && obj.dependency_exists(dep_name)) - std::cerr << dep_name << " already exists for " << obj_name << " in project.\n"; - - if (is_this_correct()) // Correct, then break out to sanity check and apply! - break; - } - } - - // This container is used for validation. - std::vector obj_vec; - - // Here we want to ensure that object exists. - { - auto validator = [&proj,&obj_vec](std::string_view s) { - if (!validate_name(s)) - return false; - if (obj_vec = proj.object(s); obj_vec.empty()) { - std::cerr << s << " does not exist in " << proj.name() << '\n'; - return false; - } - return true; - }; - get_valid_string("object (app/lib/test) name", obj_name, validator); - } - - // Here we want to validate dep name does NOT already exist before we go on. - { - auto validator = [&obj_vec,&obj_name](std::string_view s) { - if (!validate_name(s)) - return false; - bool rv = false; - for (const auto& a: obj_vec) { - rv |= !a.dependency_exists(s); - } - if(!rv) - std::cerr << s << " already exists for " << obj_name << " in project.\n"; - return rv; - }; - get_valid_string("dependency name", dep_name, validator); - } - - get_loc("from/location", dep_loc, true); - get_name("git tag/commit hash", dep_tag, true); - get_name("git release version", dep_rel, true); - get_hash("SHA-256 hash", dep_hash, true); - } - - - // Done with interactive mode, validate and store data. - - - // Get the object to update. - auto obj_vec = proj.object(obj_name); - if (obj_vec.empty()) - return cli.exit( CLI::Error("", obj_name + " does not exist in project.") ); - auto obj = obj_vec[0]; - - // If we are not in interactive mode, test for the pre-existence of the dependency. - if (!interactive && obj.dependency_exists(dep_name)) - return cli.exit( CLI::Error("", dep_name + " already exists for " + obj_name + " in project.") ); - - // Validate the location. Redundant for interactive mode, but cheap in human time. - std::ostringstream ss; - if (!antler::project::dependency::validate_location(dep_loc, dep_tag, dep_rel, dep_hash, ss)) - return cli.exit( CLI::Error("", ss.str()) ); - - // Create the dependency, store it in the object, and store the object in the project. - antler::project::dependency dep; - dep.set(dep_name, dep_loc, dep_tag, dep_rel, dep_hash); - obj.upsert_dependency(std::move(dep)); - proj.upsert(std::move(obj)); - - // Sync the project to storage. - if (!proj.sync()) - return cli.exit( CLI::Error("", "failed to write project file.") ); - return 0; -} diff --git a/aproj/aproj-add-lib.cpp b/aproj/aproj-add-lib.cpp deleted file mode 100644 index 06059d2..0000000 --- a/aproj/aproj-add-lib.cpp +++ /dev/null @@ -1,91 +0,0 @@ -/// @copyright See `LICENSE` in the root directory of this project. - -// aproj-add-app.cpp and aproj-add-lib.cpp are nearly identical while aproj-add-test.cpp has a few differences. If you change any -// of the three, make sure to keep the others similarly updated. - - -#include -#include -#include -#include - -#include - -#include - - -int main(int argc, char** argv) { - - std::filesystem::path path; - std::string name; - antler::project::language lang = antler::project::language::none; - std::string opts; - bool interactive=false; - - - // CLI set up. - - CLI::App cli{}; - common_init(cli, argv[0], "Add a library entry to the project."); - - // Positional arguments: - cli.add_option("path", path, "This must be the path to `project.yaml` or the path containing it.")->required(); - cli.add_option("app_name", name, "The name of the application to add."); - cli.add_option("app_lang", lang, "The application's language (e.g. `cpp`, `java`, `c`, etc.)."); - cli.add_option("app_options", opts, "Options to pass to the compiler."); - - // Option flag - cli.add_flag("--interactive", interactive, "Force interactive mode."); - - // Parse - CLI11_PARSE(cli,argc,argv); - - - // Load the project or exit. - auto proj = load_project_or_exit(cli, path); - - - // Interactive mode? - interactive |= (lang == antler::project::language::none); - for (const auto loop=interactive; loop;) { - // Loop until the user says values are correct. - // Only query for correct if all the info is updated. - if (!name.empty() && lang != antler::project::language::none) { - - std::cout - << '\n' - << "lib name: " << name << '\n' - << "language: " << lang << '\n' - << "options: " << opts << '\n' - << '\n'; - - if (proj.object_exists(name, antler::project::object::type_t::lib)) { - std::cerr << "Library " << name << " already exists in project. Can't add.\n\n"; - } - else { - if (proj.object_exists(name)) - std::cerr << "WARNING: " << name << " already exists in project as app and/or test.\n\n"; - - if (is_this_correct()) - break; - } - } - - get_name("library name", name); - get_language("project language", lang); - get_string("library options", opts, true); - } - - - // Sanity check and apply. - if (!name.empty() && proj.object_exists(name, antler::project::object::type_t::app)) - return cli.exit( CLI::Error("name", "lib_name already exists in project.") ); - if (lang == antler::project::language::none) - return cli.exit( CLI::Error("name", "invalid language.") ); - - auto obj = antler::project::object(antler::project::object::lib, name, lang, opts); - proj.upsert_lib(std::move(obj)); - proj.sync(); - - return 0; -} diff --git a/aproj/aproj-add-test.cpp b/aproj/aproj-add-test.cpp deleted file mode 100644 index 4f6b8dc..0000000 --- a/aproj/aproj-add-test.cpp +++ /dev/null @@ -1,90 +0,0 @@ -/// @copyright See `LICENSE` in the root directory of this project. - -// aproj-add-app.cpp and aproj-add-lib.cpp are nearly identical while aproj-add-test.cpp has a few differences. If you change any -// of the three, make sure to keep the others similarly updated. - - -#include -#include -#include -#include - -#include - -#include - - -int main(int argc, char** argv) { - - std::filesystem::path path; - std::string name; - std::string cmd; - bool interactive; - - // Set up CLI. - - CLI::App cli{}; - common_init(cli, argv[0], "Add a test entry."); - - // Positional arguments: - cli.add_option("path", path, "This must be the path to `project.yaml` or the path containing it.")->required(); - cli.add_option("test_name", name, "The name of the test to add."); - cli.add_option("test_cmd", cmd, "The test command to execute."); - - // Option flag - cli.add_flag("--interactive", interactive, "Force interactive mode."); - - // Parse - CLI11_PARSE(cli,argc,argv); - - - // Load the project or exit. - auto proj = load_project_or_exit(cli,path); - - - // Evaluate interactive mode. - interactive |= cmd.empty(); - for (const auto loop=interactive; loop;) { - // Loop until the user says values are correct. - // If name is populated, we can show the info so far. If it's not populated, then skip straight to the queries. - if (!name.empty()) { - - // Print values. - std::cout - << '\n' - << "test name: " << name << '\n' - << "command: " << cmd << '\n' - << '\n'; - - // Check to see if name is a TEST duplicate. - if (proj.object_exists(name, antler::project::object::type_t::test)) { - // Enform user of the duplicate. - std::cerr << "Test " << name << " already exists in project. Can't add.\n\n"; - } else { - // Check to see if name is otherwise a duplicate, warn if so. - if (proj.object_exists(name)) - std::cerr << "WARNING: " << name << " already exists in project as app and/or lib.\n\n"; - // Ask if the printed values are correct, if so break out of this loop. - if (is_this_correct()) - break; - } - } - - // Querry for test name. - get_name("test name", name); - // Querry for test command. - get_string("test command", cmd, true); - } - - - // Sanity check and apply. - if (!name.empty() && proj.object_exists(name, antler::project::object::type_t::test)) { - return cli.exit( CLI::Error("name", "test_name already exists in project.") ); - } - - auto obj = antler::project::object(name, cmd); - proj.upsert_test(std::move(obj)); - proj.sync(); - - return 0; -} diff --git a/aproj/aproj-common.hpp b/aproj/aproj-common.hpp index 80058f3..77493f6 100644 --- a/aproj/aproj-common.hpp +++ b/aproj/aproj-common.hpp @@ -16,12 +16,18 @@ #include +/// Load project. +/// @param path Path to the project root or project file. +/// @return The loaded project. +inline std::optional load_project(std::filesystem::path& path) noexcept { +} + /// Load project or exit function. /// @param cli The CLI app to call exit from if things go poorly. /// @param path Path to the project root or project file. /// @return The loaded project. -antler::project::project load_project_or_exit(CLI::App& cli, std::filesystem::path& path) noexcept { +inline antler::project::project load_project_or_exit(CLI::App& cli, std::filesystem::path& path) noexcept { // Get the path to the project. if (!antler::project::project::update_path(path)) diff --git a/aproj/aproj-populate.cpp b/aproj/aproj-populate.cpp deleted file mode 100644 index e3e38aa..0000000 --- a/aproj/aproj-populate.cpp +++ /dev/null @@ -1,43 +0,0 @@ -/// @copyright See `LICENSE` in the root directory of this project. - -#include -#include -#include - -#include - -#include - - -int main(int argc, char** argv) { - - std::filesystem::path path; - - - // CLI setup - - CLI::App cli{}; - common_init(cli, argv[0], "Populate the project with Build files and dependencies."); - - // Positional arguments: - cli.add_option("path", path, "This must be the path to `project.yaml` or the path containing it.")->required(); - - // Parse - CLI11_PARSE(cli,argc,argv); - - - // Load project or exit. - auto proj = load_project_or_exit(cli,path); - - - // do the work. - - auto pop_type = antler::project::project::pop::honor_deltas; - - bool result = proj.populate(pop_type); - - if (result) - return 0; - std::cerr << "Fail\n"; - return -1; -} diff --git a/aproj/aproj-prefix.hpp b/aproj/aproj-prefix.hpp deleted file mode 100644 index 12fe76c..0000000 --- a/aproj/aproj-prefix.hpp +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once -/// @copyright See `LICENSE` in the root directory of this project. - -#include - -constexpr std::string_view project_prefix = "aproj-"; diff --git a/aproj/aproj-rm-dep.cpp b/aproj/aproj-rm-dep.cpp deleted file mode 100644 index 46197ed..0000000 --- a/aproj/aproj-rm-dep.cpp +++ /dev/null @@ -1,218 +0,0 @@ -/// @copyright See `LICENSE` in the root directory of this project. - -#include -#include -#include -#include // std::sort -#include - -#include - -#include - - -template -std::string to_string(T list) { - auto pos = list.begin(); - auto end = list.end(); - std::ostringstream ss; - ss << "{ "; - if (pos != end) { - ss << *pos; - for (++pos; pos != end; ++pos) - ss << ", " << *pos; - } - ss << " }"; - return ss.str(); -} - - -antler::project::object::list_t populate_update_list( - const antler::project::object::list_t& src, ///< The list of objects. - std::string_view dep_name, ///< The dependency to remove. - std::string_view obj_name, ///< The name of the object (or empty) to remove from. - bool app, - bool lib, - bool tst) ///< Valid object types to remove from. - noexcept { - - antler::project::object::list_t rv; - for (const auto& a : src) { - switch (a.type()) { - case antler::project::object::type_t::app: - if (!app) { - continue; - } - break; - case antler::project::object::type_t::lib: - if (!lib) { - continue; - } - break; - case antler::project::object::type_t::test: - if (!tst) { - continue; - } - break; - case antler::project::object::type_t::none: - case antler::project::object::type_t::any: - std::cerr << "Unexpected type: " << a.type() << " in object: " << a.name() << '\n'; - continue; - } - - if ((obj_name.empty() || a.name() == obj_name) && a.dependency_exists(dep_name)) { - rv.emplace_back(a); - rv.back().remove_dependency(dep_name); - } - } - - return rv; -} - - -int main(int argc, char** argv) { - - std::filesystem::path path; - std::string dep_name; - std::string obj_name; - bool rm_all = false; - bool rm_app = false; - bool rm_lib = false; - bool rm_test = false; - bool interactive=false; - - - // Setup CLI. - - CLI::App cli{}; - common_init(cli, argv[0], "Remove a dependency."); - - // Positional arguments: - cli.add_option("path", path, "This must be the path to `project.yaml` or the path containing it.")->required(); - cli.add_option("dep_name", dep_name, "The name of the dependency to remove."); - - // Options - cli.add_option("--name", obj_name, "Remove the dep from only this object."); - - // Option flag - auto all_flag = cli.add_flag("--all", rm_all, "Remove dep_name from all objects (implies --app, --lib, --test). Default option."); - cli.add_flag("--app", rm_app, "Remove dep_name from application objects.")->excludes(all_flag); - cli.add_flag("--lib", rm_lib, "Remove dep_name from library objects.")->excludes(all_flag); - cli.add_flag("--test", rm_test, "Remove dep_name from test objects.")->excludes(all_flag); - cli.add_flag("--interactive", interactive, "Force interactive mode."); - - // Parse - CLI11_PARSE(cli,argc,argv); - - - // load project or exit. - auto proj = load_project_or_exit(cli,path); - - - // Get all the objects and their names. - const auto all_objs = proj.all_objects(); - - // Find all the object and dependency names. - std::vector all_obj_names; - std::vector all_dep_names; - for (const auto& a : all_objs) { - all_obj_names.emplace_back(a.name()); - for (const auto& b : a.dependencies()) - all_dep_names.emplace_back(b.name()); - } - - // Sort dep name list and make it unique. - std::sort(all_dep_names.begin(), all_dep_names.end()); - all_dep_names.erase(std::unique(all_dep_names.begin(), all_dep_names.end()), all_dep_names.end()); - // If there aren't any deps, then there's nothing to do... - if (all_dep_names.empty()) - return cli.exit( CLI::Error("path","project file does not contain any dependencies.") ); - - // Sort obj name list and make it unique. - std::sort(all_obj_names.begin(), all_obj_names.end()); - all_obj_names.erase(std::unique(all_obj_names.begin(), all_obj_names.end()), all_obj_names.end()); - - - // Set the individual rm flags if all was explicitly set OR implicitly (i.e. no rm flags at all). - if (rm_all || (!rm_app && !rm_lib && !rm_test)) { - rm_app = true; - rm_lib = true; - rm_test = true; - } - - - antler::project::object::list_t update_list; - - // An existing dep_name indicates a non--interactive mode. - interactive |= dep_name.empty(); - if (!interactive) { - // Just create the update list. - update_list = populate_update_list(all_objs, dep_name, obj_name, rm_app, rm_lib, rm_test); - } - else { - // Get input from the user. - bool first_time = true; - antler::project::object::list_t temp0 = populate_update_list(all_objs, dep_name, obj_name, rm_app, rm_lib, rm_test); - // Loop until the user is happy with the update. - for (;;) { - - if (temp0.empty()) { - // This block shows the user the available objects and provides a warning/error if this is the first pass. - dump_obj_deps(all_objs, rm_app, rm_lib, rm_test); - if (!first_time) - std::cerr << "No objects with dependency " << dep_name << "\n\n"; - } - else { - // This block provides the current values and queries the user regarding correctness. - std::cout - << '\n' - << "Dependency name: " << dep_name << '\n'; - if (!obj_name.empty()) - std::cout << "Object name: " << obj_name << '\n'; - - std::cout << "Objects that will be updated (" << temp0.size() << "):\n"; - for (auto a : temp0) - std::cout << " " << a.name() << " [" << a.type() << "]\n"; - std::cout << '\n'; - - if (is_this_correct()) { - update_list = temp0; - break; - } - } - - first_time = false; - - // dependency name - { - auto validator = [&all_dep_names](std::string_view s) -> bool { - if (validate_name(s) && std::find(all_dep_names.begin(), all_dep_names.end(), s) != all_dep_names.end()) - return true; - std::cerr << "Valid dependencies: \n"; - for (auto a : all_dep_names) - std::cout << " " << a << '\n'; - return false; - }; - get_valid_string("dependency name", dep_name, validator); - } - // object name - get_name("object name", obj_name, true); - - // Update the temporary removal list. - temp0 = populate_update_list(all_objs, dep_name, obj_name, rm_app, rm_lib, rm_test); - } - } - - // Sanity check. This might be better if it returned true in certain circumstances? - if (update_list.empty()) - return cli.exit( CLI::Error("","no objects were selected for removal.") ); - - // Update the objects that need updating. - for (auto a : update_list) - proj.upsert(std::move(a)); - - // Sync the project to storage. - if (!proj.sync()) - return cli.exit( CLI::Error("path","failed to write project file.") ); - return 0; -} diff --git a/aproj/aproj.cpp b/aproj/aproj.cpp index 2c84df8..b78c193 100644 --- a/aproj/aproj.cpp +++ b/aproj/aproj.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -15,26 +16,27 @@ #include - -#include - #include "add_to.hpp" #include "init.hpp" +#include "populate.hpp" +#include "remove_from.hpp" + +template +static V depends(V&& v) { return std::forward(v); } template -struct run_on { - run_on(CLI::App& app) - : tup(Ts{app}...) { - } +struct runner { + runner(CLI::App& app) + : tup(depends(app)...) {} template constexpr inline int exec() { if constexpr (I == sizeof...(Ts)) { + std::cerr << "Internal error, runner failure" << std::endl; return -1; } else { - auto& v = std::get(tup); - if (*v.subcommand) { - return v.exec(); + if (*std::get(tup).subcommand) { + return std::get(tup).exec(); } else { return exec(); } @@ -50,152 +52,12 @@ int main(int argc, char** argv) { const auto app_name = std::filesystem::path(argv[0]).filename().string(); CLI::App app{app_name}; - - //run_on runner{app}; - - add_to_project add_to = {app}; + runner runner{app}; CLI11_PARSE(app, argc, argv); - if (*add_to.subcommand) return add_to.exec(); - //return runner.exec(); - - //if (*init.subcommand) { - // return init.exec(); - //} else if (*add_to.subcommand) { - // return add_to.exec(); - //} - - //std::cout << "Param " << init.path << std::endl; - return 0; -} - -/* -std::filesystem::path cli_path; - std::string cli_name; - antler::project::version ver("0.0.0"); - bool interactive=false; - - - // CLI setup - - // Description needs a bit more info than our standard brief string contains; still, we cannot break brief_str. - const std::string brief_str = "Initialize a new projet creating the directory tree and a `project.yaml` file."; - - CLI::App cli{}; - common_init(cli, argv[0], brief_str); - - const std::string desc=brief_str + "\n" - + "`project.yaml` is created in PATH if PATH is an empty directory AND the filename matches PROJECT_NAME;\n" - + "otherwise, a directory matching PROJECT_NAME is created at PATH to contain `project.yaml`.\n"; - cli.description(desc); - - // Positional arguments: - cli.add_option("path", cli_path, "This is the root path to create the project in.")->required(); - cli.add_option("project_name", cli_name, "The name of the project."); - cli.add_option("version", cli_name, "The version to store in the project file."); // add default info? - - // Option flag - cli.add_flag("--interactive", interactive, "Force interactive mode."); - - // Parse - CLI11_PARSE(cli,argc,argv); - - - // Begin doing the work. (This may move into `antler::project::project` at a later date.) - - // `name` will contain the project name. It needs to be separate from cli_name because we might use both in the logic below. - std::string name; - - // Sanity check potential project directory. - std::error_code sec; - if (!std::filesystem::exists(cli_path, sec)) - name = cli_path.filename().string(); - else { - // It might be okay if it exists, but only if it's a directory AND it's empty. - if (!std::filesystem::is_directory(cli_path, sec)) - return cli.exit( CLI::Error("path", cli_path.string() + " already exists.") ); - if (!std::filesystem::is_empty(cli_path, sec)) { - if (std::filesystem::exists(cli_path / "project.yaml")) - return cli.exit( CLI::Error("path", "not initializing where a `project.yaml` file already exists.") ); - } - else if (!cli_path.has_extension()) { - name = cli_path.filename().string(); - } - } - - // Maybe copy name from cli. - if(!cli_name.empty()) - name = cli_name; - - // Resolve the path to the project root. This may be overwritten in interactive mode. - auto project_root = cli_path; - if (cli_path.filename() != name) - project_root /= name; - - // Test for interactive mode. - interactive |= cli_name.empty(); - for (const auto loop=interactive; loop;) { - // Loop until user is satisfied. - if (!name.empty()) { - // Resolve the path to the project root. - project_root = cli_path; - if (cli_path.filename() != name) - project_root /= name; - - std::cout - << '\n' - << "Path: " << project_root << '\n' - << "Project name: " << name << '\n' - << "Version: " << ver << '\n' - << '\n'; - if(!ver.is_semver()) - std::cout << "Warning: Version is NOT a SemVer.\n\n"; - - if (is_this_correct()) - break; - } - - get_name("project name",name); - get_version("project version", ver); - } - - - // Sanity check. - - if (!validate_name(name)) - return cli.exit( CLI::Error("name", std::string{"name \""} + name + "\" contains invalid chars. Expecting [0-9a-zA-Z_].") ); - - - // Do initialization here: - - // Create the root directory. - std::filesystem::create_directories(project_root, sec); - if (sec) - return cli.exit( CLI::Error("path", std::string{project_root} + " could not be created: " + sec.message()) ); - - - if (!std::filesystem::is_empty(project_root, sec)) - return cli.exit( CLI::Error("path", std::string{project_root} + " is NOT empty!") ); - - // Create the directory structure. - { - const std::vector files = { "apps", "include", "ricardian", "libs", "tests" }; - for (const auto& fn : files) { - std::filesystem::create_directory(project_root / fn, sec); - if (sec) - return cli.exit( CLI::Error("path", std::string{project_root / fn} + " could not be created: " + sec.message()) ); - } - } - - // Create an empty project and populate it. - antler::project::project proj; - proj.path(project_root / "project.yaml"); - proj.name(name); - proj.version(ver); - proj.sync(); - - - return -1; - } - */ \ No newline at end of file + return runner.exec(); +} \ No newline at end of file diff --git a/aproj/common.hpp b/aproj/common.hpp new file mode 100644 index 0000000..567ece8 --- /dev/null +++ b/aproj/common.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +#include + +namespace antler { + using proj_ptr = std::optional; + + inline proj_ptr load_project(const std::filesystem::path& path) { + auto p = std::filesystem::canonical(std::filesystem::path(path)); + if (!antler::project::project::update_path(p)) { + std::cerr << "path either did not exist or no `project.yaml` file could be found." << std::endl; + return std::nullopt; + } + return antler::project::project::parse(p); + } +} \ No newline at end of file diff --git a/aproj/init.hpp b/aproj/init.hpp index 4248ac6..b4bbec3 100644 --- a/aproj/init.hpp +++ b/aproj/init.hpp @@ -7,26 +7,28 @@ #include -struct init_project { - inline init_project(CLI::App& app) { - subcommand = app.add_subcommand("init", "Initialize a new project creating the directory tree and a `project.yaml` file."); - subcommand->add_option("path", path, "This is the root path to create the project in.")->required(); - subcommand->add_option("project_name", name, "The name of the project.")->required(); - subcommand->add_option("version", version_raw, "The version to store in the project file."); - } +namespace antler { + struct init_project { + inline init_project(CLI::App& app) { + subcommand = app.add_subcommand("init", "Initialize a new project creating the directory tree and a `project.yaml` file."); + subcommand->add_option("-p, path", path, "This is the root path to create the project in.")->required(); + subcommand->add_option("-n, project_name", name, "The name of the project.")->required(); + subcommand->add_option("-v, version", version_raw, "The version to store in the project file."); + } - int32_t exec() { - antler::project::project proj = {path, name, version_raw}; + int32_t exec() { + antler::project::project proj = {std::filesystem::path(path) / antler::project::project::manifest_name, name, version_raw}; - if (!proj.init_dirs(path)) - return -1; + if (!proj.init_dirs(path)) + return -1; + + proj.sync(); + return 0; + } - proj.sync(); - return 0; - } - - CLI::App* subcommand; - std::string path; - std::string name; - std::string version_raw; -}; \ No newline at end of file + CLI::App* subcommand; + std::string path; + std::string name; + std::string version_raw; + }; +} // namespace antler \ No newline at end of file diff --git a/aproj/populate.hpp b/aproj/populate.hpp new file mode 100644 index 0000000..81794d1 --- /dev/null +++ b/aproj/populate.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +#include + +#include + +#include "common.hpp" + +namespace antler { + struct populate_project { + inline populate_project(CLI::App& app) { + subcommand = app.add_subcommand("populate", "Populate a project's dependencies and CMake."); + subcommand->add_option("-p, path", path, "This is the path to the root of the project.")->required(); + } + + int32_t exec() { + auto pop_type = antler::project::project::pop::honor_deltas; + + auto proj = load_project(path); + if (!proj) { + return -1; + } + + return proj->populate(pop_type) ? 0 : -1; + } + + CLI::App* subcommand; + std::string path; + std::string name; + std::string version_raw; + }; +} // namespace antler \ No newline at end of file diff --git a/aproj/remove_from.hpp b/aproj/remove_from.hpp new file mode 100644 index 0000000..66f1271 --- /dev/null +++ b/aproj/remove_from.hpp @@ -0,0 +1,126 @@ +#pragma once + +#include +#include + +#include + +#include + +#include "common.hpp" + +namespace antler { + struct remove_from_project { + + template + bool remove_obj(antler::project::project& proj) { + if (!obj_name.empty()) { + std::cerr << "Name is empty" << std::endl; + return false; + } + + std::cout << "Removing object: " << obj_name << " from the project." << std::endl; + + return proj.remove(obj_name, Ty); + } + + inline bool remove_app(antler::project::project& proj) { return remove_obj(proj); } + inline bool remove_lib(antler::project::project& proj) { return remove_obj(proj); } + inline bool remove_test(antler::project::project& proj) { return remove_obj(proj); } + + bool remove_dependency(antler::project::project& proj) { + if (!dep_name.empty()) { + if (!obj_name.empty()) { + // Get the object to operate on. + try { + auto& obj = proj.object(obj_name); + + if (obj.remove_dependency(dep_name)) { + std::cout << "Removing dependency: " << dep_name << " from: " << obj_name << std::endl; + return true; + } else { + std::cerr << "Dependency: " << dep_name << " is not a dependency of: " << obj_name << std::endl; + return false; + } + } catch(...) { + std::cerr << "Object: " << obj_name << " does not exist." << std::endl; + } + } else { + // Get all the objects and their names. + auto& all_objs = proj.all_objects(); + + for (auto& o : all_objs) { + if (o.remove_dependency(dep_name)) { + std::cout << "Removing dependency: " << dep_name << " from: " << o.name() << std::endl; + } + } + } + } + + return true; + } + + remove_from_project(CLI::App& app) { + path = std::filesystem::current_path().string(); + subcommand = app.add_subcommand("remove", "Add an app, dependency, library or test to your project."); + subcommand->add_option("-p, path", path, "This must be the path to the `project.yml` or the path containing it.")->required(); + + app_subcommand = subcommand->add_subcommand("app", "Remove app from the project."); + app_subcommand->add_option("-n, name", obj_name, "The name of the app to remove.")->required(); + + lib_subcommand = subcommand->add_subcommand("lib", "Remove lib from the project."); + lib_subcommand->add_option("-n, name", obj_name, "The name of the library to add.")->required(); + + dep_subcommand = subcommand->add_subcommand("dep", "Remove a dependency from the project."); + dep_subcommand->add_option("-d, dep", dep_name, "The name of the dependency.")->required(); + dep_subcommand->add_option("-o, obj", obj_name, "The name of the object the dependency is attached to."); + + //// Option flag + //auto all_flag = cli.add_flag("--all", rm_all, "Remove dep_name from all objects (implies --app, --lib, --test). Default option."); + //cli.add_flag("--app", rm_app, "Remove dep_name from application objects.")->excludes(all_flag); + //cli.add_flag("--lib", rm_lib, "Remove dep_name from library objects.")->excludes(all_flag); + //cli.add_flag("--test", rm_test, "Remove dep_name from test objects.")->excludes(all_flag); + //cli.add_flag("--interactive", interactive, "Force interactive mode."); + + } + + int32_t exec() { + try { + auto proj = load_project(path); + + if (!proj) { + return -1; + } + + if (*app_subcommand) { + remove_app(*proj); + } else if (*lib_subcommand) { + remove_lib(*proj); + } else if (*dep_subcommand) { + remove_dependency(*proj); + /* TODO Add back after this release when we have the testing framework finished + } else if (*test_subcommand) { + add_test(*proj); + */ + } else { + std::cerr << "Need to supply either dep/app/lib/test after `add`" << std::endl; + return -1; + } + + proj->sync(); + } catch (...) { + std::cerr << "Path <" << path << "> does not exist" << std::endl; + } + return 0; + } + + CLI::App* subcommand; + CLI::App* app_subcommand; + CLI::App* dep_subcommand; + CLI::App* lib_subcommand; + CLI::App* test_subcommand; + std::string path; + std::string obj_name; + std::string dep_name; + }; +} // namespace antler \ No newline at end of file diff --git a/project/antler/project/object.hpp b/project/antler/project/object.hpp index 83e5eb1..4ec6b20 100644 --- a/project/antler/project/object.hpp +++ b/project/antler/project/object.hpp @@ -37,13 +37,12 @@ class object { /// @param name The Name of the object. /// @param lang The language type of this object. /// @param opts Compile time options for this object. May be empty. - object(type_t ot, std::string_view name, antler::project::language lang, std::string_view opts); + object(type_t ot, std::string_view name, const std::string& lang, std::string_view opts); /// Object constructor for test type. /// @param name The Name of the object. /// @param command The command to run for this test. object(std::string_view name, std::string_view command); - /// @return The type of this object. [[nodiscard]] type_t type() const noexcept; @@ -54,10 +53,10 @@ class object { /// @return Current language. - [[nodiscard]] antler::project::language language() const noexcept; + [[nodiscard]] std::string_view language() const noexcept; /// Replace any existing language info with the new value. /// @param lang The new language value to store. - void language(antler::project::language lang) noexcept; + void language(std::string_view lang) noexcept; /// @return Current options. [[nodiscard]] std::string_view options() const noexcept; @@ -89,15 +88,15 @@ class object { private: type_t m_type = none; ///< Object type: app, lib, or test. - std::string m_name; ///< Object name. + std::string m_name = ""; ///< Object name. antler::project::dependency::list_t m_dependencies; ///< list of dependencies. // app, lib: - antler::project::language m_language = language::none; ///< Language type, only valid for app or lib. - std::string m_options; ///< Compile options, only valid for app or lib. + std::string m_language = ""; ///< Language type, only valid for app or lib. + std::string m_options = ""; ///< Compile options, only valid for app or lib. // test: - std::string m_command; ///< Test command, only valid for test. + std::string m_command = ""; ///< Test command, only valid for test. }; } // namespace antler::project diff --git a/project/antler/project/project.hpp b/project/antler/project/project.hpp index d0927a2..fcc4b74 100644 --- a/project/antler/project/project.hpp +++ b/project/antler/project/project.hpp @@ -30,7 +30,7 @@ class project { // constructors project() = default; project(const std::filesystem::path&); - project(std::string_view path, std::string_view name, std::string_view version_raw) : + project(const std::filesystem::path& path, std::string_view name, std::string_view version_raw) : m_path(path), m_name(name), m_ver(version_raw) {} /// Get the project name. @@ -90,6 +90,8 @@ class project { /// @return vector with copies of the objects. [[nodiscard]] std::vector object(std::string_view name, object::type_t type = object::type_t::any) const noexcept; + [[nodiscard]] antler::project::object& object(std::string_view name); + /// @return A const ref to the application list. [[nodiscard]] const antler::project::object::list_t& apps() const noexcept; /// @return A const ref to the library list. @@ -98,6 +100,7 @@ class project { [[nodiscard]] const antler::project::object::list_t& tests() const noexcept; /// @return a list of ALL the [[nodiscard]] antler::project::object::list_t all_objects() const noexcept; + [[nodiscard]] antler::project::object::list_t& all_objects() noexcept; /// Validate the project. /// @param error_stream Stream location for printing warnings and errors. diff --git a/project/src/object.cpp b/project/src/object.cpp index 3e892fd..1c3f490 100644 --- a/project/src/object.cpp +++ b/project/src/object.cpp @@ -1,6 +1,7 @@ /// @copyright See `LICENSE` in the root directory of this project. #include +#include #include // find_if() #include @@ -14,10 +15,10 @@ object::object(type_t ot) : m_type{ ot } {} -object::object(type_t ot, std::string_view name, antler::project::language lang, std::string_view opts) +object::object(type_t ot, std::string_view name, const std::string& lang, std::string_view opts) : m_type{ ot } , m_name{ name } - , m_language{ lang } + , m_language{ boost::algorithm::to_upper_copy(lang) } , m_options{ opts } { } @@ -63,11 +64,11 @@ bool object::dependency_exists(std::string_view name) const noexcept { } -antler::project::language object::language() const noexcept { +std::string_view object::language() const noexcept { return m_language; } -void object::language(antler::project::language lang) noexcept { +void object::language(std::string_view lang) noexcept { m_language = lang; } diff --git a/project/src/project-parse.cpp b/project/src/project-parse.cpp index 1733fab..c64e85b 100644 --- a/project/src/project-parse.cpp +++ b/project/src/project-parse.cpp @@ -239,12 +239,12 @@ template os << word << " tag in " << type << " list with no value.\n"; return {}; } - auto lang = to_language(i.val()); - if (lang == language::none) { + auto lang = i.val(); + if (lang.empty()) { os << "Invalid language tag in " << type << " list: " << i.val() << "\n"; return {}; } - if (rv.language() != language::none) { + if (!rv.language().empty()) { os << "Duplicate language values in " << type << " list: " << rv.language() << ", " << lang << "\n"; return {}; } diff --git a/project/src/project-print.cpp b/project/src/project-print.cpp index 13827b6..fba921e 100644 --- a/project/src/project-print.cpp +++ b/project/src/project-print.cpp @@ -47,11 +47,6 @@ template<> return to_csubstr(key::literals[static_cast(e)]); } -template<> -[[nodiscard]] inline const c4::csubstr to_csubstr(antler::project::language e) { - return to_csubstr(antler::project::language_literals[static_cast(e)]); -} - template [[nodiscard]] inline const c4::csubstr to_csubstr_insert(T t) { std::stringstream ss; @@ -122,7 +117,7 @@ void project::print(std::ostream& os) const noexcept { if (!obj.name().empty()) map_node[to_csubstr(key::word::name)] << to_csubstr(obj.name()); // lang - if (obj.language() != language::none) + if (!obj.language().empty()) map_node[to_csubstr(key::word::lang)] << to_csubstr(obj.language()); // options if (!obj.options().empty()) diff --git a/project/src/project.cpp b/project/src/project.cpp index 221f51f..d59e8d6 100644 --- a/project/src/project.cpp +++ b/project/src/project.cpp @@ -15,15 +15,9 @@ namespace antler::project { //--- alphabetic -------------------------------------------------------------------------------------------------------- -object::list_t project::all_objects() const noexcept { - auto rv = m_apps; - rv.reserve(m_apps.size() + m_libs.size() + m_tests.size()); - for (const auto& a : m_libs) - rv.emplace_back(a); - for (const auto& a : m_tests) - rv.emplace_back(a); - return rv; -} +object::list_t& project::all_objects() noexcept { return m_apps; } +object::list_t project::all_objects() const noexcept { return m_apps; } + const object::list_t& project::apps() const noexcept { @@ -77,6 +71,22 @@ void project::name(std::string_view s) noexcept { } +antler::project::object& project::object(std::string_view name) { + auto temp = std::find_if(m_apps.begin(), m_apps.end(), [name](const auto& o) { return o.name() == name; }); + if (temp != m_apps.end()) + return *temp; + + temp = std::find_if(m_libs.begin(), m_libs.end(), [name](const auto& o) { return o.name() == name; }); + if (temp != m_libs.end()) + return *temp; + + temp= std::find_if(m_tests.begin(), m_tests.end(), [name](const auto& o) { return o.name() == name; }); + if (temp != m_tests.end()) + return *temp; + + throw std::runtime_error("object not found."); +} + std::vector project::object(std::string_view name, object::type_t type) const noexcept { std::vector rv; From 575df710ac94ca438cc8c94a05af1bbf62f26790 Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Thu, 2 Mar 2023 10:58:54 -0500 Subject: [PATCH 04/14] added update for all types, fixed bugs, cleaned up location --- aproj/aproj-common.hpp | 302 -------------------------- aproj/aproj-update-dep.cpp | 198 ----------------- aproj/aproj-validate.cpp | 39 ---- aproj/aproj.cpp | 9 +- aproj/populate.hpp | 3 +- aproj/remove_from.hpp | 22 +- aproj/update.hpp | 180 +++++++++++++++ aproj/validate.hpp | 45 ++++ project/antler/project/dependency.hpp | 4 + project/antler/project/location.hpp | 36 +-- project/src/dependency.cpp | 27 +-- project/src/location.cpp | 174 ++++++++++----- 12 files changed, 399 insertions(+), 640 deletions(-) delete mode 100644 aproj/aproj-common.hpp delete mode 100644 aproj/aproj-update-dep.cpp delete mode 100644 aproj/aproj-validate.cpp create mode 100644 aproj/update.hpp create mode 100644 aproj/validate.hpp diff --git a/aproj/aproj-common.hpp b/aproj/aproj-common.hpp deleted file mode 100644 index 77493f6..0000000 --- a/aproj/aproj-common.hpp +++ /dev/null @@ -1,302 +0,0 @@ -#pragma once - -/// @copyright See `LICENSE` in the root directory of this project. - -#include -#include -#include -#include // std::isxdigit() -#include // std::exit() - -#include - -#include -#include - -#include - - -/// Load project. -/// @param path Path to the project root or project file. -/// @return The loaded project. -inline std::optional load_project(std::filesystem::path& path) noexcept { -} - - -/// Load project or exit function. -/// @param cli The CLI app to call exit from if things go poorly. -/// @param path Path to the project root or project file. -/// @return The loaded project. -inline antler::project::project load_project_or_exit(CLI::App& cli, std::filesystem::path& path) noexcept { - - // Get the path to the project. - if (!antler::project::project::update_path(path)) - std::exit( cli.exit( CLI::Error("path","path either did not exist or no `project.yaml` file could be found.") ) ); - - // Load the project. - auto optional_proj = antler::project::project::parse(path); - if (!optional_proj) - std::exit( cli.exit( CLI::Error("path", "Failed to load project file.") ) ); - return optional_proj.value(); -} - - -/// Common init function for subcommands. -/// This function sets up expected values and prints the brief string if it was command to. -/// @param argv0 argv[0] for this executable. -/// @param brief_in v alue to store as brief string. -/// @throw std::runtime_error is thrown if cli already has `--indirect` or `--brief` flags. -inline void common_init(CLI::App& cli, std::string_view argv0, const std::string& brief_in) { - - // set brief string - std::string brief_str = brief_in; - // set exe name for usage - std::string exe_name = std::filesystem::path(argv0).filename().string(); - - cli.description(brief_in); - cli.name(exe_name); - - // Add hidden flags. - - // Indirect option - auto indirect_option = cli.add_option_function("--indirect", [&cli](const std::string& s) { cli.name(s); } ); - indirect_option->group(""); // Hide from help. - - // Brief flag. This flag dumps brief output for `aproj` to consume for it's help. - auto brief_flag = cli.add_flag_callback("--brief", [exe_name,brief_str](){ - // Test to see if exe can be convert to a subcommand, report an error if not. - if (!exe_name.starts_with(project_prefix)) { - std::cerr << "Can't provide --brief for" << exe_name << '\n'; - std::exit( -1 ); - } - // Convert exe to subcommand. - auto exe_as_subcmd = exe_name; - exe_as_subcmd.erase(0, project_prefix.size()); - std::cout << "--" << exe_as_subcmd << ' ' << brief_str << '\n'; - std::exit(0); - }); - brief_flag->group(""); // Hide from help. - -} - - -/// Print object dependencies to a stream. -/// @tparam T The list type. Nominally this is std::vector, but could be boost::container::devector<>. -/// @param obj_list The list of objects to interate over. -/// @param app Set to true to print dependencies from objects of type app, otherwise they are skipped. -/// @param lib Set to true to print dependencies from objects of type lib, otherwise they are skipped. -/// @param tst Set to true to print dependencies from objects of type tst, otherwise they are skipped. -template -inline void dump_obj_deps(const T& obj_list, bool app, bool lib, bool tst, std::ostream& os = std::cout) { - - // Print the types we are reporting. - os << "Displaying dependencies from entries of type:"; - if (app) - os << " app"; - if (lib) - os << " lib"; - if (tst) - os << " test"; - os << "\n"; - - // Then iterate over the elements - for (const auto& a : obj_list) { - // Get each object's dependencies. - const auto& list = a.dependencies(); - // Go to the next object if either the list is empty or the object type is not one intended for printing. - if (list.empty()) - continue; - switch (a.type()) { - case antler::project::object::type_t::app: - if (!app) - continue; - break; - case antler::project::object::type_t::lib: - if (!lib) - continue; - break; - case antler::project::object::type_t::test: - if (!tst) - continue; - break; - case antler::project::object::type_t::none: - case antler::project::object::type_t::any: - std::cerr << "Unexpected type: " << a.type() << " in object: " << a.name() << "\n"; - continue; - } - // Print the deps. - auto i = list.begin(); - os << " " << a.name() << " [" << a.type() << "]: " << i->name(); - for (++i; i != list.end(); ++i) - os << ", " << i->name(); - os << "\n"; - } -} - - -/// Print object dependencies to a stream. -/// @tparam T The list type. Nominally this is std::vector, but could be boost::container::devector<>. -/// @param obj_list The list of objects to interate over. -template -inline void dump_obj_deps(const T& obj_list, std::ostream& os = std::cout) { - dump_obj_deps(obj_list, true, true, true, os); -} - - -/// Ask the user "Is this is correct?" and report the result. -/// @param msg The message to print. -/// @return The result of the query: true indicates yes, correct; false indicates no, incorrect. -[[nodiscard]] inline bool is_this_correct(std::string_view msg = "Is this correct?") noexcept { - std::string yn = "x"; // yes or no? - while (yn != "y" && yn != "n") { - std::cout << msg << " [Y/n]" << std::flush; - std::getline(std::cin, yn); - for (auto& a : yn) - a = static_cast(tolower(a)); - if (yn.empty()) - yn = "y"; - } - return yn == "y"; -} - - -/// Test to see if an object (app/lib/test) name is valid. -/// @param s An object name. -/// @return true indicates s was valid; false otherwise. -[[nodiscard]] inline bool validate_name(std::string_view s) noexcept { - if (s.empty()) - return false; - for (auto a : s) { - if (!isalnum(a) && a != '_') // && a != '-' - return false; - } - return true; -} - - -/// Test to see if a hash is valid. -/// @param s The hash string to check. -/// @param byte_count Expected size of the hash in bytes. -/// @return true if s is `byte_count` bytes long and contains only valid hex values. -[[nodiscard]] inline bool validate_hash(std::string_view s, size_t byte_count = 32) noexcept { - if (s.size() != byte_count) - return false; - for (auto a : s) { - // Return false if any digit isn't a valid hex value. - if (!std::isxdigit(a)) { - return false; - } - } - return true; -} - - - -/// Ask the user for a string value. Use validator to validate the result. -/// @param label The driendly name/label/text to display in the prompt. -/// @param str Reference to the value to set. Comes in as default value. -/// @param validator A function to validate str with. -/// @param allow_empty Set to true to allow the user to clear the value. -template -inline void get_valid_string(std::string_view friendly_name, std::string& str, F&& validator, bool allow_empty = false) noexcept { - // Loop until return. - for (;;) { - // Start with the text to display to the user. - std::cout << "Enter " << friendly_name; - if (allow_empty) - std::cout << " (space to clear)"; - std::cout << ": [" << str << "]" << std::flush; - - // Get the input. - std::string temp; - std::getline(std::cin, temp); - // Test to see if the input was empty. - if (temp.empty()) { - // If it was, the default is taken if it exists, or empty if that's a valid option. - if (!str.empty() || allow_empty) - return; - } - else { - // We use a single space to clear the string... - if (temp == " ") { - // ...but only if allow empty is set. - if (allow_empty) { - str.clear(); - return; - } - } - // Otherwise, test to see if the string is valid. - else if (!temp.empty() && validator(temp)) { - str = temp; - return; - } - } - } -} - - -/// Ask the user for a string value. Do not validate result. -/// @param friendly_name The label/text to display in the prompt. -/// @param str Reference to the value to set. Comes in as default value. -/// @param allow_empty Set to true to allow the user to clear the value. -inline void get_string(std::string_view friendly_name, std::string& str, bool allow_empty = false) noexcept { - auto always_valid = [](std::string_view)->bool { return true; }; - return get_valid_string(friendly_name, str, always_valid, allow_empty); -} - - -/// Ask the user for a value. Validated with validate_name(). -/// @param friendly_name The label/text to display in the prompt. -/// @param name Reference to the value to set. Comes in as default value. -/// @param allow_empty Set to true to allow the user to clear the value. -inline void get_name(std::string_view friendly_name, std::string& name, bool allow_empty = false) noexcept { - return get_valid_string(friendly_name, name, validate_name, allow_empty); -} - - - -/// Ask the user for a hash. Validated with validate_hash(). -/// @param friendly_name The label/text to display in the prompt. -/// @param hash Reference to the value to set. Comes in as default value. -/// @param allow_empty Set to true to allow the user to clear the value. -inline void get_hash(std::string_view friendly_name, std::string& hash, bool allow_empty = false) noexcept { - auto validator = [](std::string_view s) -> bool { return validate_hash(s,32); }; - return get_valid_string(friendly_name, hash, validator, allow_empty); -} - - -/// Ask the user for a location value. Validated with antler::project::dependency::validate_location(). -/// @param friendly_name The label/text to display in the prompt. -/// @param loc Reference to the value to set. Comes in as default value. -/// @param allow_empty Set to true to allow the user to clear the value. -inline void get_loc(std::string_view friendly_name, std::string& loc, bool allow_empty = false) noexcept { - auto validator = [](std::string_view s) -> bool { return antler::project::dependency::validate_location(s); }; - return get_valid_string(friendly_name, loc, validator, allow_empty); -} - -/// Ask the user for a language value. -/// @param friendly_name The label/text to display in the prompt. -/// @param lang Reference to the language to set. -/// @param allow_empty Set to true to allow the user to "clear" the value (i.e. set the value to language::none). -inline void get_language(std::string_view friendly_name, antler::project::language& lang, bool allow_empty = false) noexcept { - auto validator = [allow_empty](std::string_view s) -> bool { - if(allow_empty && s == "none") - return true; - antler::project::language l = antler::project::to_language(s); - return l != antler::project::language::none; - }; - std::string temp = antler::project::to_string(lang); - get_valid_string(friendly_name, temp, validator, allow_empty); - lang = antler::project::to_language(temp); -} - - -/// Ask the user for a version value. -/// @param friendly_name The label/text to display in the prompt. -/// @param ver Reference to the version to set. -/// @param allow_empty Set to true to allow the user to clear the value. -inline void get_version(std::string_view friendly_name, antler::project::version& ver, bool allow_empty = false) noexcept { - std::string s = std::string{ver.raw()}; - get_string(friendly_name,s,allow_empty); - ver = s; -} diff --git a/aproj/aproj-update-dep.cpp b/aproj/aproj-update-dep.cpp deleted file mode 100644 index b6d0eb4..0000000 --- a/aproj/aproj-update-dep.cpp +++ /dev/null @@ -1,198 +0,0 @@ -/// @copyright See `LICENSE` in the root directory of this project. - -#include -#include -#include -#include - -#include - -#include - - -int main(int argc, char** argv) { - - std::filesystem::path path; - std::string obj_name; - std::string dep_name; - std::string dep_loc; - std::string dep_tag; - std::string dep_rel; - std::string dep_hash; - bool interactive=false; - - - // CLI setup. - - CLI::App cli{}; - common_init(cli, argv[0], "Update a dependency."); - - // Positional arguments: - cli.add_option("path", path, "This must be the path to `project.yaml` or the path containing it.")->required(); - cli.add_option("obj_name", obj_name, "The name of the object to receive the dependency `dep_name`."); - cli.add_option("dep_name", dep_name, "The name of the dependency to add to `obj_name`."); - cli.add_option("location", dep_loc, "Dependency location url, path, or github organization/repo shortcut."); - - // Optional arguments: - auto tag_opt = cli.add_option("--tag", dep_tag, "The github tag or commit hash; only valid when LOCATION is a github repository."); - //auto commit_opt = cli.add_option("--commit", dep_commit, "The github tag or commit hash; only valid when LOCATION is a github repository."); - //auto branch_opt = cli.add_option("--branch", dep_branch, "The github tag or commit hash; only valid when LOCATION is a github repository."); - cli.add_option("--rel,--release", dep_rel, "A github release version.")->excludes(tag_opt); - cli.add_option("--hash", dep_hash, "SHA256 hash; only valid when LOCATION gets an archive (i.e. *.tar.gz or similar)."); - // Option flag - cli.add_flag("--interactive", interactive, "Force interactive mode."); - - // Parse - CLI11_PARSE(cli,argc,argv); - - - // Load the project or exit. - auto proj = load_project_or_exit(cli,path); - - - // Get all the objects and their names. - const auto all_objs = proj.all_objects(); - - // Assuming interactive mode if dependency name was empty. - interactive |= dep_name.empty(); - bool first_time = true; - for (const auto loop=interactive; loop;) { // Interactive should be constant from here, this loop is exited via break. - - // Loop until user is satisfied. - // Set initial values if this is the first time through AND if we have obj and dep name alone. - if (first_time && !dep_name.empty() && dep_loc.empty() && dep_tag.empty() && dep_rel.empty() && dep_hash.empty()) { - // try to get object then try to get dep. - auto obj_vec = proj.object(obj_name); - if (!obj_vec.empty()) { - // Take the first dep in the list. - auto dep_opt = obj_vec[0].dependency(dep_name); - if (dep_opt) { - auto dep = *dep_opt; - dep_loc = dep.location(); - dep_tag = dep.tag(); - dep_rel = dep.release(); - dep_hash = dep.hash(); - } - } - } - first_time = false; - - // Show the user their selections if it should help them. - if (obj_name.empty() || dep_name.empty()) { - dump_obj_deps(all_objs); - } - else if(!obj_name.empty() && !dep_name.empty() && antler::project::dependency::validate_location(dep_loc, dep_tag, dep_rel, dep_hash)) { - // Get the object to operate on. - auto obj_vec = proj.object(obj_name); - - // If it doesn't exist, none of the existing values can be correct, so alert and jump straigt to individual queries. - if (obj_vec.empty()) - std::cerr << obj_name << " does not exist in project.\n"; - else { - - // We have values, so query the user if they want to apply. - std::cout - << '\n' - << "Object name (to update): " << obj_name << '\n' - << "Dependency name: " << dep_name << '\n' - << "Dependency location: " << dep_loc << '\n' - << "tag/commit hash: " << dep_tag << '\n' - << "release version: " << dep_rel << '\n' - << "SHA256 hash: " << dep_hash << '\n' - << '\n'; - - // Get object here and warn user if dep_name des NOT exists. - auto obj = obj_vec[0]; - if (!dep_name.empty() && !obj.dependency_exists(dep_name)) - std::cerr << dep_name << " does not exists for " << obj_name << " in project.\n"; - - if (is_this_correct()) // Correct, then break out to sanity check and apply! - break; - } - } - - auto old_obj_name = obj_name; - auto old_dep_name = dep_name; - - std::vector obj_vec; - // Get the object name. - { - auto validator = [&proj,&obj_vec](std::string_view s) { - if (!validate_name(s)) - return false; - if (obj_vec = proj.object(s); obj_vec.empty()) { - std::cerr << s << " does not exist in " << proj.name() << '\n'; - return false; - } - return true; - }; - get_valid_string("object (app/lib/test) name", obj_name, validator); - } - - // Get a valid dependency name here. - { - auto validator = [&obj_vec,&obj_name](std::string_view s) -> bool { - if (!validate_name(s)) - return false; - bool rv = false; - for (const auto& a: obj_vec) { - rv |= a.dependency_exists(s); - } - if(!rv) - std::cerr << s << " does not exists for " << obj_name << " in project.\n"; - return rv; - }; - get_valid_string("dependency name", dep_name, validator); - } - - // We should have obj and dep names, if they changed let's reload location etc. - if (old_obj_name != obj_name || old_dep_name != dep_name) { - // try to get object then try to get dep. - if (!obj_vec.empty()) { - auto dep_opt = obj_vec[0].dependency(dep_name); - if (dep_opt) { - auto dep = *dep_opt; - dep_loc = dep.location(); - dep_tag = dep.tag(); - dep_rel = dep.release(); - dep_hash = dep.hash(); - } - } - } - - get_loc("from/location", dep_loc, true); - get_name("git tag/commit hash", dep_tag, true); - get_name("git release version", dep_rel, true); - get_hash("SHA-256 hash", dep_hash, true); - } - - - // Done with interactive mode, validate and store data. - - - // Get the object to update. - auto obj_vec = proj.object(obj_name); - if (obj_vec.empty()) - return cli.exit( CLI::Error("", obj_name + " does not exist in project.") ); - auto obj = obj_vec[0]; - - // If we are not in interactive mode, test for the pre-existence of the dependency. - if (!interactive && obj.dependency_exists(dep_name)) - return cli.exit( CLI::Error("", dep_name + " already exists for " + obj_name + " in project.") ); - - // Validate the location. Redundant for interactive mode, but cheap in human time. - std::ostringstream ss; - if (!antler::project::dependency::validate_location(dep_loc, dep_tag, dep_rel, dep_hash, ss)) - return cli.exit( CLI::Error("", ss.str()) ); - - // Create the dependency, store it in the object, and store the object in the project. - antler::project::dependency dep; - dep.set(dep_name, dep_loc, dep_tag, dep_rel, dep_hash); - obj.upsert_dependency(std::move(dep)); - proj.upsert(std::move(obj)); - - // Sync the project to storage. - if (!proj.sync()) - return cli.exit( CLI::Error("", "failed to write project file.") ); - return 0; -} diff --git a/aproj/aproj-validate.cpp b/aproj/aproj-validate.cpp deleted file mode 100644 index 68b8b10..0000000 --- a/aproj/aproj-validate.cpp +++ /dev/null @@ -1,39 +0,0 @@ -/// @copyright See `LICENSE` in the root directory of this project. - -#include -#include -#include -#include - -#include - - -int main(int argc, char** argv) { - - std::filesystem::path path; - bool quiet = false; - - - // Setup CLI. - - CLI::App cli{}; - common_init(cli, argv[0], "Attempt to load a project.yaml file. This is the default command."); - - // Positional arguments: - cli.add_option("path", path, "This must be the path to `project.yaml` or the path containing it.")->required(); - - // Option flag - cli.add_flag("--quiet,-q", quiet, "Do NOT print the contents of the project.yaml file."); - - // Parse - CLI11_PARSE(cli,argc,argv); - - - // Load project or exit. - auto proj = load_project_or_exit(cli,path); - - if (!quiet) - std::cout << proj.to_yaml() << '\n'; - - return 0; -} diff --git a/aproj/aproj.cpp b/aproj/aproj.cpp index b78c193..3f0f989 100644 --- a/aproj/aproj.cpp +++ b/aproj/aproj.cpp @@ -15,11 +15,14 @@ #include // boost::dll::program_location() #include +#include #include "add_to.hpp" #include "init.hpp" #include "populate.hpp" #include "remove_from.hpp" +#include "update.hpp" +#include "validate.hpp" template static V depends(V&& v) { return std::forward(v); } @@ -52,10 +55,14 @@ int main(int argc, char** argv) { const auto app_name = std::filesystem::path(argv[0]).filename().string(); CLI::App app{app_name}; + antler::project::location::is_reachable("https://github.com"); + runner runner{app}; + antler::remove_from_project, + antler::update_project, + antler::validate_project> runner{app}; CLI11_PARSE(app, argc, argv); diff --git a/aproj/populate.hpp b/aproj/populate.hpp index 81794d1..8d0b610 100644 --- a/aproj/populate.hpp +++ b/aproj/populate.hpp @@ -29,7 +29,6 @@ namespace antler { CLI::App* subcommand; std::string path; - std::string name; - std::string version_raw; + bool verbose; }; } // namespace antler \ No newline at end of file diff --git a/aproj/remove_from.hpp b/aproj/remove_from.hpp index 66f1271..4070234 100644 --- a/aproj/remove_from.hpp +++ b/aproj/remove_from.hpp @@ -14,14 +14,16 @@ namespace antler { template bool remove_obj(antler::project::project& proj) { - if (!obj_name.empty()) { + if (obj_name.empty()) { std::cerr << "Name is empty" << std::endl; return false; } - std::cout << "Removing object: " << obj_name << " from the project." << std::endl; - - return proj.remove(obj_name, Ty); + if (proj.remove(obj_name, Ty)) { + std::cout << "Removing object: " << obj_name << " from the project." << std::endl; + return true; + } + return false; } inline bool remove_app(antler::project::project& proj) { return remove_obj(proj); } @@ -75,12 +77,10 @@ namespace antler { dep_subcommand->add_option("-d, dep", dep_name, "The name of the dependency.")->required(); dep_subcommand->add_option("-o, obj", obj_name, "The name of the object the dependency is attached to."); - //// Option flag - //auto all_flag = cli.add_flag("--all", rm_all, "Remove dep_name from all objects (implies --app, --lib, --test). Default option."); - //cli.add_flag("--app", rm_app, "Remove dep_name from application objects.")->excludes(all_flag); - //cli.add_flag("--lib", rm_lib, "Remove dep_name from library objects.")->excludes(all_flag); - //cli.add_flag("--test", rm_test, "Remove dep_name from test objects.")->excludes(all_flag); - //cli.add_flag("--interactive", interactive, "Force interactive mode."); + /* TODO Add back after this release when we have the testing framework finished + test_subcommand = subcommand->add_subcommand("test", "Remove a test from the project."); + test_subcommand->add_option("-n, name", dep_name, "The name of the test to remove.")->required(); + */ } @@ -100,7 +100,7 @@ namespace antler { remove_dependency(*proj); /* TODO Add back after this release when we have the testing framework finished } else if (*test_subcommand) { - add_test(*proj); + remove_test(*proj); */ } else { std::cerr << "Need to supply either dep/app/lib/test after `add`" << std::endl; diff --git a/aproj/update.hpp b/aproj/update.hpp new file mode 100644 index 0000000..2c56a1c --- /dev/null +++ b/aproj/update.hpp @@ -0,0 +1,180 @@ +#pragma once + +#include +#include + +#include + +#include + +#include "common.hpp" + +namespace antler { + struct update_project { + + template + bool update_obj(antler::project::project& proj, CLI::App& app) { + if (obj_name.empty()) { + std::cerr << "Name is empty" << std::endl; + return false; + } + + try { + auto& obj = proj.object(obj_name); + const auto* lang_opt = app.get_option_no_throw("language"); + const auto* opts_opt = app.get_option_no_throw("options"); + + + if (!lang_opt->empty()) + obj.language(lang); + + if (!opts_opt->empty()) + obj.options(opts); + + std::cout << "Updating object: " << obj_name << "\n" + << "language: " << obj.language() << "\n" + << "options: " << obj.options() << std::endl; + + //if (proj.remove(obj_name, Ty)) { + // std::cout << "Removing object: " << obj_name << " from the project." << std::endl; + // return true; + //} + return true; + } catch(...) { + std::cerr << "Object: " << obj_name << " does not exist." << std::endl; + } + return false; + } + + inline bool update_app(antler::project::project& proj) { return update_obj(proj, *app_subcommand); } + inline bool update_lib(antler::project::project& proj) { return update_obj(proj, *lib_subcommand); } + inline bool update_test(antler::project::project& proj) { return update_obj(proj, *test_subcommand); } + + bool update_dependency(antler::project::project& proj, std::string_view obj_n) { + const auto* loc_opt = dep_subcommand->get_option_no_throw("location"); + const auto* tag_opt = dep_subcommand->get_option_no_throw("tag"); + const auto* rel_opt = dep_subcommand->get_option_no_throw("release"); + const auto* dig_opt = dep_subcommand->get_option_no_throw("hash"); + + try { + auto& obj = proj.object(obj_n); + auto dep = obj.dependency(dep_name); + + if (!loc_opt->empty()) + dep->location(loc); + if (!tag_opt->empty()) + dep->tag(tag); + if (!rel_opt->empty()) + dep->release(release); + if (!dig_opt->empty()) + dep->hash(digest); + + if (dep) { + if (!dep->is_valid()) { + std::cerr << "Dependency: " << dep_name << " is invalid." << std::endl; + return false; + } + std::cout << "Updating dependency: " << dep_name << " for object: " << obj.name() << std::endl; + obj.upsert_dependency(std::move(*dep)); + return true; + } else { + std::cerr << "Dependency: " << dep_name << " not found in: " << obj_name << std::endl; + } + } catch(...) { + std::cerr << "Object: " << obj_name << " not found in project." << std::endl; + } + return false; + } + + bool update_dependency(antler::project::project& proj) { + const auto* obj_opt = dep_subcommand->get_option_no_throw("object"); + if (!obj_opt->empty()) { + return update_dependency(proj, obj_name); + } else { + // Get all the objects and their names. + auto& all_objs = proj.all_objects(); + for (auto& o : all_objs) { + if (o.dependency_exists(dep_name)) { + update_dependency(proj, o.name()); + } + } + } + return true; + } + + update_project(CLI::App& app) { + path = std::filesystem::current_path().string(); + subcommand = app.add_subcommand("update", "Update an app, dependency, library or test to your project."); + subcommand->add_option("-p, path", path, "This must be the path to the `project.yml` or the path containing it.")->required(); + + app_subcommand = subcommand->add_subcommand("app", "Remove app from the project."); + app_subcommand->add_option("-n, name", obj_name, "The name of the app to remove.")->required(); + app_subcommand->add_option("-l, language", lang, "The language of the app."); + app_subcommand->add_option("-o, options", opts, "The options used to build the app."); + + lib_subcommand = subcommand->add_subcommand("lib", "Remove lib from the project."); + lib_subcommand->add_option("-n, name", obj_name, "The name of the library to add.")->required(); + lib_subcommand->add_option("-l, language", lang, "The language of the lib."); + lib_subcommand->add_option("-o, options", opts, "The options used to build the lib."); + + dep_subcommand = subcommand->add_subcommand("dep", "Remove a dependency from the project."); + dep_subcommand->add_option("-d, dep", dep_name, "The name of the dependency.")->required(); + dep_subcommand->add_option("-o, object", obj_name, "The name of the object the dependency is attached to."); + dep_subcommand->add_option("-l, location", loc, "The location of the dependency."); + auto* tag_opt = dep_subcommand->add_option("-t, tag", tag, "The github tag or commit hash; only valid when LOCATION is a github repository."); + dep_subcommand->add_option("-r, release", release, "A github release version.")->excludes(tag_opt); + dep_subcommand->add_option("--digest, hash", digest, "SHA256 message digest; only valid when LOCATION gets an archive (i.e. *.tar.gz or similar)."); + + /* TODO Add back after this release when we have the testing framework finished + test_subcommand = subcommand->add_subcommand("test", "Remove a test from the project."); + test_subcommand->add_option("-n, name", dep_name, "The name of the test to remove.")->required(); + */ + + } + + int32_t exec() { + try { + auto proj = load_project(path); + + if (!proj) { + return -1; + } + + if (*app_subcommand) { + update_app(*proj); + } else if (*lib_subcommand) { + update_lib(*proj); + } else if (*dep_subcommand) { + update_dependency(*proj); + /* TODO Add back after this release when we have the testing framework finished + } else if (*test_subcommand) { + update_test(*proj); + */ + } else { + std::cerr << "Need to supply either dep/app/lib/test after `add`" << std::endl; + return -1; + } + + proj->sync(); + } catch (...) { + std::cerr << "Path <" << path << "> does not exist" << std::endl; + } + return 0; + } + + CLI::App* subcommand; + CLI::App* app_subcommand; + CLI::App* dep_subcommand; + CLI::App* lib_subcommand; + CLI::App* test_subcommand; + std::string path; + std::string obj_name; + std::string dep_name; + std::string lang; + std::string opts; + std::string loc; + std::string tag; + std::string release; + std::string digest; + }; +} // namespace antler \ No newline at end of file diff --git a/aproj/validate.hpp b/aproj/validate.hpp new file mode 100644 index 0000000..33e834f --- /dev/null +++ b/aproj/validate.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include +#include + +#include + +#include + +#include "common.hpp" + +namespace antler { + struct validate_project { + inline validate_project(CLI::App& app) { + subcommand = app.add_subcommand("validate", "Validate a project."); + subcommand->add_option("-p, path", path, "This is the path to the root of the project.")->required(); + subcommand->add_flag("-V, --verbose", verbose, "Verbose output."); + + } + + int32_t exec() { + try { + auto proj = load_project(path); + if (!proj) { + std::cerr << "Error validating project." << std::endl; + return -1; + } + if (verbose) { + std::cout << proj->to_yaml() << std::endl; + } else { + std::cout << "Valid format for an antler-proj project." << std::endl; + } + } catch(...) { + std::cerr << "Path: " << path << " not found." << std::endl; + return -1; + } + + return 0; + } + + CLI::App* subcommand; + std::string path; + bool verbose; + }; +} // namespace antler \ No newline at end of file diff --git a/project/antler/project/dependency.hpp b/project/antler/project/dependency.hpp index 80391b2..9c80c4d 100644 --- a/project/antler/project/dependency.hpp +++ b/project/antler/project/dependency.hpp @@ -95,6 +95,10 @@ class dependency { /// @param path The path to the patch file to remove. void patch_remove(const std::filesystem::path& path) noexcept; + /// Test to see if the dependency is valid. + /// @return true if dependency is an archive, github repo, or local and is reachable + [[nodiscard]] bool is_valid() const noexcept; + /// Test to see if a location is valid. /// @return true if s is an archive, a github repo, or an organization shorthand for a github repo. diff --git a/project/antler/project/location.hpp b/project/antler/project/location.hpp index 52c1c36..e7fd852 100644 --- a/project/antler/project/location.hpp +++ b/project/antler/project/location.hpp @@ -7,21 +7,27 @@ namespace antler::project::location { -/// @param s String to eveluate. -/// @return true if s looks like an archive (e.g. ends in `.tar.gz`, `.tar.bz2`, `.tar.zstd`). -[[nodiscard]] bool is_archive(std::string_view s); -/// @param s String to eveluate. -/// @return true if s looks like a github repository. -[[nodiscard]] bool is_github_repo(std::string_view s); -/// @param s String to eveluate. -/// @return true if s looks like an archive from github. -[[nodiscard]] bool is_github_archive(std::string_view s); -/// @param s String to eveluate. -/// @return true if s looks like a local file. -[[nodiscard]] bool is_local_file(std::string_view s); +/// @param l Location to evaluate. +/// @return true if l looks like an archive (e.g. ends in `.tar.gz`, `.tar.bz2`, `.tar.zstd`). +[[nodiscard]] bool is_archive(std::string_view l); +/// @param l Location to evaluate. +/// @return true if l looks like a github repository. +[[nodiscard]] bool is_github_repo(std::string_view l); +/// @param l Location to evaluate. +/// @return true if l looks like an archive from github. +[[nodiscard]] bool is_github_archive(std::string_view l); +/// @param l Location to evaluate. +/// @return true if l looks like a local file. +[[nodiscard]] bool is_local_file(std::string_view l); /// @note requires `gh` is installed and user is authenticated or the repo is public. -/// @param s String to eveluate. -/// @return true if s is a repo, calls `gh` to test. -[[nodiscard]] bool is_org_repo_shorthand(std::string_view s); +/// @param l Location to evaluate. +/// @return true if l is a repo, calls `gh` to test. +[[nodiscard]] bool is_github_org_repo_shorthand(std::string_view l); +/// @param l Location to evaluate. +/// @return true if l is reachable. +bool is_reachable(std::string_view l); +/// @param l Location to evaluate. +/// @return true if l looks like a url. +bool is_url(std::string_view l); } // namespace antler::project::location diff --git a/project/src/dependency.cpp b/project/src/dependency.cpp index 38ec600..7f8ecec 100644 --- a/project/src/dependency.cpp +++ b/project/src/dependency.cpp @@ -141,34 +141,27 @@ void dependency::tag(std::string_view s) noexcept { m_tag_or_commit = s; } +bool dependency::is_valid() const noexcept { + if (validate_location(m_loc)) { + //if (validate_location) + return true; + } else { + return false; + } +} bool dependency::validate_location(std::string_view s) { return location::is_archive(s) || location::is_github_repo(s) - || location::is_org_repo_shorthand(s) + || location::is_github_org_repo_shorthand(s) ; } bool dependency::validate_location(std::string_view loc, std::string_view tag, std::string_view rel, std::string_view hash, std::ostream& os) { - // If location is empty, everything else should be too. - if (loc.empty()) { - if (tag.empty() && rel.empty() && hash.empty()) - return true; - os << "If location is empty, then:"; - if (!tag.empty()) - os << " tag"; - if (!rel.empty()) - os << " release"; - if (!hash.empty()) - os << " hash"; - os << " must also be empty."; - return false; - } - if (!tag.empty()) { if (!rel.empty()) { os << "release AND tag/commit flags are not valid at the same time for location."; @@ -183,7 +176,7 @@ bool dependency::validate_location(std::string_view loc, std::string_view tag, s if (location::is_archive(loc)) { if (hash.empty()) os << "Warning: archive locations should have a SHA256 hash."; - } else if (location::is_github_repo(loc) || location::is_org_repo_shorthand(loc)) { + } else if (location::is_github_repo(loc) || location::is_github_org_repo_shorthand(loc)) { if (rel.empty() && tag.empty()) os << "Warning: github locations should have either a tag/commit or release field."; } else { diff --git a/project/src/location.cpp b/project/src/location.cpp index 267c32d..288c11f 100644 --- a/project/src/location.cpp +++ b/project/src/location.cpp @@ -14,36 +14,6 @@ namespace antler::project::location { -// object to encapsulate github api -struct github { - static size_t write_data(void* ptr, size_t sz, size_t nm, void* s) { - reinterpret_cast(s)->append(reinterpret_cast(ptr), sz*nm); - return sz*nm; - } - inline github() { - CURL* curl; - curl_global_init(CURL_GLOBAL_ALL); - curl = curl_easy_init(); - if (curl) { - curl_easy_setopt(curl, CURLOPT_URL, "https://ghcr.io/token?service=registry.docker.io&scope=repository:AntelopeIO/experimental-binaries:pull"); - //curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &github::write_data); - std::string buff; - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buff); - auto res = curl_easy_perform(curl); - if (res) { - std::cerr << curl_easy_strerror(res) << std::endl; - throw std::runtime_error("internal curl failure"); - } - curl_easy_cleanup(curl); - bearer_token = buff.substr(buff.find(":\"")+2); - bearer_token = bearer_token.substr(0, bearer_token.size()-3); - } - } - - std::string bearer_token; -}; - // TODO replace later with string_view.ends_with() and string_view.starts_with() static inline bool ends_with(std::string_view src, std::string_view comp) { const std::size_t sz = comp.size(); @@ -61,6 +31,111 @@ static inline bool starts_with(std::string_view src, std::string_view comp) { return memcmp(&src[0], &comp[0], sz) == 0; } +// object to encapsulate curl +struct curl { + inline curl() { + curl_global_init(CURL_GLOBAL_ALL); + curl_obj = curl_easy_init(); + if (!curl_obj) + throw std::runtime_error("internal error, curl not initialized"); + } + + ~curl() { + curl_easy_cleanup(curl_obj); + } + + static std::size_t write_data(void* ptr, size_t sz, size_t nm, void* s) { + reinterpret_cast(s)->append(reinterpret_cast(ptr), sz*nm); + return sz*nm; + } + + std::string authenticated_request(std::string_view token, std::string_view url) { + curl_easy_setopt(curl_obj, CURLOPT_XOAUTH2_BEARER, token.data()); + return request(url); + } + + std::string request(std::string_view url) { + curl_easy_setopt(curl_obj, CURLOPT_URL, url.data()); + curl_easy_setopt(curl_obj, CURLOPT_WRITEFUNCTION, &curl::write_data); + curl_easy_setopt(curl_obj, CURLOPT_USERAGENT, "antler-proj"); + + std::string buff; + + curl_easy_setopt(curl_obj, CURLOPT_WRITEDATA, &buff); + + auto res = curl_easy_perform(curl_obj); + if (res) { + std::cerr << "Failure: " << curl_easy_strerror(res) << std::endl; + throw std::runtime_error("internal curl failure"); + } + return buff; + } + + inline static bool is_url(std::string_view url) { return starts_with(url, "http:") || starts_with(url, "https:"); } + inline bool is_reachable(std::string_view url) { + try { + (void)request(url); + return true; + } catch(...) { + return false; + } + } + + CURL* curl_obj; +}; + +// object to encapsulate github api +struct github { + inline github() = default; + + std::string request(std::string_view org, std::string_view repo) { + bearer_token = sender.request("https://ghcr.io/token?service=registry.docker.io&scope=repository:AntelopeIO/experimental-binaries:pull"); + bearer_token = bearer_token.substr(bearer_token.find(":\"")+2); + bearer_token = bearer_token.substr(0, bearer_token.size()-3); + + std::string url = "https://api.github.com/repos/"; + url += std::string(org) + std::string("/"); + url += repo; + auto s = sender.authenticated_request(bearer_token, url.c_str()); + std::cout << s << std::endl; + return s; + } + + inline std::string request(std::string_view s) { return request(get_org(s), get_repo(s)); } + + inline bool is_reachable(std::string_view s) { + try { + (void)request(s); + return true; + } catch(...) { + return false; + } + } + + static std::string_view get_org(std::string_view s) { + auto sub = s.substr(0, s.find_last_of("/")); + auto pos = sub.find_last_of("/"); + return pos == std::string_view::npos ? + sub : + sub.substr(pos+1); + } + + static std::string_view get_repo(std::string_view s) { + auto pos = s.find_last_of("/"); + return s.substr(pos+1); + } + + static inline bool is_shorthand(std::string_view s) { + auto sub = s.substr(0, s.find_last_of("/")); + return sub.find_last_of("/") == std::string_view::npos; + } + + curl sender; + std::string bearer_token; +}; + + + bool is_archive(std::string_view s) { return ends_with(s, ".tar.gz") || ends_with(s, ".tgz") || @@ -69,39 +144,28 @@ bool is_archive(std::string_view s) { ends_with(s, ".tar.zst"); } -inline bool is_github(std::string_view s) { return starts_with(s, "https://github.com"); } +static inline bool is_github(std::string_view s) { return starts_with(s, "https://github.com"); } bool is_github_archive(std::string_view s) { return is_github(s) && is_archive(s); } -bool is_github_repo(std::string_view s) { return is_github(s) && !is_archive(s); } - -bool is_local_file(std::string_view s) { - - if (is_github(s)) - return false; - - if (starts_with(s, "file://")) - s = s.substr(7); +bool is_url(std::string_view l) { return curl::is_url(l); } - std::error_code sec; - return std::filesystem::exists(s, sec); -} +bool is_local_file(std::string_view s) { return std::filesystem::exists(s); } +bool is_github_org_repo_shorthand(std::string_view s) { return github::is_shorthand(s); } -bool is_org_repo_shorthand(std::string_view s) { +bool is_github_repo(std::string_view s) { return is_github(s) && !is_archive(s); } - std::vector splits; - boost::split(splits, s, [](const char c) { return c == '/'; }); - if (splits.size() != 2) +bool is_reachable(std::string_view l) { + if (is_archive(l) || is_url(l) || is_github_archive(l)) { + return curl{}.is_reachable(l); + } else if (is_github_repo(l) || is_github_org_repo_shorthand(l)) { + return github{}.is_reachable(l); + } else if (is_local_file(l)) { + return true; + } else { return false; - - // Set ss to the command string. - std::ostringstream ss; - ss << "gh repo view " << s; - - // Call the command, test the result. - return system::exec(ss.str()).return_code != 0; + } } - } // namespace antler::project::location From 20f2029c28eedd6655642dc003a081fb30be8d67 Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Fri, 3 Mar 2023 01:54:40 -0500 Subject: [PATCH 05/14] restructuring, remove unneeded code, refactoring to resolve some bugs --- .gitmodules | 9 + 3p_include/magic_enum.hpp | 1533 - 3p_include/ryml.hpp | 33663 ---------------- CMakeLists.txt | 7 +- aproj/CMakeLists.txt | 48 - common.cmake | 36 +- external/CMakeLists.txt | 6 + external/json | 1 + external/magic_enum | 1 + external/rapidyaml | 1 + include/CMakeLists.txt | 2 + .../antler/project/dependency.hpp | 2 +- .../antler/project/location.hpp | 2 +- .../antler/project/object.hpp | 3 +- .../antler/project/project.hpp | 24 +- .../antler/project/semver.hpp | 2 +- .../antler/project/version.hpp | 10 +- .../antler/project/version_compare.hpp | 1 - .../antler/project/version_constraint.hpp | 2 +- {project => include}/antler/string/from.hpp | 0 {project => include}/antler/system/exec.hpp | 0 include/antler/system/preprocessor.hpp | 9 + .../antler/system/version.hpp.in | 0 project/CMakeLists.txt | 43 - project/antler/project/language.hpp | 50 - project/src/key.cpp | 37 - project/src/key.hpp | 67 - project/src/language.cpp | 63 - src/CMakeLists.txt | 23 + {project/src => src}/cmake.cpp | 3 +- {project/src => src}/cmake.hpp | 0 {project/src => src}/dependency.cpp | 29 +- {project/src => src}/exec.cpp | 0 {project/src => src}/location.cpp | 12 +- {project/src => src}/object.cpp | 0 {project/src => src}/project-is_valid.cpp | 0 {project/src => src}/project-parse.cpp | 200 +- {project/src => src}/project-populate.cpp | 8 +- {project/src => src}/project-print.cpp | 90 +- {project/src => src}/project.cpp | 1 - {project/src => src}/ryml.cpp | 0 {project/src => src}/semver.cpp | 0 src/token.hpp | 45 + src/token_def.hpp | 24 + {project/src => src}/version.cpp | 20 +- {project/src => src}/version_compare.cpp | 9 +- {project/src => src}/version_constraint.cpp | 0 test/CMakeLists.txt | 2 +- {3p_include => tools}/CLI11.hpp | 2 +- tools/CMakeLists.txt | 10 + {aproj => tools}/README.md | 0 {aproj => tools}/add_to.hpp | 0 {aproj => tools}/common.hpp | 4 +- {aproj => tools}/init.hpp | 0 aproj/aproj.cpp => tools/main.cpp | 7 +- {aproj => tools}/populate.hpp | 4 +- {aproj => tools}/remove_from.hpp | 0 {aproj => tools}/update.hpp | 0 {aproj => tools}/validate.hpp | 0 59 files changed, 331 insertions(+), 35784 deletions(-) create mode 100644 .gitmodules delete mode 100644 3p_include/magic_enum.hpp delete mode 100644 3p_include/ryml.hpp delete mode 100644 aproj/CMakeLists.txt create mode 100644 external/CMakeLists.txt create mode 160000 external/json create mode 160000 external/magic_enum create mode 160000 external/rapidyaml create mode 100644 include/CMakeLists.txt rename {project => include}/antler/project/dependency.hpp (99%) rename {project => include}/antler/project/location.hpp (95%) rename {project => include}/antler/project/object.hpp (98%) rename {project => include}/antler/project/project.hpp (89%) rename {project => include}/antler/project/semver.hpp (98%) rename {project => include}/antler/project/version.hpp (92%) rename {project => include}/antler/project/version_compare.hpp (99%) rename {project => include}/antler/project/version_constraint.hpp (98%) rename {project => include}/antler/string/from.hpp (100%) rename {project => include}/antler/system/exec.hpp (100%) create mode 100644 include/antler/system/preprocessor.hpp rename {project => include}/antler/system/version.hpp.in (100%) delete mode 100644 project/CMakeLists.txt delete mode 100644 project/antler/project/language.hpp delete mode 100644 project/src/key.cpp delete mode 100644 project/src/key.hpp delete mode 100644 project/src/language.cpp create mode 100644 src/CMakeLists.txt rename {project/src => src}/cmake.cpp (98%) rename {project/src => src}/cmake.hpp (100%) rename {project/src => src}/dependency.cpp (87%) rename {project/src => src}/exec.cpp (100%) rename {project/src => src}/location.cpp (93%) rename {project/src => src}/object.cpp (100%) rename {project/src => src}/project-is_valid.cpp (100%) rename {project/src => src}/project-parse.cpp (69%) rename {project/src => src}/project-populate.cpp (94%) rename {project/src => src}/project-print.cpp (60%) rename {project/src => src}/project.cpp (99%) rename {project/src => src}/ryml.cpp (100%) rename {project/src => src}/semver.cpp (100%) create mode 100644 src/token.hpp create mode 100644 src/token_def.hpp rename {project/src => src}/version.cpp (87%) rename {project/src => src}/version_compare.cpp (86%) rename {project/src => src}/version_constraint.cpp (100%) rename {3p_include => tools}/CLI11.hpp (99%) create mode 100644 tools/CMakeLists.txt rename {aproj => tools}/README.md (100%) rename {aproj => tools}/add_to.hpp (100%) rename {aproj => tools}/common.hpp (74%) rename {aproj => tools}/init.hpp (100%) rename aproj/aproj.cpp => tools/main.cpp (81%) rename {aproj => tools}/populate.hpp (81%) rename {aproj => tools}/remove_from.hpp (100%) rename {aproj => tools}/update.hpp (100%) rename {aproj => tools}/validate.hpp (100%) diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..1996e10 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "external/rapidyaml"] + path = external/rapidyaml + url = https://github.com/biojppm/rapidyaml +[submodule "external/magic_enum"] + path = external/magic_enum + url = https://github.com/Neargye/magic_enum +[submodule "external/json"] + path = external/json + url = https://github.com/boostorg/json diff --git a/3p_include/magic_enum.hpp b/3p_include/magic_enum.hpp deleted file mode 100644 index b7ba3c7..0000000 --- a/3p_include/magic_enum.hpp +++ /dev/null @@ -1,1533 +0,0 @@ -// Sourced from: https://github.com/Neargye/magic_enum/releases/download/v0.8.2/magic_enum.hpp - -// __ __ _ ______ _____ -// | \/ | (_) | ____| / ____|_ _ -// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ -// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| -// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| -// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| -// __/ | https://github.com/Neargye/magic_enum -// |___/ version 0.8.2 -// -// Licensed under the MIT License . -// SPDX-License-Identifier: MIT -// Copyright (c) 2019 - 2022 Daniil Goncharov . -// -// 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 NEARGYE_MAGIC_ENUM_HPP -#define NEARGYE_MAGIC_ENUM_HPP - -#define MAGIC_ENUM_VERSION_MAJOR 0 -#define MAGIC_ENUM_VERSION_MINOR 8 -#define MAGIC_ENUM_VERSION_PATCH 2 - -#include -#include -#include -#include -#include -#include -#include - -#if defined(MAGIC_ENUM_CONFIG_FILE) -#include MAGIC_ENUM_CONFIG_FILE -#endif - -#if !defined(MAGIC_ENUM_USING_ALIAS_OPTIONAL) -#include -#endif -#if !defined(MAGIC_ENUM_USING_ALIAS_STRING) -#include -#endif -#if !defined(MAGIC_ENUM_USING_ALIAS_STRING_VIEW) -#include -#endif -#if !defined(MAGIC_ENUM_NO_STREAMS) -#include -#endif - -#if defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wunknown-warning-option" -# pragma clang diagnostic ignored "-Wenum-constexpr-conversion" -#elif defined(__GNUC__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wmaybe-uninitialized" // May be used uninitialized 'return {};'. -#elif defined(_MSC_VER) -# pragma warning(push) -# pragma warning(disable : 26495) // Variable 'static_string::chars_' is uninitialized. -# pragma warning(disable : 28020) // Arithmetic overflow: Using operator '-' on a 4 byte value and then casting the result to a 8 byte value. -# pragma warning(disable : 26451) // The expression '0<=_Param_(1)&&_Param_(1)<=1-1' is not true at this call. -# pragma warning(disable : 4514) // Unreferenced inline function has been removed. -#endif - -// Checks magic_enum compiler compatibility. -#if defined(__clang__) && __clang_major__ >= 5 || defined(__GNUC__) && __GNUC__ >= 9 || defined(_MSC_VER) && _MSC_VER >= 1910 -# undef MAGIC_ENUM_SUPPORTED -# define MAGIC_ENUM_SUPPORTED 1 -#endif - -// Checks magic_enum compiler aliases compatibility. -#if defined(__clang__) && __clang_major__ >= 5 || defined(__GNUC__) && __GNUC__ >= 9 || defined(_MSC_VER) && _MSC_VER >= 1920 -# undef MAGIC_ENUM_SUPPORTED_ALIASES -# define MAGIC_ENUM_SUPPORTED_ALIASES 1 -#endif - -// Enum value must be greater or equals than MAGIC_ENUM_RANGE_MIN. By default MAGIC_ENUM_RANGE_MIN = -128. -// If need another min range for all enum types by default, redefine the macro MAGIC_ENUM_RANGE_MIN. -#if !defined(MAGIC_ENUM_RANGE_MIN) -# define MAGIC_ENUM_RANGE_MIN -128 -#endif - -// Enum value must be less or equals than MAGIC_ENUM_RANGE_MAX. By default MAGIC_ENUM_RANGE_MAX = 128. -// If need another max range for all enum types by default, redefine the macro MAGIC_ENUM_RANGE_MAX. -#if !defined(MAGIC_ENUM_RANGE_MAX) -# define MAGIC_ENUM_RANGE_MAX 128 -#endif - -namespace magic_enum { - -// If need another optional type, define the macro MAGIC_ENUM_USING_ALIAS_OPTIONAL. -#if defined(MAGIC_ENUM_USING_ALIAS_OPTIONAL) -MAGIC_ENUM_USING_ALIAS_OPTIONAL -#else -using std::optional; -#endif - -// If need another string_view type, define the macro MAGIC_ENUM_USING_ALIAS_STRING_VIEW. -#if defined(MAGIC_ENUM_USING_ALIAS_STRING_VIEW) -MAGIC_ENUM_USING_ALIAS_STRING_VIEW -#else -using std::string_view; -#endif - -// If need another string type, define the macro MAGIC_ENUM_USING_ALIAS_STRING. -#if defined(MAGIC_ENUM_USING_ALIAS_STRING) -MAGIC_ENUM_USING_ALIAS_STRING -#else -using std::string; -#endif - -namespace customize { - -// Enum value must be in range [MAGIC_ENUM_RANGE_MIN, MAGIC_ENUM_RANGE_MAX]. By default MAGIC_ENUM_RANGE_MIN = -128, MAGIC_ENUM_RANGE_MAX = 128. -// If need another range for all enum types by default, redefine the macro MAGIC_ENUM_RANGE_MIN and MAGIC_ENUM_RANGE_MAX. -// If need another range for specific enum type, add specialization enum_range for necessary enum type. -template -struct enum_range { - static_assert(std::is_enum_v, "magic_enum::customize::enum_range requires enum type."); - static constexpr int min = MAGIC_ENUM_RANGE_MIN; - static constexpr int max = MAGIC_ENUM_RANGE_MAX; - static_assert(max > min, "magic_enum::customize::enum_range requires max > min."); -}; - -static_assert(MAGIC_ENUM_RANGE_MAX > MAGIC_ENUM_RANGE_MIN, "MAGIC_ENUM_RANGE_MAX must be greater than MAGIC_ENUM_RANGE_MIN."); -static_assert((MAGIC_ENUM_RANGE_MAX - MAGIC_ENUM_RANGE_MIN) < (std::numeric_limits::max)(), "MAGIC_ENUM_RANGE must be less than UINT16_MAX."); - -namespace detail { - -enum class customize_tag { - default_tag, - invalid_tag, - custom_tag -}; - -} // namespace magic_enum::customize::detail - -class customize_t : public std::pair { - public: - constexpr customize_t(string_view srt) : std::pair{detail::customize_tag::custom_tag, srt} {} - constexpr customize_t(const char* srt) : customize_t{string_view{srt}} {} - constexpr customize_t(detail::customize_tag tag) : std::pair{tag, string_view{}} { - assert(tag != detail::customize_tag::custom_tag); - } -}; - -// Default customize. -inline constexpr auto default_tag = customize_t{detail::customize_tag::default_tag}; -// Invalid customize. -inline constexpr auto invalid_tag = customize_t{detail::customize_tag::invalid_tag}; - -// If need custom names for enum, add specialization enum_name for necessary enum type. -template -constexpr customize_t enum_name(E) noexcept { - return default_tag; -} - -// If need custom type name for enum, add specialization enum_type_name for necessary enum type. -template -constexpr customize_t enum_type_name() noexcept { - return default_tag; -} - -} // namespace magic_enum::customize - -namespace detail { - -template , std::enable_if_t, int> = 0> -using enum_constant = std::integral_constant; - -enum class value_type { - default_value, - flags_value -}; - -template -inline constexpr bool always_false_v = false; - -template -struct supported -#if defined(MAGIC_ENUM_SUPPORTED) && MAGIC_ENUM_SUPPORTED || defined(MAGIC_ENUM_NO_CHECK_SUPPORT) - : std::true_type {}; -#else - : std::false_type {}; -#endif - -template -struct has_is_flags : std::false_type {}; - -template -struct has_is_flags::is_flags)>> : std::bool_constant::is_flags)>>> {}; - -template -struct range_min : std::integral_constant {}; - -template -struct range_min::min)>> : std::integral_constant::min), customize::enum_range::min> {}; - -template -struct range_max : std::integral_constant {}; - -template -struct range_max::max)>> : std::integral_constant::max), customize::enum_range::max> {}; - -template -class static_string { - public: - constexpr explicit static_string(string_view str) noexcept : static_string{str, std::make_integer_sequence{}} { - assert(str.size() == N); - } - - constexpr const char* data() const noexcept { return chars_; } - - constexpr std::uint16_t size() const noexcept { return N; } - - constexpr operator string_view() const noexcept { return {data(), size()}; } - - private: - template - constexpr static_string(string_view str, std::integer_sequence) noexcept : chars_{str[I]..., '\0'} {} - - char chars_[static_cast(N) + 1]; -}; - -template <> -class static_string<0> { - public: - constexpr explicit static_string() = default; - - constexpr explicit static_string(string_view) noexcept {} - - constexpr const char* data() const noexcept { return nullptr; } - - constexpr std::uint16_t size() const noexcept { return 0; } - - constexpr operator string_view() const noexcept { return {}; } -}; - -constexpr string_view pretty_name(string_view name) noexcept { - for (std::size_t i = name.size(); i > 0; --i) { - if (!((name[i - 1] >= '0' && name[i - 1] <= '9') || - (name[i - 1] >= 'a' && name[i - 1] <= 'z') || - (name[i - 1] >= 'A' && name[i - 1] <= 'Z') || -#if defined(MAGIC_ENUM_ENABLE_NONASCII) - (name[i - 1] & 0x80) || -#endif - (name[i - 1] == '_'))) { - name.remove_prefix(i); - break; - } - } - - if (name.size() > 0 && ((name[0] >= 'a' && name[0] <= 'z') || - (name[0] >= 'A' && name[0] <= 'Z') || -#if defined(MAGIC_ENUM_ENABLE_NONASCII) - (name[0]) & 0x80) || -#endif - (name[0] == '_'))) { - return name; - } - return {}; // Invalid name. -} - -class case_insensitive { - static constexpr char to_lower(char c) noexcept { - return (c >= 'A' && c <= 'Z') ? static_cast(c + ('a' - 'A')) : c; - } - - public: - template - constexpr auto operator()([[maybe_unused]] L lhs, [[maybe_unused]] R rhs) const noexcept -> std::enable_if_t, char> && std::is_same_v, char>, bool> { -#if defined(MAGIC_ENUM_ENABLE_NONASCII) - static_assert(always_false_v, "magic_enum::case_insensitive not supported Non-ASCII feature."); - return false; -#else - return to_lower(lhs) == to_lower(rhs); -#endif - } -}; - -constexpr std::size_t find(string_view str, char c) noexcept { -#if defined(__clang__) && __clang_major__ < 9 && defined(__GLIBCXX__) || defined(_MSC_VER) && _MSC_VER < 1920 && !defined(__clang__) -// https://stackoverflow.com/questions/56484834/constexpr-stdstring-viewfind-last-of-doesnt-work-on-clang-8-with-libstdc -// https://developercommunity.visualstudio.com/content/problem/360432/vs20178-regression-c-failed-in-test.html - constexpr bool workaround = true; -#else - constexpr bool workaround = false; -#endif - - if constexpr (workaround) { - for (std::size_t i = 0; i < str.size(); ++i) { - if (str[i] == c) { - return i; - } - } - - return string_view::npos; - } else { - return str.find(c); - } -} - -template -constexpr std::array, N> to_array(T (&a)[N], std::index_sequence) noexcept { - return {{a[I]...}}; -} - -template -constexpr bool is_default_predicate() noexcept { - return std::is_same_v, std::equal_to> || - std::is_same_v, std::equal_to<>>; -} - -template -constexpr bool is_nothrow_invocable() { - return is_default_predicate() || - std::is_nothrow_invocable_r_v; -} - -template -constexpr bool cmp_equal(string_view lhs, string_view rhs, [[maybe_unused]] BinaryPredicate&& p) noexcept(is_nothrow_invocable()) { -#if defined(_MSC_VER) && _MSC_VER < 1920 && !defined(__clang__) - // https://developercommunity.visualstudio.com/content/problem/360432/vs20178-regression-c-failed-in-test.html - // https://developercommunity.visualstudio.com/content/problem/232218/c-constexpr-string-view.html - constexpr bool workaround = true; -#else - constexpr bool workaround = false; -#endif - - if constexpr (!is_default_predicate() || workaround) { - if (lhs.size() != rhs.size()) { - return false; - } - - const auto size = lhs.size(); - for (std::size_t i = 0; i < size; ++i) { - if (!p(lhs[i], rhs[i])) { - return false; - } - } - - return true; - } else { - return lhs == rhs; - } -} - -template -constexpr bool cmp_less(L lhs, R rhs) noexcept { - static_assert(std::is_integral_v && std::is_integral_v, "magic_enum::detail::cmp_less requires integral type."); - - if constexpr (std::is_signed_v == std::is_signed_v) { - // If same signedness (both signed or both unsigned). - return lhs < rhs; - } else if constexpr (std::is_same_v) { // bool special case - return static_cast(lhs) < rhs; - } else if constexpr (std::is_same_v) { // bool special case - return lhs < static_cast(rhs); - } else if constexpr (std::is_signed_v) { - // If 'right' is negative, then result is 'false', otherwise cast & compare. - return rhs > 0 && lhs < static_cast>(rhs); - } else { - // If 'left' is negative, then result is 'true', otherwise cast & compare. - return lhs < 0 || static_cast>(lhs) < rhs; - } -} - -template -constexpr I log2(I value) noexcept { - static_assert(std::is_integral_v, "magic_enum::detail::log2 requires integral type."); - - if constexpr (std::is_same_v) { // bool special case - return assert(false), value; - } else { - auto ret = I{0}; - for (; value > I{1}; value >>= I{1}, ++ret) {} - - return ret; - } -} - -template -inline constexpr bool is_enum_v = std::is_enum_v && std::is_same_v>; - -template -constexpr auto n() noexcept { - static_assert(is_enum_v, "magic_enum::detail::n requires enum type."); - - if constexpr (supported::value) { -#if defined(__clang__) || defined(__GNUC__) - constexpr auto name = pretty_name({__PRETTY_FUNCTION__, sizeof(__PRETTY_FUNCTION__) - 2}); -#elif defined(_MSC_VER) - constexpr auto name = pretty_name({__FUNCSIG__, sizeof(__FUNCSIG__) - 17}); -#else - constexpr auto name = string_view{}; -#endif - return name; - } else { - return string_view{}; // Unsupported compiler or Invalid customize. - } -} - -template -constexpr auto type_name() noexcept { - static_assert(is_enum_v, "magic_enum::detail::type_name requires enum type."); - - [[maybe_unused]] constexpr auto custom = customize::enum_type_name(); - static_assert(std::is_same_v, customize::customize_t>, "magic_enum::customize requires customize_t type."); - if constexpr (custom.first == customize::detail::customize_tag::custom_tag) { - constexpr auto name = custom.second; - static_assert(!name.empty(), "magic_enum::customize requires not empty string."); - return static_string{name}; - } else if constexpr (custom.first == customize::detail::customize_tag::invalid_tag) { - return static_string<0>{}; - } else if constexpr (custom.first == customize::detail::customize_tag::default_tag) { - constexpr auto name = n(); - return static_string{name}; - } else { - static_assert(detail::always_false_v, "magic_enum::customize invalid."); - } -} - -template -inline constexpr auto type_name_v = type_name(); - -template -constexpr auto n() noexcept { - static_assert(is_enum_v, "magic_enum::detail::n requires enum type."); - - if constexpr (supported::value) { -#if defined(__clang__) || defined(__GNUC__) - constexpr auto name = pretty_name({__PRETTY_FUNCTION__, sizeof(__PRETTY_FUNCTION__) - 2}); -#elif defined(_MSC_VER) - constexpr auto name = pretty_name({__FUNCSIG__, sizeof(__FUNCSIG__) - 17}); -#else - constexpr auto name = string_view{}; -#endif - return name; - } else { - return string_view{}; // Unsupported compiler or Invalid customize. - } -} - -template -constexpr auto enum_name() noexcept { - static_assert(is_enum_v, "magic_enum::detail::enum_name requires enum type."); - - [[maybe_unused]] constexpr auto custom = customize::enum_name(V); - static_assert(std::is_same_v, customize::customize_t>, "magic_enum::customize requires customize_t type."); - if constexpr (custom.first == customize::detail::customize_tag::custom_tag) { - constexpr auto name = custom.second; - static_assert(!name.empty(), "magic_enum::customize requires not empty string."); - return static_string{name}; - } else if constexpr (custom.first == customize::detail::customize_tag::invalid_tag) { - return static_string<0>{}; - } else if constexpr (custom.first == customize::detail::customize_tag::default_tag) { - constexpr auto name = n(); - return static_string{name}; - } else { - static_assert(detail::always_false_v, "magic_enum::customize invalid."); - } -} - -template -inline constexpr auto enum_name_v = enum_name(); - -template -constexpr bool is_valid() noexcept { - static_assert(is_enum_v, "magic_enum::detail::is_valid requires enum type."); - -#if defined(__clang__) && __clang_major__ >= 16 - // https://reviews.llvm.org/D130058, https://reviews.llvm.org/D131307 - constexpr E v = __builtin_bit_cast(E, V); -#else - constexpr E v = static_cast(V); -#endif - [[maybe_unused]] constexpr auto custom = customize::enum_name(v); - static_assert(std::is_same_v, customize::customize_t>, "magic_enum::customize requires customize_t type."); - if constexpr (custom.first == customize::detail::customize_tag::custom_tag) { - constexpr auto name = custom.second; - static_assert(!name.empty(), "magic_enum::customize requires not empty string."); - return name.size() != 0; - } else if constexpr (custom.first == customize::detail::customize_tag::default_tag) { - return n().size() != 0; - } else { - return false; - } -} - -template > -constexpr U ualue(std::size_t i) noexcept { - static_assert(is_enum_v, "magic_enum::detail::ualue requires enum type."); - - if constexpr (std::is_same_v) { // bool special case - static_assert(O == 0, "magic_enum::detail::ualue requires valid offset."); - - return static_cast(i); - } else if constexpr (IsFlags) { - return static_cast(U{1} << static_cast(static_cast(i) + O)); - } else { - return static_cast(static_cast(i) + O); - } -} - -template > -constexpr E value(std::size_t i) noexcept { - static_assert(is_enum_v, "magic_enum::detail::value requires enum type."); - - return static_cast(ualue(i)); -} - -template > -constexpr int reflected_min() noexcept { - static_assert(is_enum_v, "magic_enum::detail::reflected_min requires enum type."); - - if constexpr (IsFlags) { - return 0; - } else { - constexpr auto lhs = range_min::value; - constexpr auto rhs = (std::numeric_limits::min)(); - - if constexpr (cmp_less(rhs, lhs)) { - return lhs; - } else { - return rhs; - } - } -} - -template > -constexpr int reflected_max() noexcept { - static_assert(is_enum_v, "magic_enum::detail::reflected_max requires enum type."); - - if constexpr (IsFlags) { - return std::numeric_limits::digits - 1; - } else { - constexpr auto lhs = range_max::value; - constexpr auto rhs = (std::numeric_limits::max)(); - - if constexpr (cmp_less(lhs, rhs)) { - return lhs; - } else { - return rhs; - } - } -} - -template -inline constexpr auto reflected_min_v = reflected_min(); - -template -inline constexpr auto reflected_max_v = reflected_max(); - -template -constexpr std::size_t values_count(const bool (&valid)[N]) noexcept { - auto count = std::size_t{0}; - for (std::size_t i = 0; i < N; ++i) { - if (valid[i]) { - ++count; - } - } - - return count; -} - -template -constexpr auto values(std::index_sequence) noexcept { - static_assert(is_enum_v, "magic_enum::detail::values requires enum type."); - constexpr bool valid[sizeof...(I)] = {is_valid(I)>()...}; - constexpr std::size_t count = values_count(valid); - - if constexpr (count > 0) { - E values[count] = {}; - for (std::size_t i = 0, v = 0; v < count; ++i) { - if (valid[i]) { - values[v++] = value(i); - } - } - - return to_array(values, std::make_index_sequence{}); - } else { - return std::array{}; - } -} - -template > -constexpr auto values() noexcept { - static_assert(is_enum_v, "magic_enum::detail::values requires enum type."); - constexpr auto min = reflected_min_v; - constexpr auto max = reflected_max_v; - constexpr auto range_size = max - min + 1; - static_assert(range_size > 0, "magic_enum::enum_range requires valid size."); - static_assert(range_size < (std::numeric_limits::max)(), "magic_enum::enum_range requires valid size."); - - return values(std::make_index_sequence{}); -} - -template > -constexpr bool is_flags_enum() noexcept { - static_assert(is_enum_v, "magic_enum::detail::is_flags_enum requires enum type."); - - if constexpr (has_is_flags::value) { - return customize::enum_range::is_flags; - } else if constexpr (std::is_same_v) { // bool special case - return false; - } else { -#if defined(MAGIC_ENUM_NO_CHECK_FLAGS) - return false; -#else - constexpr auto flags_values = values(); - constexpr auto default_values = values(); - if (flags_values.size() == 0 || default_values.size() > flags_values.size()) { - return false; - } - for (std::size_t i = 0; i < default_values.size(); ++i) { - const auto v = static_cast(default_values[i]); - if (v != 0 && (v & (v - 1)) != 0) { - return false; - } - } - return flags_values.size() > 0; -#endif - } -} - -template -inline constexpr bool is_flags_v = is_flags_enum(); - -template -inline constexpr std::array values_v = values>(); - -template > -using values_t = decltype((values_v)); - -template -inline constexpr auto count_v = values_v.size(); - -template > -inline constexpr auto min_v = (count_v > 0) ? static_cast(values_v.front()) : U{0}; - -template > -inline constexpr auto max_v = (count_v > 0) ? static_cast(values_v.back()) : U{0}; - -template -constexpr auto names(std::index_sequence) noexcept { - static_assert(is_enum_v, "magic_enum::detail::names requires enum type."); - - return std::array{{enum_name_v[I]>...}}; -} - -template -inline constexpr std::array names_v = names(std::make_index_sequence>{}); - -template > -using names_t = decltype((names_v)); - -template -constexpr auto entries(std::index_sequence) noexcept { - static_assert(is_enum_v, "magic_enum::detail::entries requires enum type."); - - return std::array, sizeof...(I)>{{{values_v[I], enum_name_v[I]>}...}}; -} - -template -inline constexpr std::array entries_v = entries(std::make_index_sequence>{}); - -template > -using entries_t = decltype((entries_v)); - -template > -constexpr bool is_sparse() noexcept { - static_assert(is_enum_v, "magic_enum::detail::is_sparse requires enum type."); - - if constexpr (count_v == 0) { - return false; - } else if constexpr (std::is_same_v) { // bool special case - return false; - } else { - constexpr auto max = is_flags_v ? log2(max_v) : max_v; - constexpr auto min = is_flags_v ? log2(min_v) : min_v; - constexpr auto range_size = max - min + 1; - - return range_size != count_v; - } -} - -template -inline constexpr bool is_sparse_v = is_sparse(); - -template > -constexpr U values_ors() noexcept { - static_assert(is_enum_v, "magic_enum::detail::values_ors requires enum type."); - - auto ors = U{0}; - for (std::size_t i = 0; i < count_v; ++i) { - ors |= static_cast(values_v[i]); - } - - return ors; -} - -template -struct enable_if_enum {}; - -template -struct enable_if_enum { - using type = R; - static_assert(supported::value, "magic_enum unsupported compiler (https://github.com/Neargye/magic_enum#compiler-compatibility)."); -}; - -template , typename D = std::decay_t> -using enable_if_t = typename enable_if_enum && std::is_invocable_r_v, R>::type; - -template , typename D = std::decay_t> -using enable_if_default_t = typename enable_if_enum && VT == value_type::default_value && std::is_invocable_r_v, R>::type; - -template , typename D = std::decay_t> -using enable_if_flags_t = typename enable_if_enum && VT == value_type::flags_value && std::is_invocable_r_v, R>::type; - -template >, int> = 0> -using enum_concept = T; - -template > -struct is_scoped_enum : std::false_type {}; - -template -struct is_scoped_enum : std::bool_constant>> {}; - -template > -struct is_unscoped_enum : std::false_type {}; - -template -struct is_unscoped_enum : std::bool_constant>> {}; - -template >> -struct underlying_type {}; - -template -struct underlying_type : std::underlying_type> {}; - -#if defined(MAGIC_ENUM_ENABLE_HASH) - -template -inline constexpr bool has_hash = true; - -template -struct constexpr_hash_t; - -template -struct constexpr_hash_t>> { - constexpr auto operator()(Value value) const noexcept { - using U = typename underlying_type::type; - if constexpr (std::is_same_v) { // bool special case - return static_cast(value); - } else { - return static_cast(value); - } - } - using secondary_hash = constexpr_hash_t; -}; - -template -struct constexpr_hash_t>> { - static constexpr std::uint32_t crc_table[256] { - 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, 0x706af48fL, 0xe963a535L, 0x9e6495a3L, - 0x0edb8832L, 0x79dcb8a4L, 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, 0x90bf1d91L, - 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, - 0x136c9856L, 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, 0xfa0f3d63L, 0x8d080df5L, - 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, - 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, - 0x26d930acL, 0x51de003aL, 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, 0xb8bda50fL, - 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, - 0x76dc4190L, 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL, 0x9fbfe4a5L, 0xe8b8d433L, - 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, - 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, - 0x65b0d9c6L, 0x12b7e950L, 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, 0xfbd44c65L, - 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, - 0x4369e96aL, 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, 0xaa0a4c5fL, 0xdd0d7cc9L, - 0x5005713cL, 0x270241aaL, 0xbe0b1010L, 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL, - 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, - 0xedb88320L, 0x9abfb3b6L, 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, 0x73dc1683L, - 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, - 0xf00f9344L, 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, 0x196c3671L, 0x6e6b06e7L, - 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, - 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, - 0xd80d2bdaL, 0xaf0a1b4cL, 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, 0x4669be79L, - 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, - 0xc5ba3bbeL, 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L, 0x2cd99e8bL, 0x5bdeae1dL, - 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L, - 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL, 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, - 0x86d3d2d4L, 0xf1d4e242L, 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, 0x18b74777L, - 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, - 0xa00ae278L, 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, 0x4969474dL, 0x3e6e77dbL, - 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, - 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L, 0xcdd70693L, 0x54de5729L, 0x23d967bfL, - 0xb3667a2eL, 0xc4614ab8L, 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, 0x2d02ef8dL - }; - constexpr std::uint32_t operator()(string_view value) const noexcept { - auto crc = static_cast(0xffffffffL); - for (const auto c : value) { - crc = (crc >> 8) ^ crc_table[(crc ^ static_cast(c)) & 0xff]; - } - return crc ^ 0xffffffffL; - } - - struct secondary_hash { - constexpr std::uint32_t operator()(string_view value) const noexcept { - auto acc = static_cast(2166136261ULL); - for (const auto c : value) { - acc = ((acc ^ static_cast(c)) * static_cast(16777619ULL)) & (std::numeric_limits::max)(); - } - return static_cast(acc); - } - }; -}; - -template -inline constexpr Hash hash_v{}; - -template -constexpr auto calculate_cases(std::size_t Page) noexcept { - constexpr std::array values = *GlobValues; - constexpr std::size_t size = values.size(); - - using switch_t = std::invoke_result_t; - static_assert(std::is_integral_v && !std::is_same_v); - const std::size_t values_to = (std::min)(static_cast(256), size - Page); - - std::array result{}; - auto fill = result.begin(); - { - auto first = values.begin() + static_cast(Page); - auto last = values.begin() + static_cast(Page + values_to); - while (first != last) { - *fill++ = hash_v(*first++); - } - } - - // dead cases, try to avoid case collisions - for (switch_t last_value = result[values_to - 1]; fill != result.end() && last_value != (std::numeric_limits::max)(); *fill++ = ++last_value) { - } - - { - auto it = result.begin(); - auto last_value = (std::numeric_limits::min)(); - for (; fill != result.end(); *fill++ = last_value++) { - while (last_value == *it) { - ++last_value, ++it; - } - } - } - - return result; -} - -template -constexpr R invoke_r(F&& f, Args&&... args) noexcept(std::is_nothrow_invocable_r_v) { - if constexpr (std::is_void_v) { - std::forward(f)(std::forward(args)...); - } else { - return static_cast(std::forward(f)(std::forward(args)...)); - } -} - -enum class case_call_t { - index, - value -}; - -template -inline constexpr auto default_result_type_lambda = []() noexcept(std::is_nothrow_default_constructible_v) { return T{}; }; - -template <> -inline constexpr auto default_result_type_lambda = []() noexcept {}; - -template -constexpr bool has_duplicate() noexcept { - using value_t = std::decay_t; - using hash_value_t = std::invoke_result_t; - std::arraysize()> hashes{}; - std::size_t size = 0; - for (auto elem : *Arr) { - hashes[size] = hash_v(elem); - for (auto i = size++; i > 0; --i) { - if (hashes[i] < hashes[i - 1]) { - auto tmp = hashes[i]; - hashes[i] = hashes[i - 1]; - hashes[i - 1] = tmp; - } else if (hashes[i] == hashes[i - 1]) { - return false; - } else { - break; - } - } - } - return true; -} - -#define MAGIC_ENUM_FOR_EACH_256(T) T(0)T(1)T(2)T(3)T(4)T(5)T(6)T(7)T(8)T(9)T(10)T(11)T(12)T(13)T(14)T(15)T(16)T(17)T(18)T(19)T(20)T(21)T(22)T(23)T(24)T(25)T(26)T(27)T(28)T(29)T(30)T(31) \ - T(32)T(33)T(34)T(35)T(36)T(37)T(38)T(39)T(40)T(41)T(42)T(43)T(44)T(45)T(46)T(47)T(48)T(49)T(50)T(51)T(52)T(53)T(54)T(55)T(56)T(57)T(58)T(59)T(60)T(61)T(62)T(63) \ - T(64)T(65)T(66)T(67)T(68)T(69)T(70)T(71)T(72)T(73)T(74)T(75)T(76)T(77)T(78)T(79)T(80)T(81)T(82)T(83)T(84)T(85)T(86)T(87)T(88)T(89)T(90)T(91)T(92)T(93)T(94)T(95) \ - T(96)T(97)T(98)T(99)T(100)T(101)T(102)T(103)T(104)T(105)T(106)T(107)T(108)T(109)T(110)T(111)T(112)T(113)T(114)T(115)T(116)T(117)T(118)T(119)T(120)T(121)T(122)T(123)T(124)T(125)T(126)T(127) \ - T(128)T(129)T(130)T(131)T(132)T(133)T(134)T(135)T(136)T(137)T(138)T(139)T(140)T(141)T(142)T(143)T(144)T(145)T(146)T(147)T(148)T(149)T(150)T(151)T(152)T(153)T(154)T(155)T(156)T(157)T(158)T(159) \ - T(160)T(161)T(162)T(163)T(164)T(165)T(166)T(167)T(168)T(169)T(170)T(171)T(172)T(173)T(174)T(175)T(176)T(177)T(178)T(179)T(180)T(181)T(182)T(183)T(184)T(185)T(186)T(187)T(188)T(189)T(190)T(191) \ - T(192)T(193)T(194)T(195)T(196)T(197)T(198)T(199)T(200)T(201)T(202)T(203)T(204)T(205)T(206)T(207)T(208)T(209)T(210)T(211)T(212)T(213)T(214)T(215)T(216)T(217)T(218)T(219)T(220)T(221)T(222)T(223) \ - T(224)T(225)T(226)T(227)T(228)T(229)T(230)T(231)T(232)T(233)T(234)T(235)T(236)T(237)T(238)T(239)T(240)T(241)T(242)T(243)T(244)T(245)T(246)T(247)T(248)T(249)T(250)T(251)T(252)T(253)T(254)T(255) - -#define MAGIC_ENUM_CASE(val) \ - case cases[val]: \ - if constexpr ((val) + Page < size) { \ - if (!pred(values[val + Page], searched)) { \ - break; \ - } \ - if constexpr (CallValue == case_call_t::index) { \ - if constexpr (std::is_invocable_r_v>) { \ - return detail::invoke_r(std::forward(lambda), std::integral_constant{}); \ - } else if constexpr (std::is_invocable_v>) { \ - assert(false && "magic_enum::detail::constexpr_switch wrong result type."); \ - } \ - } else if constexpr (CallValue == case_call_t::value) { \ - if constexpr (std::is_invocable_r_v>) { \ - return detail::invoke_r(std::forward(lambda), enum_constant{}); \ - } else if constexpr (std::is_invocable_r_v>) { \ - assert(false && "magic_enum::detail::constexpr_switch wrong result type."); \ - } \ - } \ - break; \ - } else [[fallthrough]]; - -template ::value_type>, - typename BinaryPredicate = std::equal_to<>, - typename Lambda, - typename ResultGetterType> -constexpr decltype(auto) constexpr_switch( - Lambda&& lambda, - typename std::decay_t::value_type searched, - ResultGetterType&& def, - BinaryPredicate&& pred = {}) { - using result_t = std::invoke_result_t; - using hash_t = std::conditional_t(), Hash, typename Hash::secondary_hash>; - static_assert(has_duplicate(), "magic_enum::detail::constexpr_switch duplicated hash found, please report it: https://github.com/Neargye/magic_enum/issues."); - constexpr std::array values = *GlobValues; - constexpr std::size_t size = values.size(); - constexpr std::array cases = calculate_cases(Page); - - switch (hash_v(searched)) { - MAGIC_ENUM_FOR_EACH_256(MAGIC_ENUM_CASE) - default: - if constexpr (size > 256 + Page) { - return constexpr_switch(std::forward(lambda), searched, std::forward(def)); - } - break; - } - return def(); -} - -#undef MAGIC_ENUM_FOR_EACH_256 -#undef MAGIC_ENUM_CASE - -#else -template -inline constexpr bool has_hash = false; -#endif - -template -constexpr auto for_each(F&& f, std::index_sequence) { - static_assert(is_enum_v, "magic_enum::detail::for_each requires enum type."); - constexpr bool has_void_return = (std::is_void_v[I]>>> || ...); - constexpr bool all_same_return = (std::is_same_v[0]>>, std::invoke_result_t[I]>>> && ...); - - if constexpr (has_void_return) { - (f(enum_constant[I]>{}), ...); - } else if constexpr (all_same_return) { - return std::array{f(enum_constant[I]>{})...}; - } else { - return std::tuple{f(enum_constant[I]>{})...}; - } -} - -template -constexpr bool all_invocable(std::index_sequence) { - static_assert(is_enum_v, "magic_enum::detail::all_invocable requires enum type."); - - if constexpr (count_v == 0) { - return false; - } else { - return (std::is_invocable_v[I]>> && ...); - } -} - -} // namespace magic_enum::detail - -// Checks is magic_enum supported compiler. -inline constexpr bool is_magic_enum_supported = detail::supported::value; - -template -using Enum = detail::enum_concept; - -// Checks whether T is an Unscoped enumeration type. -// Provides the member constant value which is equal to true, if T is an [Unscoped enumeration](https://en.cppreference.com/w/cpp/language/enum#Unscoped_enumeration) type. Otherwise, value is equal to false. -template -struct is_unscoped_enum : detail::is_unscoped_enum {}; - -template -inline constexpr bool is_unscoped_enum_v = is_unscoped_enum::value; - -// Checks whether T is an Scoped enumeration type. -// Provides the member constant value which is equal to true, if T is an [Scoped enumeration](https://en.cppreference.com/w/cpp/language/enum#Scoped_enumerations) type. Otherwise, value is equal to false. -template -struct is_scoped_enum : detail::is_scoped_enum {}; - -template -inline constexpr bool is_scoped_enum_v = is_scoped_enum::value; - -// If T is a complete enumeration type, provides a member typedef type that names the underlying type of T. -// Otherwise, if T is not an enumeration type, there is no member type. Otherwise (T is an incomplete enumeration type), the program is ill-formed. -template -struct underlying_type : detail::underlying_type {}; - -template -using underlying_type_t = typename underlying_type::type; - -template -using enum_constant = detail::enum_constant; - -// Returns type name of enum. -template -[[nodiscard]] constexpr auto enum_type_name() noexcept -> detail::enable_if_t { - constexpr string_view name = detail::type_name_v>; - static_assert(!name.empty(), "magic_enum::enum_type_name enum type does not have a name."); - - return name; -} - -// Returns number of enum values. -template -[[nodiscard]] constexpr auto enum_count() noexcept -> detail::enable_if_t { - return detail::count_v>; -} - -// Returns enum value at specified index. -// No bounds checking is performed: the behavior is undefined if index >= number of enum values. -template -[[nodiscard]] constexpr auto enum_value(std::size_t index) noexcept -> detail::enable_if_t> { - using D = std::decay_t; - - if constexpr (detail::is_sparse_v) { - return assert((index < detail::count_v)), detail::values_v[index]; - } else { - constexpr bool is_flag = detail::is_flags_v; - constexpr auto min = is_flag ? detail::log2(detail::min_v) : detail::min_v; - - return assert((index < detail::count_v)), detail::value(index); - } -} - -// Returns enum value at specified index. -template -[[nodiscard]] constexpr auto enum_value() noexcept -> detail::enable_if_t> { - using D = std::decay_t; - static_assert(I < detail::count_v, "magic_enum::enum_value out of range."); - - return enum_value(I); -} - -// Returns std::array with enum values, sorted by enum value. -template -[[nodiscard]] constexpr auto enum_values() noexcept -> detail::enable_if_t> { - return detail::values_v>; -} - -// Returns integer value from enum value. -template -[[nodiscard]] constexpr auto enum_integer(E value) noexcept -> detail::enable_if_t> { - return static_cast>(value); -} - -// Returns underlying value from enum value. -template -[[nodiscard]] constexpr auto enum_underlying(E value) noexcept -> detail::enable_if_t> { - return static_cast>(value); -} - -// Obtains index in enum values from enum value. -// Returns optional with index. -template -[[nodiscard]] constexpr auto enum_index(E value) noexcept -> detail::enable_if_t> { - using D = std::decay_t; - using U = underlying_type_t; - - if constexpr (detail::count_v == 0) { - return {}; // Empty enum. - } else if constexpr (detail::is_sparse_v || detail::is_flags_v) { -#if defined(MAGIC_ENUM_ENABLE_HASH) - return detail::constexpr_switch<&detail::values_v, detail::case_call_t::index>( - [](std::size_t i) { return optional{i}; }, - value, - detail::default_result_type_lambda>); -#else - for (std::size_t i = 0; i < detail::count_v; ++i) { - if (enum_value(i) == value) { - return i; - } - } - return {}; // Invalid value or out of range. -#endif - } else { - const auto v = static_cast(value); - if (v >= detail::min_v && v <= detail::max_v) { - return static_cast(v - detail::min_v); - } - return {}; // Invalid value or out of range. - } -} - -// Obtains index in enum values from static storage enum variable. -template -[[nodiscard]] constexpr auto enum_index() noexcept -> detail::enable_if_t { - constexpr auto index = enum_index>(V); - static_assert(index, "magic_enum::enum_index enum value does not have a index."); - - return *index; -} - -// Returns name from static storage enum variable. -// This version is much lighter on the compile times and is not restricted to the enum_range limitation. -template -[[nodiscard]] constexpr auto enum_name() noexcept -> detail::enable_if_t { - constexpr string_view name = detail::enum_name_v, V>; - static_assert(!name.empty(), "magic_enum::enum_name enum value does not have a name."); - - return name; -} - -// Returns name from enum value. -// If enum value does not have name or value out of range, returns empty string. -template -[[nodiscard]] constexpr auto enum_name(E value) noexcept -> detail::enable_if_default_t { - using D = std::decay_t; - - if (const auto i = enum_index(value)) { - return detail::names_v[*i]; - } - return {}; -} - -// Returns name from enum value. -// If enum value does not have name or value out of range, returns empty string. -template -[[nodiscard]] constexpr auto enum_name(E value) -> detail::enable_if_default_t { - using D = std::decay_t; - - return enum_name(value); -} - -// Returns name from enum value. -// If enum value does not have name or value out of range, returns empty string. -template -[[nodiscard]] auto enum_name(E value) -> detail::enable_if_flags_t { - using D = std::decay_t; - using U = underlying_type_t; - static_assert(detail::is_flags_v, "magic_enum::enum_flags_name requires enum-flags type."); - - string name; - auto check_value = U{0}; - for (std::size_t i = 0; i < detail::count_v; ++i) { - if (const auto v = static_cast(enum_value(i)); (static_cast(value) & v) != 0) { - check_value |= v; - const auto n = detail::names_v[i]; - if (!name.empty()) { - name.append(1, '|'); - } - name.append(n.data(), n.size()); - } - } - - if (check_value != 0 && check_value == static_cast(value)) { - return name; - } - return {}; // Invalid value or out of range. -} - -// Returns name from enum value. -// If enum value does not have name or value out of range, returns empty string. -template -[[nodiscard]] auto enum_name(E value) -> detail::enable_if_flags_t { - using D = std::decay_t; - static_assert(detail::is_flags_v, "magic_enum::enum_flags_name requires enum-flags type."); - - return enum_name(value); -} - -// Returns name from enum-flags value. -// If enum-flags value does not have name or value out of range, returns empty string. -template -[[nodiscard]] auto enum_flags_name(E value) -> detail::enable_if_t { - using D = std::decay_t; - static_assert(detail::is_flags_v, "magic_enum::enum_flags_name requires enum-flags type."); - - return enum_name(value); -} - -// Returns std::array with names, sorted by enum value. -template -[[nodiscard]] constexpr auto enum_names() noexcept -> detail::enable_if_t> { - return detail::names_v>; -} - -// Returns std::array with pairs (value, name), sorted by enum value. -template -[[nodiscard]] constexpr auto enum_entries() noexcept -> detail::enable_if_t> { - return detail::entries_v>; -} - -// Allows you to write magic_enum::enum_cast("bar", magic_enum::case_insensitive); -inline constexpr auto case_insensitive = detail::case_insensitive{}; - -// Obtains enum value from integer value. -// Returns optional with enum value. -template -[[nodiscard]] constexpr auto enum_cast(underlying_type_t value) noexcept -> detail::enable_if_t>> { - using D = std::decay_t; - using U = underlying_type_t; - - if constexpr (detail::count_v == 0) { - return {}; // Empty enum. - } else if constexpr (VT == detail::value_type::flags_value && detail::is_flags_v) { - if constexpr (detail::is_sparse_v) { - auto check_value = U{0}; - for (std::size_t i = 0; i < detail::count_v; ++i) { - if (const auto v = static_cast(enum_value(i)); (value & v) != 0) { - check_value |= v; - } - } - - if (check_value != 0 && check_value == value) { - return static_cast(value); - } - } else { - constexpr auto min = detail::min_v; - constexpr auto max = detail::values_ors(); - - if (value >= min && value <= max) { - return static_cast(value); - } - } - return {}; // Invalid value or out of range. - } else { -#if defined(MAGIC_ENUM_ENABLE_HASH) - return detail::constexpr_switch<&detail::values_v, detail::case_call_t::value>( - [](D v) { return optional{v}; }, - static_cast(value), - detail::default_result_type_lambda>); -#else - if constexpr (detail::is_sparse_v || detail::is_flags_v) { - for (std::size_t i = 0; i < detail::count_v; ++i) { - if (value == static_cast(enum_value(i))) { - return static_cast(value); - } - } - } else { - if (value >= detail::min_v && value <= detail::max_v) { - return static_cast(value); - } - } - return {}; // Invalid value or out of range. -#endif - } -} - -// Obtains enum-flags value from integer value. -// Returns optional with enum-flags value. -template -[[nodiscard]] constexpr auto enum_flags_cast(underlying_type_t value) noexcept -> detail::enable_if_t>> { - using D = std::decay_t; - static_assert(detail::is_flags_v, "magic_enum::enum_flags_cast requires enum-flags type."); - - return enum_cast(value); -} - -// Obtains enum value from name. -// Returns optional with enum value. -template > -[[nodiscard]] constexpr auto enum_cast(string_view value, [[maybe_unused]] BinaryPredicate p = {}) noexcept(detail::is_nothrow_invocable()) -> detail::enable_if_t>, BinaryPredicate> { - using D = std::decay_t; - using U = underlying_type_t; - - if constexpr (detail::count_v == 0) { - return {}; // Empty enum. - } else if constexpr (VT == detail::value_type::flags_value && detail::is_flags_v) { - auto result = U{0}; - while (!value.empty()) { - const auto d = detail::find(value, '|'); - const auto s = (d == string_view::npos) ? value : value.substr(0, d); - auto f = U{0}; - for (std::size_t i = 0; i < detail::count_v; ++i) { - if (detail::cmp_equal(s, detail::names_v[i], p)) { - f = static_cast(enum_value(i)); - result |= f; - break; - } - } - if (f == U{0}) { - return {}; // Invalid value or out of range. - } - value.remove_prefix((d == string_view::npos) ? value.size() : d + 1); - } - - if (result != U{0}) { - return static_cast(result); - } - return {}; // Invalid value or out of range. - } else { - if constexpr (detail::is_default_predicate() && detail::has_hash) { -#if defined(MAGIC_ENUM_ENABLE_HASH) - return detail::constexpr_switch<&detail::names_v, detail::case_call_t::index>( - [](std::size_t i) { return optional{detail::values_v[i]}; }, - value, - detail::default_result_type_lambda>, - [&p](string_view lhs, string_view rhs) { return detail::cmp_equal(lhs, rhs, p); }); -#endif - } else { - for (std::size_t i = 0; i < detail::count_v; ++i) { - if (detail::cmp_equal(value, detail::names_v[i], p)) { - return enum_value(i); - } - } - return {}; // Invalid value or out of range. - } - } -} - -// Obtains enum-flags value from name. -// Returns optional with enum-flags value. -template > -[[nodiscard]] constexpr auto enum_flags_cast(string_view value, [[maybe_unused]] BinaryPredicate p = {}) noexcept(detail::is_nothrow_invocable()) -> detail::enable_if_t>, BinaryPredicate> { - using D = std::decay_t; - static_assert(detail::is_flags_v, "magic_enum::enum_flags_cast requires enum-flags type."); - - return enum_cast(value, std::move(p)); -} - -// Checks whether enum contains value with such value. -template -[[nodiscard]] constexpr auto enum_contains(E value) noexcept -> detail::enable_if_t { - using D = std::decay_t; - using U = underlying_type_t; - - return static_cast(enum_cast(static_cast(value))); -} - -// Checks whether enum-flags contains value with such value. -template -[[nodiscard]] constexpr auto enum_flags_contains(E value) noexcept -> detail::enable_if_t { - using D = std::decay_t; - static_assert(detail::is_flags_v, "magic_enum::enum_flags_contains requires enum-flags type."); - - return enum_contains(value); -} - -// Checks whether enum contains value with such integer value. -template -[[nodiscard]] constexpr auto enum_contains(underlying_type_t value) noexcept -> detail::enable_if_t { - using D = std::decay_t; - - return static_cast(enum_cast(value)); -} - -// Checks whether enum-flags contains value with such integer value. -template -[[nodiscard]] constexpr auto enum_flags_contains(underlying_type_t value) noexcept -> detail::enable_if_t { - using D = std::decay_t; - static_assert(detail::is_flags_v, "magic_enum::enum_flags_contains requires enum-flags type."); - - return enum_contains(value); -} - -// Checks whether enum contains enumerator with such name. -template > -[[nodiscard]] constexpr auto enum_contains(string_view value, BinaryPredicate p = {}) noexcept(detail::is_nothrow_invocable()) -> detail::enable_if_t { - using D = std::decay_t; - - return static_cast(enum_cast(value, std::move(p))); -} - -// Checks whether enum-flags contains enumerator with such name. -template > -[[nodiscard]] constexpr auto enum_flags_contains(string_view value, BinaryPredicate p = {}) noexcept(detail::is_nothrow_invocable()) -> detail::enable_if_t { - using D = std::decay_t; - static_assert(detail::is_flags_v, "magic_enum::enum_flags_contains requires enum-flags type."); - - return enum_contains(value, std::move(p)); -} - -template = 0> -constexpr auto enum_for_each(F&& f) { - using D = std::decay_t; - static_assert(std::is_enum_v, "magic_enum::enum_for_each requires enum type."); - constexpr auto sep = std::make_index_sequence>{}; - - if constexpr (detail::all_invocable(sep)) { - return detail::for_each(std::forward(f), sep); - } else { - static_assert(detail::always_false_v, "magic_enum::enum_for_each requires invocable of all enum value."); - } -} - -template -inline constexpr auto as_flags = AsFlags ? detail::value_type::flags_value : detail::value_type::default_value; - -#if !defined(MAGIC_ENUM_NO_STREAMS) - -namespace ostream_operators { - -template = 0> -std::basic_ostream& operator<<(std::basic_ostream& os, E value) { - using D = std::decay_t; - using U = underlying_type_t; - - if constexpr (detail::supported::value) { - if (const auto name = enum_name>>(value); !name.empty()) { - for (const auto c : name) { - os.put(c); - } - return os; - } - } - return (os << static_cast(value)); -} - -template = 0> -std::basic_ostream& operator<<(std::basic_ostream& os, optional value) { - return value ? (os << *value) : os; -} - -} // namespace magic_enum::ostream_operators - -namespace istream_operators { - -template = 0> -std::basic_istream& operator>>(std::basic_istream& is, E& value) { - using D = std::decay_t; - - std::basic_string s; - is >> s; - if (const auto v = enum_cast>>(s)) { - value = *v; - } else { - is.setstate(std::basic_ios::failbit); - } - return is; -} - -} // namespace magic_enum::istream_operators - -namespace iostream_operators { - -using namespace ostream_operators; -using namespace istream_operators; - -} // namespace magic_enum::iostream_operators - -#endif - -namespace bitwise_operators { - -template = 0> -constexpr E operator~(E rhs) noexcept { - return static_cast(~static_cast>(rhs)); -} - -template = 0> -constexpr E operator|(E lhs, E rhs) noexcept { - return static_cast(static_cast>(lhs) | static_cast>(rhs)); -} - -template = 0> -constexpr E operator&(E lhs, E rhs) noexcept { - return static_cast(static_cast>(lhs) & static_cast>(rhs)); -} - -template = 0> -constexpr E operator^(E lhs, E rhs) noexcept { - return static_cast(static_cast>(lhs) ^ static_cast>(rhs)); -} - -template = 0> -constexpr E& operator|=(E& lhs, E rhs) noexcept { - return lhs = (lhs | rhs); -} - -template = 0> -constexpr E& operator&=(E& lhs, E rhs) noexcept { - return lhs = (lhs & rhs); -} - -template = 0> -constexpr E& operator^=(E& lhs, E rhs) noexcept { - return lhs = (lhs ^ rhs); -} - -} // namespace magic_enum::bitwise_operators - -} // namespace magic_enum - -#if defined(__clang__) -# pragma clang diagnostic pop -#elif defined(__GNUC__) -# pragma GCC diagnostic pop -#elif defined(_MSC_VER) -# pragma warning(pop) -#endif - -#endif // NEARGYE_MAGIC_ENUM_HPP diff --git a/3p_include/ryml.hpp b/3p_include/ryml.hpp deleted file mode 100644 index 974ab58..0000000 --- a/3p_include/ryml.hpp +++ /dev/null @@ -1,33663 +0,0 @@ -// Sourced from: https://github.com/biojppm/rapidyaml/releases/download/v0.5.0/rapidyaml-0.5.0.hpp -// Note: Additins were made to this file including lines: 1-2, 5215-5218, 16132-16138 -#ifndef _RYML_SINGLE_HEADER_AMALGAMATED_HPP_ -#define _RYML_SINGLE_HEADER_AMALGAMATED_HPP_ - -// -// Rapid YAML - a library to parse and emit YAML, and do it fast. -// -// https://github.com/biojppm/rapidyaml -// -// DO NOT EDIT. This file is generated automatically. -// This is an amalgamated single-header version of the library. -// -// INSTRUCTIONS: -// - Include at will in any header of your project -// - In one (and only one) of your project source files, -// #define RYML_SINGLE_HDR_DEFINE_NOW and then include this header. -// This will enable the function and class definitions in -// the header file. -// - To compile into a shared library, just define the -// preprocessor symbol RYML_SHARED . This will take -// care of symbol export/import. -// - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// LICENSE.txt -// https://github.com/biojppm/rapidyaml/LICENSE.txt -//-------------------------------------------------------------------------------- -//******************************************************************************** - -// Copyright (c) 2018, Joao Paulo Magalhaes -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. -// - - // shared library: export when defining -#if defined(RYML_SHARED) && defined(RYML_SINGLE_HDR_DEFINE_NOW) && !defined(RYML_EXPORTS) -#define RYML_EXPORTS -#endif - - - // propagate defines to c4core -#if defined(RYML_SINGLE_HDR_DEFINE_NOW) && !defined(C4CORE_SINGLE_HDR_DEFINE_NOW) -#define C4CORE_SINGLE_HDR_DEFINE_NOW -#endif - -#if defined(RYML_EXPORTS) && !defined(C4CORE_EXPORTS) -#define C4CORE_EXPORTS -#endif - -#if defined(RYML_SHARED) && !defined(C4CORE_SHARED) -#define C4CORE_SHARED -#endif - -// workaround for include removal while amalgamating -// resulting in missing in arm-none-eabi-g++ -// https://github.com/biojppm/rapidyaml/issues/193 -#include - - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/c4core_all.hpp -// https://github.com/biojppm/rapidyaml/src/c4/c4core_all.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4CORE_SINGLE_HEADER_AMALGAMATED_HPP_ -#define _C4CORE_SINGLE_HEADER_AMALGAMATED_HPP_ - -// -// c4core - C++ utilities -// -// https://github.com/biojppm/c4core -// -// DO NOT EDIT. This file is generated automatically. -// This is an amalgamated single-header version of the library. -// -// INSTRUCTIONS: -// - Include at will in any header of your project -// - In one (and only one) of your project source files, -// #define C4CORE_SINGLE_HDR_DEFINE_NOW and then include this header. -// This will enable the function and class definitions in -// the header file. -// - To compile into a shared library, just define the -// preprocessor symbol C4CORE_SHARED . This will take -// care of symbol export/import. -// - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// LICENSE.txt -// https://github.com/biojppm/c4core/LICENSE.txt -//-------------------------------------------------------------------------------- -//******************************************************************************** - -// Copyright (c) 2018, Joao Paulo Magalhaes -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. -// - -// shared library: export when defining -#if defined(C4CORE_SHARED) && defined(C4CORE_SINGLE_HDR_DEFINE_NOW) && !defined(C4CORE_EXPORTS) -#define C4CORE_EXPORTS -#endif - - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/export.hpp -// https://github.com/biojppm/c4core/src/c4/export.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef C4_EXPORT_HPP_ -#define C4_EXPORT_HPP_ - -#ifdef _WIN32 - #ifdef C4CORE_SHARED - #ifdef C4CORE_EXPORTS - #define C4CORE_EXPORT __declspec(dllexport) - #else - #define C4CORE_EXPORT __declspec(dllimport) - #endif - #else - #define C4CORE_EXPORT - #endif -#else - #define C4CORE_EXPORT -#endif - -#endif /* C4CORE_EXPORT_HPP_ */ - - -// (end https://github.com/biojppm/c4core/src/c4/export.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/preprocessor.hpp -// https://github.com/biojppm/c4core/src/c4/preprocessor.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_PREPROCESSOR_HPP_ -#define _C4_PREPROCESSOR_HPP_ - -/** @file preprocessor.hpp Contains basic macros and preprocessor utilities. - * @ingroup basic_headers */ - -#ifdef __clang__ - /* NOTE: using , ## __VA_ARGS__ to deal with zero-args calls to - * variadic macros is not portable, but works in clang, gcc, msvc, icc. - * clang requires switching off compiler warnings for pedantic mode. - * @see http://stackoverflow.com/questions/32047685/variadic-macro-without-arguments */ -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" // warning: token pasting of ',' and __VA_ARGS__ is a GNU extension -#elif defined(__GNUC__) - /* GCC also issues a warning for zero-args calls to variadic macros. - * This warning is switched on with -pedantic and apparently there is no - * easy way to turn it off as with clang. But marking this as a system - * header works. - * @see https://gcc.gnu.org/onlinedocs/cpp/System-Headers.html - * @see http://stackoverflow.com/questions/35587137/ */ -# pragma GCC system_header -#endif - -#define C4_WIDEN(str) L"" str - -#define C4_COUNTOF(arr) (sizeof(arr)/sizeof((arr)[0])) - -#define C4_EXPAND(arg) arg - -/** useful in some macro calls with template arguments */ -#define C4_COMMA , -/** useful in some macro calls with template arguments - * @see C4_COMMA */ -#define C4_COMMA_X C4_COMMA - -/** expand and quote */ -#define C4_XQUOTE(arg) _C4_XQUOTE(arg) -#define _C4_XQUOTE(arg) C4_QUOTE(arg) -#define C4_QUOTE(arg) #arg - -/** expand and concatenate */ -#define C4_XCAT(arg1, arg2) _C4_XCAT(arg1, arg2) -#define _C4_XCAT(arg1, arg2) C4_CAT(arg1, arg2) -#define C4_CAT(arg1, arg2) arg1##arg2 - -#define C4_VERSION_CAT(major, minor, patch) ((major)*10000 + (minor)*100 + (patch)) - -/** A preprocessor foreach. Spectacular trick taken from: - * http://stackoverflow.com/a/1872506/5875572 - * The first argument is for a macro receiving a single argument, - * which will be called with every subsequent argument. There is - * currently a limit of 32 arguments, and at least 1 must be provided. - * -Example: -@code{.cpp} -struct Example { - int a; - int b; - int c; -}; -// define a one-arg macro to be called -#define PRN_STRUCT_OFFSETS(field) PRN_STRUCT_OFFSETS_(Example, field) -#define PRN_STRUCT_OFFSETS_(structure, field) printf(C4_XQUOTE(structure) ":" C4_XQUOTE(field)" - offset=%zu\n", offsetof(structure, field)); - -// now call the macro for a, b and c -C4_FOR_EACH(PRN_STRUCT_OFFSETS, a, b, c); -@endcode */ -#define C4_FOR_EACH(what, ...) C4_FOR_EACH_SEP(what, ;, __VA_ARGS__) - -/** same as C4_FOR_EACH(), but use a custom separator between statements. - * If a comma is needed as the separator, use the C4_COMMA macro. - * @see C4_FOR_EACH - * @see C4_COMMA - */ -#define C4_FOR_EACH_SEP(what, sep, ...) _C4_FOR_EACH_(_C4_FOR_EACH_NARG(__VA_ARGS__), what, sep, __VA_ARGS__) - -/// @cond dev - -#define _C4_FOR_EACH_01(what, sep, x) what(x) sep -#define _C4_FOR_EACH_02(what, sep, x, ...) what(x) sep _C4_FOR_EACH_01(what, sep, __VA_ARGS__) -#define _C4_FOR_EACH_03(what, sep, x, ...) what(x) sep _C4_FOR_EACH_02(what, sep, __VA_ARGS__) -#define _C4_FOR_EACH_04(what, sep, x, ...) what(x) sep _C4_FOR_EACH_03(what, sep, __VA_ARGS__) -#define _C4_FOR_EACH_05(what, sep, x, ...) what(x) sep _C4_FOR_EACH_04(what, sep, __VA_ARGS__) -#define _C4_FOR_EACH_06(what, sep, x, ...) what(x) sep _C4_FOR_EACH_05(what, sep, __VA_ARGS__) -#define _C4_FOR_EACH_07(what, sep, x, ...) what(x) sep _C4_FOR_EACH_06(what, sep, __VA_ARGS__) -#define _C4_FOR_EACH_08(what, sep, x, ...) what(x) sep _C4_FOR_EACH_07(what, sep, __VA_ARGS__) -#define _C4_FOR_EACH_09(what, sep, x, ...) what(x) sep _C4_FOR_EACH_08(what, sep, __VA_ARGS__) -#define _C4_FOR_EACH_10(what, sep, x, ...) what(x) sep _C4_FOR_EACH_09(what, sep, __VA_ARGS__) -#define _C4_FOR_EACH_11(what, sep, x, ...) what(x) sep _C4_FOR_EACH_10(what, sep, __VA_ARGS__) -#define _C4_FOR_EACH_12(what, sep, x, ...) what(x) sep _C4_FOR_EACH_11(what, sep, __VA_ARGS__) -#define _C4_FOR_EACH_13(what, sep, x, ...) what(x) sep _C4_FOR_EACH_12(what, sep, __VA_ARGS__) -#define _C4_FOR_EACH_14(what, sep, x, ...) what(x) sep _C4_FOR_EACH_13(what, sep, __VA_ARGS__) -#define _C4_FOR_EACH_15(what, sep, x, ...) what(x) sep _C4_FOR_EACH_14(what, sep, __VA_ARGS__) -#define _C4_FOR_EACH_16(what, sep, x, ...) what(x) sep _C4_FOR_EACH_15(what, sep, __VA_ARGS__) -#define _C4_FOR_EACH_17(what, sep, x, ...) what(x) sep _C4_FOR_EACH_16(what, sep, __VA_ARGS__) -#define _C4_FOR_EACH_18(what, sep, x, ...) what(x) sep _C4_FOR_EACH_17(what, sep, __VA_ARGS__) -#define _C4_FOR_EACH_19(what, sep, x, ...) what(x) sep _C4_FOR_EACH_18(what, sep, __VA_ARGS__) -#define _C4_FOR_EACH_20(what, sep, x, ...) what(x) sep _C4_FOR_EACH_19(what, sep, __VA_ARGS__) -#define _C4_FOR_EACH_21(what, sep, x, ...) what(x) sep _C4_FOR_EACH_20(what, sep, __VA_ARGS__) -#define _C4_FOR_EACH_22(what, sep, x, ...) what(x) sep _C4_FOR_EACH_21(what, sep, __VA_ARGS__) -#define _C4_FOR_EACH_23(what, sep, x, ...) what(x) sep _C4_FOR_EACH_22(what, sep, __VA_ARGS__) -#define _C4_FOR_EACH_24(what, sep, x, ...) what(x) sep _C4_FOR_EACH_23(what, sep, __VA_ARGS__) -#define _C4_FOR_EACH_25(what, sep, x, ...) what(x) sep _C4_FOR_EACH_24(what, sep, __VA_ARGS__) -#define _C4_FOR_EACH_26(what, sep, x, ...) what(x) sep _C4_FOR_EACH_25(what, sep, __VA_ARGS__) -#define _C4_FOR_EACH_27(what, sep, x, ...) what(x) sep _C4_FOR_EACH_26(what, sep, __VA_ARGS__) -#define _C4_FOR_EACH_28(what, sep, x, ...) what(x) sep _C4_FOR_EACH_27(what, sep, __VA_ARGS__) -#define _C4_FOR_EACH_29(what, sep, x, ...) what(x) sep _C4_FOR_EACH_28(what, sep, __VA_ARGS__) -#define _C4_FOR_EACH_30(what, sep, x, ...) what(x) sep _C4_FOR_EACH_29(what, sep, __VA_ARGS__) -#define _C4_FOR_EACH_31(what, sep, x, ...) what(x) sep _C4_FOR_EACH_30(what, sep, __VA_ARGS__) -#define _C4_FOR_EACH_32(what, sep, x, ...) what(x) sep _C4_FOR_EACH_31(what, sep, __VA_ARGS__) -#define _C4_FOR_EACH_NARG(...) _C4_FOR_EACH_NARG_(__VA_ARGS__, _C4_FOR_EACH_RSEQ_N()) -#define _C4_FOR_EACH_NARG_(...) _C4_FOR_EACH_ARG_N(__VA_ARGS__) -#define _C4_FOR_EACH_ARG_N(_01, _02, _03, _04, _05, _06, _07, _08, _09, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, N, ...) N -#define _C4_FOR_EACH_RSEQ_N() 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 09, 08, 07, 06, 05, 04, 03, 02, 01 -#define _C4_FOR_EACH_(N, what, sep, ...) C4_XCAT(_C4_FOR_EACH_, N)(what, sep, __VA_ARGS__) - -/// @endcond - -#ifdef __clang__ -# pragma clang diagnostic pop -#endif - -#endif /* _C4_PREPROCESSOR_HPP_ */ - - -// (end https://github.com/biojppm/c4core/src/c4/preprocessor.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/platform.hpp -// https://github.com/biojppm/c4core/src/c4/platform.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_PLATFORM_HPP_ -#define _C4_PLATFORM_HPP_ - -/** @file platform.hpp Provides platform information macros - * @ingroup basic_headers */ - -// see also https://sourceforge.net/p/predef/wiki/OperatingSystems/ - -#if defined(_WIN64) -# define C4_WIN -# define C4_WIN64 -#elif defined(_WIN32) -# define C4_WIN -# define C4_WIN32 -#elif defined(__ANDROID__) -# define C4_ANDROID -#elif defined(__APPLE__) -# include "TargetConditionals.h" -# if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR -# define C4_IOS -# elif TARGET_OS_MAC || TARGET_OS_OSX -# define C4_MACOS -# else -# error "Unknown Apple platform" -# endif -#elif defined(__linux__) || defined(__linux) -# define C4_UNIX -# define C4_LINUX -#elif defined(__unix__) || defined(__unix) -# define C4_UNIX -#elif defined(__arm__) || defined(__aarch64__) -# define C4_ARM -#elif defined(SWIG) -# define C4_SWIG -#else -# error "unknown platform" -#endif - -#if defined(__posix) || defined(C4_UNIX) || defined(C4_LINUX) -# define C4_POSIX -#endif - - -#endif /* _C4_PLATFORM_HPP_ */ - - -// (end https://github.com/biojppm/c4core/src/c4/platform.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/cpu.hpp -// https://github.com/biojppm/c4core/src/c4/cpu.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_CPU_HPP_ -#define _C4_CPU_HPP_ - -/** @file cpu.hpp Provides processor information macros - * @ingroup basic_headers */ - -// see also https://sourceforge.net/p/predef/wiki/Architectures/ -// see also https://sourceforge.net/p/predef/wiki/Endianness/ -// see also https://github.com/googlesamples/android-ndk/blob/android-mk/hello-jni/jni/hello-jni.c -// see http://code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/global/qprocessordetection.h - -#ifdef __ORDER_LITTLE_ENDIAN__ - #define _C4EL __ORDER_LITTLE_ENDIAN__ -#else - #define _C4EL 1234 -#endif - -#ifdef __ORDER_BIG_ENDIAN__ - #define _C4EB __ORDER_BIG_ENDIAN__ -#else - #define _C4EB 4321 -#endif - -// mixed byte order (eg, PowerPC or ia64) -#define _C4EM 1111 - -#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(_M_X64) - #define C4_CPU_X86_64 - #define C4_WORDSIZE 8 - #define C4_BYTE_ORDER _C4EL - -#elif defined(__i386) || defined(__i386__) || defined(_M_IX86) - #define C4_CPU_X86 - #define C4_WORDSIZE 4 - #define C4_BYTE_ORDER _C4EL - -#elif defined(__arm__) || defined(_M_ARM) \ - || defined(__TARGET_ARCH_ARM) || defined(__aarch64__) || defined(_M_ARM64) - #if defined(__aarch64__) || defined(_M_ARM64) - #define C4_CPU_ARM64 - #define C4_CPU_ARMV8 - #define C4_WORDSIZE 8 - #else - #define C4_CPU_ARM - #define C4_WORDSIZE 4 - #if defined(__ARM_ARCH_8__) || defined(__ARM_ARCH_8A__) \ - || (defined(__ARCH_ARM) && __ARCH_ARM >= 8) - || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM >= 8) \ - #define C4_CPU_ARMV8 - #elif defined(__ARM_ARCH_7__) || defined(_ARM_ARCH_7) \ - || defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7R__) \ - || defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7S__) \ - || defined(__ARM_ARCH_7EM__) \ - || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM >= 7) \ - || (defined(_M_ARM) && _M_ARM >= 7) - #define C4_CPU_ARMV7 - #elif defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) \ - || defined(__ARM_ARCH_6T2__) || defined(__ARM_ARCH_6Z__) \ - || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6ZK__) \ - || defined(__ARM_ARCH_6M__) || defined(__ARM_ARCH_6KZ__) \ - || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM >= 6) - #define C4_CPU_ARMV6 - #elif defined(__ARM_ARCH_5TEJ__) \ - || defined(__ARM_ARCH_5TE__) \ - || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM >= 5) - #define C4_CPU_ARMV5 - #elif defined(__ARM_ARCH_4T__) \ - || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM >= 4) - #define C4_CPU_ARMV4 - #else - #error "unknown CPU architecture: ARM" - #endif - #endif - #if defined(__ARMEL__) || defined(__LITTLE_ENDIAN__) || defined(__AARCH64EL__) \ - || (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) \ - || defined(_MSC_VER) // winarm64 does not provide any of the above macros, - // but advises little-endianess: - // https://docs.microsoft.com/en-us/cpp/build/overview-of-arm-abi-conventions?view=msvc-170 - // So if it is visual studio compiling, we'll assume little endian. - #define C4_BYTE_ORDER _C4EL - #elif defined(__ARMEB__) || defined(__BIG_ENDIAN__) || defined(__AARCH64EB__) \ - || (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) - #define C4_BYTE_ORDER _C4EB - #elif defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_PDP_ENDIAN__) - #define C4_BYTE_ORDER _C4EM - #else - #error "unknown endianness" - #endif - -#elif defined(__ia64) || defined(__ia64__) || defined(_M_IA64) - #define C4_CPU_IA64 - #define C4_WORDSIZE 8 - #define C4_BYTE_ORDER _C4EM - // itanium is bi-endian - check byte order below - -#elif defined(__ppc__) || defined(__ppc) || defined(__powerpc__) \ - || defined(_ARCH_COM) || defined(_ARCH_PWR) || defined(_ARCH_PPC) \ - || defined(_M_MPPC) || defined(_M_PPC) - #if defined(__ppc64__) || defined(__powerpc64__) || defined(__64BIT__) - #define C4_CPU_PPC64 - #define C4_WORDSIZE 8 - #else - #define C4_CPU_PPC - #define C4_WORDSIZE 4 - #endif - #define C4_BYTE_ORDER _C4EM - // ppc is bi-endian - check byte order below - -#elif defined(__s390x__) || defined(__zarch__) || defined(__SYSC_ZARCH_) -# define C4_CPU_S390_X -# define C4_WORDSIZE 8 -# define C4_BYTE_ORDER _C4EB - -#elif defined(__riscv) - #if __riscv_xlen == 64 - #define C4_CPU_RISCV64 - #define C4_WORDSIZE 8 - #else - #define C4_CPU_RISCV32 - #define C4_WORDSIZE 4 - #endif - #define C4_BYTE_ORDER _C4EL - -#elif defined(__EMSCRIPTEN__) -# define C4_BYTE_ORDER _C4EL -# define C4_WORDSIZE 4 - -#elif defined(SWIG) - #error "please define CPU architecture macros when compiling with swig" - -#else - #error "unknown CPU architecture" -#endif - -#define C4_LITTLE_ENDIAN (C4_BYTE_ORDER == _C4EL) -#define C4_BIG_ENDIAN (C4_BYTE_ORDER == _C4EB) -#define C4_MIXED_ENDIAN (C4_BYTE_ORDER == _C4EM) - -#endif /* _C4_CPU_HPP_ */ - - -// (end https://github.com/biojppm/c4core/src/c4/cpu.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/compiler.hpp -// https://github.com/biojppm/c4core/src/c4/compiler.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_COMPILER_HPP_ -#define _C4_COMPILER_HPP_ - -/** @file compiler.hpp Provides compiler information macros - * @ingroup basic_headers */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/platform.hpp -//#include "c4/platform.hpp" -#if !defined(C4_PLATFORM_HPP_) && !defined(_C4_PLATFORM_HPP_) -#error "amalgamate: file c4/platform.hpp must have been included at this point" -#endif /* C4_PLATFORM_HPP_ */ - - -// Compilers: -// C4_MSVC -// Visual Studio 2022: MSVC++ 17, 1930 -// Visual Studio 2019: MSVC++ 16, 1920 -// Visual Studio 2017: MSVC++ 15 -// Visual Studio 2015: MSVC++ 14 -// Visual Studio 2013: MSVC++ 13 -// Visual Studio 2013: MSVC++ 12 -// Visual Studio 2012: MSVC++ 11 -// Visual Studio 2010: MSVC++ 10 -// Visual Studio 2008: MSVC++ 09 -// Visual Studio 2005: MSVC++ 08 -// C4_CLANG -// C4_GCC -// C4_ICC (intel compiler) -/** @see http://sourceforge.net/p/predef/wiki/Compilers/ for a list of compiler identifier macros */ -/** @see https://msdn.microsoft.com/en-us/library/b0084kay.aspx for VS2013 predefined macros */ - -#if defined(_MSC_VER)// && (defined(C4_WIN) || defined(C4_XBOX) || defined(C4_UE4)) -# define C4_MSVC -# define C4_MSVC_VERSION_2022 17 -# define C4_MSVC_VERSION_2019 16 -# define C4_MSVC_VERSION_2017 15 -# define C4_MSVC_VERSION_2015 14 -# define C4_MSVC_VERSION_2013 12 -# define C4_MSVC_VERSION_2012 11 -# if _MSC_VER >= 1930 -# define C4_MSVC_VERSION C4_MSVC_VERSION_2022 // visual studio 2022 -# define C4_MSVC_2022 -# elif _MSC_VER >= 1920 -# define C4_MSVC_VERSION C_4MSVC_VERSION_2019 // visual studio 2019 -# define C4_MSVC_2019 -# elif _MSC_VER >= 1910 -# define C4_MSVC_VERSION C4_MSVC_VERSION_2017 // visual studio 2017 -# define C4_MSVC_2017 -# elif _MSC_VER == 1900 -# define C4_MSVC_VERSION C4_MSVC_VERSION_2015 // visual studio 2015 -# define C4_MSVC_2015 -# elif _MSC_VER == 1800 -# error "MSVC version not supported" -# define C4_MSVC_VERSION C4_MSVC_VERSION_2013 // visual studio 2013 -# define C4_MSVC_2013 -# elif _MSC_VER == 1700 -# error "MSVC version not supported" -# define C4_MSVC_VERSION C4_MSVC_VERSION_2012 // visual studio 2012 -# define C4_MSVC_2012 -# elif _MSC_VER == 1600 -# error "MSVC version not supported" -# define C4_MSVC_VERSION 10 // visual studio 2010 -# define C4_MSVC_2010 -# elif _MSC_VER == 1500 -# error "MSVC version not supported" -# define C4_MSVC_VERSION 09 // visual studio 2008 -# define C4_MSVC_2008 -# elif _MSC_VER == 1400 -# error "MSVC version not supported" -# define C4_MSVC_VERSION 08 // visual studio 2005 -# define C4_MSVC_2005 -# else -# error "MSVC version not supported" -# endif // _MSC_VER -#else -# define C4_MSVC_VERSION 0 // visual studio not present -# define C4_GCC_LIKE -# ifdef __INTEL_COMPILER // check ICC before checking GCC, as ICC defines __GNUC__ too -# define C4_ICC -# define C4_ICC_VERSION __INTEL_COMPILER -# elif defined(__APPLE_CC__) -# define C4_XCODE -# if defined(__clang__) -# define C4_CLANG -# ifndef __apple_build_version__ -# define C4_CLANG_VERSION C4_VERSION_ENCODED(__clang_major__, __clang_minor__, __clang_patchlevel__) -# else -# define C4_CLANG_VERSION __apple_build_version__ -# endif -# else -# define C4_XCODE_VERSION __APPLE_CC__ -# endif -# elif defined(__clang__) -# define C4_CLANG -# ifndef __apple_build_version__ -# define C4_CLANG_VERSION C4_VERSION_ENCODED(__clang_major__, __clang_minor__, __clang_patchlevel__) -# else -# define C4_CLANG_VERSION __apple_build_version__ -# endif -# elif defined(__GNUC__) -# define C4_GCC -# if defined(__GNUC_PATCHLEVEL__) -# define C4_GCC_VERSION C4_VERSION_ENCODED(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) -# else -# define C4_GCC_VERSION C4_VERSION_ENCODED(__GNUC__, __GNUC_MINOR__, 0) -# endif -# if __GNUC__ < 5 -# if __GNUC__ == 4 && __GNUC_MINOR__ >= 8 -// provided by cmake sub-project -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/gcc-4.8.hpp -//# include "c4/gcc-4.8.hpp" -#if !defined(C4_GCC_4_8_HPP_) && !defined(_C4_GCC_4_8_HPP_) -#error "amalgamate: file c4/gcc-4.8.hpp must have been included at this point" -#endif /* C4_GCC_4_8_HPP_ */ - -# else -// we do not support GCC < 4.8: -// * misses std::is_trivially_copyable -// * misses std::align -// * -Wshadow has false positives when a local function parameter has the same name as a method -# error "GCC < 4.8 is not supported" -# endif -# endif -# endif -#endif // defined(C4_WIN) && defined(_MSC_VER) - -#endif /* _C4_COMPILER_HPP_ */ - - -// (end https://github.com/biojppm/c4core/src/c4/compiler.hpp) - -// these includes are needed to work around conditional -// includes in the gcc4.8 shim -#include -#include -#include - - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// cmake/compat/c4/gcc-4.8.hpp -// https://github.com/biojppm/c4core/cmake/compat/c4/gcc-4.8.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_COMPAT_GCC_4_8_HPP_ -#define _C4_COMPAT_GCC_4_8_HPP_ - -#if __GNUC__ == 4 && __GNUC_MINOR__ >= 8 -/* STL polyfills for old GNU compilers */ - -_Pragma("GCC diagnostic ignored \"-Wshadow\"") -_Pragma("GCC diagnostic ignored \"-Wmissing-field-initializers\"") - -#if __cplusplus -//included above: -//#include -//included above: -//#include - -namespace std { - -template -struct is_trivially_copyable : public integral_constant::value && __has_trivial_destructor(_Tp) && - (__has_trivial_constructor(_Tp) || __has_trivial_copy(_Tp) || __has_trivial_assign(_Tp))> -{ }; - -template -using is_trivially_copy_constructible = has_trivial_copy_constructor<_Tp>; - -template -using is_trivially_default_constructible = has_trivial_default_constructor<_Tp>; - -template -using is_trivially_copy_assignable = has_trivial_copy_assign<_Tp>; - -/* not supported */ -template -struct is_trivially_move_constructible : false_type -{ }; - -/* not supported */ -template -struct is_trivially_move_assignable : false_type -{ }; - -inline void *align(size_t __align, size_t __size, void*& __ptr, size_t& __space) noexcept -{ - if (__space < __size) - return nullptr; - const auto __intptr = reinterpret_cast(__ptr); - const auto __aligned = (__intptr - 1u + __align) & -__align; - const auto __diff = __aligned - __intptr; - if (__diff > (__space - __size)) - return nullptr; - else - { - __space -= __diff; - return __ptr = reinterpret_cast(__aligned); - } -} -typedef long double max_align_t ; - -} -#else // __cplusplus - -//included above: -//#include -// see https://sourceware.org/bugzilla/show_bug.cgi?id=25399 (ubuntu gcc-4.8) -#define memset(s, c, count) __builtin_memset(s, c, count) - -#endif // __cplusplus - -#endif // __GNUC__ == 4 && __GNUC_MINOR__ >= 8 - -#endif // _C4_COMPAT_GCC_4_8_HPP_ - - -// (end https://github.com/biojppm/c4core/cmake/compat/c4/gcc-4.8.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/language.hpp -// https://github.com/biojppm/c4core/src/c4/language.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_LANGUAGE_HPP_ -#define _C4_LANGUAGE_HPP_ - -/** @file language.hpp Provides language standard information macros and - * compiler agnostic utility macros: namespace facilities, function attributes, - * variable attributes, etc. - * @ingroup basic_headers */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/preprocessor.hpp -//#include "c4/preprocessor.hpp" -#if !defined(C4_PREPROCESSOR_HPP_) && !defined(_C4_PREPROCESSOR_HPP_) -#error "amalgamate: file c4/preprocessor.hpp must have been included at this point" -#endif /* C4_PREPROCESSOR_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/compiler.hpp -//#include "c4/compiler.hpp" -#if !defined(C4_COMPILER_HPP_) && !defined(_C4_COMPILER_HPP_) -#error "amalgamate: file c4/compiler.hpp must have been included at this point" -#endif /* C4_COMPILER_HPP_ */ - - -/* Detect C++ standard. - * @see http://stackoverflow.com/a/7132549/5875572 */ -#ifndef C4_CPP -# ifdef _MSC_VER -# if _MSC_VER >= 1910 // >VS2015: VS2017, VS2019 -# if (!defined(_MSVC_LANG)) -# error _MSVC not defined -# endif -# if _MSVC_LANG >= 201705L -# define C4_CPP 20 -# define C4_CPP20 -# elif _MSVC_LANG == 201703L -# define C4_CPP 17 -# define C4_CPP17 -# elif _MSVC_LANG >= 201402L -# define C4_CPP 14 -# define C4_CPP14 -# elif _MSVC_LANG >= 201103L -# define C4_CPP 11 -# define C4_CPP11 -# else -# error C++ lesser than C++11 not supported -# endif -# else -# if _MSC_VER == 1900 -# define C4_CPP 14 // VS2015 is c++14 https://devblogs.microsoft.com/cppblog/c111417-features-in-vs-2015-rtm/ -# define C4_CPP14 -# elif _MSC_VER == 1800 // VS2013 -# define C4_CPP 11 -# define C4_CPP11 -# else -# error C++ lesser than C++11 not supported -# endif -# endif -# elif defined(__INTEL_COMPILER) // https://software.intel.com/en-us/node/524490 -# ifdef __INTEL_CXX20_MODE__ // not sure about this -# define C4_CPP 20 -# define C4_CPP20 -# elif defined __INTEL_CXX17_MODE__ // not sure about this -# define C4_CPP 17 -# define C4_CPP17 -# elif defined __INTEL_CXX14_MODE__ // not sure about this -# define C4_CPP 14 -# define C4_CPP14 -# elif defined __INTEL_CXX11_MODE__ -# define C4_CPP 11 -# define C4_CPP11 -# else -# error C++ lesser than C++11 not supported -# endif -# else -# ifndef __cplusplus -# error __cplusplus is not defined? -# endif -# if __cplusplus == 1 -# error cannot handle __cplusplus==1 -# elif __cplusplus >= 201709L -# define C4_CPP 20 -# define C4_CPP20 -# elif __cplusplus >= 201703L -# define C4_CPP 17 -# define C4_CPP17 -# elif __cplusplus >= 201402L -# define C4_CPP 14 -# define C4_CPP14 -# elif __cplusplus >= 201103L -# define C4_CPP 11 -# define C4_CPP11 -# elif __cplusplus >= 199711L -# error C++ lesser than C++11 not supported -# endif -# endif -#else -# ifdef C4_CPP == 20 -# define C4_CPP20 -# elif C4_CPP == 17 -# define C4_CPP17 -# elif C4_CPP == 14 -# define C4_CPP14 -# elif C4_CPP == 11 -# define C4_CPP11 -# elif C4_CPP == 98 -# define C4_CPP98 -# error C++ lesser than C++11 not supported -# else -# error C4_CPP must be one of 20, 17, 14, 11, 98 -# endif -#endif - -#ifdef C4_CPP20 -# define C4_CPP17 -# define C4_CPP14 -# define C4_CPP11 -#elif defined(C4_CPP17) -# define C4_CPP14 -# define C4_CPP11 -#elif defined(C4_CPP14) -# define C4_CPP11 -#endif - -/** lifted from this answer: http://stackoverflow.com/a/20170989/5875572 */ -#ifndef _MSC_VER -# if __cplusplus < 201103 -# define C4_CONSTEXPR11 -# define C4_CONSTEXPR14 -//# define C4_NOEXCEPT -# elif __cplusplus == 201103 -# define C4_CONSTEXPR11 constexpr -# define C4_CONSTEXPR14 -//# define C4_NOEXCEPT noexcept -# else -# define C4_CONSTEXPR11 constexpr -# define C4_CONSTEXPR14 constexpr -//# define C4_NOEXCEPT noexcept -# endif -#else // _MSC_VER -# if _MSC_VER < 1900 -# define C4_CONSTEXPR11 -# define C4_CONSTEXPR14 -//# define C4_NOEXCEPT -# elif _MSC_VER < 2000 -# define C4_CONSTEXPR11 constexpr -# define C4_CONSTEXPR14 -//# define C4_NOEXCEPT noexcept -# else -# define C4_CONSTEXPR11 constexpr -# define C4_CONSTEXPR14 constexpr -//# define C4_NOEXCEPT noexcept -# endif -#endif // _MSC_VER - - -#if C4_CPP < 17 -#define C4_IF_CONSTEXPR -#define C4_INLINE_CONSTEXPR constexpr -#else -#define C4_IF_CONSTEXPR constexpr -#define C4_INLINE_CONSTEXPR inline constexpr -#endif - - -//------------------------------------------------------------ - -#define _C4_BEGIN_NAMESPACE(ns) namespace ns { -#define _C4_END_NAMESPACE(ns) } - -// MSVC cant handle the C4_FOR_EACH macro... need to fix this -//#define C4_BEGIN_NAMESPACE(...) C4_FOR_EACH_SEP(_C4_BEGIN_NAMESPACE, , __VA_ARGS__) -//#define C4_END_NAMESPACE(...) C4_FOR_EACH_SEP(_C4_END_NAMESPACE, , __VA_ARGS__) -#define C4_BEGIN_NAMESPACE(ns) namespace ns { -#define C4_END_NAMESPACE(ns) } - -#define C4_BEGIN_HIDDEN_NAMESPACE namespace /*hidden*/ { -#define C4_END_HIDDEN_NAMESPACE } /* namespace hidden */ - -//------------------------------------------------------------ - -#ifndef C4_API -# if defined(_MSC_VER) -# if defined(C4_EXPORT) -# define C4_API __declspec(dllexport) -# elif defined(C4_IMPORT) -# define C4_API __declspec(dllimport) -# else -# define C4_API -# endif -# else -# define C4_API -# endif -#endif - -#ifndef _MSC_VER ///< @todo assuming gcc-like compiler. check it is actually so. -/** for function attributes in GCC, - * @see https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes */ -/** for __builtin functions in GCC, - * @see https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html */ -# define C4_RESTRICT __restrict__ -# define C4_RESTRICT_FN __attribute__((restrict)) -# define C4_NO_INLINE __attribute__((noinline)) -# define C4_ALWAYS_INLINE inline __attribute__((always_inline)) -# define C4_CONST __attribute__((const)) -# define C4_PURE __attribute__((pure)) -/** force inlining of every callee function */ -# define C4_FLATTEN __atribute__((flatten)) -/** mark a function as hot, ie as having a visible impact in CPU time - * thus making it more likely to inline, etc - * @see http://stackoverflow.com/questions/15028990/semantics-of-gcc-hot-attribute */ -# define C4_HOT __attribute__((hot)) -/** mark a function as cold, ie as NOT having a visible impact in CPU time - * @see http://stackoverflow.com/questions/15028990/semantics-of-gcc-hot-attribute */ -# define C4_COLD __attribute__((cold)) -# define C4_EXPECT(x, y) __builtin_expect(x, y) ///< @see https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html -# define C4_LIKELY(x) __builtin_expect(x, 1) -# define C4_UNLIKELY(x) __builtin_expect(x, 0) -# define C4_UNREACHABLE() __builtin_unreachable() -# define C4_ATTR_FORMAT(...) //__attribute__((format (__VA_ARGS__))) ///< @see https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes -# define C4_NORETURN __attribute__((noreturn)) -#else -# define C4_RESTRICT __restrict -# define C4_RESTRICT_FN __declspec(restrict) -# define C4_NO_INLINE __declspec(noinline) -# define C4_ALWAYS_INLINE inline __forceinline -/** these are not available in VS AFAIK */ -# define C4_CONST -# define C4_PURE -# define C4_FLATTEN -# define C4_HOT /** @todo */ -# define C4_COLD /** @todo */ -# define C4_EXPECT(x, y) x /** @todo */ -# define C4_LIKELY(x) x /** @todo */ -# define C4_UNLIKELY(x) x /** @todo */ -# define C4_UNREACHABLE() /** @todo */ -# define C4_ATTR_FORMAT(...) /** */ -# define C4_NORETURN /** @todo */ -#endif - -#ifndef _MSC_VER -# define C4_FUNC __FUNCTION__ -# define C4_PRETTY_FUNC __PRETTY_FUNCTION__ -#else /// @todo assuming gcc-like compiler. check it is actually so. -# define C4_FUNC __FUNCTION__ -# define C4_PRETTY_FUNC __FUNCSIG__ -#endif - -/** prevent compiler warnings about a specific var being unused */ -#define C4_UNUSED(var) (void)var - -#if C4_CPP >= 17 -#define C4_STATIC_ASSERT(cond) static_assert(cond) -#else -#define C4_STATIC_ASSERT(cond) static_assert((cond), #cond) -#endif -#define C4_STATIC_ASSERT_MSG(cond, msg) static_assert((cond), #cond ": " msg) - -/** @def C4_DONT_OPTIMIZE idea lifted from GoogleBenchmark. - * @see https://github.com/google/benchmark/blob/master/include/benchmark/benchmark_api.h */ -namespace c4 { -namespace detail { -#ifdef __GNUC__ -# define C4_DONT_OPTIMIZE(var) c4::detail::dont_optimize(var) -template< class T > -C4_ALWAYS_INLINE void dont_optimize(T const& value) { asm volatile("" : : "g"(value) : "memory"); } -#else -# define C4_DONT_OPTIMIZE(var) c4::detail::use_char_pointer(reinterpret_cast< const char* >(&var)) -void use_char_pointer(char const volatile*); -#endif -} // namespace detail -} // namespace c4 - -/** @def C4_KEEP_EMPTY_LOOP prevent an empty loop from being optimized out. - * @see http://stackoverflow.com/a/7084193/5875572 */ -#ifndef _MSC_VER -# define C4_KEEP_EMPTY_LOOP { asm(""); } -#else -# define C4_KEEP_EMPTY_LOOP { char c; C4_DONT_OPTIMIZE(c); } -#endif - -/** @def C4_VA_LIST_REUSE_MUST_COPY - * @todo I strongly suspect that this is actually only in UNIX platforms. revisit this. */ -#ifdef __GNUC__ -# define C4_VA_LIST_REUSE_MUST_COPY -#endif - -#endif /* _C4_LANGUAGE_HPP_ */ - - -// (end https://github.com/biojppm/c4core/src/c4/language.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/types.hpp -// https://github.com/biojppm/c4core/src/c4/types.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_TYPES_HPP_ -#define _C4_TYPES_HPP_ - -//included above: -//#include -#include -//included above: -//#include - -#if __cplusplus >= 201103L -#include // for integer_sequence and friends -#endif - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/preprocessor.hpp -//#include "c4/preprocessor.hpp" -#if !defined(C4_PREPROCESSOR_HPP_) && !defined(_C4_PREPROCESSOR_HPP_) -#error "amalgamate: file c4/preprocessor.hpp must have been included at this point" -#endif /* C4_PREPROCESSOR_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/language.hpp -//#include "c4/language.hpp" -#if !defined(C4_LANGUAGE_HPP_) && !defined(_C4_LANGUAGE_HPP_) -#error "amalgamate: file c4/language.hpp must have been included at this point" -#endif /* C4_LANGUAGE_HPP_ */ - - -/** @file types.hpp basic types, and utility macros and traits for types. - * @ingroup basic_headers */ - -/** @defgroup types Type utilities */ - -namespace c4 { - -/** @defgroup intrinsic_types Intrinsic types - * @ingroup types - * @{ */ - -using cbyte = const char; /**< a constant byte */ -using byte = char; /**< a mutable byte */ - -using i8 = int8_t; -using i16 = int16_t; -using i32 = int32_t; -using i64 = int64_t; -using u8 = uint8_t; -using u16 = uint16_t; -using u32 = uint32_t; -using u64 = uint64_t; - -using f32 = float; -using f64 = double; - -using ssize_t = typename std::make_signed::type; - -/** @} */ - -//-------------------------------------------------- - -/** @defgroup utility_types Utility types - * @ingroup types - * @{ */ - -// some tag types - -/** a tag type for initializing the containers with variadic arguments a la - * initializer_list, minus the initializer_list overload problems. - */ -struct aggregate_t {}; -/** @see aggregate_t */ -constexpr const aggregate_t aggregate{}; - -/** a tag type for specifying the initial capacity of allocatable contiguous storage */ -struct with_capacity_t {}; -/** @see with_capacity_t */ -constexpr const with_capacity_t with_capacity{}; - -/** a tag type for disambiguating template parameter packs in variadic template overloads */ -struct varargs_t {}; -/** @see with_capacity_t */ -constexpr const varargs_t varargs{}; - - -//-------------------------------------------------- - -/** whether a value should be used in place of a const-reference in argument passing. */ -template -struct cref_uses_val -{ - enum { value = ( - std::is_scalar::value - || - ( -#if C4_CPP >= 20 - (std::is_trivially_copyable::value && std::is_standard_layout::value) -#else - std::is_pod::value -#endif - && - sizeof(T) <= sizeof(size_t))) }; -}; -/** utility macro to override the default behaviour for c4::fastcref - @see fastcref */ -#define C4_CREF_USES_VAL(T) \ -template<> \ -struct cref_uses_val \ -{ \ - enum { value = true }; \ -}; - -/** Whether to use pass-by-value or pass-by-const-reference in a function argument - * or return type. */ -template -using fastcref = typename std::conditional::value, T, T const&>::type; - -//-------------------------------------------------- - -/** Just what its name says. Useful sometimes as a default empty policy class. */ -struct EmptyStruct -{ - template EmptyStruct(T && ...){} -}; - -/** Just what its name says. Useful sometimes as a default policy class to - * be inherited from. */ -struct EmptyStructVirtual -{ - virtual ~EmptyStructVirtual() = default; - template EmptyStructVirtual(T && ...){} -}; - - -/** */ -template -struct inheritfrom : public T {}; - -//-------------------------------------------------- -// Utilities to make a class obey size restrictions (eg, min size or size multiple of). -// DirectX usually makes this restriction with uniform buffers. -// This is also useful for padding to prevent false-sharing. - -/** how many bytes must be added to size such that the result is at least minsize? */ -C4_ALWAYS_INLINE constexpr size_t min_remainder(size_t size, size_t minsize) noexcept -{ - return size < minsize ? minsize-size : 0; -} - -/** how many bytes must be added to size such that the result is a multiple of multipleof? */ -C4_ALWAYS_INLINE constexpr size_t mult_remainder(size_t size, size_t multipleof) noexcept -{ - return (((size % multipleof) != 0) ? (multipleof-(size % multipleof)) : 0); -} - -/* force the following class to be tightly packed. */ -#pragma pack(push, 1) -/** pad a class with more bytes at the end. - * @see http://stackoverflow.com/questions/21092415/force-c-structure-to-pack-tightly */ -template -struct Padded : public T -{ - using T::T; - using T::operator=; - Padded(T const& val) : T(val) {} - Padded(T && val) : T(val) {} - char ___c4padspace___[BytesToPadAtEnd]; -}; -#pragma pack(pop) -/** When the padding argument is 0, we cannot declare the char[] array. */ -template -struct Padded : public T -{ - using T::T; - using T::operator=; - Padded(T const& val) : T(val) {} - Padded(T && val) : T(val) {} -}; - -/** make T have a size which is at least Min bytes */ -template -using MinSized = Padded; - -/** make T have a size which is a multiple of Mult bytes */ -template -using MultSized = Padded; - -/** make T have a size which is simultaneously: - * -bigger or equal than Min - * -a multiple of Mult */ -template -using MinMultSized = MultSized, Mult>; - -/** make T be suitable for use as a uniform buffer. (at least with DirectX). */ -template -using UbufSized = MinMultSized; - - -//----------------------------------------------------------------------------- - -#define C4_NO_COPY_CTOR(ty) ty(ty const&) = delete -#define C4_NO_MOVE_CTOR(ty) ty(ty &&) = delete -#define C4_NO_COPY_ASSIGN(ty) ty& operator=(ty const&) = delete -#define C4_NO_MOVE_ASSIGN(ty) ty& operator=(ty &&) = delete -#define C4_DEFAULT_COPY_CTOR(ty) ty(ty const&) noexcept = default -#define C4_DEFAULT_MOVE_CTOR(ty) ty(ty &&) noexcept = default -#define C4_DEFAULT_COPY_ASSIGN(ty) ty& operator=(ty const&) noexcept = default -#define C4_DEFAULT_MOVE_ASSIGN(ty) ty& operator=(ty &&) noexcept = default - -#define C4_NO_COPY_OR_MOVE_CTOR(ty) \ - C4_NO_COPY_CTOR(ty); \ - C4_NO_MOVE_CTOR(ty) - -#define C4_NO_COPY_OR_MOVE_ASSIGN(ty) \ - C4_NO_COPY_ASSIGN(ty); \ - C4_NO_MOVE_ASSIGN(ty) - -#define C4_NO_COPY_OR_MOVE(ty) \ - C4_NO_COPY_OR_MOVE_CTOR(ty); \ - C4_NO_COPY_OR_MOVE_ASSIGN(ty) - -#define C4_DEFAULT_COPY_AND_MOVE_CTOR(ty) \ - C4_DEFAULT_COPY_CTOR(ty); \ - C4_DEFAULT_MOVE_CTOR(ty) - -#define C4_DEFAULT_COPY_AND_MOVE_ASSIGN(ty) \ - C4_DEFAULT_COPY_ASSIGN(ty); \ - C4_DEFAULT_MOVE_ASSIGN(ty) - -#define C4_DEFAULT_COPY_AND_MOVE(ty) \ - C4_DEFAULT_COPY_AND_MOVE_CTOR(ty); \ - C4_DEFAULT_COPY_AND_MOVE_ASSIGN(ty) - -/** @see https://en.cppreference.com/w/cpp/named_req/TriviallyCopyable */ -#define C4_MUST_BE_TRIVIAL_COPY(ty) \ - static_assert(std::is_trivially_copyable::value, #ty " must be trivially copyable") - -/** @} */ - - -//----------------------------------------------------------------------------- - -/** @defgroup traits_types Type traits utilities - * @ingroup types - * @{ */ - -// http://stackoverflow.com/questions/10821380/is-t-an-instance-of-a-template-in-c -template class X, typename T> struct is_instance_of_tpl : std::false_type {}; -template class X, typename... Y> struct is_instance_of_tpl> : std::true_type {}; - -//----------------------------------------------------------------------------- - -/** SFINAE. use this macro to enable a template function overload -based on a compile-time condition. -@code -// define an overload for a non-pod type -template::value)> -void foo() { std::cout << "pod type\n"; } - -// define an overload for a non-pod type -template::value)> -void foo() { std::cout << "nonpod type\n"; } - -struct non_pod -{ - non_pod() : name("asdfkjhasdkjh") {} - const char *name; -}; - -int main() -{ - foo(); // prints "pod type" - foo(); // prints "nonpod type" -} -@endcode */ -#define C4_REQUIRE_T(cond) typename std::enable_if::type* = nullptr - -/** enable_if for a return type - * @see C4_REQUIRE_T */ -#define C4_REQUIRE_R(cond, type_) typename std::enable_if::type - -//----------------------------------------------------------------------------- -/** define a traits class reporting whether a type provides a member typedef */ -#define C4_DEFINE_HAS_TYPEDEF(member_typedef) \ -template \ -struct has_##stype \ -{ \ -private: \ - \ - typedef char yes; \ - typedef struct { char array[2]; } no; \ - \ - template \ - static yes _test(typename C::member_typedef*); \ - \ - template \ - static no _test(...); \ - \ -public: \ - \ - enum { value = (sizeof(_test(0)) == sizeof(yes)) }; \ - \ -} - - -/** @} */ - - -//----------------------------------------------------------------------------- - - -/** @defgroup type_declarations Type declaration utilities - * @ingroup types - * @{ */ - -#define _c4_DEFINE_ARRAY_TYPES_WITHOUT_ITERATOR(T, I) \ - \ - using size_type = I; \ - using ssize_type = typename std::make_signed::type; \ - using difference_type = typename std::make_signed::type; \ - \ - using value_type = T; \ - using pointer = T*; \ - using const_pointer = T const*; \ - using reference = T&; \ - using const_reference = T const& - -#define _c4_DEFINE_TUPLE_ARRAY_TYPES_WITHOUT_ITERATOR(interior_types, I) \ - \ - using size_type = I; \ - using ssize_type = typename std::make_signed::type; \ - using difference_type = typename std::make_signed::type; \ - \ - template using value_type = typename std::tuple_element< n, std::tuple>::type; \ - template using pointer = value_type*; \ - template using const_pointer = value_type const*; \ - template using reference = value_type&; \ - template using const_reference = value_type const& - - -#define _c4_DEFINE_ARRAY_TYPES(T, I) \ - \ - _c4_DEFINE_ARRAY_TYPES_WITHOUT_ITERATOR(T, I); \ - \ - using iterator = T*; \ - using const_iterator = T const*; \ - using reverse_iterator = std::reverse_iterator; \ - using const_reverse_iterator = std::reverse_iterator - - -#define _c4_DEFINE_TUPLE_ARRAY_TYPES(interior_types, I) \ - \ - _c4_DEFINE_TUPLE_ARRAY_TYPES_WITHOUT_ITERATOR(interior_types, I); \ - \ - template using iterator = value_type*; \ - template using const_iterator = value_type const*; \ - template using reverse_iterator = std::reverse_iterator< value_type*>; \ - template using const_reverse_iterator = std::reverse_iterator< value_type const*> - - - -/** @} */ - - -//----------------------------------------------------------------------------- - - -/** @defgroup compatility_utilities Backport implementation of some Modern C++ utilities - * @ingroup types - * @{ */ - -//----------------------------------------------------------------------------- -// index_sequence and friends are available only for C++14 and later. -// A C++11 implementation is provided here. -// This implementation was copied over from clang. -// see http://llvm.org/viewvc/llvm-project/libcxx/trunk/include/utility?revision=211563&view=markup#l687 - -#if __cplusplus > 201103L - -using std::integer_sequence; -using std::index_sequence; -using std::make_integer_sequence; -using std::make_index_sequence; -using std::index_sequence_for; - -#else - -/** C++11 implementation of integer sequence - * @see https://en.cppreference.com/w/cpp/utility/integer_sequence - * @see taken from clang: http://llvm.org/viewvc/llvm-project/libcxx/trunk/include/utility?revision=211563&view=markup#l687 */ -template -struct integer_sequence -{ - static_assert(std::is_integral<_Tp>::value, - "std::integer_sequence can only be instantiated with an integral type" ); - using value_type = _Tp; - static constexpr size_t size() noexcept { return sizeof...(_Ip); } -}; - -/** C++11 implementation of index sequence - * @see https://en.cppreference.com/w/cpp/utility/integer_sequence - * @see taken from clang: http://llvm.org/viewvc/llvm-project/libcxx/trunk/include/utility?revision=211563&view=markup#l687 */ -template -using index_sequence = integer_sequence; - -/** @cond DONT_DOCUMENT_THIS */ -namespace __detail { - -template -struct __repeat; - -template -struct __repeat, _Extra...> -{ - using type = integer_sequence<_Tp, - _Np..., - sizeof...(_Np) + _Np..., - 2 * sizeof...(_Np) + _Np..., - 3 * sizeof...(_Np) + _Np..., - 4 * sizeof...(_Np) + _Np..., - 5 * sizeof...(_Np) + _Np..., - 6 * sizeof...(_Np) + _Np..., - 7 * sizeof...(_Np) + _Np..., - _Extra...>; -}; - -template struct __parity; -template struct __make : __parity<_Np % 8>::template __pmake<_Np> {}; - -template<> struct __make<0> { using type = integer_sequence; }; -template<> struct __make<1> { using type = integer_sequence; }; -template<> struct __make<2> { using type = integer_sequence; }; -template<> struct __make<3> { using type = integer_sequence; }; -template<> struct __make<4> { using type = integer_sequence; }; -template<> struct __make<5> { using type = integer_sequence; }; -template<> struct __make<6> { using type = integer_sequence; }; -template<> struct __make<7> { using type = integer_sequence; }; - -template<> struct __parity<0> { template struct __pmake : __repeat::type> {}; }; -template<> struct __parity<1> { template struct __pmake : __repeat::type, _Np - 1> {}; }; -template<> struct __parity<2> { template struct __pmake : __repeat::type, _Np - 2, _Np - 1> {}; }; -template<> struct __parity<3> { template struct __pmake : __repeat::type, _Np - 3, _Np - 2, _Np - 1> {}; }; -template<> struct __parity<4> { template struct __pmake : __repeat::type, _Np - 4, _Np - 3, _Np - 2, _Np - 1> {}; }; -template<> struct __parity<5> { template struct __pmake : __repeat::type, _Np - 5, _Np - 4, _Np - 3, _Np - 2, _Np - 1> {}; }; -template<> struct __parity<6> { template struct __pmake : __repeat::type, _Np - 6, _Np - 5, _Np - 4, _Np - 3, _Np - 2, _Np - 1> {}; }; -template<> struct __parity<7> { template struct __pmake : __repeat::type, _Np - 7, _Np - 6, _Np - 5, _Np - 4, _Np - 3, _Np - 2, _Np - 1> {}; }; - -template -struct __convert -{ - template struct __result; - template<_Tp ..._Np> struct __result> - { - using type = integer_sequence<_Up, _Np...>; - }; -}; - -template -struct __convert<_Tp, _Tp> -{ - template struct __result - { - using type = _Up; - }; -}; - -template -using __make_integer_sequence_unchecked = typename __detail::__convert::template __result::type>::type; - -template -struct __make_integer_sequence -{ - static_assert(std::is_integral<_Tp>::value, - "std::make_integer_sequence can only be instantiated with an integral type" ); - static_assert(0 <= _Ep, "std::make_integer_sequence input shall not be negative"); - typedef __make_integer_sequence_unchecked<_Tp, _Ep> type; -}; - -} // namespace __detail -/** @endcond */ - - -/** C++11 implementation of index sequence - * @see https://en.cppreference.com/w/cpp/utility/integer_sequence - * @see taken from clang: http://llvm.org/viewvc/llvm-project/libcxx/trunk/include/utility?revision=211563&view=markup#l687 */ -template -using make_integer_sequence = typename __detail::__make_integer_sequence<_Tp, _Np>::type; - -/** C++11 implementation of index sequence - * @see https://en.cppreference.com/w/cpp/utility/integer_sequence - * @see taken from clang: http://llvm.org/viewvc/llvm-project/libcxx/trunk/include/utility?revision=211563&view=markup#l687 */ -template -using make_index_sequence = make_integer_sequence; - -/** C++11 implementation of index sequence - * @see https://en.cppreference.com/w/cpp/utility/integer_sequence - * @see taken from clang: http://llvm.org/viewvc/llvm-project/libcxx/trunk/include/utility?revision=211563&view=markup#l687 */ -template -using index_sequence_for = make_index_sequence; -#endif - -/** @} */ - - -} // namespace c4 - -#endif /* _C4_TYPES_HPP_ */ - - -// (end https://github.com/biojppm/c4core/src/c4/types.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/config.hpp -// https://github.com/biojppm/c4core/src/c4/config.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_CONFIG_HPP_ -#define _C4_CONFIG_HPP_ - -/** @defgroup basic_headers Basic headers - * @brief Headers providing basic macros, platform+cpu+compiler information, - * C++ facilities and basic typedefs. */ - -/** @file config.hpp Contains configuration defines and includes the basic_headers. - * @ingroup basic_headers */ - -//#define C4_DEBUG - -#define C4_ERROR_SHOWS_FILELINE -//#define C4_ERROR_SHOWS_FUNC -//#define C4_ERROR_THROWS_EXCEPTION -//#define C4_NO_ALLOC_DEFAULTS -//#define C4_REDEFINE_CPPNEW - -#ifndef C4_SIZE_TYPE -# define C4_SIZE_TYPE size_t -#endif - -#ifndef C4_STR_SIZE_TYPE -# define C4_STR_SIZE_TYPE C4_SIZE_TYPE -#endif - -#ifndef C4_TIME_TYPE -# define C4_TIME_TYPE double -#endif - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/export.hpp -//#include "c4/export.hpp" -#if !defined(C4_EXPORT_HPP_) && !defined(_C4_EXPORT_HPP_) -#error "amalgamate: file c4/export.hpp must have been included at this point" -#endif /* C4_EXPORT_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/preprocessor.hpp -//#include "c4/preprocessor.hpp" -#if !defined(C4_PREPROCESSOR_HPP_) && !defined(_C4_PREPROCESSOR_HPP_) -#error "amalgamate: file c4/preprocessor.hpp must have been included at this point" -#endif /* C4_PREPROCESSOR_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/platform.hpp -//#include "c4/platform.hpp" -#if !defined(C4_PLATFORM_HPP_) && !defined(_C4_PLATFORM_HPP_) -#error "amalgamate: file c4/platform.hpp must have been included at this point" -#endif /* C4_PLATFORM_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/cpu.hpp -//#include "c4/cpu.hpp" -#if !defined(C4_CPU_HPP_) && !defined(_C4_CPU_HPP_) -#error "amalgamate: file c4/cpu.hpp must have been included at this point" -#endif /* C4_CPU_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/compiler.hpp -//#include "c4/compiler.hpp" -#if !defined(C4_COMPILER_HPP_) && !defined(_C4_COMPILER_HPP_) -#error "amalgamate: file c4/compiler.hpp must have been included at this point" -#endif /* C4_COMPILER_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/language.hpp -//#include "c4/language.hpp" -#if !defined(C4_LANGUAGE_HPP_) && !defined(_C4_LANGUAGE_HPP_) -#error "amalgamate: file c4/language.hpp must have been included at this point" -#endif /* C4_LANGUAGE_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/types.hpp -//#include "c4/types.hpp" -#if !defined(C4_TYPES_HPP_) && !defined(_C4_TYPES_HPP_) -#error "amalgamate: file c4/types.hpp must have been included at this point" -#endif /* C4_TYPES_HPP_ */ - - -#endif // _C4_CONFIG_HPP_ - - -// (end https://github.com/biojppm/c4core/src/c4/config.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/ext/debugbreak/debugbreak.h -// https://github.com/biojppm/c4core/src/c4/ext/debugbreak/debugbreak.h -//-------------------------------------------------------------------------------- -//******************************************************************************** - -/* Copyright (c) 2011-2021, Scott Tsai - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ -#ifndef DEBUG_BREAK_H -#define DEBUG_BREAK_H - -#ifdef _MSC_VER - -#define debug_break __debugbreak - -#else - -#ifdef __cplusplus -extern "C" { -#endif - -#define DEBUG_BREAK_USE_TRAP_INSTRUCTION 1 -#define DEBUG_BREAK_USE_BULTIN_TRAP 2 -#define DEBUG_BREAK_USE_SIGTRAP 3 - -#if defined(__i386__) || defined(__x86_64__) - #define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_TRAP_INSTRUCTION -__inline__ static void trap_instruction(void) -{ - __asm__ volatile("int $0x03"); -} -#elif defined(__thumb__) - #define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_TRAP_INSTRUCTION -/* FIXME: handle __THUMB_INTERWORK__ */ -__attribute__((always_inline)) -__inline__ static void trap_instruction(void) -{ - /* See 'arm-linux-tdep.c' in GDB source. - * Both instruction sequences below work. */ -#if 1 - /* 'eabi_linux_thumb_le_breakpoint' */ - __asm__ volatile(".inst 0xde01"); -#else - /* 'eabi_linux_thumb2_le_breakpoint' */ - __asm__ volatile(".inst.w 0xf7f0a000"); -#endif - - /* Known problem: - * After a breakpoint hit, can't 'stepi', 'step', or 'continue' in GDB. - * 'step' would keep getting stuck on the same instruction. - * - * Workaround: use the new GDB commands 'debugbreak-step' and - * 'debugbreak-continue' that become available - * after you source the script from GDB: - * - * $ gdb -x debugbreak-gdb.py <... USUAL ARGUMENTS ...> - * - * 'debugbreak-step' would jump over the breakpoint instruction with - * roughly equivalent of: - * (gdb) set $instruction_len = 2 - * (gdb) tbreak *($pc + $instruction_len) - * (gdb) jump *($pc + $instruction_len) - */ -} -#elif defined(__arm__) && !defined(__thumb__) - #define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_TRAP_INSTRUCTION -__attribute__((always_inline)) -__inline__ static void trap_instruction(void) -{ - /* See 'arm-linux-tdep.c' in GDB source, - * 'eabi_linux_arm_le_breakpoint' */ - __asm__ volatile(".inst 0xe7f001f0"); - /* Known problem: - * Same problem and workaround as Thumb mode */ -} -#elif defined(__aarch64__) && defined(__APPLE__) - #define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_BULTIN_DEBUGTRAP -#elif defined(__aarch64__) - #define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_TRAP_INSTRUCTION -__attribute__((always_inline)) -__inline__ static void trap_instruction(void) -{ - /* See 'aarch64-tdep.c' in GDB source, - * 'aarch64_default_breakpoint' */ - __asm__ volatile(".inst 0xd4200000"); -} -#elif defined(__powerpc__) - /* PPC 32 or 64-bit, big or little endian */ - #define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_TRAP_INSTRUCTION -__attribute__((always_inline)) -__inline__ static void trap_instruction(void) -{ - /* See 'rs6000-tdep.c' in GDB source, - * 'rs6000_breakpoint' */ - __asm__ volatile(".4byte 0x7d821008"); - - /* Known problem: - * After a breakpoint hit, can't 'stepi', 'step', or 'continue' in GDB. - * 'step' stuck on the same instruction ("twge r2,r2"). - * - * The workaround is the same as ARM Thumb mode: use debugbreak-gdb.py - * or manually jump over the instruction. */ -} -#elif defined(__riscv) - /* RISC-V 32 or 64-bit, whether the "C" extension - * for compressed, 16-bit instructions are supported or not */ - #define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_TRAP_INSTRUCTION -__attribute__((always_inline)) -__inline__ static void trap_instruction(void) -{ - /* See 'riscv-tdep.c' in GDB source, - * 'riscv_sw_breakpoint_from_kind' */ - __asm__ volatile(".4byte 0x00100073"); -} -#else - #define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_SIGTRAP -#endif - - -#ifndef DEBUG_BREAK_IMPL -#error "debugbreak.h is not supported on this target" -#elif DEBUG_BREAK_IMPL == DEBUG_BREAK_USE_TRAP_INSTRUCTION -__attribute__((always_inline)) -__inline__ static void debug_break(void) -{ - trap_instruction(); -} -#elif DEBUG_BREAK_IMPL == DEBUG_BREAK_USE_BULTIN_DEBUGTRAP -__attribute__((always_inline)) -__inline__ static void debug_break(void) -{ - __builtin_debugtrap(); -} -#elif DEBUG_BREAK_IMPL == DEBUG_BREAK_USE_BULTIN_TRAP -__attribute__((always_inline)) -__inline__ static void debug_break(void) -{ - __builtin_trap(); -} -#elif DEBUG_BREAK_IMPL == DEBUG_BREAK_USE_SIGTRAP -#include -__attribute__((always_inline)) -__inline__ static void debug_break(void) -{ - raise(SIGTRAP); -} -#else -#error "invalid DEBUG_BREAK_IMPL value" -#endif - -#ifdef __cplusplus -} -#endif - -#endif /* ifdef _MSC_VER */ - -#endif /* ifndef DEBUG_BREAK_H */ - - -// (end https://github.com/biojppm/c4core/src/c4/ext/debugbreak/debugbreak.h) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/error.hpp -// https://github.com/biojppm/c4core/src/c4/error.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_ERROR_HPP_ -#define _C4_ERROR_HPP_ - -/** @file error.hpp Facilities for error reporting and runtime assertions. */ - -/** @defgroup error_checking Error checking */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/config.hpp -//#include "c4/config.hpp" -#if !defined(C4_CONFIG_HPP_) && !defined(_C4_CONFIG_HPP_) -#error "amalgamate: file c4/config.hpp must have been included at this point" -#endif /* C4_CONFIG_HPP_ */ - - -#ifdef _DOXYGEN_ - /** if this is defined and exceptions are enabled, then calls to C4_ERROR() - * will throw an exception - * @ingroup error_checking */ -# define C4_EXCEPTIONS_ENABLED - /** if this is defined and exceptions are enabled, then calls to C4_ERROR() - * will throw an exception - * @see C4_EXCEPTIONS_ENABLED - * @ingroup error_checking */ -# define C4_ERROR_THROWS_EXCEPTION - /** evaluates to noexcept when C4_ERROR might be called and - * exceptions are disabled. Otherwise, defaults to nothing. - * @ingroup error_checking */ -# define C4_NOEXCEPT -#endif // _DOXYGEN_ - -#if defined(C4_EXCEPTIONS_ENABLED) && defined(C4_ERROR_THROWS_EXCEPTION) -# define C4_NOEXCEPT -#else -# define C4_NOEXCEPT noexcept -#endif - - -namespace c4 { -namespace detail { -struct fail_type__ {}; -} // detail -} // c4 -#define C4_STATIC_ERROR(dummy_type, errmsg) \ - static_assert(std::is_same::value, errmsg) - - -//----------------------------------------------------------------------------- - -#define C4_ASSERT_SAME_TYPE(ty1, ty2) \ - C4_STATIC_ASSERT(std::is_same::value) - -#define C4_ASSERT_DIFF_TYPE(ty1, ty2) \ - C4_STATIC_ASSERT( ! std::is_same::value) - - -//----------------------------------------------------------------------------- - -#ifdef _DOXYGEN_ -/** utility macro that triggers a breakpoint when - * the debugger is attached and NDEBUG is not defined. - * @ingroup error_checking */ -# define C4_DEBUG_BREAK() -#endif // _DOXYGEN_ - - -#if defined(NDEBUG) || defined(C4_NO_DEBUG_BREAK) -# define C4_DEBUG_BREAK() -#else -# ifdef __clang__ -# pragma clang diagnostic push -# if !defined(__APPLE_CC__) -# if __clang_major__ >= 10 -# pragma clang diagnostic ignored "-Wgnu-inline-cpp-without-extern" // debugbreak/debugbreak.h:50:16: error: 'gnu_inline' attribute without 'extern' in C++ treated as externally available, this changed in Clang 10 [-Werror,-Wgnu-inline-cpp-without-extern] -# endif -# else -# if __clang_major__ >= 13 -# pragma clang diagnostic ignored "-Wgnu-inline-cpp-without-extern" // debugbreak/debugbreak.h:50:16: error: 'gnu_inline' attribute without 'extern' in C++ treated as externally available, this changed in Clang 10 [-Werror,-Wgnu-inline-cpp-without-extern] -# endif -# endif -# elif defined(__GNUC__) -# endif -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/ext/debugbreak/debugbreak.h -//# include -#if !defined(DEBUG_BREAK_H) && !defined(_DEBUG_BREAK_H) -#error "amalgamate: file c4/ext/debugbreak/debugbreak.h must have been included at this point" -#endif /* DEBUG_BREAK_H */ - -# define C4_DEBUG_BREAK() if(c4::is_debugger_attached()) { ::debug_break(); } -# ifdef __clang__ -# pragma clang diagnostic pop -# elif defined(__GNUC__) -# endif -#endif - -namespace c4 { -C4CORE_EXPORT bool is_debugger_attached(); -} // namespace c4 - - -//----------------------------------------------------------------------------- - -#ifdef __clang__ - /* NOTE: using , ## __VA_ARGS__ to deal with zero-args calls to - * variadic macros is not portable, but works in clang, gcc, msvc, icc. - * clang requires switching off compiler warnings for pedantic mode. - * @see http://stackoverflow.com/questions/32047685/variadic-macro-without-arguments */ -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" // warning: token pasting of ',' and __VA_ARGS__ is a GNU extension -#elif defined(__GNUC__) - /* GCC also issues a warning for zero-args calls to variadic macros. - * This warning is switched on with -pedantic and apparently there is no - * easy way to turn it off as with clang. But marking this as a system - * header works. - * @see https://gcc.gnu.org/onlinedocs/cpp/System-Headers.html - * @see http://stackoverflow.com/questions/35587137/ */ -# pragma GCC system_header -#endif - - -//----------------------------------------------------------------------------- - -namespace c4 { - -typedef enum : uint32_t { - /** when an error happens and the debugger is attached, call C4_DEBUG_BREAK(). - * Without effect otherwise. */ - ON_ERROR_DEBUGBREAK = 0x01 << 0, - /** when an error happens log a message. */ - ON_ERROR_LOG = 0x01 << 1, - /** when an error happens invoke a callback if it was set with - * set_error_callback(). */ - ON_ERROR_CALLBACK = 0x01 << 2, - /** when an error happens call std::terminate(). */ - ON_ERROR_ABORT = 0x01 << 3, - /** when an error happens and exceptions are enabled throw an exception. - * Without effect otherwise. */ - ON_ERROR_THROW = 0x01 << 4, - /** the default flags. */ - ON_ERROR_DEFAULTS = ON_ERROR_DEBUGBREAK|ON_ERROR_LOG|ON_ERROR_CALLBACK|ON_ERROR_ABORT -} ErrorFlags_e; -using error_flags = uint32_t; -C4CORE_EXPORT void set_error_flags(error_flags f); -C4CORE_EXPORT error_flags get_error_flags(); - - -using error_callback_type = void (*)(const char* msg, size_t msg_size); -C4CORE_EXPORT void set_error_callback(error_callback_type cb); -C4CORE_EXPORT error_callback_type get_error_callback(); - - -//----------------------------------------------------------------------------- -/** RAII class controling the error settings inside a scope. */ -struct ScopedErrorSettings -{ - error_flags m_flags; - error_callback_type m_callback; - - explicit ScopedErrorSettings(error_callback_type cb) - : m_flags(get_error_flags()), - m_callback(get_error_callback()) - { - set_error_callback(cb); - } - explicit ScopedErrorSettings(error_flags flags) - : m_flags(get_error_flags()), - m_callback(get_error_callback()) - { - set_error_flags(flags); - } - explicit ScopedErrorSettings(error_flags flags, error_callback_type cb) - : m_flags(get_error_flags()), - m_callback(get_error_callback()) - { - set_error_flags(flags); - set_error_callback(cb); - } - ~ScopedErrorSettings() - { - set_error_flags(m_flags); - set_error_callback(m_callback); - } -}; - - -//----------------------------------------------------------------------------- - -/** source location */ -struct srcloc; - -C4CORE_EXPORT void handle_error(srcloc s, const char *fmt, ...); -C4CORE_EXPORT void handle_warning(srcloc s, const char *fmt, ...); - - -# define C4_ERROR(msg, ...) \ - do { \ - if(c4::get_error_flags() & c4::ON_ERROR_DEBUGBREAK) \ - { \ - C4_DEBUG_BREAK() \ - } \ - c4::handle_error(C4_SRCLOC(), msg, ## __VA_ARGS__); \ - } while(0) - - -# define C4_WARNING(msg, ...) \ - c4::handle_warning(C4_SRCLOC(), msg, ## __VA_ARGS__) - - -#if defined(C4_ERROR_SHOWS_FILELINE) && defined(C4_ERROR_SHOWS_FUNC) - -struct srcloc -{ - const char *file = ""; - const char *func = ""; - int line = 0; -}; -#define C4_SRCLOC() c4::srcloc{__FILE__, C4_PRETTY_FUNC, __LINE__} - -#elif defined(C4_ERROR_SHOWS_FILELINE) - -struct srcloc -{ - const char *file; - int line; -}; -#define C4_SRCLOC() c4::srcloc{__FILE__, __LINE__} - -#elif ! defined(C4_ERROR_SHOWS_FUNC) - -struct srcloc -{ -}; -#define C4_SRCLOC() c4::srcloc() - -#else -# error not implemented -#endif - - -//----------------------------------------------------------------------------- -// assertions - -// Doxygen needs this so that only one definition counts -#ifdef _DOXYGEN_ - /** Explicitly enables assertions, independently of NDEBUG status. - * This is meant to allow enabling assertions even when NDEBUG is defined. - * Defaults to undefined. - * @ingroup error_checking */ -# define C4_USE_ASSERT - /** assert that a condition is true; this is turned off when NDEBUG - * is defined and C4_USE_ASSERT is not true. - * @ingroup error_checking */ -# define C4_ASSERT - /** same as C4_ASSERT(), additionally prints a printf-formatted message - * @ingroup error_checking */ -# define C4_ASSERT_MSG - /** evaluates to C4_NOEXCEPT when C4_XASSERT is disabled; otherwise, defaults - * to noexcept - * @ingroup error_checking */ -# define C4_NOEXCEPT_A -#endif // _DOXYGEN_ - -#ifndef C4_USE_ASSERT -# ifdef NDEBUG -# define C4_USE_ASSERT 0 -# else -# define C4_USE_ASSERT 1 -# endif -#endif - -#if C4_USE_ASSERT -# define C4_ASSERT(cond) C4_CHECK(cond) -# define C4_ASSERT_MSG(cond, /*fmt, */...) C4_CHECK_MSG(cond, ## __VA_ARGS__) -# define C4_ASSERT_IF(predicate, cond) if(predicate) { C4_ASSERT(cond); } -# define C4_NOEXCEPT_A C4_NOEXCEPT -#else -# define C4_ASSERT(cond) -# define C4_ASSERT_MSG(cond, /*fmt, */...) -# define C4_ASSERT_IF(predicate, cond) -# define C4_NOEXCEPT_A noexcept -#endif - - -//----------------------------------------------------------------------------- -// extreme assertions - -// Doxygen needs this so that only one definition counts -#ifdef _DOXYGEN_ - /** Explicitly enables extreme assertions; this is meant to allow enabling - * assertions even when NDEBUG is defined. Defaults to undefined. - * @ingroup error_checking */ -# define C4_USE_XASSERT - /** extreme assertion: can be switched off independently of - * the regular assertion; use for example for bounds checking in hot code. - * Turned on only when C4_USE_XASSERT is defined - * @ingroup error_checking */ -# define C4_XASSERT - /** same as C4_XASSERT(), and additionally prints a printf-formatted message - * @ingroup error_checking */ -# define C4_XASSERT_MSG - /** evaluates to C4_NOEXCEPT when C4_XASSERT is disabled; otherwise, defaults to noexcept - * @ingroup error_checking */ -# define C4_NOEXCEPT_X -#endif // _DOXYGEN_ - -#ifndef C4_USE_XASSERT -# define C4_USE_XASSERT C4_USE_ASSERT -#endif - -#if C4_USE_XASSERT -# define C4_XASSERT(cond) C4_CHECK(cond) -# define C4_XASSERT_MSG(cond, /*fmt, */...) C4_CHECK_MSG(cond, ## __VA_ARGS__) -# define C4_XASSERT_IF(predicate, cond) if(predicate) { C4_XASSERT(cond); } -# define C4_NOEXCEPT_X C4_NOEXCEPT -#else -# define C4_XASSERT(cond) -# define C4_XASSERT_MSG(cond, /*fmt, */...) -# define C4_XASSERT_IF(predicate, cond) -# define C4_NOEXCEPT_X noexcept -#endif - - -//----------------------------------------------------------------------------- -// checks: never switched-off - -/** Check that a condition is true, or raise an error when not - * true. Unlike C4_ASSERT(), this check is not disabled in non-debug - * builds. - * @see C4_ASSERT - * @ingroup error_checking - * - * @todo add constexpr-compatible compile-time assert: - * https://akrzemi1.wordpress.com/2017/05/18/asserts-in-constexpr-functions/ - */ -#define C4_CHECK(cond) \ - do { \ - if(C4_UNLIKELY(!(cond))) \ - { \ - C4_ERROR("check failed: %s", #cond); \ - } \ - } while(0) - - -/** like C4_CHECK(), and additionally log a printf-style message. - * @see C4_CHECK - * @ingroup error_checking */ -#define C4_CHECK_MSG(cond, fmt, ...) \ - do { \ - if(C4_UNLIKELY(!(cond))) \ - { \ - C4_ERROR("check failed: " #cond "\n" fmt, ## __VA_ARGS__); \ - } \ - } while(0) - - -//----------------------------------------------------------------------------- -// Common error conditions - -#define C4_NOT_IMPLEMENTED() C4_ERROR("NOT IMPLEMENTED") -#define C4_NOT_IMPLEMENTED_MSG(/*msg, */...) C4_ERROR("NOT IMPLEMENTED: " ## __VA_ARGS__) -#define C4_NOT_IMPLEMENTED_IF(condition) do { if(C4_UNLIKELY(condition)) { C4_ERROR("NOT IMPLEMENTED"); } } while(0) -#define C4_NOT_IMPLEMENTED_IF_MSG(condition, /*msg, */...) do { if(C4_UNLIKELY(condition)) { C4_ERROR("NOT IMPLEMENTED: " ## __VA_ARGS__); } } while(0) - -#define C4_NEVER_REACH() do { C4_ERROR("never reach this point"); C4_UNREACHABLE(); } while(0) -#define C4_NEVER_REACH_MSG(/*msg, */...) do { C4_ERROR("never reach this point: " ## __VA_ARGS__); C4_UNREACHABLE(); } while(0) - - - -//----------------------------------------------------------------------------- -// helpers for warning suppression -// idea adapted from https://github.com/onqtam/doctest/ - - -#ifdef C4_MSVC -#define C4_SUPPRESS_WARNING_MSVC_PUSH __pragma(warning(push)) -#define C4_SUPPRESS_WARNING_MSVC(w) __pragma(warning(disable : w)) -#define C4_SUPPRESS_WARNING_MSVC_POP __pragma(warning(pop)) -#define C4_SUPPRESS_WARNING_MSVC_WITH_PUSH(w) \ - C4_SUPPRESS_WARNING_MSVC_PUSH \ - C4_SUPPRESS_WARNING_MSVC(w) -#else // C4_MSVC -#define C4_SUPPRESS_WARNING_MSVC_PUSH -#define C4_SUPPRESS_WARNING_MSVC(w) -#define C4_SUPPRESS_WARNING_MSVC_POP -#define C4_SUPPRESS_WARNING_MSVC_WITH_PUSH(w) -#endif // C4_MSVC - - -#ifdef C4_CLANG -#define C4_PRAGMA_TO_STR(x) _Pragma(#x) -#define C4_SUPPRESS_WARNING_CLANG_PUSH _Pragma("clang diagnostic push") -#define C4_SUPPRESS_WARNING_CLANG(w) C4_PRAGMA_TO_STR(clang diagnostic ignored w) -#define C4_SUPPRESS_WARNING_CLANG_POP _Pragma("clang diagnostic pop") -#define C4_SUPPRESS_WARNING_CLANG_WITH_PUSH(w) \ - C4_SUPPRESS_WARNING_CLANG_PUSH \ - C4_SUPPRESS_WARNING_CLANG(w) -#else // C4_CLANG -#define C4_SUPPRESS_WARNING_CLANG_PUSH -#define C4_SUPPRESS_WARNING_CLANG(w) -#define C4_SUPPRESS_WARNING_CLANG_POP -#define C4_SUPPRESS_WARNING_CLANG_WITH_PUSH(w) -#endif // C4_CLANG - - -#ifdef C4_GCC -#define C4_PRAGMA_TO_STR(x) _Pragma(#x) -#define C4_SUPPRESS_WARNING_GCC_PUSH _Pragma("GCC diagnostic push") -#define C4_SUPPRESS_WARNING_GCC(w) C4_PRAGMA_TO_STR(GCC diagnostic ignored w) -#define C4_SUPPRESS_WARNING_GCC_POP _Pragma("GCC diagnostic pop") -#define C4_SUPPRESS_WARNING_GCC_WITH_PUSH(w) \ - C4_SUPPRESS_WARNING_GCC_PUSH \ - C4_SUPPRESS_WARNING_GCC(w) -#else // C4_GCC -#define C4_SUPPRESS_WARNING_GCC_PUSH -#define C4_SUPPRESS_WARNING_GCC(w) -#define C4_SUPPRESS_WARNING_GCC_POP -#define C4_SUPPRESS_WARNING_GCC_WITH_PUSH(w) -#endif // C4_GCC - - -#define C4_SUPPRESS_WARNING_GCC_CLANG_PUSH \ - C4_SUPPRESS_WARNING_GCC_PUSH \ - C4_SUPPRESS_WARNING_CLANG_PUSH - -#define C4_SUPPRESS_WARNING_GCC_CLANG(w) \ - C4_SUPPRESS_WARNING_GCC(w) \ - C4_SUPPRESS_WARNING_CLANG(w) - -#define C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH(w) \ - C4_SUPPRESS_WARNING_GCC_WITH_PUSH(w) \ - C4_SUPPRESS_WARNING_CLANG_WITH_PUSH(w) - -#define C4_SUPPRESS_WARNING_GCC_CLANG_POP \ - C4_SUPPRESS_WARNING_GCC_POP \ - C4_SUPPRESS_WARNING_CLANG_POP - -} // namespace c4 - -#ifdef __clang__ -# pragma clang diagnostic pop -#endif - -#endif /* _C4_ERROR_HPP_ */ - - -// (end https://github.com/biojppm/c4core/src/c4/error.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/memory_util.hpp -// https://github.com/biojppm/c4core/src/c4/memory_util.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_MEMORY_UTIL_HPP_ -#define _C4_MEMORY_UTIL_HPP_ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/config.hpp -//#include "c4/config.hpp" -#if !defined(C4_CONFIG_HPP_) && !defined(_C4_CONFIG_HPP_) -#error "amalgamate: file c4/config.hpp must have been included at this point" -#endif /* C4_CONFIG_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/error.hpp -//#include "c4/error.hpp" -#if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) -#error "amalgamate: file c4/error.hpp must have been included at this point" -#endif /* C4_ERROR_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/compiler.hpp -//#include "c4/compiler.hpp" -#if !defined(C4_COMPILER_HPP_) && !defined(_C4_COMPILER_HPP_) -#error "amalgamate: file c4/compiler.hpp must have been included at this point" -#endif /* C4_COMPILER_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/cpu.hpp -//#include "c4/cpu.hpp" -#if !defined(C4_CPU_HPP_) && !defined(_C4_CPU_HPP_) -#error "amalgamate: file c4/cpu.hpp must have been included at this point" -#endif /* C4_CPU_HPP_ */ - -#ifdef C4_MSVC -#include -#endif -//included above: -//#include - -#if (defined(__GNUC__) && __GNUC__ >= 10) || defined(__has_builtin) -#define _C4_USE_LSB_INTRINSIC(which) __has_builtin(which) -#define _C4_USE_MSB_INTRINSIC(which) __has_builtin(which) -#elif defined(C4_MSVC) -#define _C4_USE_LSB_INTRINSIC(which) true -#define _C4_USE_MSB_INTRINSIC(which) true -#else -// let's try our luck -#define _C4_USE_LSB_INTRINSIC(which) true -#define _C4_USE_MSB_INTRINSIC(which) true -#endif - - -/** @file memory_util.hpp Some memory utilities. */ - -namespace c4 { - -/** set the given memory to zero */ -C4_ALWAYS_INLINE void mem_zero(void* mem, size_t num_bytes) -{ - memset(mem, 0, num_bytes); -} -/** set the given memory to zero */ -template -C4_ALWAYS_INLINE void mem_zero(T* mem, size_t num_elms) -{ - memset(mem, 0, sizeof(T) * num_elms); -} -/** set the given memory to zero */ -template -C4_ALWAYS_INLINE void mem_zero(T* mem) -{ - memset(mem, 0, sizeof(T)); -} - -C4_ALWAYS_INLINE C4_CONST bool mem_overlaps(void const* a, void const* b, size_t sza, size_t szb) -{ - // thanks @timwynants - return (((const char*)b + szb) > a && b < ((const char*)a+sza)); -} - -void mem_repeat(void* dest, void const* pattern, size_t pattern_size, size_t num_times); - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -template -C4_ALWAYS_INLINE C4_CONST bool is_aligned(T *ptr, uintptr_t alignment=alignof(T)) -{ - return (uintptr_t(ptr) & (alignment - uintptr_t(1))) == uintptr_t(0); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -// least significant bit - -/** @name msb Compute the least significant bit - * @note the input value must be nonzero - * @note the input type must be unsigned - */ -/** @{ */ - -// https://graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightLinear -#define _c4_lsb_fallback \ - unsigned c = 0; \ - v = (v ^ (v - 1)) >> 1; /* Set v's trailing 0s to 1s and zero rest */ \ - for(; v; ++c) \ - v >>= 1; \ - return (unsigned) c - -// u8 -template -C4_CONSTEXPR14 -auto lsb(I v) noexcept - -> typename std::enable_if::type -{ - C4_STATIC_ASSERT(std::is_unsigned::value); - C4_ASSERT(v != 0); - #if _C4_USE_LSB_INTRINSIC(__builtin_ctz) - // upcast to use the intrinsic, it's cheaper. - #ifdef C4_MSVC - #if !defined(C4_CPU_ARM64) && !defined(C4_CPU_ARM) - unsigned long bit; - _BitScanForward(&bit, (unsigned long)v); - return bit; - #else - _c4_lsb_fallback; - #endif - #else - return (unsigned)__builtin_ctz((unsigned)v); - #endif - #else - _c4_lsb_fallback; - #endif -} - -// u16 -template -C4_CONSTEXPR14 -auto lsb(I v) noexcept - -> typename std::enable_if::type -{ - C4_STATIC_ASSERT(std::is_unsigned::value); - C4_ASSERT(v != 0); - #if _C4_USE_LSB_INTRINSIC(__builtin_ctz) - // upcast to use the intrinsic, it's cheaper. - // Then remember that the upcast makes it to 31bits - #ifdef C4_MSVC - #if !defined(C4_CPU_ARM64) && !defined(C4_CPU_ARM) - unsigned long bit; - _BitScanForward(&bit, (unsigned long)v); - return bit; - #else - _c4_lsb_fallback; - #endif - #else - return (unsigned)__builtin_ctz((unsigned)v); - #endif - #else - _c4_lsb_fallback; - #endif -} - -// u32 -template -C4_CONSTEXPR14 -auto lsb(I v) noexcept - -> typename std::enable_if::type -{ - C4_STATIC_ASSERT(std::is_unsigned::value); - C4_ASSERT(v != 0); - #if _C4_USE_LSB_INTRINSIC(__builtin_ctz) - #ifdef C4_MSVC - #if !defined(C4_CPU_ARM64) && !defined(C4_CPU_ARM) - unsigned long bit; - _BitScanForward(&bit, v); - return bit; - #else - _c4_lsb_fallback; - #endif - #else - return (unsigned)__builtin_ctz((unsigned)v); - #endif - #else - _c4_lsb_fallback; - #endif -} - -// u64 in 64bits -template -C4_CONSTEXPR14 -auto lsb(I v) noexcept - -> typename std::enable_if::type -{ - C4_STATIC_ASSERT(std::is_unsigned::value); - C4_ASSERT(v != 0); - #if _C4_USE_LSB_INTRINSIC(__builtin_ctzl) - #if defined(C4_MSVC) - #if !defined(C4_CPU_ARM64) && !defined(C4_CPU_ARM) - unsigned long bit; - _BitScanForward64(&bit, v); - return bit; - #else - _c4_lsb_fallback; - #endif - #else - return (unsigned)__builtin_ctzl((unsigned long)v); - #endif - #else - _c4_lsb_fallback; - #endif -} - -// u64 in 32bits -template -C4_CONSTEXPR14 -auto lsb(I v) noexcept - -> typename std::enable_if::type -{ - C4_STATIC_ASSERT(std::is_unsigned::value); - C4_ASSERT(v != 0); - #if _C4_USE_LSB_INTRINSIC(__builtin_ctzll) - #if defined(C4_MSVC) - #if !defined(C4_CPU_X86) && !defined(C4_CPU_ARM64) && !defined(C4_CPU_ARM) - unsigned long bit; - _BitScanForward64(&bit, v); - return bit; - #else - _c4_lsb_fallback; - #endif - #else - return (unsigned)__builtin_ctzll((unsigned long long)v); - #endif - #else - _c4_lsb_fallback; - #endif -} - -#undef _c4_lsb_fallback - -/** @} */ - - -namespace detail { -template struct _lsb11; -template -struct _lsb11 -{ - enum : unsigned { num = _lsb11>1), num_bits+I(1), (((val>>1)&I(1))!=I(0))>::num }; -}; -template -struct _lsb11 -{ - enum : unsigned { num = num_bits }; -}; -} // namespace detail - - -/** TMP version of lsb(); this needs to be implemented with template - * meta-programming because C++11 cannot use a constexpr function with - * local variables - * @see lsb */ -template -struct lsb11 -{ - static_assert(number != 0, "lsb: number must be nonzero"); - enum : unsigned { value = detail::_lsb11::num}; -}; - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -// most significant bit - - -/** @name msb Compute the most significant bit - * @note the input value must be nonzero - * @note the input type must be unsigned - */ -/** @{ */ - - -#define _c4_msb8_fallback \ - unsigned n = 0; \ - if(v & I(0xf0)) v >>= 4, n |= I(4); \ - if(v & I(0x0c)) v >>= 2, n |= I(2); \ - if(v & I(0x02)) v >>= 1, n |= I(1); \ - return n - -#define _c4_msb16_fallback \ - unsigned n = 0; \ - if(v & I(0xff00)) v >>= 8, n |= I(8); \ - if(v & I(0x00f0)) v >>= 4, n |= I(4); \ - if(v & I(0x000c)) v >>= 2, n |= I(2); \ - if(v & I(0x0002)) v >>= 1, n |= I(1); \ - return n - -#define _c4_msb32_fallback \ - unsigned n = 0; \ - if(v & I(0xffff0000)) v >>= 16, n |= 16; \ - if(v & I(0x0000ff00)) v >>= 8, n |= 8; \ - if(v & I(0x000000f0)) v >>= 4, n |= 4; \ - if(v & I(0x0000000c)) v >>= 2, n |= 2; \ - if(v & I(0x00000002)) v >>= 1, n |= 1; \ - return n - -#define _c4_msb64_fallback \ - unsigned n = 0; \ - if(v & I(0xffffffff00000000)) v >>= 32, n |= I(32); \ - if(v & I(0x00000000ffff0000)) v >>= 16, n |= I(16); \ - if(v & I(0x000000000000ff00)) v >>= 8, n |= I(8); \ - if(v & I(0x00000000000000f0)) v >>= 4, n |= I(4); \ - if(v & I(0x000000000000000c)) v >>= 2, n |= I(2); \ - if(v & I(0x0000000000000002)) v >>= 1, n |= I(1); \ - return n - - -// u8 -template -C4_CONSTEXPR14 -auto msb(I v) noexcept - -> typename std::enable_if::type -{ - C4_STATIC_ASSERT(std::is_unsigned::value); - C4_ASSERT(v != 0); - #if _C4_USE_MSB_INTRINSIC(__builtin_clz) - // upcast to use the intrinsic, it's cheaper. - // Then remember that the upcast makes it to 31bits - #ifdef C4_MSVC - #if !defined(C4_CPU_ARM64) && !defined(C4_CPU_ARM) - unsigned long bit; - _BitScanReverse(&bit, (unsigned long)v); - return bit; - #else - _c4_msb8_fallback; - #endif - #else - return 31u - (unsigned)__builtin_clz((unsigned)v); - #endif - #else - _c4_msb8_fallback; - #endif -} - -// u16 -template -C4_CONSTEXPR14 -auto msb(I v) noexcept - -> typename std::enable_if::type -{ - C4_STATIC_ASSERT(std::is_unsigned::value); - C4_ASSERT(v != 0); - #if _C4_USE_MSB_INTRINSIC(__builtin_clz) - // upcast to use the intrinsic, it's cheaper. - // Then remember that the upcast makes it to 31bits - #ifdef C4_MSVC - #if !defined(C4_CPU_ARM64) && !defined(C4_CPU_ARM) - unsigned long bit; - _BitScanReverse(&bit, (unsigned long)v); - return bit; - #else - _c4_msb16_fallback; - #endif - #else - return 31u - (unsigned)__builtin_clz((unsigned)v); - #endif - #else - _c4_msb16_fallback; - #endif -} - -// u32 -template -C4_CONSTEXPR14 -auto msb(I v) noexcept - -> typename std::enable_if::type -{ - C4_STATIC_ASSERT(std::is_unsigned::value); - C4_ASSERT(v != 0); - #if _C4_USE_MSB_INTRINSIC(__builtin_clz) - #ifdef C4_MSVC - #if !defined(C4_CPU_ARM64) && !defined(C4_CPU_ARM) - unsigned long bit; - _BitScanReverse(&bit, v); - return bit; - #else - _c4_msb32_fallback; - #endif - #else - return 31u - (unsigned)__builtin_clz((unsigned)v); - #endif - #else - _c4_msb32_fallback; - #endif -} - -// u64 in 64bits -template -C4_CONSTEXPR14 -auto msb(I v) noexcept - -> typename std::enable_if::type -{ - C4_STATIC_ASSERT(std::is_unsigned::value); - C4_ASSERT(v != 0); - #if _C4_USE_MSB_INTRINSIC(__builtin_clzl) - #ifdef C4_MSVC - #if !defined(C4_CPU_ARM64) && !defined(C4_CPU_ARM) - unsigned long bit; - _BitScanReverse64(&bit, v); - return bit; - #else - _c4_msb64_fallback; - #endif - #else - return 63u - (unsigned)__builtin_clzl((unsigned long)v); - #endif - #else - _c4_msb64_fallback; - #endif -} - -// u64 in 32bits -template -C4_CONSTEXPR14 -auto msb(I v) noexcept - -> typename std::enable_if::type -{ - C4_STATIC_ASSERT(std::is_unsigned::value); - C4_ASSERT(v != 0); - #if _C4_USE_MSB_INTRINSIC(__builtin_clzll) - #ifdef C4_MSVC - #if !defined(C4_CPU_X86) && !defined(C4_CPU_ARM64) && !defined(C4_CPU_ARM) - unsigned long bit; - _BitScanReverse64(&bit, v); - return bit; - #else - _c4_msb64_fallback; - #endif - #else - return 63u - (unsigned)__builtin_clzll((unsigned long long)v); - #endif - #else - _c4_msb64_fallback; - #endif -} - -#undef _c4_msb8_fallback -#undef _c4_msb16_fallback -#undef _c4_msb32_fallback -#undef _c4_msb64_fallback - -/** @} */ - - -namespace detail { -template struct _msb11; -template -struct _msb11< I, val, num_bits, false> -{ - enum : unsigned { num = _msb11>1), num_bits+I(1), ((val>>1)==I(0))>::num }; -}; -template -struct _msb11 -{ - static_assert(val == 0, "bad implementation"); - enum : unsigned { num = (unsigned)(num_bits-1) }; -}; -} // namespace detail - - -/** TMP version of msb(); this needs to be implemented with template - * meta-programming because C++11 cannot use a constexpr function with - * local variables - * @see msb */ -template -struct msb11 -{ - enum : unsigned { value = detail::_msb11::num }; -}; - - - -#undef _C4_USE_LSB_INTRINSIC -#undef _C4_USE_MSB_INTRINSIC - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -// there is an implicit conversion below; it happens when E or B are -// narrower than int, and thus any operation will upcast the result to -// int, and then downcast to assign -C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wconversion") - -/** integer power; this function is constexpr-14 because of the local - * variables */ -template -C4_CONSTEXPR14 C4_CONST auto ipow(B base, E exponent) noexcept -> typename std::enable_if::value, B>::type -{ - C4_STATIC_ASSERT(std::is_integral::value); - B r = B(1); - if(exponent >= 0) - { - for(E e = 0; e < exponent; ++e) - r *= base; - } - else - { - exponent *= E(-1); - for(E e = 0; e < exponent; ++e) - r /= base; - } - return r; -} - -/** integer power; this function is constexpr-14 because of the local - * variables */ -template -C4_CONSTEXPR14 C4_CONST auto ipow(E exponent) noexcept -> typename std::enable_if::value, B>::type -{ - C4_STATIC_ASSERT(std::is_integral::value); - B r = B(1); - if(exponent >= 0) - { - for(E e = 0; e < exponent; ++e) - r *= base; - } - else - { - exponent *= E(-1); - for(E e = 0; e < exponent; ++e) - r /= base; - } - return r; -} - -/** integer power; this function is constexpr-14 because of the local - * variables */ -template -C4_CONSTEXPR14 C4_CONST auto ipow(E exponent) noexcept -> typename std::enable_if::value, B>::type -{ - C4_STATIC_ASSERT(std::is_integral::value); - B r = B(1); - B bbase = B(base); - if(exponent >= 0) - { - for(E e = 0; e < exponent; ++e) - r *= bbase; - } - else - { - exponent *= E(-1); - for(E e = 0; e < exponent; ++e) - r /= bbase; - } - return r; -} - -/** integer power; this function is constexpr-14 because of the local - * variables */ -template -C4_CONSTEXPR14 C4_CONST auto ipow(B base, E exponent) noexcept -> typename std::enable_if::value, B>::type -{ - C4_STATIC_ASSERT(std::is_integral::value); - B r = B(1); - for(E e = 0; e < exponent; ++e) - r *= base; - return r; -} - -/** integer power; this function is constexpr-14 because of the local - * variables */ -template -C4_CONSTEXPR14 C4_CONST auto ipow(E exponent) noexcept -> typename std::enable_if::value, B>::type -{ - C4_STATIC_ASSERT(std::is_integral::value); - B r = B(1); - for(E e = 0; e < exponent; ++e) - r *= base; - return r; -} -/** integer power; this function is constexpr-14 because of the local - * variables */ -template -C4_CONSTEXPR14 C4_CONST auto ipow(E exponent) noexcept -> typename std::enable_if::value, B>::type -{ - C4_STATIC_ASSERT(std::is_integral::value); - B r = B(1); - B bbase = B(base); - for(E e = 0; e < exponent; ++e) - r *= bbase; - return r; -} - -C4_SUPPRESS_WARNING_GCC_CLANG_POP - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -/** return a mask with all bits set [first_bit,last_bit[; this function - * is constexpr-14 because of the local variables */ -template -C4_CONSTEXPR14 I contiguous_mask(I first_bit, I last_bit) -{ - I r = 0; - for(I i = first_bit; i < last_bit; ++i) - { - r |= (I(1) << i); - } - return r; -} - - -namespace detail { - -template -struct _ctgmsk11; - -template -struct _ctgmsk11< I, val, first, last, true> -{ - enum : I { value = _ctgmsk11::value }; -}; - -template -struct _ctgmsk11< I, val, first, last, false> -{ - enum : I { value = val }; -}; - -} // namespace detail - - -/** TMP version of contiguous_mask(); this needs to be implemented with template - * meta-programming because C++11 cannot use a constexpr function with - * local variables - * @see contiguous_mask */ -template -struct contiguous_mask11 -{ - enum : I { value = detail::_ctgmsk11::value }; -}; - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -/** use Empty Base Class Optimization to reduce the size of a pair of - * potentially empty types*/ - -namespace detail { -typedef enum { - tpc_same, - tpc_same_empty, - tpc_both_empty, - tpc_first_empty, - tpc_second_empty, - tpc_general -} TightPairCase_e; - -template -constexpr TightPairCase_e tpc_which_case() -{ - return std::is_same::value ? - std::is_empty::value ? - tpc_same_empty - : - tpc_same - : - std::is_empty::value && std::is_empty::value ? - tpc_both_empty - : - std::is_empty::value ? - tpc_first_empty - : - std::is_empty::value ? - tpc_second_empty - : - tpc_general - ; -} - -template -struct tight_pair -{ -private: - - First m_first; - Second m_second; - -public: - - using first_type = First; - using second_type = Second; - - tight_pair() : m_first(), m_second() {} - tight_pair(First const& f, Second const& s) : m_first(f), m_second(s) {} - - C4_ALWAYS_INLINE C4_CONSTEXPR14 First & first () { return m_first; } - C4_ALWAYS_INLINE C4_CONSTEXPR14 First const& first () const { return m_first; } - C4_ALWAYS_INLINE C4_CONSTEXPR14 Second & second() { return m_second; } - C4_ALWAYS_INLINE C4_CONSTEXPR14 Second const& second() const { return m_second; } -}; - -template -struct tight_pair : public First -{ - static_assert(std::is_same::value, "bad implementation"); - - using first_type = First; - using second_type = Second; - - tight_pair() : First() {} - tight_pair(First const& f, Second const& /*s*/) : First(f) {} - - C4_ALWAYS_INLINE C4_CONSTEXPR14 First & first () { return static_cast(*this); } - C4_ALWAYS_INLINE C4_CONSTEXPR14 First const& first () const { return static_cast(*this); } - C4_ALWAYS_INLINE C4_CONSTEXPR14 Second & second() { return reinterpret_cast(*this); } - C4_ALWAYS_INLINE C4_CONSTEXPR14 Second const& second() const { return reinterpret_cast(*this); } -}; - -template -struct tight_pair : public First, public Second -{ - using first_type = First; - using second_type = Second; - - tight_pair() : First(), Second() {} - tight_pair(First const& f, Second const& s) : First(f), Second(s) {} - - C4_ALWAYS_INLINE C4_CONSTEXPR14 First & first () { return static_cast(*this); } - C4_ALWAYS_INLINE C4_CONSTEXPR14 First const& first () const { return static_cast(*this); } - C4_ALWAYS_INLINE C4_CONSTEXPR14 Second & second() { return static_cast(*this); } - C4_ALWAYS_INLINE C4_CONSTEXPR14 Second const& second() const { return static_cast(*this); } -}; - -template -struct tight_pair : public First -{ - Second m_second; - - using first_type = First; - using second_type = Second; - - tight_pair() : First() {} - tight_pair(First const& f, Second const& s) : First(f), m_second(s) {} - - C4_ALWAYS_INLINE C4_CONSTEXPR14 First & first () { return static_cast(*this); } - C4_ALWAYS_INLINE C4_CONSTEXPR14 First const& first () const { return static_cast(*this); } - C4_ALWAYS_INLINE C4_CONSTEXPR14 Second & second() { return m_second; } - C4_ALWAYS_INLINE C4_CONSTEXPR14 Second const& second() const { return m_second; } -}; - -template -struct tight_pair : public First -{ - Second m_second; - - using first_type = First; - using second_type = Second; - - tight_pair() : First(), m_second() {} - tight_pair(First const& f, Second const& s) : First(f), m_second(s) {} - - C4_ALWAYS_INLINE C4_CONSTEXPR14 First & first () { return static_cast(*this); } - C4_ALWAYS_INLINE C4_CONSTEXPR14 First const& first () const { return static_cast(*this); } - C4_ALWAYS_INLINE C4_CONSTEXPR14 Second & second() { return m_second; } - C4_ALWAYS_INLINE C4_CONSTEXPR14 Second const& second() const { return m_second; } -}; - -template -struct tight_pair : public Second -{ - First m_first; - - using first_type = First; - using second_type = Second; - - tight_pair() : Second(), m_first() {} - tight_pair(First const& f, Second const& s) : Second(s), m_first(f) {} - - C4_ALWAYS_INLINE C4_CONSTEXPR14 First & first () { return m_first; } - C4_ALWAYS_INLINE C4_CONSTEXPR14 First const& first () const { return m_first; } - C4_ALWAYS_INLINE C4_CONSTEXPR14 Second & second() { return static_cast(*this); } - C4_ALWAYS_INLINE C4_CONSTEXPR14 Second const& second() const { return static_cast(*this); } -}; - -} // namespace detail - -template -using tight_pair = detail::tight_pair()>; - -} // namespace c4 - -#endif /* _C4_MEMORY_UTIL_HPP_ */ - - -// (end https://github.com/biojppm/c4core/src/c4/memory_util.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/memory_resource.hpp -// https://github.com/biojppm/c4core/src/c4/memory_resource.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_MEMORY_RESOURCE_HPP_ -#define _C4_MEMORY_RESOURCE_HPP_ - -/** @file memory_resource.hpp Provides facilities to allocate typeless - * memory, via the memory resource model consecrated with C++17. */ - -/** @defgroup memory memory utilities */ - -/** @defgroup raw_memory_alloc Raw memory allocation - * @ingroup memory - */ - -/** @defgroup memory_resources Memory resources - * @ingroup memory - */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/config.hpp -//#include "c4/config.hpp" -#if !defined(C4_CONFIG_HPP_) && !defined(_C4_CONFIG_HPP_) -#error "amalgamate: file c4/config.hpp must have been included at this point" -#endif /* C4_CONFIG_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/error.hpp -//#include "c4/error.hpp" -#if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) -#error "amalgamate: file c4/error.hpp must have been included at this point" -#endif /* C4_ERROR_HPP_ */ - - -namespace c4 { - -// need these forward decls here -struct MemoryResource; -struct MemoryResourceMalloc; -struct MemoryResourceStack; -MemoryResourceMalloc* get_memory_resource_malloc(); -MemoryResourceStack* get_memory_resource_stack(); -namespace detail { MemoryResource*& get_memory_resource(); } - - -// c-style allocation --------------------------------------------------------- - -// this API provides aligned allocation functions. -// These functions forward the call to a user-modifiable function. - - -// aligned allocation. - -/** Aligned allocation. Merely calls the current get_aalloc() function. - * @see get_aalloc() - * @ingroup raw_memory_alloc */ -void* aalloc(size_t sz, size_t alignment); - -/** Aligned free. Merely calls the current get_afree() function. - * @see get_afree() - * @ingroup raw_memory_alloc */ -void afree(void* ptr); - -/** Aligned reallocation. Merely calls the current get_arealloc() function. - * @see get_arealloc() - * @ingroup raw_memory_alloc */ -void* arealloc(void* ptr, size_t oldsz, size_t newsz, size_t alignment); - - -// allocation setup facilities. - -/** Function pointer type for aligned allocation - * @see set_aalloc() - * @ingroup raw_memory_alloc */ -using aalloc_pfn = void* (*)(size_t size, size_t alignment); - -/** Function pointer type for aligned deallocation - * @see set_afree() - * @ingroup raw_memory_alloc */ -using afree_pfn = void (*)(void *ptr); - -/** Function pointer type for aligned reallocation - * @see set_arealloc() - * @ingroup raw_memory_alloc */ -using arealloc_pfn = void* (*)(void *ptr, size_t oldsz, size_t newsz, size_t alignment); - - -// allocation function pointer setters/getters - -/** Set the global aligned allocation function. - * @see aalloc() - * @see get_aalloc() - * @ingroup raw_memory_alloc */ -void set_aalloc(aalloc_pfn fn); - -/** Set the global aligned deallocation function. - * @see afree() - * @see get_afree() - * @ingroup raw_memory_alloc */ -void set_afree(afree_pfn fn); - -/** Set the global aligned reallocation function. - * @see arealloc() - * @see get_arealloc() - * @ingroup raw_memory_alloc */ -void set_arealloc(arealloc_pfn fn); - - -/** Get the global aligned reallocation function. - * @see arealloc() - * @ingroup raw_memory_alloc */ -aalloc_pfn get_aalloc(); - -/** Get the global aligned deallocation function. - * @see afree() - * @ingroup raw_memory_alloc */ -afree_pfn get_afree(); - -/** Get the global aligned reallocation function. - * @see arealloc() - * @ingroup raw_memory_alloc */ -arealloc_pfn get_arealloc(); - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -// c++-style allocation ------------------------------------------------------- - -/** C++17-style memory_resource base class. See http://en.cppreference.com/w/cpp/experimental/memory_resource - * @ingroup memory_resources */ -struct MemoryResource -{ - const char *name = nullptr; - virtual ~MemoryResource() {} - - void* allocate(size_t sz, size_t alignment=alignof(max_align_t), void *hint=nullptr) - { - void *mem = this->do_allocate(sz, alignment, hint); - C4_CHECK_MSG(mem != nullptr, "could not allocate %lu bytes", sz); - return mem; - } - - void* reallocate(void* ptr, size_t oldsz, size_t newsz, size_t alignment=alignof(max_align_t)) - { - void *mem = this->do_reallocate(ptr, oldsz, newsz, alignment); - C4_CHECK_MSG(mem != nullptr, "could not reallocate from %lu to %lu bytes", oldsz, newsz); - return mem; - } - - void deallocate(void* ptr, size_t sz, size_t alignment=alignof(max_align_t)) - { - this->do_deallocate(ptr, sz, alignment); - } - -protected: - - virtual void* do_allocate(size_t sz, size_t alignment, void* hint) = 0; - virtual void* do_reallocate(void* ptr, size_t oldsz, size_t newsz, size_t alignment) = 0; - virtual void do_deallocate(void* ptr, size_t sz, size_t alignment) = 0; - -}; - -/** get the current global memory resource. To avoid static initialization - * order problems, this is implemented using a function call to ensure - * that it is available when first used. - * @ingroup memory_resources */ -C4_ALWAYS_INLINE MemoryResource* get_memory_resource() -{ - return detail::get_memory_resource(); -} - -/** set the global memory resource - * @ingroup memory_resources */ -C4_ALWAYS_INLINE void set_memory_resource(MemoryResource* mr) -{ - C4_ASSERT(mr != nullptr); - detail::get_memory_resource() = mr; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -/** A c4::aalloc-based memory resource. Thread-safe if the implementation - * called by c4::aalloc() is safe. - * @ingroup memory_resources */ -struct MemoryResourceMalloc : public MemoryResource -{ - - MemoryResourceMalloc() { name = "malloc"; } - virtual ~MemoryResourceMalloc() override {} - -protected: - - virtual void* do_allocate(size_t sz, size_t alignment, void *hint) override - { - C4_UNUSED(hint); - return c4::aalloc(sz, alignment); - } - - virtual void do_deallocate(void* ptr, size_t sz, size_t alignment) override - { - C4_UNUSED(sz); - C4_UNUSED(alignment); - c4::afree(ptr); - } - - virtual void* do_reallocate(void* ptr, size_t oldsz, size_t newsz, size_t alignment) override - { - return c4::arealloc(ptr, oldsz, newsz, alignment); - } - -}; - -/** returns a malloc-based memory resource - * @ingroup memory_resources */ -C4_ALWAYS_INLINE MemoryResourceMalloc* get_memory_resource_malloc() -{ - /** @todo use a nifty counter: - * https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Nifty_Counter */ - static MemoryResourceMalloc mr; - return &mr; -} - -namespace detail { -C4_ALWAYS_INLINE MemoryResource* & get_memory_resource() -{ - /** @todo use a nifty counter: - * https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Nifty_Counter */ - thread_local static MemoryResource* mr = get_memory_resource_malloc(); - return mr; -} -} // namespace detail - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -namespace detail { - -/** Allows a memory resource to obtain its memory from another memory resource. - * @ingroup memory_resources */ -struct DerivedMemoryResource : public MemoryResource -{ -public: - - DerivedMemoryResource(MemoryResource *mr_=nullptr) : m_local(mr_ ? mr_ : get_memory_resource()) {} - -private: - - MemoryResource *m_local; - -protected: - - virtual void* do_allocate(size_t sz, size_t alignment, void* hint) override - { - return m_local->allocate(sz, alignment, hint); - } - - virtual void* do_reallocate(void* ptr, size_t oldsz, size_t newsz, size_t alignment) override - { - return m_local->reallocate(ptr, oldsz, newsz, alignment); - } - - virtual void do_deallocate(void* ptr, size_t sz, size_t alignment) override - { - return m_local->deallocate(ptr, sz, alignment); - } -}; - -/** Provides common facilities for memory resource consisting of a single memory block - * @ingroup memory_resources */ -struct _MemoryResourceSingleChunk : public DerivedMemoryResource -{ - - C4_NO_COPY_OR_MOVE(_MemoryResourceSingleChunk); - - using impl_type = DerivedMemoryResource; - -public: - - _MemoryResourceSingleChunk(MemoryResource *impl=nullptr) : DerivedMemoryResource(impl) { name = "linear_malloc"; } - - /** initialize with owned memory, allocated from the given (or the global) memory resource */ - _MemoryResourceSingleChunk(size_t sz, MemoryResource *impl=nullptr) : _MemoryResourceSingleChunk(impl) { acquire(sz); } - /** initialize with borrowed memory */ - _MemoryResourceSingleChunk(void *mem, size_t sz) : _MemoryResourceSingleChunk() { acquire(mem, sz); } - - virtual ~_MemoryResourceSingleChunk() override { release(); } - -public: - - void const* mem() const { return m_mem; } - - size_t capacity() const { return m_size; } - size_t size() const { return m_pos; } - size_t slack() const { C4_ASSERT(m_size >= m_pos); return m_size - m_pos; } - -public: - - char *m_mem{nullptr}; - size_t m_size{0}; - size_t m_pos{0}; - bool m_owner; - -public: - - /** set the internal pointer to the beginning of the linear buffer */ - void clear() { m_pos = 0; } - - /** initialize with owned memory, allocated from the global memory resource */ - void acquire(size_t sz); - /** initialize with borrowed memory */ - void acquire(void *mem, size_t sz); - /** release the memory */ - void release(); - -}; - -} // namespace detail - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -/** provides a linear memory resource. Allocates incrementally from a linear - * buffer, without ever deallocating. Deallocations are a no-op, and the - * memory is freed only when the resource is release()d. The memory used by - * this object can be either owned or borrowed. When borrowed, no calls to - * malloc/free take place. - * - * @ingroup memory_resources */ -struct MemoryResourceLinear : public detail::_MemoryResourceSingleChunk -{ - - C4_NO_COPY_OR_MOVE(MemoryResourceLinear); - -public: - - using detail::_MemoryResourceSingleChunk::_MemoryResourceSingleChunk; - -protected: - - virtual void* do_allocate(size_t sz, size_t alignment, void *hint) override; - virtual void do_deallocate(void* ptr, size_t sz, size_t alignment) override; - virtual void* do_reallocate(void* ptr, size_t oldsz, size_t newsz, size_t alignment) override; -}; - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -/** provides a stack-type malloc-based memory resource. - * @ingroup memory_resources */ -struct MemoryResourceStack : public detail::_MemoryResourceSingleChunk -{ - - C4_NO_COPY_OR_MOVE(MemoryResourceStack); - -public: - - using detail::_MemoryResourceSingleChunk::_MemoryResourceSingleChunk; - -protected: - - virtual void* do_allocate(size_t sz, size_t alignment, void *hint) override; - virtual void do_deallocate(void* ptr, size_t sz, size_t alignment) override; - virtual void* do_reallocate(void* ptr, size_t oldsz, size_t newsz, size_t alignment) override; -}; - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -/** provides a linear array-based memory resource. - * @see MemoryResourceLinear - * @ingroup memory_resources */ -template -struct MemoryResourceLinearArr : public MemoryResourceLinear -{ - #ifdef _MSC_VER - #pragma warning(push) - #pragma warning(disable: 4324) // structure was padded due to alignment specifier - #endif - alignas(alignof(max_align_t)) char m_arr[N]; - #ifdef _MSC_VER - #pragma warning(pop) - #endif - MemoryResourceLinearArr() : MemoryResourceLinear(m_arr, N) { name = "linear_arr"; } -}; - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -struct AllocationCounts -{ - struct Item - { - ssize_t allocs; - ssize_t size; - - void add(size_t sz) - { - ++allocs; - size += static_cast(sz); - } - void rem(size_t sz) - { - --allocs; - size -= static_cast(sz); - } - Item max(Item const& that) const - { - Item r(*this); - r.allocs = r.allocs > that.allocs ? r.allocs : that.allocs; - r.size = r.size > that.size ? r.size : that.size; - return r; - } - }; - - Item curr = {0, 0}; - Item total = {0, 0}; - Item max = {0, 0}; - - void clear_counts() - { - curr = {0, 0}; - total = {0, 0}; - max = {0, 0}; - } - - void update(AllocationCounts const& that) - { - curr.allocs += that.curr.allocs; - curr.size += that.curr.size; - total.allocs += that.total.allocs; - total.size += that.total.size; - max.allocs += that.max.allocs; - max.size += that.max.size; - } - - void add_counts(void* ptr, size_t sz) - { - if(ptr == nullptr) return; - curr.add(sz); - total.add(sz); - max = max.max(curr); - } - - void rem_counts(void *ptr, size_t sz) - { - if(ptr == nullptr) return; - curr.rem(sz); - } - - AllocationCounts operator- (AllocationCounts const& that) const - { - AllocationCounts r(*this); - r.curr.allocs -= that.curr.allocs; - r.curr.size -= that.curr.size; - r.total.allocs -= that.total.allocs; - r.total.size -= that.total.size; - r.max.allocs -= that.max.allocs; - r.max.size -= that.max.size; - return r; - } - - AllocationCounts operator+ (AllocationCounts const& that) const - { - AllocationCounts r(*this); - r.curr.allocs += that.curr.allocs; - r.curr.size += that.curr.size; - r.total.allocs += that.total.allocs; - r.total.size += that.total.size; - r.max.allocs += that.max.allocs; - r.max.size += that.max.size; - return r; - } -}; - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -/** a MemoryResource which latches onto another MemoryResource - * and counts allocations and sizes. - * @ingroup memory_resources */ -class MemoryResourceCounts : public MemoryResource -{ -public: - - MemoryResourceCounts() : m_resource(get_memory_resource()) - { - C4_ASSERT(m_resource != this); - name = "MemoryResourceCounts"; - } - MemoryResourceCounts(MemoryResource *res) : m_resource(res) - { - C4_ASSERT(m_resource != this); - name = "MemoryResourceCounts"; - } - - MemoryResource *resource() { return m_resource; } - AllocationCounts const& counts() const { return m_counts; } - -protected: - - MemoryResource *m_resource; - AllocationCounts m_counts; - -protected: - - virtual void* do_allocate(size_t sz, size_t alignment, void * /*hint*/) override - { - void *ptr = m_resource->allocate(sz, alignment); - m_counts.add_counts(ptr, sz); - return ptr; - } - - virtual void do_deallocate(void* ptr, size_t sz, size_t alignment) override - { - m_counts.rem_counts(ptr, sz); - m_resource->deallocate(ptr, sz, alignment); - } - - virtual void* do_reallocate(void* ptr, size_t oldsz, size_t newsz, size_t alignment) override - { - m_counts.rem_counts(ptr, oldsz); - void* nptr = m_resource->reallocate(ptr, oldsz, newsz, alignment); - m_counts.add_counts(nptr, newsz); - return nptr; - } - -}; - -//----------------------------------------------------------------------------- -/** RAII class which binds a memory resource with a scope duration. - * @ingroup memory_resources */ -struct ScopedMemoryResource -{ - MemoryResource *m_original; - - ScopedMemoryResource(MemoryResource *r) - : - m_original(get_memory_resource()) - { - set_memory_resource(r); - } - - ~ScopedMemoryResource() - { - set_memory_resource(m_original); - } -}; - -//----------------------------------------------------------------------------- -/** RAII class which counts allocations and frees inside a scope. Can - * optionally set also the memory resource to be used. - * @ingroup memory_resources */ -struct ScopedMemoryResourceCounts -{ - MemoryResourceCounts mr; - - ScopedMemoryResourceCounts() : mr() - { - set_memory_resource(&mr); - } - ScopedMemoryResourceCounts(MemoryResource *m) : mr(m) - { - set_memory_resource(&mr); - } - ~ScopedMemoryResourceCounts() - { - set_memory_resource(mr.resource()); - } -}; - -} // namespace c4 - -#endif /* _C4_MEMORY_RESOURCE_HPP_ */ - - -// (end https://github.com/biojppm/c4core/src/c4/memory_resource.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/ctor_dtor.hpp -// https://github.com/biojppm/c4core/src/c4/ctor_dtor.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_CTOR_DTOR_HPP_ -#define _C4_CTOR_DTOR_HPP_ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/preprocessor.hpp -//#include "c4/preprocessor.hpp" -#if !defined(C4_PREPROCESSOR_HPP_) && !defined(_C4_PREPROCESSOR_HPP_) -#error "amalgamate: file c4/preprocessor.hpp must have been included at this point" -#endif /* C4_PREPROCESSOR_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/language.hpp -//#include "c4/language.hpp" -#if !defined(C4_LANGUAGE_HPP_) && !defined(_C4_LANGUAGE_HPP_) -#error "amalgamate: file c4/language.hpp must have been included at this point" -#endif /* C4_LANGUAGE_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/memory_util.hpp -//#include "c4/memory_util.hpp" -#if !defined(C4_MEMORY_UTIL_HPP_) && !defined(_C4_MEMORY_UTIL_HPP_) -#error "amalgamate: file c4/memory_util.hpp must have been included at this point" -#endif /* C4_MEMORY_UTIL_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/error.hpp -//#include "c4/error.hpp" -#if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) -#error "amalgamate: file c4/error.hpp must have been included at this point" -#endif /* C4_ERROR_HPP_ */ - - -//included above: -//#include -//included above: -//#include // std::forward - -/** @file ctor_dtor.hpp object construction and destruction facilities. - * Some of these are not yet available in C++11. */ - -namespace c4 { - -/** default-construct an object, trivial version */ -template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type -construct(U *ptr) noexcept -{ - memset(ptr, 0, sizeof(U)); -} -/** default-construct an object, non-trivial version */ -template C4_ALWAYS_INLINE typename std ::enable_if< ! std::is_trivially_default_constructible::value, void>::type -construct(U* ptr) noexcept -{ - new ((void*)ptr) U(); -} - -/** default-construct n objects, trivial version */ -template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type -construct_n(U* ptr, I n) noexcept -{ - memset(ptr, 0, n * sizeof(U)); -} -/** default-construct n objects, non-trivial version */ -template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_default_constructible::value, void>::type -construct_n(U* ptr, I n) noexcept -{ - for(I i = 0; i < n; ++i) - { - new ((void*)(ptr + i)) U(); - } -} - -#ifdef __clang__ -# pragma clang diagnostic push -#elif defined(__GNUC__) -# pragma GCC diagnostic push -# if __GNUC__ >= 6 -# pragma GCC diagnostic ignored "-Wnull-dereference" -# endif -#endif - -template -inline void construct(U* ptr, Args&&... args) -{ - new ((void*)ptr) U(std::forward(args)...); -} -template -inline void construct_n(U* ptr, I n, Args&&... args) -{ - for(I i = 0; i < n; ++i) - { - new ((void*)(ptr + i)) U(args...); - } -} - -#ifdef __clang__ -# pragma clang diagnostic pop -#elif defined(__GNUC__) -# pragma GCC diagnostic pop -#endif - - -//----------------------------------------------------------------------------- -// copy-construct - -template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type -copy_construct(U* dst, U const* src) noexcept -{ - C4_ASSERT(dst != src); - memcpy(dst, src, sizeof(U)); -} -template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_copy_constructible::value, void>::type -copy_construct(U* dst, U const* src) -{ - C4_ASSERT(dst != src); - new ((void*)dst) U(*src); -} -template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type -copy_construct_n(U* dst, U const* src, I n) noexcept -{ - C4_ASSERT(dst != src); - memcpy(dst, src, n * sizeof(U)); -} -template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_copy_constructible::value, void>::type -copy_construct_n(U* dst, U const* src, I n) -{ - C4_ASSERT(dst != src); - for(I i = 0; i < n; ++i) - { - new ((void*)(dst + i)) U(*(src + i)); - } -} - -template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type -copy_construct(U* dst, U src) noexcept // pass by value for scalar types -{ - *dst = src; -} -template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_scalar::value, void>::type -copy_construct(U* dst, U const& src) // pass by reference for non-scalar types -{ - C4_ASSERT(dst != &src); - new ((void*)dst) U(src); -} -template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type -copy_construct_n(U* dst, U src, I n) noexcept // pass by value for scalar types -{ - for(I i = 0; i < n; ++i) - { - dst[i] = src; - } -} -template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_scalar::value, void>::type -copy_construct_n(U* dst, U const& src, I n) // pass by reference for non-scalar types -{ - C4_ASSERT(dst != &src); - for(I i = 0; i < n; ++i) - { - new ((void*)(dst + i)) U(src); - } -} - -template -C4_ALWAYS_INLINE void copy_construct(U (&dst)[N], U const (&src)[N]) noexcept -{ - copy_construct_n(dst, src, N); -} - -//----------------------------------------------------------------------------- -// copy-assign - -template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type -copy_assign(U* dst, U const* src) noexcept -{ - C4_ASSERT(dst != src); - memcpy(dst, src, sizeof(U)); -} -template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_copy_assignable::value, void>::type -copy_assign(U* dst, U const* src) noexcept -{ - C4_ASSERT(dst != src); - *dst = *src; -} -template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type -copy_assign_n(U* dst, U const* src, I n) noexcept -{ - C4_ASSERT(dst != src); - memcpy(dst, src, n * sizeof(U)); -} -template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_copy_assignable::value, void>::type -copy_assign_n(U* dst, U const* src, I n) noexcept -{ - C4_ASSERT(dst != src); - for(I i = 0; i < n; ++i) - { - dst[i] = src[i]; - } -} - -template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type -copy_assign(U* dst, U src) noexcept // pass by value for scalar types -{ - *dst = src; -} -template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_scalar::value, void>::type -copy_assign(U* dst, U const& src) noexcept // pass by reference for non-scalar types -{ - C4_ASSERT(dst != &src); - *dst = src; -} -template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type -copy_assign_n(U* dst, U src, I n) noexcept // pass by value for scalar types -{ - for(I i = 0; i < n; ++i) - { - dst[i] = src; - } -} -template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_scalar::value, void>::type -copy_assign_n(U* dst, U const& src, I n) noexcept // pass by reference for non-scalar types -{ - C4_ASSERT(dst != &src); - for(I i = 0; i < n; ++i) - { - dst[i] = src; - } -} - -template -C4_ALWAYS_INLINE void copy_assign(U (&dst)[N], U const (&src)[N]) noexcept -{ - copy_assign_n(dst, src, N); -} - -//----------------------------------------------------------------------------- -// move-construct - -template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type -move_construct(U* dst, U* src) noexcept -{ - C4_ASSERT(dst != src); - memcpy(dst, src, sizeof(U)); -} -template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_move_constructible::value, void>::type -move_construct(U* dst, U* src) noexcept -{ - C4_ASSERT(dst != src); - new ((void*)dst) U(std::move(*src)); -} -template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type -move_construct_n(U* dst, U* src, I n) noexcept -{ - C4_ASSERT(dst != src); - memcpy(dst, src, n * sizeof(U)); -} -template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_move_constructible::value, void>::type -move_construct_n(U* dst, U* src, I n) noexcept -{ - C4_ASSERT(dst != src); - for(I i = 0; i < n; ++i) - { - new ((void*)(dst + i)) U(std::move(src[i])); - } -} - -//----------------------------------------------------------------------------- -// move-assign - -template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type -move_assign(U* dst, U* src) noexcept -{ - C4_ASSERT(dst != src); - memcpy(dst, src, sizeof(U)); -} -template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_move_assignable::value, void>::type -move_assign(U* dst, U* src) noexcept -{ - C4_ASSERT(dst != src); - *dst = std::move(*src); -} -template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type -move_assign_n(U* dst, U* src, I n) noexcept -{ - C4_ASSERT(dst != src); - memcpy(dst, src, n * sizeof(U)); -} -template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_move_assignable::value, void>::type -move_assign_n(U* dst, U* src, I n) noexcept -{ - C4_ASSERT(dst != src); - for(I i = 0; i < n; ++i) - { - *(dst + i) = std::move(*(src + i)); - } -} - -//----------------------------------------------------------------------------- -// destroy - -template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type -destroy(U* ptr) noexcept -{ - C4_UNUSED(ptr); // nothing to do -} -template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_destructible::value, void>::type -destroy(U* ptr) noexcept -{ - ptr->~U(); -} -template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type -destroy_n(U* ptr, I n) noexcept -{ - C4_UNUSED(ptr); - C4_UNUSED(n); // nothing to do -} -template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_destructible::value, void>::type -destroy_n(U* ptr, I n) noexcept -{ - for(I i = 0; i C4_ALWAYS_INLINE typename std::enable_if::value, void>::type -make_room(U *buf, I bufsz, I room) C4_NOEXCEPT_A -{ - C4_ASSERT(bufsz >= 0 && room >= 0); - if(room >= bufsz) - { - memcpy (buf + room, buf, bufsz * sizeof(U)); - } - else - { - memmove(buf + room, buf, bufsz * sizeof(U)); - } -} -/** makes room at the beginning of buf, which has a current size of bufsz */ -template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_move_constructible::value, void>::type -make_room(U *buf, I bufsz, I room) C4_NOEXCEPT_A -{ - C4_ASSERT(bufsz >= 0 && room >= 0); - if(room >= bufsz) - { - for(I i = 0; i < bufsz; ++i) - { - new ((void*)(buf + (i + room))) U(std::move(buf[i])); - } - } - else - { - for(I i = 0; i < bufsz; ++i) - { - I w = bufsz-1 - i; // do a backwards loop - new ((void*)(buf + (w + room))) U(std::move(buf[w])); - } - } -} - -/** make room to the right of pos */ -template -C4_ALWAYS_INLINE void make_room(U *buf, I bufsz, I currsz, I pos, I room) -{ - C4_ASSERT(pos >= 0 && pos <= currsz); - C4_ASSERT(currsz <= bufsz); - C4_ASSERT(room + currsz <= bufsz); - C4_UNUSED(bufsz); - make_room(buf + pos, currsz - pos, room); -} - - -/** make room to the right of pos, copying to the beginning of a different buffer */ -template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type -make_room(U *dst, U const* src, I srcsz, I room, I pos) C4_NOEXCEPT_A -{ - C4_ASSERT(srcsz >= 0 && room >= 0 && pos >= 0); - C4_ASSERT(pos < srcsz || (pos == 0 && srcsz == 0)); - memcpy(dst , src , pos * sizeof(U)); - memcpy(dst + room + pos, src + pos, (srcsz - pos) * sizeof(U)); -} -/** make room to the right of pos, copying to the beginning of a different buffer */ -template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_move_constructible::value, void>::type -make_room(U *dst, U const* src, I srcsz, I room, I pos) -{ - C4_ASSERT(srcsz >= 0 && room >= 0 && pos >= 0); - C4_ASSERT(pos < srcsz || (pos == 0 && srcsz == 0)); - for(I i = 0; i < pos; ++i) - { - new ((void*)(dst + i)) U(std::move(src[i])); - } - src += pos; - dst += room + pos; - for(I i = 0, e = srcsz - pos; i < e; ++i) - { - new ((void*)(dst + i)) U(std::move(src[i])); - } -} - -template -C4_ALWAYS_INLINE void make_room -( - U * dst, I dstsz, - U const* src, I srcsz, - I room, I pos -) -{ - C4_ASSERT(pos >= 0 && pos < srcsz || (srcsz == 0 && pos == 0)); - C4_ASSERT(pos >= 0 && pos < dstsz || (dstsz == 0 && pos == 0)); - C4_ASSERT(srcsz+room <= dstsz); - C4_UNUSED(dstsz); - make_room(dst, src, srcsz, room, pos); -} - - -//----------------------------------------------------------------------------- -/** destroy room at the beginning of buf, which has a current size of n */ -template C4_ALWAYS_INLINE typename std::enable_if::value || (std::is_standard_layout::value && std::is_trivial::value), void>::type -destroy_room(U *buf, I n, I room) C4_NOEXCEPT_A -{ - C4_ASSERT(n >= 0 && room >= 0); - C4_ASSERT(room <= n); - if(room < n) - { - memmove(buf, buf + room, (n - room) * sizeof(U)); - } - else - { - // nothing to do - no need to destroy scalar types - } -} -/** destroy room at the beginning of buf, which has a current size of n */ -template C4_ALWAYS_INLINE typename std::enable_if< ! (std::is_scalar::value || (std::is_standard_layout::value && std::is_trivial::value)), void>::type -destroy_room(U *buf, I n, I room) -{ - C4_ASSERT(n >= 0 && room >= 0); - C4_ASSERT(room <= n); - if(room < n) - { - for(I i = 0, e = n - room; i < e; ++i) - { - buf[i] = std::move(buf[i + room]); - } - } - else - { - for(I i = 0; i < n; ++i) - { - buf[i].~U(); - } - } -} - -/** destroy room to the right of pos, copying to a different buffer */ -template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type -destroy_room(U *dst, U const* src, I n, I room, I pos) C4_NOEXCEPT_A -{ - C4_ASSERT(n >= 0 && room >= 0 && pos >= 0); - C4_ASSERT(pos C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_move_constructible::value, void>::type -destroy_room(U *dst, U const* src, I n, I room, I pos) -{ - C4_ASSERT(n >= 0 && room >= 0 && pos >= 0); - C4_ASSERT(pos < n); - C4_ASSERT(pos + room <= n); - for(I i = 0; i < pos; ++i) - { - new ((void*)(dst + i)) U(std::move(src[i])); - } - src += room + pos; - dst += pos; - for(I i = 0, e = n - pos - room; i < e; ++i) - { - new ((void*)(dst + i)) U(std::move(src[i])); - } -} - -} // namespace c4 - -#undef _C4REQUIRE - -#endif /* _C4_CTOR_DTOR_HPP_ */ - - -// (end https://github.com/biojppm/c4core/src/c4/ctor_dtor.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/allocator.hpp -// https://github.com/biojppm/c4core/src/c4/allocator.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_ALLOCATOR_HPP_ -#define _C4_ALLOCATOR_HPP_ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/memory_resource.hpp -//#include "c4/memory_resource.hpp" -#if !defined(C4_MEMORY_RESOURCE_HPP_) && !defined(_C4_MEMORY_RESOURCE_HPP_) -#error "amalgamate: file c4/memory_resource.hpp must have been included at this point" -#endif /* C4_MEMORY_RESOURCE_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/ctor_dtor.hpp -//#include "c4/ctor_dtor.hpp" -#if !defined(C4_CTOR_DTOR_HPP_) && !defined(_C4_CTOR_DTOR_HPP_) -#error "amalgamate: file c4/ctor_dtor.hpp must have been included at this point" -#endif /* C4_CTOR_DTOR_HPP_ */ - - -#include // std::allocator_traits -//included above: -//#include - -/** @file allocator.hpp Contains classes to make typeful allocations (note - * that memory resources are typeless) */ - -/** @defgroup mem_res_providers Memory resource providers - * @brief Policy classes which provide a memory resource for - * use in an allocator. - * @ingroup memory - */ - -/** @defgroup allocators Allocators - * @brief Lightweight classes that act as handles to specific memory - * resources and provide typeful memory. - * @ingroup memory - */ - -namespace c4 { - -namespace detail { -template inline size_t size_for (size_t num_objs) noexcept { return num_objs * sizeof(T); } -template< > inline size_t size_for(size_t num_objs) noexcept { return num_objs; } -} // namespace detail - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -/** provides a per-allocator memory resource - * @ingroup mem_res_providers */ -class MemRes -{ -public: - - MemRes() : m_resource(get_memory_resource()) {} - MemRes(MemoryResource* r) noexcept : m_resource(r ? r : get_memory_resource()) {} - - inline MemoryResource* resource() const { return m_resource; } - -private: - - MemoryResource* m_resource; - -}; - - -/** the allocators using this will default to the global memory resource - * @ingroup mem_res_providers */ -class MemResGlobal -{ -public: - - MemResGlobal() {} - MemResGlobal(MemoryResource* r) noexcept { C4_UNUSED(r); C4_ASSERT(r == get_memory_resource()); } - - inline MemoryResource* resource() const { return get_memory_resource(); } -}; - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -namespace detail { -template -struct _AllocatorUtil; - -template -struct has_no_alloc - : public std::integral_constant::value) - && std::is_constructible::value> {}; - -// std::uses_allocator_v && std::is_constructible -// ie can construct(std::allocator_arg_t, MemoryResource*, Args...) -template -struct has_alloc_arg - : public std::integral_constant::value - && std::is_constructible::value> {}; -// std::uses_allocator && std::is_constructible -// ie, can construct(Args..., MemoryResource*) -template -struct has_alloc - : public std::integral_constant::value - && std::is_constructible::value> {}; - -} // namespace detail - - -template -struct detail::_AllocatorUtil : public MemRes -{ - using MemRes::MemRes; - - /** for construct: - * @see http://en.cppreference.com/w/cpp/experimental/polymorphic_allocator/construct */ - - // 1. types with no allocators - template - C4_ALWAYS_INLINE typename std::enable_if::value, void>::type - construct(U *ptr, Args &&...args) - { - c4::construct(ptr, std::forward(args)...); - } - template - C4_ALWAYS_INLINE typename std::enable_if::value, void>::type - construct_n(U* ptr, I n, Args&&... args) - { - c4::construct_n(ptr, n, std::forward(args)...); - } - - // 2. types using allocators (ie, containers) - - // 2.1. can construct(std::allocator_arg_t, MemoryResource*, Args...) - template - C4_ALWAYS_INLINE typename std::enable_if::value, void>::type - construct(U* ptr, Args&&... args) - { - c4::construct(ptr, std::allocator_arg, this->resource(), std::forward(args)...); - } - template - C4_ALWAYS_INLINE typename std::enable_if::value, void>::type - construct_n(U* ptr, I n, Args&&... args) - { - c4::construct_n(ptr, n, std::allocator_arg, this->resource(), std::forward(args)...); - } - - // 2.2. can construct(Args..., MemoryResource*) - template - C4_ALWAYS_INLINE typename std::enable_if::value, void>::type - construct(U* ptr, Args&&... args) - { - c4::construct(ptr, std::forward(args)..., this->resource()); - } - template - C4_ALWAYS_INLINE typename std::enable_if::value, void>::type - construct_n(U* ptr, I n, Args&&... args) - { - c4::construct_n(ptr, n, std::forward(args)..., this->resource()); - } - - template - static C4_ALWAYS_INLINE void destroy(U* ptr) - { - c4::destroy(ptr); - } - template - static C4_ALWAYS_INLINE void destroy_n(U* ptr, I n) - { - c4::destroy_n(ptr, n); - } -}; - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -/** An allocator is simply a proxy to a memory resource. - * @param T - * @param MemResProvider - * @ingroup allocators */ -template -class Allocator : public detail::_AllocatorUtil -{ -public: - - using impl_type = detail::_AllocatorUtil; - - using value_type = T; - using pointer = T*; - using const_pointer = T const*; - using reference = T&; - using const_reference = T const&; - using size_type = size_t; - using difference_type = std::ptrdiff_t; - using propagate_on_container_move_assigment = std::true_type; - -public: - - template - bool operator== (Allocator const& that) const - { - return this->resource() == that.resource(); - } - template - bool operator!= (Allocator const& that) const - { - return this->resource() != that.resource(); - } - -public: - - template friend class Allocator; - template - struct rebind - { - using other = Allocator; - }; - template - typename rebind::other rebound() - { - return typename rebind::other(*this); - } - -public: - - using impl_type::impl_type; - Allocator() : impl_type() {} // VS demands this - - template Allocator(Allocator const& that) : impl_type(that.resource()) {} - - Allocator(Allocator const&) = default; - Allocator(Allocator &&) = default; - - Allocator& operator= (Allocator const&) = default; // WTF? why? @see http://en.cppreference.com/w/cpp/memory/polymorphic_allocator - Allocator& operator= (Allocator &&) = default; - - /** returns a default-constructed polymorphic allocator object - * @see http://en.cppreference.com/w/cpp/memory/polymorphic_allocator/select_on_container_copy_construction */ - Allocator select_on_container_copy_construct() const { return Allocator(*this); } - - T* allocate(size_t num_objs, size_t alignment=alignof(T)) - { - C4_ASSERT(this->resource() != nullptr); - C4_ASSERT(alignment >= alignof(T)); - void* vmem = this->resource()->allocate(detail::size_for(num_objs), alignment); - T* mem = static_cast(vmem); - return mem; - } - - void deallocate(T * ptr, size_t num_objs, size_t alignment=alignof(T)) - { - C4_ASSERT(this->resource() != nullptr); - C4_ASSERT(alignment>= alignof(T)); - this->resource()->deallocate(ptr, detail::size_for(num_objs), alignment); - } - - T* reallocate(T* ptr, size_t oldnum, size_t newnum, size_t alignment=alignof(T)) - { - C4_ASSERT(this->resource() != nullptr); - C4_ASSERT(alignment >= alignof(T)); - void* vmem = this->resource()->reallocate(ptr, detail::size_for(oldnum), detail::size_for(newnum), alignment); - T* mem = static_cast(vmem); - return mem; - } - -}; - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -/** @ingroup allocators */ -template -class SmallAllocator : public detail::_AllocatorUtil -{ - static_assert(Alignment >= alignof(T), "invalid alignment"); - - using impl_type = detail::_AllocatorUtil; - - alignas(Alignment) char m_arr[N * sizeof(T)]; - size_t m_num{0}; - -public: - - using value_type = T; - using pointer = T*; - using const_pointer = T const*; - using reference = T&; - using const_reference = T const&; - using size_type = size_t; - using difference_type = std::ptrdiff_t; - using propagate_on_container_move_assigment = std::true_type; - - template - bool operator== (SmallAllocator const&) const - { - return false; - } - template - bool operator!= (SmallAllocator const&) const - { - return true; - } - -public: - - template friend class SmallAllocator; - template - struct rebind - { - using other = SmallAllocator; - }; - template - typename rebind::other rebound() - { - return typename rebind::other(*this); - } - -public: - - using impl_type::impl_type; - SmallAllocator() : impl_type() {} // VS demands this - - template - SmallAllocator(SmallAllocator const& that) : impl_type(that.resource()) - { - C4_ASSERT(that.m_num == 0); - } - - SmallAllocator(SmallAllocator const&) = default; - SmallAllocator(SmallAllocator &&) = default; - - SmallAllocator& operator= (SmallAllocator const&) = default; // WTF? why? @see http://en.cppreference.com/w/cpp/memory/polymorphic_allocator - SmallAllocator& operator= (SmallAllocator &&) = default; - - /** returns a default-constructed polymorphic allocator object - * @see http://en.cppreference.com/w/cpp/memory/polymorphic_allocator/select_on_container_copy_construction */ - SmallAllocator select_on_container_copy_construct() const { return SmallAllocator(*this); } - - T* allocate(size_t num_objs, size_t alignment=Alignment) - { - C4_ASSERT(this->resource() != nullptr); - C4_ASSERT(alignment >= alignof(T)); - void *vmem; - if(m_num + num_objs <= N) - { - vmem = (m_arr + m_num * sizeof(T)); - } - else - { - vmem = this->resource()->allocate(num_objs * sizeof(T), alignment); - } - m_num += num_objs; - T *mem = static_cast(vmem); - return mem; - } - - void deallocate(T * ptr, size_t num_objs, size_t alignment=Alignment) - { - C4_ASSERT(m_num >= num_objs); - m_num -= num_objs; - if((char*)ptr >= m_arr && (char*)ptr < m_arr + (N * sizeof(T))) - { - return; - } - C4_ASSERT(this->resource() != nullptr); - C4_ASSERT(alignment >= alignof(T)); - this->resource()->deallocate(ptr, num_objs * sizeof(T), alignment); - } - - T* reallocate(T * ptr, size_t oldnum, size_t newnum, size_t alignment=Alignment) - { - C4_ASSERT(this->resource() != nullptr); - C4_ASSERT(alignment >= alignof(T)); - if(oldnum <= N && newnum <= N) - { - return m_arr; - } - else if(oldnum <= N && newnum > N) - { - return allocate(newnum, alignment); - } - else if(oldnum > N && newnum <= N) - { - deallocate(ptr, oldnum, alignment); - return m_arr; - } - void* vmem = this->resource()->reallocate(ptr, oldnum * sizeof(T), newnum * sizeof(T), alignment); - T* mem = static_cast(vmem); - return mem; - } - -}; - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -/** An allocator making use of the global memory resource. - * @ingroup allocators */ -template using allocator = Allocator; -/** An allocator with a per-instance memory resource - * @ingroup allocators */ -template using allocator_mr = Allocator; - -/** @ingroup allocators */ -template using small_allocator = SmallAllocator; -/** @ingroup allocators */ -template using small_allocator_mr = SmallAllocator; - -} // namespace c4 - -#endif /* _C4_ALLOCATOR_HPP_ */ - - -// (end https://github.com/biojppm/c4core/src/c4/allocator.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/char_traits.hpp -// https://github.com/biojppm/c4core/src/c4/char_traits.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_CHAR_TRAITS_HPP_ -#define _C4_CHAR_TRAITS_HPP_ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/config.hpp -//#include "c4/config.hpp" -#if !defined(C4_CONFIG_HPP_) && !defined(_C4_CONFIG_HPP_) -#error "amalgamate: file c4/config.hpp must have been included at this point" -#endif /* C4_CONFIG_HPP_ */ - - -#include // needed because of std::char_traits -#include -#include - -namespace c4 { - -C4_ALWAYS_INLINE bool isspace(char c) { return std::isspace(c) != 0; } -C4_ALWAYS_INLINE bool isspace(wchar_t c) { return std::iswspace(static_cast(c)) != 0; } - -//----------------------------------------------------------------------------- -template -struct char_traits; - -template<> -struct char_traits : public std::char_traits -{ - constexpr static const char whitespace_chars[] = " \f\n\r\t\v"; - constexpr static const size_t num_whitespace_chars = sizeof(whitespace_chars) - 1; -}; - -template<> -struct char_traits : public std::char_traits -{ - constexpr static const wchar_t whitespace_chars[] = L" \f\n\r\t\v"; - constexpr static const size_t num_whitespace_chars = sizeof(whitespace_chars) - 1; -}; - - -//----------------------------------------------------------------------------- -namespace detail { -template -struct needed_chars; -template<> -struct needed_chars -{ - template - C4_ALWAYS_INLINE constexpr static SizeType for_bytes(SizeType num_bytes) - { - return num_bytes; - } -}; -template<> -struct needed_chars -{ - template - C4_ALWAYS_INLINE constexpr static SizeType for_bytes(SizeType num_bytes) - { - // wchar_t is not necessarily 2 bytes. - return (num_bytes / static_cast(sizeof(wchar_t))) + ((num_bytes & static_cast(SizeType(sizeof(wchar_t)) - SizeType(1))) != 0); - } -}; -} // namespace detail - -/** get the number of C characters needed to store a number of bytes */ -template -C4_ALWAYS_INLINE constexpr SizeType num_needed_chars(SizeType num_bytes) -{ - return detail::needed_chars::for_bytes(num_bytes); -} - - -//----------------------------------------------------------------------------- - -/** get the given text string as either char or wchar_t according to the given type */ -#define C4_TXTTY(txt, type) \ - /* is there a smarter way to do this? */\ - c4::detail::literal_as::get(txt, C4_WIDEN(txt)) - -namespace detail { -template -struct literal_as; - -template<> -struct literal_as -{ - C4_ALWAYS_INLINE static constexpr const char* get(const char* str, const wchar_t *) - { - return str; - } -}; -template<> -struct literal_as -{ - C4_ALWAYS_INLINE static constexpr const wchar_t* get(const char*, const wchar_t *wstr) - { - return wstr; - } -}; -} // namespace detail - -} // namespace c4 - -#endif /* _C4_CHAR_TRAITS_HPP_ */ - - -// (end https://github.com/biojppm/c4core/src/c4/char_traits.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/hash.hpp -// https://github.com/biojppm/c4core/src/c4/hash.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_HASH_HPP_ -#define _C4_HASH_HPP_ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/config.hpp -//#include "c4/config.hpp" -#if !defined(C4_CONFIG_HPP_) && !defined(_C4_CONFIG_HPP_) -#error "amalgamate: file c4/config.hpp must have been included at this point" -#endif /* C4_CONFIG_HPP_ */ - -#include - -/** @file hash.hpp */ - -/** @defgroup hash Hash utils - * @see http://aras-p.info/blog/2016/08/02/Hash-Functions-all-the-way-down/ */ - -namespace c4 { - -namespace detail { - -/** @internal - * @ingroup hash - * @see this was taken a great answer in stackoverflow: - * https://stackoverflow.com/a/34597785/5875572 - * @see http://aras-p.info/blog/2016/08/02/Hash-Functions-all-the-way-down/ */ -template -class basic_fnv1a final -{ - - static_assert(std::is_unsigned::value, "need unsigned integer"); - -public: - - using result_type = ResultT; - -private: - - result_type state_ {}; - -public: - - C4_CONSTEXPR14 basic_fnv1a() noexcept : state_ {OffsetBasis} {} - - C4_CONSTEXPR14 void update(const void *const data, const size_t size) noexcept - { - auto cdata = static_cast(data); - auto acc = this->state_; - for(size_t i = 0; i < size; ++i) - { - const auto next = size_t(cdata[i]); - acc = (acc ^ next) * Prime; - } - this->state_ = acc; - } - - C4_CONSTEXPR14 result_type digest() const noexcept - { - return this->state_; - } - -}; - -using fnv1a_32 = basic_fnv1a; -using fnv1a_64 = basic_fnv1a; - -template struct fnv1a; -template<> struct fnv1a<32> { using type = fnv1a_32; }; -template<> struct fnv1a<64> { using type = fnv1a_64; }; - -} // namespace detail - - -/** @ingroup hash */ -template -using fnv1a_t = typename detail::fnv1a::type; - - -/** @ingroup hash */ -C4_CONSTEXPR14 inline size_t hash_bytes(const void *const data, const size_t size) noexcept -{ - fnv1a_t fn{}; - fn.update(data, size); - return fn.digest(); -} - -/** - * @overload hash_bytes - * @ingroup hash */ -template -C4_CONSTEXPR14 inline size_t hash_bytes(const char (&str)[N]) noexcept -{ - fnv1a_t fn{}; - fn.update(str, N); - return fn.digest(); -} - -} // namespace c4 - - -#endif // _C4_HASH_HPP_ - - -// (end https://github.com/biojppm/c4core/src/c4/hash.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/szconv.hpp -// https://github.com/biojppm/c4core/src/c4/szconv.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_SZCONV_HPP_ -#define _C4_SZCONV_HPP_ - -/** @file szconv.hpp utilities to deal safely with narrowing conversions */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/config.hpp -//#include "c4/config.hpp" -#if !defined(C4_CONFIG_HPP_) && !defined(_C4_CONFIG_HPP_) -#error "amalgamate: file c4/config.hpp must have been included at this point" -#endif /* C4_CONFIG_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/error.hpp -//#include "c4/error.hpp" -#if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) -#error "amalgamate: file c4/error.hpp must have been included at this point" -#endif /* C4_ERROR_HPP_ */ - - -#include - -namespace c4 { - -/** @todo this would be so much easier with calls to numeric_limits::max()... */ -template -struct is_narrower_size : std::conditional -< - (std::is_signed::value == std::is_signed::value) - ? - (sizeof(SizeOut) < sizeof(SizeIn)) - : - ( - (sizeof(SizeOut) < sizeof(SizeIn)) - || - ( - (sizeof(SizeOut) == sizeof(SizeIn)) - && - (std::is_signed::value && std::is_unsigned::value) - ) - ), - std::true_type, - std::false_type ->::type -{ - static_assert(std::is_integral::value, "must be integral type"); - static_assert(std::is_integral::value, "must be integral type"); -}; - - -/** when SizeOut is wider than SizeIn, assignment can occur without reservations */ -template -C4_ALWAYS_INLINE -typename std::enable_if< ! is_narrower_size::value, SizeOut>::type -szconv(SizeIn sz) noexcept -{ - return static_cast(sz); -} - -/** when SizeOut is narrower than SizeIn, narrowing will occur, so we check - * for overflow. Note that this check is done only if C4_XASSERT is enabled. - * @see C4_XASSERT */ -template -C4_ALWAYS_INLINE -typename std::enable_if::value, SizeOut>::type -szconv(SizeIn sz) C4_NOEXCEPT_X -{ - C4_XASSERT(sz >= 0); - C4_XASSERT_MSG((SizeIn)sz <= (SizeIn)std::numeric_limits::max(), "size conversion overflow: in=%zu", (size_t)sz); - SizeOut szo = static_cast(sz); - return szo; -} - -} // namespace c4 - -#endif /* _C4_SZCONV_HPP_ */ - - -// (end https://github.com/biojppm/c4core/src/c4/szconv.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/blob.hpp -// https://github.com/biojppm/c4core/src/c4/blob.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_BLOB_HPP_ -#define _C4_BLOB_HPP_ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/types.hpp -//#include "c4/types.hpp" -#if !defined(C4_TYPES_HPP_) && !defined(_C4_TYPES_HPP_) -#error "amalgamate: file c4/types.hpp must have been included at this point" -#endif /* C4_TYPES_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/error.hpp -//#include "c4/error.hpp" -#if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) -#error "amalgamate: file c4/error.hpp must have been included at this point" -#endif /* C4_ERROR_HPP_ */ - - -/** @file blob.hpp Mutable and immutable binary data blobs. -*/ - -namespace c4 { - -template -struct blob_ -{ - T * buf; - size_t len; - - C4_ALWAYS_INLINE blob_() noexcept : buf(), len() {} - - C4_ALWAYS_INLINE blob_(blob_ const& that) noexcept = default; - C4_ALWAYS_INLINE blob_(blob_ && that) noexcept = default; - C4_ALWAYS_INLINE blob_& operator=(blob_ && that) noexcept = default; - C4_ALWAYS_INLINE blob_& operator=(blob_ const& that) noexcept = default; - - // need to sfinae out copy constructors! (why? isn't the above sufficient?) - #define _C4_REQUIRE_NOT_SAME class=typename std::enable_if<( ! std::is_same::value) && ( ! std::is_pointer::value), T>::type - template C4_ALWAYS_INLINE blob_(U &var) noexcept : buf(reinterpret_cast(&var)), len(sizeof(U)) {} - template C4_ALWAYS_INLINE blob_& operator= (U &var) noexcept { buf = reinterpret_cast(&var); len = sizeof(U); return *this; } - #undef _C4_REQUIRE_NOT_SAME - - template C4_ALWAYS_INLINE blob_(U (&arr)[N]) noexcept : buf(reinterpret_cast(arr)), len(sizeof(U) * N) {} - template C4_ALWAYS_INLINE blob_& operator= (U (&arr)[N]) noexcept { buf = reinterpret_cast(arr); len = sizeof(U) * N; return *this; } - - template - C4_ALWAYS_INLINE blob_(U *ptr, size_t n) noexcept : buf(reinterpret_cast(ptr)), len(sizeof(U) * n) { C4_ASSERT(is_aligned(ptr)); } - C4_ALWAYS_INLINE blob_(void *ptr, size_t n) noexcept : buf(reinterpret_cast(ptr)), len(n) {} - C4_ALWAYS_INLINE blob_(void const *ptr, size_t n) noexcept : buf(reinterpret_cast(ptr)), len(n) {} -}; - -/** an immutable binary blob */ -using cblob = blob_; -/** a mutable binary blob */ -using blob = blob_< byte>; - -C4_MUST_BE_TRIVIAL_COPY(blob); -C4_MUST_BE_TRIVIAL_COPY(cblob); - -} // namespace c4 - -#endif // _C4_BLOB_HPP_ - - -// (end https://github.com/biojppm/c4core/src/c4/blob.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/substr_fwd.hpp -// https://github.com/biojppm/c4core/src/c4/substr_fwd.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_SUBSTR_FWD_HPP_ -#define _C4_SUBSTR_FWD_HPP_ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/export.hpp -//#include "c4/export.hpp" -#if !defined(C4_EXPORT_HPP_) && !defined(_C4_EXPORT_HPP_) -#error "amalgamate: file c4/export.hpp must have been included at this point" -#endif /* C4_EXPORT_HPP_ */ - - -namespace c4 { - -#ifndef DOXYGEN -template struct basic_substring; -using csubstr = C4CORE_EXPORT basic_substring; -using substr = C4CORE_EXPORT basic_substring; -#endif // !DOXYGEN - -} // namespace c4 - -#endif /* _C4_SUBSTR_FWD_HPP_ */ - - -// (end https://github.com/biojppm/c4core/src/c4/substr_fwd.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/substr.hpp -// https://github.com/biojppm/c4core/src/c4/substr.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_SUBSTR_HPP_ -#define _C4_SUBSTR_HPP_ - -/** @file substr.hpp read+write string views */ - -//included above: -//#include -//included above: -//#include -//included above: -//#include - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/config.hpp -//#include "c4/config.hpp" -#if !defined(C4_CONFIG_HPP_) && !defined(_C4_CONFIG_HPP_) -#error "amalgamate: file c4/config.hpp must have been included at this point" -#endif /* C4_CONFIG_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/error.hpp -//#include "c4/error.hpp" -#if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) -#error "amalgamate: file c4/error.hpp must have been included at this point" -#endif /* C4_ERROR_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/substr_fwd.hpp -//#include "c4/substr_fwd.hpp" -#if !defined(C4_SUBSTR_FWD_HPP_) && !defined(_C4_SUBSTR_FWD_HPP_) -#error "amalgamate: file c4/substr_fwd.hpp must have been included at this point" -#endif /* C4_SUBSTR_FWD_HPP_ */ - - -#ifdef __clang__ -# pragma clang diagnostic push -#elif defined(__GNUC__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wtype-limits" // disable warnings on size_t>=0, used heavily in assertions below. These assertions are a preparation step for providing the index type as a template parameter. -# pragma GCC diagnostic ignored "-Wuseless-cast" -#endif - - -namespace c4 { - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -namespace detail { - -template -static inline void _do_reverse(C *C4_RESTRICT first, C *C4_RESTRICT last) -{ - while(last > first) - { - C tmp = *last; - *last-- = *first; - *first++ = tmp; - } -} - -} // namespace detail - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -// utility macros to deuglify SFINAE code; undefined after the class. -// https://stackoverflow.com/questions/43051882/how-to-disable-a-class-member-funrtion-for-certain-template-types -#define C4_REQUIRE_RW(ret_type) \ - template \ - typename std::enable_if< ! std::is_const::value, ret_type>::type -// non-const-to-const -#define C4_NC2C(ty) \ - typename std::enable_if::value && ( ! std::is_const::value), ty>::type - - -/** a non-owning string-view, consisting of a character pointer - * and a length. - * - * @note The pointer is explicitly restricted. - * @note Because of a C++ limitation, there cannot coexist overloads for - * constructing from a char[N] and a char*; the latter will always be chosen - * by the compiler. To construct an object of this type, call to_substr() or - * to_csubstr(). For a more detailed explanation on why the overloads cannot - * coexist, see http://cplusplus.bordoon.com/specializeForCharacterArrays.html - * - * @see to_substr() - * @see to_csubstr() - */ -template -struct C4CORE_EXPORT basic_substring -{ -public: - - /** a restricted pointer to the first character of the substring */ - C * C4_RESTRICT str; - /** the length of the substring */ - size_t len; - -public: - - /** @name Types */ - /** @{ */ - - using CC = typename std::add_const::type; //!< CC=const char - using NCC_ = typename std::remove_const::type; //!< NCC_=non const char - - using ro_substr = basic_substring; - using rw_substr = basic_substring; - - using char_type = C; - using size_type = size_t; - - using iterator = C*; - using const_iterator = CC*; - - enum : size_t { npos = (size_t)-1, NONE = (size_t)-1 }; - - /// convert automatically to substring of const C - operator ro_substr () const { ro_substr s(str, len); return s; } - - - operator const std::string_view () const { return std::string_view(str,len); } - - - /** @} */ - -public: - - /** @name Default construction and assignment */ - /** @{ */ - - constexpr basic_substring() : str(nullptr), len(0) {} - - constexpr basic_substring(basic_substring const&) = default; - constexpr basic_substring(basic_substring &&) = default; - constexpr basic_substring(std::nullptr_t) : str(nullptr), len(0) {} - - basic_substring& operator= (basic_substring const&) = default; - basic_substring& operator= (basic_substring &&) = default; - basic_substring& operator= (std::nullptr_t) { str = nullptr; len = 0; return *this; } - - /** @} */ - -public: - - /** @name Construction and assignment from characters with the same type */ - /** @{ */ - - //basic_substring(C *s_) : str(s_), len(s_ ? strlen(s_) : 0) {} - /** the overload for receiving a single C* pointer will always - * hide the array[N] overload. So it is disabled. If you want to - * construct a substr from a single pointer containing a C-style string, - * you can call c4::to_substr()/c4::to_csubstr(). - * @see c4::to_substr() - * @see c4::to_csubstr() */ - template - constexpr basic_substring(C (&s_)[N]) noexcept : str(s_), len(N-1) {} - basic_substring(C *s_, size_t len_) : str(s_), len(len_) { C4_ASSERT(str || !len_); } - basic_substring(C *beg_, C *end_) : str(beg_), len(static_cast(end_ - beg_)) { C4_ASSERT(end_ >= beg_); } - - //basic_substring& operator= (C *s_) { this->assign(s_); return *this; } - template - basic_substring& operator= (C (&s_)[N]) { this->assign(s_); return *this; } - - //void assign(C *s_) { str = (s_); len = (s_ ? strlen(s_) : 0); } - /** the overload for receiving a single C* pointer will always - * hide the array[N] overload. So it is disabled. If you want to - * construct a substr from a single pointer containing a C-style string, - * you can call c4::to_substr()/c4::to_csubstr(). - * @see c4::to_substr() - * @see c4::to_csubstr() */ - template - void assign(C (&s_)[N]) { str = (s_); len = (N-1); } - void assign(C *s_, size_t len_) { str = s_; len = len_; C4_ASSERT(str || !len_); } - void assign(C *beg_, C *end_) { C4_ASSERT(end_ >= beg_); str = (beg_); len = (end_ - beg_); } - - void clear() { str = nullptr; len = 0; } - - /** @} */ - -public: - - /** @name Construction from non-const characters */ - /** @{ */ - - // when the char type is const, allow construction and assignment from non-const chars - - /** only available when the char type is const */ - template explicit basic_substring(C4_NC2C(U) (&s_)[N]) { str = s_; len = N-1; } - /** only available when the char type is const */ - template< class U=NCC_> basic_substring(C4_NC2C(U) *s_, size_t len_) { str = s_; len = len_; } - /** only available when the char type is const */ - template< class U=NCC_> basic_substring(C4_NC2C(U) *beg_, C4_NC2C(U) *end_) { C4_ASSERT(end_ >= beg_); str = beg_; len = end_ - beg_; } - - /** only available when the char type is const */ - template void assign(C4_NC2C(U) (&s_)[N]) { str = s_; len = N-1; } - /** only available when the char type is const */ - template< class U=NCC_> void assign(C4_NC2C(U) *s_, size_t len_) { str = s_; len = len_; } - /** only available when the char type is const */ - template< class U=NCC_> void assign(C4_NC2C(U) *beg_, C4_NC2C(U) *end_) { C4_ASSERT(end_ >= beg_); str = beg_; len = end_ - beg_; } - - /** only available when the char type is const */ - template - basic_substring& operator=(C4_NC2C(U) (&s_)[N]) { str = s_; len = N-1; return *this; } - - /** @} */ - -public: - - /** @name Standard accessor methods */ - /** @{ */ - - C4_ALWAYS_INLINE C4_PURE bool has_str() const noexcept { return ! empty() && str[0] != C(0); } - C4_ALWAYS_INLINE C4_PURE bool empty() const noexcept { return (len == 0 || str == nullptr); } - C4_ALWAYS_INLINE C4_PURE bool not_empty() const noexcept { return (len != 0 && str != nullptr); } - C4_ALWAYS_INLINE C4_PURE size_t size() const noexcept { return len; } - - C4_ALWAYS_INLINE C4_PURE iterator begin() noexcept { return str; } - C4_ALWAYS_INLINE C4_PURE iterator end () noexcept { return str + len; } - - C4_ALWAYS_INLINE C4_PURE const_iterator begin() const noexcept { return str; } - C4_ALWAYS_INLINE C4_PURE const_iterator end () const noexcept { return str + len; } - - C4_ALWAYS_INLINE C4_PURE C * data() noexcept { return str; } - C4_ALWAYS_INLINE C4_PURE C const* data() const noexcept { return str; } - - C4_ALWAYS_INLINE C4_PURE C & operator[] (size_t i) noexcept { C4_ASSERT(i >= 0 && i < len); return str[i]; } - C4_ALWAYS_INLINE C4_PURE C const& operator[] (size_t i) const noexcept { C4_ASSERT(i >= 0 && i < len); return str[i]; } - - C4_ALWAYS_INLINE C4_PURE C & front() noexcept { C4_ASSERT(len > 0 && str != nullptr); return *str; } - C4_ALWAYS_INLINE C4_PURE C const& front() const noexcept { C4_ASSERT(len > 0 && str != nullptr); return *str; } - - C4_ALWAYS_INLINE C4_PURE C & back() noexcept { C4_ASSERT(len > 0 && str != nullptr); return *(str + len - 1); } - C4_ALWAYS_INLINE C4_PURE C const& back() const noexcept { C4_ASSERT(len > 0 && str != nullptr); return *(str + len - 1); } - - /** @} */ - -public: - - /** @name Comparison methods */ - /** @{ */ - - C4_PURE int compare(C const c) const noexcept - { - C4_XASSERT((str != nullptr) || len == 0); - if(C4_LIKELY(str != nullptr && len > 0)) - return (*str != c) ? *str - c : (static_cast(len) - 1); - else - return -1; - } - - C4_PURE int compare(const char *C4_RESTRICT that, size_t sz) const noexcept - { - C4_XASSERT(that || sz == 0); - C4_XASSERT(str || len == 0); - if(C4_LIKELY(str && that)) - { - { - const size_t min = len < sz ? len : sz; - for(size_t i = 0; i < min; ++i) - if(str[i] != that[i]) - return str[i] < that[i] ? -1 : 1; - } - if(len < sz) - return -1; - else if(len == sz) - return 0; - else - return 1; - } - else if(len == sz) - { - C4_XASSERT(len == 0 && sz == 0); - return 0; - } - return len < sz ? -1 : 1; - } - - C4_ALWAYS_INLINE C4_PURE int compare(ro_substr const that) const noexcept { return this->compare(that.str, that.len); } - - C4_ALWAYS_INLINE C4_PURE bool operator== (std::nullptr_t) const noexcept { return str == nullptr; } - C4_ALWAYS_INLINE C4_PURE bool operator!= (std::nullptr_t) const noexcept { return str != nullptr; } - - C4_ALWAYS_INLINE C4_PURE bool operator== (C const c) const noexcept { return this->compare(c) == 0; } - C4_ALWAYS_INLINE C4_PURE bool operator!= (C const c) const noexcept { return this->compare(c) != 0; } - C4_ALWAYS_INLINE C4_PURE bool operator< (C const c) const noexcept { return this->compare(c) < 0; } - C4_ALWAYS_INLINE C4_PURE bool operator> (C const c) const noexcept { return this->compare(c) > 0; } - C4_ALWAYS_INLINE C4_PURE bool operator<= (C const c) const noexcept { return this->compare(c) <= 0; } - C4_ALWAYS_INLINE C4_PURE bool operator>= (C const c) const noexcept { return this->compare(c) >= 0; } - - template C4_ALWAYS_INLINE C4_PURE bool operator== (basic_substring const that) const noexcept { return this->compare(that) == 0; } - template C4_ALWAYS_INLINE C4_PURE bool operator!= (basic_substring const that) const noexcept { return this->compare(that) != 0; } - template C4_ALWAYS_INLINE C4_PURE bool operator< (basic_substring const that) const noexcept { return this->compare(that) < 0; } - template C4_ALWAYS_INLINE C4_PURE bool operator> (basic_substring const that) const noexcept { return this->compare(that) > 0; } - template C4_ALWAYS_INLINE C4_PURE bool operator<= (basic_substring const that) const noexcept { return this->compare(that) <= 0; } - template C4_ALWAYS_INLINE C4_PURE bool operator>= (basic_substring const that) const noexcept { return this->compare(that) >= 0; } - - template C4_ALWAYS_INLINE C4_PURE bool operator== (const char (&that)[N]) const noexcept { return this->compare(that, N-1) == 0; } - template C4_ALWAYS_INLINE C4_PURE bool operator!= (const char (&that)[N]) const noexcept { return this->compare(that, N-1) != 0; } - template C4_ALWAYS_INLINE C4_PURE bool operator< (const char (&that)[N]) const noexcept { return this->compare(that, N-1) < 0; } - template C4_ALWAYS_INLINE C4_PURE bool operator> (const char (&that)[N]) const noexcept { return this->compare(that, N-1) > 0; } - template C4_ALWAYS_INLINE C4_PURE bool operator<= (const char (&that)[N]) const noexcept { return this->compare(that, N-1) <= 0; } - template C4_ALWAYS_INLINE C4_PURE bool operator>= (const char (&that)[N]) const noexcept { return this->compare(that, N-1) >= 0; } - - /** @} */ - -public: - - /** @name Sub-selection methods */ - /** @{ */ - - /** true if *this is a substring of that (ie, from the same buffer) */ - C4_ALWAYS_INLINE C4_PURE bool is_sub(ro_substr const that) const noexcept - { - return that.is_super(*this); - } - - /** true if that is a substring of *this (ie, from the same buffer) */ - C4_ALWAYS_INLINE C4_PURE bool is_super(ro_substr const that) const noexcept - { - if(C4_LIKELY(len > 0)) - return that.str >= str && that.str+that.len <= str+len; - else - return that.len == 0 && that.str == str && str != nullptr; - } - - /** true if there is overlap of at least one element between that and *this */ - C4_ALWAYS_INLINE C4_PURE bool overlaps(ro_substr const that) const noexcept - { - // thanks @timwynants - return that.str+that.len > str && that.str < str+len; - } - -public: - - /** return [first,len[ */ - C4_ALWAYS_INLINE C4_PURE basic_substring sub(size_t first) const noexcept - { - C4_ASSERT(first >= 0 && first <= len); - return basic_substring(str + first, len - first); - } - - /** return [first,first+num[. If num==npos, return [first,len[ */ - C4_ALWAYS_INLINE C4_PURE basic_substring sub(size_t first, size_t num) const noexcept - { - C4_ASSERT(first >= 0 && first <= len); - C4_ASSERT((num >= 0 && num <= len) || (num == npos)); - size_t rnum = num != npos ? num : len - first; - C4_ASSERT((first >= 0 && first + rnum <= len) || (num == 0)); - return basic_substring(str + first, rnum); - } - - /** return [first,last[. If last==npos, return [first,len[ */ - C4_ALWAYS_INLINE C4_PURE basic_substring range(size_t first, size_t last=npos) const noexcept - { - C4_ASSERT(first >= 0 && first <= len); - last = last != npos ? last : len; - C4_ASSERT(first <= last); - C4_ASSERT(last >= 0 && last <= len); - return basic_substring(str + first, last - first); - } - - /** return the first @p num elements: [0,num[*/ - C4_ALWAYS_INLINE C4_PURE basic_substring first(size_t num) const noexcept - { - C4_ASSERT(num <= len || num == npos); - return basic_substring(str, num != npos ? num : len); - } - - /** return the last @num elements: [len-num,len[*/ - C4_ALWAYS_INLINE C4_PURE basic_substring last(size_t num) const noexcept - { - C4_ASSERT(num <= len || num == npos); - return num != npos ? - basic_substring(str + len - num, num) : - *this; - } - - /** offset from the ends: return [left,len-right[ ; ie, trim a - number of characters from the left and right. This is - equivalent to python's negative list indices. */ - C4_ALWAYS_INLINE C4_PURE basic_substring offs(size_t left, size_t right) const noexcept - { - C4_ASSERT(left >= 0 && left <= len); - C4_ASSERT(right >= 0 && right <= len); - C4_ASSERT(left <= len - right + 1); - return basic_substring(str + left, len - right - left); - } - - /** return [0, pos[ . Same as .first(pos), but provided for compatibility with .right_of() */ - C4_ALWAYS_INLINE C4_PURE basic_substring left_of(size_t pos) const noexcept - { - C4_ASSERT(pos <= len || pos == npos); - return (pos != npos) ? - basic_substring(str, pos) : - *this; - } - - /** return [0, pos+include_pos[ . Same as .first(pos+1), but provided for compatibility with .right_of() */ - C4_ALWAYS_INLINE C4_PURE basic_substring left_of(size_t pos, bool include_pos) const noexcept - { - C4_ASSERT(pos <= len || pos == npos); - return (pos != npos) ? - basic_substring(str, pos+include_pos) : - *this; - } - - /** return [pos+1, len[ */ - C4_ALWAYS_INLINE C4_PURE basic_substring right_of(size_t pos) const noexcept - { - C4_ASSERT(pos <= len || pos == npos); - return (pos != npos) ? - basic_substring(str + (pos + 1), len - (pos + 1)) : - basic_substring(str + len, size_t(0)); - } - - /** return [pos+!include_pos, len[ */ - C4_ALWAYS_INLINE C4_PURE basic_substring right_of(size_t pos, bool include_pos) const noexcept - { - C4_ASSERT(pos <= len || pos == npos); - return (pos != npos) ? - basic_substring(str + (pos + !include_pos), len - (pos + !include_pos)) : - basic_substring(str + len, size_t(0)); - } - -public: - - /** given @p subs a substring of the current string, get the - * portion of the current string to the left of it */ - C4_ALWAYS_INLINE C4_PURE basic_substring left_of(ro_substr const subs) const noexcept - { - C4_ASSERT(is_super(subs) || subs.empty()); - auto ssb = subs.begin(); - auto b = begin(); - auto e = end(); - if(ssb >= b && ssb <= e) - return sub(0, static_cast(ssb - b)); - else - return sub(0, 0); - } - - /** given @p subs a substring of the current string, get the - * portion of the current string to the right of it */ - C4_ALWAYS_INLINE C4_PURE basic_substring right_of(ro_substr const subs) const noexcept - { - C4_ASSERT(is_super(subs) || subs.empty()); - auto sse = subs.end(); - auto b = begin(); - auto e = end(); - if(sse >= b && sse <= e) - return sub(static_cast(sse - b), static_cast(e - sse)); - else - return sub(0, 0); - } - - /** @} */ - -public: - - /** @name Removing characters (trim()) / patterns (strip()) from the tips of the string */ - /** @{ */ - - /** trim left */ - basic_substring triml(const C c) const - { - if( ! empty()) - { - size_t pos = first_not_of(c); - if(pos != npos) - return sub(pos); - } - return sub(0, 0); - } - /** trim left ANY of the characters. - * @see stripl() to remove a pattern from the left */ - basic_substring triml(ro_substr chars) const - { - if( ! empty()) - { - size_t pos = first_not_of(chars); - if(pos != npos) - return sub(pos); - } - return sub(0, 0); - } - - /** trim the character c from the right */ - basic_substring trimr(const C c) const - { - if( ! empty()) - { - size_t pos = last_not_of(c, npos); - if(pos != npos) - return sub(0, pos+1); - } - return sub(0, 0); - } - /** trim right ANY of the characters - * @see stripr() to remove a pattern from the right */ - basic_substring trimr(ro_substr chars) const - { - if( ! empty()) - { - size_t pos = last_not_of(chars, npos); - if(pos != npos) - return sub(0, pos+1); - } - return sub(0, 0); - } - - /** trim the character c left and right */ - basic_substring trim(const C c) const - { - return triml(c).trimr(c); - } - /** trim left and right ANY of the characters - * @see strip() to remove a pattern from the left and right */ - basic_substring trim(ro_substr const chars) const - { - return triml(chars).trimr(chars); - } - - /** remove a pattern from the left - * @see triml() to remove characters*/ - basic_substring stripl(ro_substr pattern) const - { - if( ! begins_with(pattern)) - return *this; - return sub(pattern.len < len ? pattern.len : len); - } - - /** remove a pattern from the right - * @see trimr() to remove characters*/ - basic_substring stripr(ro_substr pattern) const - { - if( ! ends_with(pattern)) - return *this; - return left_of(len - (pattern.len < len ? pattern.len : len)); - } - - /** @} */ - -public: - - /** @name Lookup methods */ - /** @{ */ - - inline size_t find(const C c, size_t start_pos=0) const - { - return first_of(c, start_pos); - } - inline size_t find(ro_substr pattern, size_t start_pos=0) const - { - C4_ASSERT(start_pos == npos || (start_pos >= 0 && start_pos <= len)); - if(len < pattern.len) return npos; - for(size_t i = start_pos, e = len - pattern.len + 1; i < e; ++i) - { - bool gotit = true; - for(size_t j = 0; j < pattern.len; ++j) - { - C4_ASSERT(i + j < len); - if(str[i + j] != pattern.str[j]) - { - gotit = false; - break; - } - } - if(gotit) - { - return i; - } - } - return npos; - } - -public: - - /** count the number of occurrences of c */ - inline size_t count(const C c, size_t pos=0) const - { - C4_ASSERT(pos >= 0 && pos <= len); - size_t num = 0; - pos = find(c, pos); - while(pos != npos) - { - ++num; - pos = find(c, pos + 1); - } - return num; - } - - /** count the number of occurrences of s */ - inline size_t count(ro_substr c, size_t pos=0) const - { - C4_ASSERT(pos >= 0 && pos <= len); - size_t num = 0; - pos = find(c, pos); - while(pos != npos) - { - ++num; - pos = find(c, pos + c.len); - } - return num; - } - - /** get the substr consisting of the first occurrence of @p c after @p pos, or an empty substr if none occurs */ - inline basic_substring select(const C c, size_t pos=0) const - { - pos = find(c, pos); - return pos != npos ? sub(pos, 1) : basic_substring(); - } - - /** get the substr consisting of the first occurrence of @p pattern after @p pos, or an empty substr if none occurs */ - inline basic_substring select(ro_substr pattern, size_t pos=0) const - { - pos = find(pattern, pos); - return pos != npos ? sub(pos, pattern.len) : basic_substring(); - } - -public: - - struct first_of_any_result - { - size_t which; - size_t pos; - inline operator bool() const { return which != NONE && pos != npos; } - }; - - first_of_any_result first_of_any(ro_substr s0, ro_substr s1) const - { - ro_substr s[2] = {s0, s1}; - return first_of_any_iter(&s[0], &s[0] + 2); - } - - first_of_any_result first_of_any(ro_substr s0, ro_substr s1, ro_substr s2) const - { - ro_substr s[3] = {s0, s1, s2}; - return first_of_any_iter(&s[0], &s[0] + 3); - } - - first_of_any_result first_of_any(ro_substr s0, ro_substr s1, ro_substr s2, ro_substr s3) const - { - ro_substr s[4] = {s0, s1, s2, s3}; - return first_of_any_iter(&s[0], &s[0] + 4); - } - - first_of_any_result first_of_any(ro_substr s0, ro_substr s1, ro_substr s2, ro_substr s3, ro_substr s4) const - { - ro_substr s[5] = {s0, s1, s2, s3, s4}; - return first_of_any_iter(&s[0], &s[0] + 5); - } - - template - first_of_any_result first_of_any_iter(It first_span, It last_span) const - { - for(size_t i = 0; i < len; ++i) - { - size_t curr = 0; - for(It it = first_span; it != last_span; ++curr, ++it) - { - auto const& chars = *it; - if((i + chars.len) > len) continue; - bool gotit = true; - for(size_t j = 0; j < chars.len; ++j) - { - C4_ASSERT(i + j < len); - if(str[i + j] != chars[j]) - { - gotit = false; - break; - } - } - if(gotit) - { - return {curr, i}; - } - } - } - return {NONE, npos}; - } - -public: - - /** true if the first character of the string is @p c */ - bool begins_with(const C c) const - { - return len > 0 ? str[0] == c : false; - } - - /** true if the first @p num characters of the string are @p c */ - bool begins_with(const C c, size_t num) const - { - if(len < num) - { - return false; - } - for(size_t i = 0; i < num; ++i) - { - if(str[i] != c) - { - return false; - } - } - return true; - } - - /** true if the string begins with the given @p pattern */ - bool begins_with(ro_substr pattern) const - { - if(len < pattern.len) - { - return false; - } - for(size_t i = 0; i < pattern.len; ++i) - { - if(str[i] != pattern[i]) - { - return false; - } - } - return true; - } - - /** true if the first character of the string is any of the given @p chars */ - bool begins_with_any(ro_substr chars) const - { - if(len == 0) - { - return false; - } - for(size_t i = 0; i < chars.len; ++i) - { - if(str[0] == chars.str[i]) - { - return true; - } - } - return false; - } - - /** true if the last character of the string is @p c */ - bool ends_with(const C c) const - { - return len > 0 ? str[len-1] == c : false; - } - - /** true if the last @p num characters of the string are @p c */ - bool ends_with(const C c, size_t num) const - { - if(len < num) - { - return false; - } - for(size_t i = len - num; i < len; ++i) - { - if(str[i] != c) - { - return false; - } - } - return true; - } - - /** true if the string ends with the given @p pattern */ - bool ends_with(ro_substr pattern) const - { - if(len < pattern.len) - { - return false; - } - for(size_t i = 0, s = len-pattern.len; i < pattern.len; ++i) - { - if(str[s+i] != pattern[i]) - { - return false; - } - } - return true; - } - - /** true if the last character of the string is any of the given @p chars */ - bool ends_with_any(ro_substr chars) const - { - if(len == 0) - { - return false; - } - for(size_t i = 0; i < chars.len; ++i) - { - if(str[len - 1] == chars[i]) - { - return true; - } - } - return false; - } - -public: - - /** @return the first position where c is found in the string, or npos if none is found */ - size_t first_of(const C c, size_t start=0) const - { - C4_ASSERT(start == npos || (start >= 0 && start <= len)); - for(size_t i = start; i < len; ++i) - { - if(str[i] == c) - return i; - } - return npos; - } - - /** @return the last position where c is found in the string, or npos if none is found */ - size_t last_of(const C c, size_t start=npos) const - { - C4_ASSERT(start == npos || (start >= 0 && start <= len)); - if(start == npos) - start = len; - for(size_t i = start-1; i != size_t(-1); --i) - { - if(str[i] == c) - return i; - } - return npos; - } - - /** @return the first position where ANY of the chars is found in the string, or npos if none is found */ - size_t first_of(ro_substr chars, size_t start=0) const - { - C4_ASSERT(start == npos || (start >= 0 && start <= len)); - for(size_t i = start; i < len; ++i) - { - for(size_t j = 0; j < chars.len; ++j) - { - if(str[i] == chars[j]) - return i; - } - } - return npos; - } - - /** @return the last position where ANY of the chars is found in the string, or npos if none is found */ - size_t last_of(ro_substr chars, size_t start=npos) const - { - C4_ASSERT(start == npos || (start >= 0 && start <= len)); - if(start == npos) - start = len; - for(size_t i = start-1; i != size_t(-1); --i) - { - for(size_t j = 0; j < chars.len; ++j) - { - if(str[i] == chars[j]) - return i; - } - } - return npos; - } - -public: - - size_t first_not_of(const C c, size_t start=0) const - { - C4_ASSERT((start >= 0 && start <= len) || (start == len && len == 0)); - for(size_t i = start; i < len; ++i) - { - if(str[i] != c) - return i; - } - return npos; - } - - size_t last_not_of(const C c, size_t start=npos) const - { - C4_ASSERT(start == npos || (start >= 0 && start <= len)); - if(start == npos) - start = len; - for(size_t i = start-1; i != size_t(-1); --i) - { - if(str[i] != c) - return i; - } - return npos; - } - - size_t first_not_of(ro_substr chars, size_t start=0) const - { - C4_ASSERT((start >= 0 && start <= len) || (start == len && len == 0)); - for(size_t i = start; i < len; ++i) - { - bool gotit = true; - for(size_t j = 0; j < chars.len; ++j) - { - if(str[i] == chars.str[j]) - { - gotit = false; - break; - } - } - if(gotit) - { - return i; - } - } - return npos; - } - - size_t last_not_of(ro_substr chars, size_t start=npos) const - { - C4_ASSERT(start == npos || (start >= 0 && start <= len)); - if(start == npos) - start = len; - for(size_t i = start-1; i != size_t(-1); --i) - { - bool gotit = true; - for(size_t j = 0; j < chars.len; ++j) - { - if(str[i] == chars.str[j]) - { - gotit = false; - break; - } - } - if(gotit) - { - return i; - } - } - return npos; - } - - /** @} */ - -public: - - /** @name Range lookup methods */ - /** @{ */ - - /** get the range delimited by an open-close pair of characters. - * @note There must be no nested pairs. - * @note No checks for escapes are performed. */ - basic_substring pair_range(CC open, CC close) const - { - size_t b = find(open); - if(b == npos) - return basic_substring(); - size_t e = find(close, b+1); - if(e == npos) - return basic_substring(); - basic_substring ret = range(b, e+1); - C4_ASSERT(ret.sub(1).find(open) == npos); - return ret; - } - - /** get the range delimited by a single open-close character (eg, quotes). - * @note The open-close character can be escaped. */ - basic_substring pair_range_esc(CC open_close, CC escape=CC('\\')) - { - size_t b = find(open_close); - if(b == npos) return basic_substring(); - for(size_t i = b+1; i < len; ++i) - { - CC c = str[i]; - if(c == open_close) - { - if(str[i-1] != escape) - { - return range(b, i+1); - } - } - } - return basic_substring(); - } - - /** get the range delimited by an open-close pair of characters, - * with possibly nested occurrences. No checks for escapes are - * performed. */ - basic_substring pair_range_nested(CC open, CC close) const - { - size_t b = find(open); - if(b == npos) return basic_substring(); - size_t e, curr = b+1, count = 0; - const char both[] = {open, close, '\0'}; - while((e = first_of(both, curr)) != npos) - { - if(str[e] == open) - { - ++count; - curr = e+1; - } - else if(str[e] == close) - { - if(count == 0) return range(b, e+1); - --count; - curr = e+1; - } - } - return basic_substring(); - } - - basic_substring unquoted() const - { - constexpr const C dq('"'), sq('\''); - if(len >= 2 && (str[len - 2] != C('\\')) && - ((begins_with(sq) && ends_with(sq)) - || - (begins_with(dq) && ends_with(dq)))) - { - return range(1, len -1); - } - return *this; - } - - /** @} */ - -public: - - /** @name Number-matching query methods */ - /** @{ */ - - /** @return true if the substring contents are a floating-point or integer number. - * @note any leading or trailing whitespace will return false. */ - bool is_number() const - { - if(empty() || (first_non_empty_span().empty())) - return false; - if(first_uint_span() == *this) - return true; - if(first_int_span() == *this) - return true; - if(first_real_span() == *this) - return true; - return false; - } - - /** @return true if the substring contents are a real number. - * @note any leading or trailing whitespace will return false. */ - bool is_real() const - { - if(empty() || (first_non_empty_span().empty())) - return false; - if(first_real_span() == *this) - return true; - return false; - } - - /** @return true if the substring contents are an integer number. - * @note any leading or trailing whitespace will return false. */ - bool is_integer() const - { - if(empty() || (first_non_empty_span().empty())) - return false; - if(first_uint_span() == *this) - return true; - if(first_int_span() == *this) - return true; - return false; - } - - /** @return true if the substring contents are an unsigned integer number. - * @note any leading or trailing whitespace will return false. */ - bool is_unsigned_integer() const - { - if(empty() || (first_non_empty_span().empty())) - return false; - if(first_uint_span() == *this) - return true; - return false; - } - - /** get the first span consisting exclusively of non-empty characters */ - basic_substring first_non_empty_span() const - { - constexpr const ro_substr empty_chars(" \n\r\t"); - size_t pos = first_not_of(empty_chars); - if(pos == npos) - return first(0); - auto ret = sub(pos); - pos = ret.first_of(empty_chars); - return ret.first(pos); - } - - /** get the first span which can be interpreted as an unsigned integer */ - basic_substring first_uint_span() const - { - basic_substring ne = first_non_empty_span(); - if(ne.empty()) - return ne; - if(ne.str[0] == '-') - return first(0); - size_t skip_start = (ne.str[0] == '+') ? 1 : 0; - return ne._first_integral_span(skip_start); - } - - /** get the first span which can be interpreted as a signed integer */ - basic_substring first_int_span() const - { - basic_substring ne = first_non_empty_span(); - if(ne.empty()) - return ne; - size_t skip_start = (ne.str[0] == '+' || ne.str[0] == '-') ? 1 : 0; - return ne._first_integral_span(skip_start); - } - - basic_substring _first_integral_span(size_t skip_start) const - { - C4_ASSERT(!empty()); - if(skip_start == len) - return first(0); - C4_ASSERT(skip_start < len); - if(len >= skip_start + 3) - { - if(str[skip_start] != '0') - { - for(size_t i = skip_start; i < len; ++i) - { - char c = str[i]; - if(c < '0' || c > '9') - return i > skip_start && _is_delim_char(c) ? first(i) : first(0); - } - } - else - { - char next = str[skip_start + 1]; - if(next == 'x' || next == 'X') - { - skip_start += 2; - for(size_t i = skip_start; i < len; ++i) - { - const char c = str[i]; - if( ! _is_hex_char(c)) - return i > skip_start && _is_delim_char(c) ? first(i) : first(0); - } - return *this; - } - else if(next == 'b' || next == 'B') - { - skip_start += 2; - for(size_t i = skip_start; i < len; ++i) - { - const char c = str[i]; - if(c != '0' && c != '1') - return i > skip_start && _is_delim_char(c) ? first(i) : first(0); - } - return *this; - } - else if(next == 'o' || next == 'O') - { - skip_start += 2; - for(size_t i = skip_start; i < len; ++i) - { - const char c = str[i]; - if(c < '0' || c > '7') - return i > skip_start && _is_delim_char(c) ? first(i) : first(0); - } - return *this; - } - } - } - // must be a decimal, or it is not a an number - for(size_t i = skip_start; i < len; ++i) - { - const char c = str[i]; - if(c < '0' || c > '9') - return i > skip_start && _is_delim_char(c) ? first(i) : first(0); - } - return *this; - } - - /** get the first span which can be interpreted as a real (floating-point) number */ - basic_substring first_real_span() const - { - basic_substring ne = first_non_empty_span(); - if(ne.empty()) - return ne; - size_t skip_start = (ne.str[0] == '+' || ne.str[0] == '-'); - C4_ASSERT(skip_start == 0 || skip_start == 1); - // if we have at least three digits after the leading sign, it - // can be decimal, or hex, or bin or oct. Ex: - // non-decimal: 0x0, 0b0, 0o0 - // decimal: 1.0, 10., 1e1, 100, inf, nan, infinity - if(ne.len >= skip_start+3) - { - // if it does not have leading 0, it must be decimal, or it is not a real - if(ne.str[skip_start] != '0') - { - if(ne.str[skip_start] == 'i') // is it infinity or inf? - { - basic_substring word = ne._word_follows(skip_start + 1, "nfinity"); - if(word.len) - return word; - return ne._word_follows(skip_start + 1, "nf"); - } - else if(ne.str[skip_start] == 'n') // is it nan? - { - return ne._word_follows(skip_start + 1, "an"); - } - else // must be a decimal, or it is not a real - { - return ne._first_real_span_dec(skip_start); - } - } - else // starts with 0. is it 0x, 0b or 0o? - { - const char next = ne.str[skip_start + 1]; - // hexadecimal - if(next == 'x' || next == 'X') - return ne._first_real_span_hex(skip_start + 2); - // binary - else if(next == 'b' || next == 'B') - return ne._first_real_span_bin(skip_start + 2); - // octal - else if(next == 'o' || next == 'O') - return ne._first_real_span_oct(skip_start + 2); - // none of the above. may still be a decimal. - else - return ne._first_real_span_dec(skip_start); // do not skip the 0. - } - } - // less than 3 chars after the leading sign. It is either a - // decimal or it is not a real. (cannot be any of 0x0, etc). - return ne._first_real_span_dec(skip_start); - } - - /** true if the character is a delimiter character *at the end* */ - static constexpr C4_ALWAYS_INLINE C4_CONST bool _is_delim_char(char c) noexcept - { - return c == ' ' || c == '\n' - || c == ']' || c == ')' || c == '}' - || c == ',' || c == ';' || c == '\r' || c == '\t' || c == '\0'; - } - - /** true if the character is in [0-9a-fA-F] */ - static constexpr C4_ALWAYS_INLINE C4_CONST bool _is_hex_char(char c) noexcept - { - return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); - } - - C4_NO_INLINE C4_PURE basic_substring _word_follows(size_t pos, csubstr word) const noexcept - { - size_t posend = pos + word.len; - if(len >= posend && sub(pos, word.len) == word) - if(len == posend || _is_delim_char(str[posend])) - return first(posend); - return first(0); - } - - // this function is declared inside the class to avoid a VS error with __declspec(dllimport) - C4_NO_INLINE C4_PURE basic_substring _first_real_span_dec(size_t pos) const noexcept - { - bool intchars = false; - bool fracchars = false; - bool powchars; - // integral part - for( ; pos < len; ++pos) - { - const char c = str[pos]; - if(c >= '0' && c <= '9') - { - intchars = true; - } - else if(c == '.') - { - ++pos; - goto fractional_part_dec; - } - else if(c == 'e' || c == 'E') - { - ++pos; - goto power_part_dec; - } - else if(_is_delim_char(c)) - { - return intchars ? first(pos) : first(0); - } - else - { - return first(0); - } - } - // no . or p were found; this is either an integral number - // or not a number at all - return intchars ? - *this : - first(0); - fractional_part_dec: - C4_ASSERT(pos > 0); - C4_ASSERT(str[pos - 1] == '.'); - for( ; pos < len; ++pos) - { - const char c = str[pos]; - if(c >= '0' && c <= '9') - { - fracchars = true; - } - else if(c == 'e' || c == 'E') - { - ++pos; - goto power_part_dec; - } - else if(_is_delim_char(c)) - { - return intchars || fracchars ? first(pos) : first(0); - } - else - { - return first(0); - } - } - return intchars || fracchars ? - *this : - first(0); - power_part_dec: - C4_ASSERT(pos > 0); - C4_ASSERT(str[pos - 1] == 'e' || str[pos - 1] == 'E'); - // either a + or a - is expected here, followed by more chars. - // also, using (pos+1) in this check will cause an early - // return when no more chars follow the sign. - if(len <= (pos+1) || ((!intchars) && (!fracchars))) - return first(0); - ++pos; // this was the sign. - // ... so the (pos+1) ensures that we enter the loop and - // hence that there exist chars in the power part - powchars = false; - for( ; pos < len; ++pos) - { - const char c = str[pos]; - if(c >= '0' && c <= '9') - powchars = true; - else if(powchars && _is_delim_char(c)) - return first(pos); - else - return first(0); - } - return *this; - } - - // this function is declared inside the class to avoid a VS error with __declspec(dllimport) - C4_NO_INLINE C4_PURE basic_substring _first_real_span_hex(size_t pos) const noexcept - { - bool intchars = false; - bool fracchars = false; - bool powchars; - // integral part - for( ; pos < len; ++pos) - { - const char c = str[pos]; - if(_is_hex_char(c)) - { - intchars = true; - } - else if(c == '.') - { - ++pos; - goto fractional_part_hex; - } - else if(c == 'p' || c == 'P') - { - ++pos; - goto power_part_hex; - } - else if(_is_delim_char(c)) - { - return intchars ? first(pos) : first(0); - } - else - { - return first(0); - } - } - // no . or p were found; this is either an integral number - // or not a number at all - return intchars ? - *this : - first(0); - fractional_part_hex: - C4_ASSERT(pos > 0); - C4_ASSERT(str[pos - 1] == '.'); - for( ; pos < len; ++pos) - { - const char c = str[pos]; - if(_is_hex_char(c)) - { - fracchars = true; - } - else if(c == 'p' || c == 'P') - { - ++pos; - goto power_part_hex; - } - else if(_is_delim_char(c)) - { - return intchars || fracchars ? first(pos) : first(0); - } - else - { - return first(0); - } - } - return intchars || fracchars ? - *this : - first(0); - power_part_hex: - C4_ASSERT(pos > 0); - C4_ASSERT(str[pos - 1] == 'p' || str[pos - 1] == 'P'); - // either a + or a - is expected here, followed by more chars. - // also, using (pos+1) in this check will cause an early - // return when no more chars follow the sign. - if(len <= (pos+1) || (str[pos] != '+' && str[pos] != '-') || ((!intchars) && (!fracchars))) - return first(0); - ++pos; // this was the sign. - // ... so the (pos+1) ensures that we enter the loop and - // hence that there exist chars in the power part - powchars = false; - for( ; pos < len; ++pos) - { - const char c = str[pos]; - if(c >= '0' && c <= '9') - powchars = true; - else if(powchars && _is_delim_char(c)) - return first(pos); - else - return first(0); - } - return *this; - } - - // this function is declared inside the class to avoid a VS error with __declspec(dllimport) - C4_NO_INLINE C4_PURE basic_substring _first_real_span_bin(size_t pos) const noexcept - { - bool intchars = false; - bool fracchars = false; - bool powchars; - // integral part - for( ; pos < len; ++pos) - { - const char c = str[pos]; - if(c == '0' || c == '1') - { - intchars = true; - } - else if(c == '.') - { - ++pos; - goto fractional_part_bin; - } - else if(c == 'p' || c == 'P') - { - ++pos; - goto power_part_bin; - } - else if(_is_delim_char(c)) - { - return intchars ? first(pos) : first(0); - } - else - { - return first(0); - } - } - // no . or p were found; this is either an integral number - // or not a number at all - return intchars ? - *this : - first(0); - fractional_part_bin: - C4_ASSERT(pos > 0); - C4_ASSERT(str[pos - 1] == '.'); - for( ; pos < len; ++pos) - { - const char c = str[pos]; - if(c == '0' || c == '1') - { - fracchars = true; - } - else if(c == 'p' || c == 'P') - { - ++pos; - goto power_part_bin; - } - else if(_is_delim_char(c)) - { - return intchars || fracchars ? first(pos) : first(0); - } - else - { - return first(0); - } - } - return intchars || fracchars ? - *this : - first(0); - power_part_bin: - C4_ASSERT(pos > 0); - C4_ASSERT(str[pos - 1] == 'p' || str[pos - 1] == 'P'); - // either a + or a - is expected here, followed by more chars. - // also, using (pos+1) in this check will cause an early - // return when no more chars follow the sign. - if(len <= (pos+1) || (str[pos] != '+' && str[pos] != '-') || ((!intchars) && (!fracchars))) - return first(0); - ++pos; // this was the sign. - // ... so the (pos+1) ensures that we enter the loop and - // hence that there exist chars in the power part - powchars = false; - for( ; pos < len; ++pos) - { - const char c = str[pos]; - if(c >= '0' && c <= '9') - powchars = true; - else if(powchars && _is_delim_char(c)) - return first(pos); - else - return first(0); - } - return *this; - } - - // this function is declared inside the class to avoid a VS error with __declspec(dllimport) - C4_NO_INLINE C4_PURE basic_substring _first_real_span_oct(size_t pos) const noexcept - { - bool intchars = false; - bool fracchars = false; - bool powchars; - // integral part - for( ; pos < len; ++pos) - { - const char c = str[pos]; - if(c >= '0' && c <= '7') - { - intchars = true; - } - else if(c == '.') - { - ++pos; - goto fractional_part_oct; - } - else if(c == 'p' || c == 'P') - { - ++pos; - goto power_part_oct; - } - else if(_is_delim_char(c)) - { - return intchars ? first(pos) : first(0); - } - else - { - return first(0); - } - } - // no . or p were found; this is either an integral number - // or not a number at all - return intchars ? - *this : - first(0); - fractional_part_oct: - C4_ASSERT(pos > 0); - C4_ASSERT(str[pos - 1] == '.'); - for( ; pos < len; ++pos) - { - const char c = str[pos]; - if(c >= '0' && c <= '7') - { - fracchars = true; - } - else if(c == 'p' || c == 'P') - { - ++pos; - goto power_part_oct; - } - else if(_is_delim_char(c)) - { - return intchars || fracchars ? first(pos) : first(0); - } - else - { - return first(0); - } - } - return intchars || fracchars ? - *this : - first(0); - power_part_oct: - C4_ASSERT(pos > 0); - C4_ASSERT(str[pos - 1] == 'p' || str[pos - 1] == 'P'); - // either a + or a - is expected here, followed by more chars. - // also, using (pos+1) in this check will cause an early - // return when no more chars follow the sign. - if(len <= (pos+1) || (str[pos] != '+' && str[pos] != '-') || ((!intchars) && (!fracchars))) - return first(0); - ++pos; // this was the sign. - // ... so the (pos+1) ensures that we enter the loop and - // hence that there exist chars in the power part - powchars = false; - for( ; pos < len; ++pos) - { - const char c = str[pos]; - if(c >= '0' && c <= '9') - powchars = true; - else if(powchars && _is_delim_char(c)) - return first(pos); - else - return first(0); - } - return *this; - } - - /** @} */ - -public: - - /** @name Splitting methods */ - /** @{ */ - - /** returns true if the string has not been exhausted yet, meaning - * it's ok to call next_split() again. When no instance of sep - * exists in the string, returns the full string. When the input - * is an empty string, the output string is the empty string. */ - bool next_split(C sep, size_t *C4_RESTRICT start_pos, basic_substring *C4_RESTRICT out) const - { - if(C4_LIKELY(*start_pos < len)) - { - for(size_t i = *start_pos, e = len; i < e; i++) - { - if(str[i] == sep) - { - out->assign(str + *start_pos, i - *start_pos); - *start_pos = i+1; - return true; - } - } - out->assign(str + *start_pos, len - *start_pos); - *start_pos = len + 1; - return true; - } - else - { - bool valid = len > 0 && (*start_pos == len); - if(valid && !empty() && str[len-1] == sep) - { - out->assign(str + len, (size_t)0); // the cast is needed to prevent overload ambiguity - } - else - { - out->assign(str + len + 1, (size_t)0); // the cast is needed to prevent overload ambiguity - } - *start_pos = len + 1; - return valid; - } - } - -private: - - struct split_proxy_impl - { - struct split_iterator_impl - { - split_proxy_impl const* m_proxy; - basic_substring m_str; - size_t m_pos; - NCC_ m_sep; - - split_iterator_impl(split_proxy_impl const* proxy, size_t pos, C sep) - : m_proxy(proxy), m_pos(pos), m_sep(sep) - { - _tick(); - } - - void _tick() - { - m_proxy->m_str.next_split(m_sep, &m_pos, &m_str); - } - - split_iterator_impl& operator++ () { _tick(); return *this; } - split_iterator_impl operator++ (int) { split_iterator_impl it = *this; _tick(); return it; } - - basic_substring& operator* () { return m_str; } - basic_substring* operator-> () { return &m_str; } - - bool operator!= (split_iterator_impl const& that) const - { - return !(this->operator==(that)); - } - bool operator== (split_iterator_impl const& that) const - { - C4_XASSERT((m_sep == that.m_sep) && "cannot compare split iterators with different separators"); - if(m_str.size() != that.m_str.size()) - return false; - if(m_str.data() != that.m_str.data()) - return false; - return m_pos == that.m_pos; - } - }; - - basic_substring m_str; - size_t m_start_pos; - C m_sep; - - split_proxy_impl(basic_substring str_, size_t start_pos, C sep) - : m_str(str_), m_start_pos(start_pos), m_sep(sep) - { - } - - split_iterator_impl begin() const - { - auto it = split_iterator_impl(this, m_start_pos, m_sep); - return it; - } - split_iterator_impl end() const - { - size_t pos = m_str.size() + 1; - auto it = split_iterator_impl(this, pos, m_sep); - return it; - } - }; - -public: - - using split_proxy = split_proxy_impl; - - /** a view into the splits */ - split_proxy split(C sep, size_t start_pos=0) const - { - C4_XASSERT((start_pos >= 0 && start_pos < len) || empty()); - auto ss = sub(0, len); - auto it = split_proxy(ss, start_pos, sep); - return it; - } - -public: - - /** pop right: return the first split from the right. Use - * gpop_left() to get the reciprocal part. - */ - basic_substring pop_right(C sep=C('/'), bool skip_empty=false) const - { - if(C4_LIKELY(len > 1)) - { - auto pos = last_of(sep); - if(pos != npos) - { - if(pos + 1 < len) // does not end with sep - { - return sub(pos + 1); // return from sep to end - } - else // the string ends with sep - { - if( ! skip_empty) - { - return sub(pos + 1, 0); - } - auto ppos = last_not_of(sep); // skip repeated seps - if(ppos == npos) // the string is all made of seps - { - return sub(0, 0); - } - // find the previous sep - auto pos0 = last_of(sep, ppos); - if(pos0 == npos) // only the last sep exists - { - return sub(0); // return the full string (because skip_empty is true) - } - ++pos0; - return sub(pos0); - } - } - else // no sep was found, return the full string - { - return *this; - } - } - else if(len == 1) - { - if(begins_with(sep)) - { - return sub(0, 0); - } - return *this; - } - else // an empty string - { - return basic_substring(); - } - } - - /** return the first split from the left. Use gpop_right() to get - * the reciprocal part. */ - basic_substring pop_left(C sep = C('/'), bool skip_empty=false) const - { - if(C4_LIKELY(len > 1)) - { - auto pos = first_of(sep); - if(pos != npos) - { - if(pos > 0) // does not start with sep - { - return sub(0, pos); // return everything up to it - } - else // the string starts with sep - { - if( ! skip_empty) - { - return sub(0, 0); - } - auto ppos = first_not_of(sep); // skip repeated seps - if(ppos == npos) // the string is all made of seps - { - return sub(0, 0); - } - // find the next sep - auto pos0 = first_of(sep, ppos); - if(pos0 == npos) // only the first sep exists - { - return sub(0); // return the full string (because skip_empty is true) - } - C4_XASSERT(pos0 > 0); - // return everything up to the second sep - return sub(0, pos0); - } - } - else // no sep was found, return the full string - { - return sub(0); - } - } - else if(len == 1) - { - if(begins_with(sep)) - { - return sub(0, 0); - } - return sub(0); - } - else // an empty string - { - return basic_substring(); - } - } - -public: - - /** greedy pop left. eg, csubstr("a/b/c").gpop_left('/')="c" */ - basic_substring gpop_left(C sep = C('/'), bool skip_empty=false) const - { - auto ss = pop_right(sep, skip_empty); - ss = left_of(ss); - if(ss.find(sep) != npos) - { - if(ss.ends_with(sep)) - { - if(skip_empty) - { - ss = ss.trimr(sep); - } - else - { - ss = ss.sub(0, ss.len-1); // safe to subtract because ends_with(sep) is true - } - } - } - return ss; - } - - /** greedy pop right. eg, csubstr("a/b/c").gpop_right('/')="a" */ - basic_substring gpop_right(C sep = C('/'), bool skip_empty=false) const - { - auto ss = pop_left(sep, skip_empty); - ss = right_of(ss); - if(ss.find(sep) != npos) - { - if(ss.begins_with(sep)) - { - if(skip_empty) - { - ss = ss.triml(sep); - } - else - { - ss = ss.sub(1); - } - } - } - return ss; - } - - /** @} */ - -public: - - /** @name Path-like manipulation methods */ - /** @{ */ - - basic_substring basename(C sep=C('/')) const - { - auto ss = pop_right(sep, /*skip_empty*/true); - ss = ss.trimr(sep); - return ss; - } - - basic_substring dirname(C sep=C('/')) const - { - auto ss = basename(sep); - ss = ss.empty() ? *this : left_of(ss); - return ss; - } - - C4_ALWAYS_INLINE basic_substring name_wo_extshort() const - { - return gpop_left('.'); - } - - C4_ALWAYS_INLINE basic_substring name_wo_extlong() const - { - return pop_left('.'); - } - - C4_ALWAYS_INLINE basic_substring extshort() const - { - return pop_right('.'); - } - - C4_ALWAYS_INLINE basic_substring extlong() const - { - return gpop_right('.'); - } - - /** @} */ - -public: - - /** @name Content-modification methods (only for non-const C) */ - /** @{ */ - - /** convert the string to upper-case - * @note this method requires that the string memory is writeable and is SFINAEd out for const C */ - C4_REQUIRE_RW(void) toupper() - { - for(size_t i = 0; i < len; ++i) - { - str[i] = static_cast(::toupper(str[i])); - } - } - - /** convert the string to lower-case - * @note this method requires that the string memory is writeable and is SFINAEd out for const C */ - C4_REQUIRE_RW(void) tolower() - { - for(size_t i = 0; i < len; ++i) - { - str[i] = static_cast(::tolower(str[i])); - } - } - -public: - - /** fill the entire contents with the given @p val - * @note this method requires that the string memory is writeable and is SFINAEd out for const C */ - C4_REQUIRE_RW(void) fill(C val) - { - for(size_t i = 0; i < len; ++i) - { - str[i] = val; - } - } - -public: - - /** set the current substring to a copy of the given csubstr - * @note this method requires that the string memory is writeable and is SFINAEd out for const C */ - C4_REQUIRE_RW(void) copy_from(ro_substr that, size_t ifirst=0, size_t num=npos) - { - C4_ASSERT(ifirst >= 0 && ifirst <= len); - num = num != npos ? num : len - ifirst; - num = num < that.len ? num : that.len; - C4_ASSERT(ifirst + num >= 0 && ifirst + num <= len); - // calling memcpy with null strings is undefined behavior - // and will wreak havoc in calling code's branches. - // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637 - if(num) - memcpy(str + sizeof(C) * ifirst, that.str, sizeof(C) * num); - } - -public: - - /** reverse in place - * @note this method requires that the string memory is writeable and is SFINAEd out for const C */ - C4_REQUIRE_RW(void) reverse() - { - if(len == 0) return; - detail::_do_reverse(str, str + len - 1); - } - - /** revert a subpart in place - * @note this method requires that the string memory is writeable and is SFINAEd out for const C */ - C4_REQUIRE_RW(void) reverse_sub(size_t ifirst, size_t num) - { - C4_ASSERT(ifirst >= 0 && ifirst <= len); - C4_ASSERT(ifirst + num >= 0 && ifirst + num <= len); - if(num == 0) return; - detail::_do_reverse(str + ifirst, str + ifirst + num - 1); - } - - /** revert a range in place - * @note this method requires that the string memory is writeable and is SFINAEd out for const C */ - C4_REQUIRE_RW(void) reverse_range(size_t ifirst, size_t ilast) - { - C4_ASSERT(ifirst >= 0 && ifirst <= len); - C4_ASSERT(ilast >= 0 && ilast <= len); - if(ifirst == ilast) return; - detail::_do_reverse(str + ifirst, str + ilast - 1); - } - -public: - - /** erase part of the string. eg, with char s[] = "0123456789", - * substr(s).erase(3, 2) = "01256789", and s is now "01245678989" - * @note this method requires that the string memory is writeable and is SFINAEd out for const C */ - C4_REQUIRE_RW(basic_substring) erase(size_t pos, size_t num) - { - C4_ASSERT(pos >= 0 && pos+num <= len); - size_t num_to_move = len - pos - num; - memmove(str + pos, str + pos + num, sizeof(C) * num_to_move); - return basic_substring{str, len - num}; - } - - /** @note this method requires that the string memory is writeable and is SFINAEd out for const C */ - C4_REQUIRE_RW(basic_substring) erase_range(size_t first, size_t last) - { - C4_ASSERT(first <= last); - return erase(first, static_cast(last-first)); - } - - /** erase a part of the string. - * @note @p sub must be a substring of this string - * @note this method requires that the string memory is writeable and is SFINAEd out for const C */ - C4_REQUIRE_RW(basic_substring) erase(ro_substr sub) - { - C4_ASSERT(is_super(sub)); - C4_ASSERT(sub.str >= str); - return erase(static_cast(sub.str - str), sub.len); - } - -public: - - /** replace every occurrence of character @p value with the character @p repl - * @return the number of characters that were replaced - * @note this method requires that the string memory is writeable and is SFINAEd out for const C */ - C4_REQUIRE_RW(size_t) replace(C value, C repl, size_t pos=0) - { - C4_ASSERT((pos >= 0 && pos <= len) || pos == npos); - size_t did_it = 0; - while((pos = find(value, pos)) != npos) - { - str[pos++] = repl; - ++did_it; - } - return did_it; - } - - /** replace every occurrence of each character in @p value with - * the character @p repl. - * @return the number of characters that were replaced - * @note this method requires that the string memory is writeable and is SFINAEd out for const C */ - C4_REQUIRE_RW(size_t) replace(ro_substr chars, C repl, size_t pos=0) - { - C4_ASSERT((pos >= 0 && pos <= len) || pos == npos); - size_t did_it = 0; - while((pos = first_of(chars, pos)) != npos) - { - str[pos++] = repl; - ++did_it; - } - return did_it; - } - - /** replace @p pattern with @p repl, and write the result into - * @dst. pattern and repl don't need equal sizes. - * - * @return the required size for dst. No overflow occurs if - * dst.len is smaller than the required size; this can be used to - * determine the required size for an existing container. */ - size_t replace_all(rw_substr dst, ro_substr pattern, ro_substr repl, size_t pos=0) const - { - C4_ASSERT( ! pattern.empty()); //!< @todo relax this precondition - C4_ASSERT( ! this ->overlaps(dst)); //!< @todo relax this precondition - C4_ASSERT( ! pattern.overlaps(dst)); - C4_ASSERT( ! repl .overlaps(dst)); - C4_ASSERT((pos >= 0 && pos <= len) || pos == npos); - C4_SUPPRESS_WARNING_GCC_PUSH - C4_SUPPRESS_WARNING_GCC("-Warray-bounds") // gcc11 has a false positive here - #if (!defined(__clang__)) && (defined(__GNUC__) && (__GNUC__ >= 7)) - C4_SUPPRESS_WARNING_GCC("-Wstringop-overflow") // gcc11 has a false positive here - #endif - #define _c4append(first, last) \ - { \ - C4_ASSERT((last) >= (first)); \ - size_t num = static_cast((last) - (first)); \ - if(num > 0 && sz + num <= dst.len) \ - { \ - memcpy(dst.str + sz, first, num * sizeof(C)); \ - } \ - sz += num; \ - } - size_t sz = 0; - size_t b = pos; - _c4append(str, str + pos); - do { - size_t e = find(pattern, b); - if(e == npos) - { - _c4append(str + b, str + len); - break; - } - _c4append(str + b, str + e); - _c4append(repl.begin(), repl.end()); - b = e + pattern.size(); - } while(b < len && b != npos); - return sz; - #undef _c4append - C4_SUPPRESS_WARNING_GCC_POP - } - - /** @} */ - -}; // template class basic_substring - - -#undef C4_REQUIRE_RW -#undef C4_REQUIRE_RO -#undef C4_NC2C - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -/** Because of a C++ limitation, substr cannot provide simultaneous - * overloads for constructing from a char[N] and a char*; the latter - * will always be chosen by the compiler. So this specialization is - * provided to simplify obtaining a substr from a char*. Being a - * function has the advantage of highlighting the strlen() cost. - * - * @see to_csubstr - * @see For a more detailed explanation on why the overloads cannot - * coexist, see http://cplusplus.bordoon.com/specializeForCharacterArrays.html */ -inline substr to_substr(char *s) -{ - return substr(s, s ? strlen(s) : 0); -} - -/** Because of a C++ limitation, substr cannot provide simultaneous - * overloads for constructing from a char[N] and a char*; the latter - * will always be chosen by the compiler. So this specialization is - * provided to simplify obtaining a substr from a char*. Being a - * function has the advantage of highlighting the strlen() cost. - * - * @see to_substr - * @see For a more detailed explanation on why the overloads cannot - * coexist, see http://cplusplus.bordoon.com/specializeForCharacterArrays.html */ -inline csubstr to_csubstr(char *s) -{ - return csubstr(s, s ? strlen(s) : 0); -} - -/** Because of a C++ limitation, substr cannot provide simultaneous - * overloads for constructing from a const char[N] and a const char*; - * the latter will always be chosen by the compiler. So this - * specialization is provided to simplify obtaining a substr from a - * char*. Being a function has the advantage of highlighting the - * strlen() cost. - * - * @overload to_csubstr - * @see to_substr - * @see For a more detailed explanation on why the overloads cannot - * coexist, see http://cplusplus.bordoon.com/specializeForCharacterArrays.html */ -inline csubstr to_csubstr(const char *s) -{ - return csubstr(s, s ? strlen(s) : 0); -} - - -/** neutral version for use in generic code */ -inline csubstr to_csubstr(csubstr s) -{ - return s; -} - -/** neutral version for use in generic code */ -inline csubstr to_csubstr(substr s) -{ - return s; -} - -/** neutral version for use in generic code */ -inline substr to_substr(substr s) -{ - return s; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -template inline bool operator== (const C (&s)[N], basic_substring const that) { return that.compare(s) == 0; } -template inline bool operator!= (const C (&s)[N], basic_substring const that) { return that.compare(s) != 0; } -template inline bool operator< (const C (&s)[N], basic_substring const that) { return that.compare(s) > 0; } -template inline bool operator> (const C (&s)[N], basic_substring const that) { return that.compare(s) < 0; } -template inline bool operator<= (const C (&s)[N], basic_substring const that) { return that.compare(s) >= 0; } -template inline bool operator>= (const C (&s)[N], basic_substring const that) { return that.compare(s) <= 0; } - -template inline bool operator== (C const c, basic_substring const that) { return that.compare(c) == 0; } -template inline bool operator!= (C const c, basic_substring const that) { return that.compare(c) != 0; } -template inline bool operator< (C const c, basic_substring const that) { return that.compare(c) > 0; } -template inline bool operator> (C const c, basic_substring const that) { return that.compare(c) < 0; } -template inline bool operator<= (C const c, basic_substring const that) { return that.compare(c) >= 0; } -template inline bool operator>= (C const c, basic_substring const that) { return that.compare(c) <= 0; } - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -/** @define C4_SUBSTR_NO_OSTREAM_LSHIFT doctest does not deal well with - * template operator<< - * @see https://github.com/onqtam/doctest/pull/431 */ -#ifndef C4_SUBSTR_NO_OSTREAM_LSHIFT -#ifdef __clang__ -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wsign-conversion" -#elif defined(__GNUC__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wsign-conversion" -#endif - -/** output the string to a stream */ -template -inline OStream& operator<< (OStream& os, basic_substring s) -{ - os.write(s.str, s.len); - return os; -} - -// this causes ambiguity -///** this is used by google test */ -//template -//inline void PrintTo(basic_substring s, OStream* os) -//{ -// os->write(s.str, s.len); -//} - -#ifdef __clang__ -# pragma clang diagnostic pop -#elif defined(__GNUC__) -# pragma GCC diagnostic pop -#endif -#endif // !C4_SUBSTR_NO_OSTREAM_LSHIFT - -} // namespace c4 - - -#ifdef __clang__ -# pragma clang diagnostic pop -#elif defined(__GNUC__) -# pragma GCC diagnostic pop -#endif - -#endif /* _C4_SUBSTR_HPP_ */ - - -// (end https://github.com/biojppm/c4core/src/c4/substr.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/ext/fast_float.hpp -// https://github.com/biojppm/c4core/src/c4/ext/fast_float.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_EXT_FAST_FLOAT_HPP_ -#define _C4_EXT_FAST_FLOAT_HPP_ - -#ifdef _MSC_VER -# pragma warning(push) -# pragma warning(disable: 4996) // snprintf/scanf: this function or variable may be unsafe -#elif defined(__clang__) || defined(__APPLE_CC__) || defined(_LIBCPP_VERSION) -# pragma clang diagnostic push -# if (defined(__clang_major__) && _clang_major__ >= 9) || defined(__APPLE_CC__) -# pragma clang diagnostic ignored "-Wfortify-source" -# endif -# pragma clang diagnostic ignored "-Wshift-count-overflow" -#elif defined(__GNUC__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wuseless-cast" -#endif - -// fast_float by Daniel Lemire -// fast_float by João Paulo Magalhaes - - -// with contributions from Eugene Golushkov -// with contributions from Maksim Kita -// with contributions from Marcin Wojdyr -// with contributions from Neal Richardson -// with contributions from Tim Paine -// with contributions from Fabio Pellacini - - -// Permission is hereby granted, free of charge, to any -// person obtaining a copy of this software and associated -// documentation files (the "Software"), to deal in the -// Software without restriction, including without -// limitation the rights to use, copy, modify, merge, -// publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software -// is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice -// shall be included in all copies or substantial portions -// of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - - -#ifndef FASTFLOAT_FAST_FLOAT_H -#define FASTFLOAT_FAST_FLOAT_H - -#include - -namespace fast_float { -enum chars_format { - scientific = 1<<0, - fixed = 1<<2, - hex = 1<<3, - general = fixed | scientific -}; - - -struct from_chars_result { - const char *ptr; - std::errc ec; -}; - -struct parse_options { - constexpr explicit parse_options(chars_format fmt = chars_format::general, - char dot = '.') - : format(fmt), decimal_point(dot) {} - - /** Which number formats are accepted */ - chars_format format; - /** The character used as decimal point */ - char decimal_point; -}; - -/** - * This function parses the character sequence [first,last) for a number. It parses floating-point numbers expecting - * a locale-indepent format equivalent to what is used by std::strtod in the default ("C") locale. - * The resulting floating-point value is the closest floating-point values (using either float or double), - * using the "round to even" convention for values that would otherwise fall right in-between two values. - * That is, we provide exact parsing according to the IEEE standard. - * - * Given a successful parse, the pointer (`ptr`) in the returned value is set to point right after the - * parsed number, and the `value` referenced is set to the parsed value. In case of error, the returned - * `ec` contains a representative error, otherwise the default (`std::errc()`) value is stored. - * - * The implementation does not throw and does not allocate memory (e.g., with `new` or `malloc`). - * - * Like the C++17 standard, the `fast_float::from_chars` functions take an optional last argument of - * the type `fast_float::chars_format`. It is a bitset value: we check whether - * `fmt & fast_float::chars_format::fixed` and `fmt & fast_float::chars_format::scientific` are set - * to determine whether we allowe the fixed point and scientific notation respectively. - * The default is `fast_float::chars_format::general` which allows both `fixed` and `scientific`. - */ -template -from_chars_result from_chars(const char *first, const char *last, - T &value, chars_format fmt = chars_format::general) noexcept; - -/** - * Like from_chars, but accepts an `options` argument to govern number parsing. - */ -template -from_chars_result from_chars_advanced(const char *first, const char *last, - T &value, parse_options options) noexcept; - -} -#endif // FASTFLOAT_FAST_FLOAT_H - - -#ifndef FASTFLOAT_FLOAT_COMMON_H -#define FASTFLOAT_FLOAT_COMMON_H - -#include -//included above: -//#include -#include -//included above: -//#include - -#if (defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) \ - || defined(__amd64) || defined(__aarch64__) || defined(_M_ARM64) \ - || defined(__MINGW64__) \ - || defined(__s390x__) \ - || (defined(__ppc64__) || defined(__PPC64__) || defined(__ppc64le__) || defined(__PPC64LE__)) \ - || defined(__EMSCRIPTEN__)) -#define FASTFLOAT_64BIT -#elif (defined(__i386) || defined(__i386__) || defined(_M_IX86) \ - || defined(__arm__) || defined(_M_ARM) \ - || defined(__MINGW32__)) -#define FASTFLOAT_32BIT -#else - // Need to check incrementally, since SIZE_MAX is a size_t, avoid overflow. - // We can never tell the register width, but the SIZE_MAX is a good approximation. - // UINTPTR_MAX and INTPTR_MAX are optional, so avoid them for max portability. - #if SIZE_MAX == 0xffff - #error Unknown platform (16-bit, unsupported) - #elif SIZE_MAX == 0xffffffff - #define FASTFLOAT_32BIT - #elif SIZE_MAX == 0xffffffffffffffff - #define FASTFLOAT_64BIT - #else - #error Unknown platform (not 32-bit, not 64-bit?) - #endif -#endif - -#if ((defined(_WIN32) || defined(_WIN64)) && !defined(__clang__)) -//included above: -//#include -#endif - -#if defined(_MSC_VER) && !defined(__clang__) -#define FASTFLOAT_VISUAL_STUDIO 1 -#endif - -#ifdef _WIN32 -#define FASTFLOAT_IS_BIG_ENDIAN 0 -#else -#if defined(__APPLE__) || defined(__FreeBSD__) -#include -#elif defined(sun) || defined(__sun) -#include -#else -#include -#endif -# -#ifndef __BYTE_ORDER__ -// safe choice -#define FASTFLOAT_IS_BIG_ENDIAN 0 -#endif -# -#ifndef __ORDER_LITTLE_ENDIAN__ -// safe choice -#define FASTFLOAT_IS_BIG_ENDIAN 0 -#endif -# -#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ -#define FASTFLOAT_IS_BIG_ENDIAN 0 -#else -#define FASTFLOAT_IS_BIG_ENDIAN 1 -#endif -#endif - -#ifdef FASTFLOAT_VISUAL_STUDIO -#define fastfloat_really_inline __forceinline -#else -#define fastfloat_really_inline inline __attribute__((always_inline)) -#endif - -#ifndef FASTFLOAT_ASSERT -#define FASTFLOAT_ASSERT(x) { if (!(x)) abort(); } -#endif - -#ifndef FASTFLOAT_DEBUG_ASSERT -//included above: -//#include -#define FASTFLOAT_DEBUG_ASSERT(x) assert(x) -#endif - -// rust style `try!()` macro, or `?` operator -#define FASTFLOAT_TRY(x) { if (!(x)) return false; } - -namespace fast_float { - -// Compares two ASCII strings in a case insensitive manner. -inline bool fastfloat_strncasecmp(const char *input1, const char *input2, - size_t length) { - char running_diff{0}; - for (size_t i = 0; i < length; i++) { - running_diff |= (input1[i] ^ input2[i]); - } - return (running_diff == 0) || (running_diff == 32); -} - -#ifndef FLT_EVAL_METHOD -#error "FLT_EVAL_METHOD should be defined, please include cfloat." -#endif - -// a pointer and a length to a contiguous block of memory -template -struct span { - const T* ptr; - size_t length; - span(const T* _ptr, size_t _length) : ptr(_ptr), length(_length) {} - span() : ptr(nullptr), length(0) {} - - constexpr size_t len() const noexcept { - return length; - } - - const T& operator[](size_t index) const noexcept { - FASTFLOAT_DEBUG_ASSERT(index < length); - return ptr[index]; - } -}; - -struct value128 { - uint64_t low; - uint64_t high; - value128(uint64_t _low, uint64_t _high) : low(_low), high(_high) {} - value128() : low(0), high(0) {} -}; - -/* result might be undefined when input_num is zero */ -fastfloat_really_inline int leading_zeroes(uint64_t input_num) { - assert(input_num > 0); -#ifdef FASTFLOAT_VISUAL_STUDIO - #if defined(_M_X64) || defined(_M_ARM64) - unsigned long leading_zero = 0; - // Search the mask data from most significant bit (MSB) - // to least significant bit (LSB) for a set bit (1). - _BitScanReverse64(&leading_zero, input_num); - return (int)(63 - leading_zero); - #else - int last_bit = 0; - if(input_num & uint64_t(0xffffffff00000000)) input_num >>= 32, last_bit |= 32; - if(input_num & uint64_t( 0xffff0000)) input_num >>= 16, last_bit |= 16; - if(input_num & uint64_t( 0xff00)) input_num >>= 8, last_bit |= 8; - if(input_num & uint64_t( 0xf0)) input_num >>= 4, last_bit |= 4; - if(input_num & uint64_t( 0xc)) input_num >>= 2, last_bit |= 2; - if(input_num & uint64_t( 0x2)) input_num >>= 1, last_bit |= 1; - return 63 - last_bit; - #endif -#else - return __builtin_clzll(input_num); -#endif -} - -#ifdef FASTFLOAT_32BIT - -// slow emulation routine for 32-bit -fastfloat_really_inline uint64_t emulu(uint32_t x, uint32_t y) { - return x * (uint64_t)y; -} - -// slow emulation routine for 32-bit -#if !defined(__MINGW64__) -fastfloat_really_inline uint64_t _umul128(uint64_t ab, uint64_t cd, - uint64_t *hi) { - uint64_t ad = emulu((uint32_t)(ab >> 32), (uint32_t)cd); - uint64_t bd = emulu((uint32_t)ab, (uint32_t)cd); - uint64_t adbc = ad + emulu((uint32_t)ab, (uint32_t)(cd >> 32)); - uint64_t adbc_carry = !!(adbc < ad); - uint64_t lo = bd + (adbc << 32); - *hi = emulu((uint32_t)(ab >> 32), (uint32_t)(cd >> 32)) + (adbc >> 32) + - (adbc_carry << 32) + !!(lo < bd); - return lo; -} -#endif // !__MINGW64__ - -#endif // FASTFLOAT_32BIT - - -// compute 64-bit a*b -fastfloat_really_inline value128 full_multiplication(uint64_t a, - uint64_t b) { - value128 answer; -#ifdef _M_ARM64 - // ARM64 has native support for 64-bit multiplications, no need to emulate - answer.high = __umulh(a, b); - answer.low = a * b; -#elif defined(FASTFLOAT_32BIT) || (defined(_WIN64) && !defined(__clang__)) - answer.low = _umul128(a, b, &answer.high); // _umul128 not available on ARM64 -#elif defined(FASTFLOAT_64BIT) - __uint128_t r = ((__uint128_t)a) * b; - answer.low = uint64_t(r); - answer.high = uint64_t(r >> 64); -#else - #error Not implemented -#endif - return answer; -} - -struct adjusted_mantissa { - uint64_t mantissa{0}; - int32_t power2{0}; // a negative value indicates an invalid result - adjusted_mantissa() = default; - bool operator==(const adjusted_mantissa &o) const { - return mantissa == o.mantissa && power2 == o.power2; - } - bool operator!=(const adjusted_mantissa &o) const { - return mantissa != o.mantissa || power2 != o.power2; - } -}; - -// Bias so we can get the real exponent with an invalid adjusted_mantissa. -constexpr static int32_t invalid_am_bias = -0x8000; - -constexpr static double powers_of_ten_double[] = { - 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, - 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22}; -constexpr static float powers_of_ten_float[] = {1e0, 1e1, 1e2, 1e3, 1e4, 1e5, - 1e6, 1e7, 1e8, 1e9, 1e10}; - -template struct binary_format { - static inline constexpr int mantissa_explicit_bits(); - static inline constexpr int minimum_exponent(); - static inline constexpr int infinite_power(); - static inline constexpr int sign_index(); - static inline constexpr int min_exponent_fast_path(); - static inline constexpr int max_exponent_fast_path(); - static inline constexpr int max_exponent_round_to_even(); - static inline constexpr int min_exponent_round_to_even(); - static inline constexpr uint64_t max_mantissa_fast_path(); - static inline constexpr int largest_power_of_ten(); - static inline constexpr int smallest_power_of_ten(); - static inline constexpr T exact_power_of_ten(int64_t power); - static inline constexpr size_t max_digits(); -}; - -template <> inline constexpr int binary_format::mantissa_explicit_bits() { - return 52; -} -template <> inline constexpr int binary_format::mantissa_explicit_bits() { - return 23; -} - -template <> inline constexpr int binary_format::max_exponent_round_to_even() { - return 23; -} - -template <> inline constexpr int binary_format::max_exponent_round_to_even() { - return 10; -} - -template <> inline constexpr int binary_format::min_exponent_round_to_even() { - return -4; -} - -template <> inline constexpr int binary_format::min_exponent_round_to_even() { - return -17; -} - -template <> inline constexpr int binary_format::minimum_exponent() { - return -1023; -} -template <> inline constexpr int binary_format::minimum_exponent() { - return -127; -} - -template <> inline constexpr int binary_format::infinite_power() { - return 0x7FF; -} -template <> inline constexpr int binary_format::infinite_power() { - return 0xFF; -} - -template <> inline constexpr int binary_format::sign_index() { return 63; } -template <> inline constexpr int binary_format::sign_index() { return 31; } - -template <> inline constexpr int binary_format::min_exponent_fast_path() { -#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) - return 0; -#else - return -22; -#endif -} -template <> inline constexpr int binary_format::min_exponent_fast_path() { -#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) - return 0; -#else - return -10; -#endif -} - -template <> inline constexpr int binary_format::max_exponent_fast_path() { - return 22; -} -template <> inline constexpr int binary_format::max_exponent_fast_path() { - return 10; -} - -template <> inline constexpr uint64_t binary_format::max_mantissa_fast_path() { - return uint64_t(2) << mantissa_explicit_bits(); -} -template <> inline constexpr uint64_t binary_format::max_mantissa_fast_path() { - return uint64_t(2) << mantissa_explicit_bits(); -} - -template <> -inline constexpr double binary_format::exact_power_of_ten(int64_t power) { - return powers_of_ten_double[power]; -} -template <> -inline constexpr float binary_format::exact_power_of_ten(int64_t power) { - - return powers_of_ten_float[power]; -} - - -template <> -inline constexpr int binary_format::largest_power_of_ten() { - return 308; -} -template <> -inline constexpr int binary_format::largest_power_of_ten() { - return 38; -} - -template <> -inline constexpr int binary_format::smallest_power_of_ten() { - return -342; -} -template <> -inline constexpr int binary_format::smallest_power_of_ten() { - return -65; -} - -template <> inline constexpr size_t binary_format::max_digits() { - return 769; -} -template <> inline constexpr size_t binary_format::max_digits() { - return 114; -} - -template -fastfloat_really_inline void to_float(bool negative, adjusted_mantissa am, T &value) { - uint64_t word = am.mantissa; - word |= uint64_t(am.power2) << binary_format::mantissa_explicit_bits(); - word = negative - ? word | (uint64_t(1) << binary_format::sign_index()) : word; -#if FASTFLOAT_IS_BIG_ENDIAN == 1 - if (std::is_same::value) { - ::memcpy(&value, (char *)&word + 4, sizeof(T)); // extract value at offset 4-7 if float on big-endian - } else { - ::memcpy(&value, &word, sizeof(T)); - } -#else - // For little-endian systems: - ::memcpy(&value, &word, sizeof(T)); -#endif -} - -} // namespace fast_float - -#endif - - -#ifndef FASTFLOAT_ASCII_NUMBER_H -#define FASTFLOAT_ASCII_NUMBER_H - -//included above: -//#include -//included above: -//#include -//included above: -//#include -#include - - -namespace fast_float { - -// Next function can be micro-optimized, but compilers are entirely -// able to optimize it well. -fastfloat_really_inline bool is_integer(char c) noexcept { return c >= '0' && c <= '9'; } - -fastfloat_really_inline uint64_t byteswap(uint64_t val) { - return (val & 0xFF00000000000000) >> 56 - | (val & 0x00FF000000000000) >> 40 - | (val & 0x0000FF0000000000) >> 24 - | (val & 0x000000FF00000000) >> 8 - | (val & 0x00000000FF000000) << 8 - | (val & 0x0000000000FF0000) << 24 - | (val & 0x000000000000FF00) << 40 - | (val & 0x00000000000000FF) << 56; -} - -fastfloat_really_inline uint64_t read_u64(const char *chars) { - uint64_t val; - ::memcpy(&val, chars, sizeof(uint64_t)); -#if FASTFLOAT_IS_BIG_ENDIAN == 1 - // Need to read as-if the number was in little-endian order. - val = byteswap(val); -#endif - return val; -} - -fastfloat_really_inline void write_u64(uint8_t *chars, uint64_t val) { -#if FASTFLOAT_IS_BIG_ENDIAN == 1 - // Need to read as-if the number was in little-endian order. - val = byteswap(val); -#endif - ::memcpy(chars, &val, sizeof(uint64_t)); -} - -// credit @aqrit -fastfloat_really_inline uint32_t parse_eight_digits_unrolled(uint64_t val) { - const uint64_t mask = 0x000000FF000000FF; - const uint64_t mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32) - const uint64_t mul2 = 0x0000271000000001; // 1 + (10000ULL << 32) - val -= 0x3030303030303030; - val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8; - val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32; - return uint32_t(val); -} - -fastfloat_really_inline uint32_t parse_eight_digits_unrolled(const char *chars) noexcept { - return parse_eight_digits_unrolled(read_u64(chars)); -} - -// credit @aqrit -fastfloat_really_inline bool is_made_of_eight_digits_fast(uint64_t val) noexcept { - return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) & - 0x8080808080808080)); -} - -fastfloat_really_inline bool is_made_of_eight_digits_fast(const char *chars) noexcept { - return is_made_of_eight_digits_fast(read_u64(chars)); -} - -typedef span byte_span; - -struct parsed_number_string { - int64_t exponent{0}; - uint64_t mantissa{0}; - const char *lastmatch{nullptr}; - bool negative{false}; - bool valid{false}; - bool too_many_digits{false}; - // contains the range of the significant digits - byte_span integer{}; // non-nullable - byte_span fraction{}; // nullable -}; - -// Assuming that you use no more than 19 digits, this will -// parse an ASCII string. -fastfloat_really_inline -parsed_number_string parse_number_string(const char *p, const char *pend, parse_options options) noexcept { - const chars_format fmt = options.format; - const char decimal_point = options.decimal_point; - - parsed_number_string answer; - answer.valid = false; - answer.too_many_digits = false; - answer.negative = (*p == '-'); - if (*p == '-') { // C++17 20.19.3.(7.1) explicitly forbids '+' sign here - ++p; - if (p == pend) { - return answer; - } - if (!is_integer(*p) && (*p != decimal_point)) { // a sign must be followed by an integer or the dot - return answer; - } - } - const char *const start_digits = p; - - uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad) - - while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(p)) { - i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok - p += 8; - } - while ((p != pend) && is_integer(*p)) { - // a multiplication by 10 is cheaper than an arbitrary integer - // multiplication - i = 10 * i + - uint64_t(*p - '0'); // might overflow, we will handle the overflow later - ++p; - } - const char *const end_of_integer_part = p; - int64_t digit_count = int64_t(end_of_integer_part - start_digits); - answer.integer = byte_span(start_digits, size_t(digit_count)); - int64_t exponent = 0; - if ((p != pend) && (*p == decimal_point)) { - ++p; - const char* before = p; - // can occur at most twice without overflowing, but let it occur more, since - // for integers with many digits, digit parsing is the primary bottleneck. - while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(p)) { - i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok - p += 8; - } - while ((p != pend) && is_integer(*p)) { - uint8_t digit = uint8_t(*p - '0'); - ++p; - i = i * 10 + digit; // in rare cases, this will overflow, but that's ok - } - exponent = before - p; - answer.fraction = byte_span(before, size_t(p - before)); - digit_count -= exponent; - } - // we must have encountered at least one integer! - if (digit_count == 0) { - return answer; - } - int64_t exp_number = 0; // explicit exponential part - if ((fmt & chars_format::scientific) && (p != pend) && (('e' == *p) || ('E' == *p))) { - const char * location_of_e = p; - ++p; - bool neg_exp = false; - if ((p != pend) && ('-' == *p)) { - neg_exp = true; - ++p; - } else if ((p != pend) && ('+' == *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1) - ++p; - } - if ((p == pend) || !is_integer(*p)) { - if(!(fmt & chars_format::fixed)) { - // We are in error. - return answer; - } - // Otherwise, we will be ignoring the 'e'. - p = location_of_e; - } else { - while ((p != pend) && is_integer(*p)) { - uint8_t digit = uint8_t(*p - '0'); - if (exp_number < 0x10000000) { - exp_number = 10 * exp_number + digit; - } - ++p; - } - if(neg_exp) { exp_number = - exp_number; } - exponent += exp_number; - } - } else { - // If it scientific and not fixed, we have to bail out. - if((fmt & chars_format::scientific) && !(fmt & chars_format::fixed)) { return answer; } - } - answer.lastmatch = p; - answer.valid = true; - - // If we frequently had to deal with long strings of digits, - // we could extend our code by using a 128-bit integer instead - // of a 64-bit integer. However, this is uncommon. - // - // We can deal with up to 19 digits. - if (digit_count > 19) { // this is uncommon - // It is possible that the integer had an overflow. - // We have to handle the case where we have 0.0000somenumber. - // We need to be mindful of the case where we only have zeroes... - // E.g., 0.000000000...000. - const char *start = start_digits; - while ((start != pend) && (*start == '0' || *start == decimal_point)) { - if(*start == '0') { digit_count --; } - start++; - } - if (digit_count > 19) { - answer.too_many_digits = true; - // Let us start again, this time, avoiding overflows. - // We don't need to check if is_integer, since we use the - // pre-tokenized spans from above. - i = 0; - p = answer.integer.ptr; - const char* int_end = p + answer.integer.len(); - const uint64_t minimal_nineteen_digit_integer{1000000000000000000}; - while((i < minimal_nineteen_digit_integer) && (p != int_end)) { - i = i * 10 + uint64_t(*p - '0'); - ++p; - } - if (i >= minimal_nineteen_digit_integer) { // We have a big integers - exponent = end_of_integer_part - p + exp_number; - } else { // We have a value with a fractional component. - p = answer.fraction.ptr; - const char* frac_end = p + answer.fraction.len(); - while((i < minimal_nineteen_digit_integer) && (p != frac_end)) { - i = i * 10 + uint64_t(*p - '0'); - ++p; - } - exponent = answer.fraction.ptr - p + exp_number; - } - // We have now corrected both exponent and i, to a truncated value - } - } - answer.exponent = exponent; - answer.mantissa = i; - return answer; -} - -} // namespace fast_float - -#endif - - -#ifndef FASTFLOAT_FAST_TABLE_H -#define FASTFLOAT_FAST_TABLE_H - -//included above: -//#include - -namespace fast_float { - -/** - * When mapping numbers from decimal to binary, - * we go from w * 10^q to m * 2^p but we have - * 10^q = 5^q * 2^q, so effectively - * we are trying to match - * w * 2^q * 5^q to m * 2^p. Thus the powers of two - * are not a concern since they can be represented - * exactly using the binary notation, only the powers of five - * affect the binary significand. - */ - -/** - * The smallest non-zero float (binary64) is 2^−1074. - * We take as input numbers of the form w x 10^q where w < 2^64. - * We have that w * 10^-343 < 2^(64-344) 5^-343 < 2^-1076. - * However, we have that - * (2^64-1) * 10^-342 = (2^64-1) * 2^-342 * 5^-342 > 2^−1074. - * Thus it is possible for a number of the form w * 10^-342 where - * w is a 64-bit value to be a non-zero floating-point number. - ********* - * Any number of form w * 10^309 where w>= 1 is going to be - * infinite in binary64 so we never need to worry about powers - * of 5 greater than 308. - */ -template -struct powers_template { - -constexpr static int smallest_power_of_five = binary_format::smallest_power_of_ten(); -constexpr static int largest_power_of_five = binary_format::largest_power_of_ten(); -constexpr static int number_of_entries = 2 * (largest_power_of_five - smallest_power_of_five + 1); -// Powers of five from 5^-342 all the way to 5^308 rounded toward one. -static const uint64_t power_of_five_128[number_of_entries]; -}; - -template -const uint64_t powers_template::power_of_five_128[number_of_entries] = { - 0xeef453d6923bd65a,0x113faa2906a13b3f, - 0x9558b4661b6565f8,0x4ac7ca59a424c507, - 0xbaaee17fa23ebf76,0x5d79bcf00d2df649, - 0xe95a99df8ace6f53,0xf4d82c2c107973dc, - 0x91d8a02bb6c10594,0x79071b9b8a4be869, - 0xb64ec836a47146f9,0x9748e2826cdee284, - 0xe3e27a444d8d98b7,0xfd1b1b2308169b25, - 0x8e6d8c6ab0787f72,0xfe30f0f5e50e20f7, - 0xb208ef855c969f4f,0xbdbd2d335e51a935, - 0xde8b2b66b3bc4723,0xad2c788035e61382, - 0x8b16fb203055ac76,0x4c3bcb5021afcc31, - 0xaddcb9e83c6b1793,0xdf4abe242a1bbf3d, - 0xd953e8624b85dd78,0xd71d6dad34a2af0d, - 0x87d4713d6f33aa6b,0x8672648c40e5ad68, - 0xa9c98d8ccb009506,0x680efdaf511f18c2, - 0xd43bf0effdc0ba48,0x212bd1b2566def2, - 0x84a57695fe98746d,0x14bb630f7604b57, - 0xa5ced43b7e3e9188,0x419ea3bd35385e2d, - 0xcf42894a5dce35ea,0x52064cac828675b9, - 0x818995ce7aa0e1b2,0x7343efebd1940993, - 0xa1ebfb4219491a1f,0x1014ebe6c5f90bf8, - 0xca66fa129f9b60a6,0xd41a26e077774ef6, - 0xfd00b897478238d0,0x8920b098955522b4, - 0x9e20735e8cb16382,0x55b46e5f5d5535b0, - 0xc5a890362fddbc62,0xeb2189f734aa831d, - 0xf712b443bbd52b7b,0xa5e9ec7501d523e4, - 0x9a6bb0aa55653b2d,0x47b233c92125366e, - 0xc1069cd4eabe89f8,0x999ec0bb696e840a, - 0xf148440a256e2c76,0xc00670ea43ca250d, - 0x96cd2a865764dbca,0x380406926a5e5728, - 0xbc807527ed3e12bc,0xc605083704f5ecf2, - 0xeba09271e88d976b,0xf7864a44c633682e, - 0x93445b8731587ea3,0x7ab3ee6afbe0211d, - 0xb8157268fdae9e4c,0x5960ea05bad82964, - 0xe61acf033d1a45df,0x6fb92487298e33bd, - 0x8fd0c16206306bab,0xa5d3b6d479f8e056, - 0xb3c4f1ba87bc8696,0x8f48a4899877186c, - 0xe0b62e2929aba83c,0x331acdabfe94de87, - 0x8c71dcd9ba0b4925,0x9ff0c08b7f1d0b14, - 0xaf8e5410288e1b6f,0x7ecf0ae5ee44dd9, - 0xdb71e91432b1a24a,0xc9e82cd9f69d6150, - 0x892731ac9faf056e,0xbe311c083a225cd2, - 0xab70fe17c79ac6ca,0x6dbd630a48aaf406, - 0xd64d3d9db981787d,0x92cbbccdad5b108, - 0x85f0468293f0eb4e,0x25bbf56008c58ea5, - 0xa76c582338ed2621,0xaf2af2b80af6f24e, - 0xd1476e2c07286faa,0x1af5af660db4aee1, - 0x82cca4db847945ca,0x50d98d9fc890ed4d, - 0xa37fce126597973c,0xe50ff107bab528a0, - 0xcc5fc196fefd7d0c,0x1e53ed49a96272c8, - 0xff77b1fcbebcdc4f,0x25e8e89c13bb0f7a, - 0x9faacf3df73609b1,0x77b191618c54e9ac, - 0xc795830d75038c1d,0xd59df5b9ef6a2417, - 0xf97ae3d0d2446f25,0x4b0573286b44ad1d, - 0x9becce62836ac577,0x4ee367f9430aec32, - 0xc2e801fb244576d5,0x229c41f793cda73f, - 0xf3a20279ed56d48a,0x6b43527578c1110f, - 0x9845418c345644d6,0x830a13896b78aaa9, - 0xbe5691ef416bd60c,0x23cc986bc656d553, - 0xedec366b11c6cb8f,0x2cbfbe86b7ec8aa8, - 0x94b3a202eb1c3f39,0x7bf7d71432f3d6a9, - 0xb9e08a83a5e34f07,0xdaf5ccd93fb0cc53, - 0xe858ad248f5c22c9,0xd1b3400f8f9cff68, - 0x91376c36d99995be,0x23100809b9c21fa1, - 0xb58547448ffffb2d,0xabd40a0c2832a78a, - 0xe2e69915b3fff9f9,0x16c90c8f323f516c, - 0x8dd01fad907ffc3b,0xae3da7d97f6792e3, - 0xb1442798f49ffb4a,0x99cd11cfdf41779c, - 0xdd95317f31c7fa1d,0x40405643d711d583, - 0x8a7d3eef7f1cfc52,0x482835ea666b2572, - 0xad1c8eab5ee43b66,0xda3243650005eecf, - 0xd863b256369d4a40,0x90bed43e40076a82, - 0x873e4f75e2224e68,0x5a7744a6e804a291, - 0xa90de3535aaae202,0x711515d0a205cb36, - 0xd3515c2831559a83,0xd5a5b44ca873e03, - 0x8412d9991ed58091,0xe858790afe9486c2, - 0xa5178fff668ae0b6,0x626e974dbe39a872, - 0xce5d73ff402d98e3,0xfb0a3d212dc8128f, - 0x80fa687f881c7f8e,0x7ce66634bc9d0b99, - 0xa139029f6a239f72,0x1c1fffc1ebc44e80, - 0xc987434744ac874e,0xa327ffb266b56220, - 0xfbe9141915d7a922,0x4bf1ff9f0062baa8, - 0x9d71ac8fada6c9b5,0x6f773fc3603db4a9, - 0xc4ce17b399107c22,0xcb550fb4384d21d3, - 0xf6019da07f549b2b,0x7e2a53a146606a48, - 0x99c102844f94e0fb,0x2eda7444cbfc426d, - 0xc0314325637a1939,0xfa911155fefb5308, - 0xf03d93eebc589f88,0x793555ab7eba27ca, - 0x96267c7535b763b5,0x4bc1558b2f3458de, - 0xbbb01b9283253ca2,0x9eb1aaedfb016f16, - 0xea9c227723ee8bcb,0x465e15a979c1cadc, - 0x92a1958a7675175f,0xbfacd89ec191ec9, - 0xb749faed14125d36,0xcef980ec671f667b, - 0xe51c79a85916f484,0x82b7e12780e7401a, - 0x8f31cc0937ae58d2,0xd1b2ecb8b0908810, - 0xb2fe3f0b8599ef07,0x861fa7e6dcb4aa15, - 0xdfbdcece67006ac9,0x67a791e093e1d49a, - 0x8bd6a141006042bd,0xe0c8bb2c5c6d24e0, - 0xaecc49914078536d,0x58fae9f773886e18, - 0xda7f5bf590966848,0xaf39a475506a899e, - 0x888f99797a5e012d,0x6d8406c952429603, - 0xaab37fd7d8f58178,0xc8e5087ba6d33b83, - 0xd5605fcdcf32e1d6,0xfb1e4a9a90880a64, - 0x855c3be0a17fcd26,0x5cf2eea09a55067f, - 0xa6b34ad8c9dfc06f,0xf42faa48c0ea481e, - 0xd0601d8efc57b08b,0xf13b94daf124da26, - 0x823c12795db6ce57,0x76c53d08d6b70858, - 0xa2cb1717b52481ed,0x54768c4b0c64ca6e, - 0xcb7ddcdda26da268,0xa9942f5dcf7dfd09, - 0xfe5d54150b090b02,0xd3f93b35435d7c4c, - 0x9efa548d26e5a6e1,0xc47bc5014a1a6daf, - 0xc6b8e9b0709f109a,0x359ab6419ca1091b, - 0xf867241c8cc6d4c0,0xc30163d203c94b62, - 0x9b407691d7fc44f8,0x79e0de63425dcf1d, - 0xc21094364dfb5636,0x985915fc12f542e4, - 0xf294b943e17a2bc4,0x3e6f5b7b17b2939d, - 0x979cf3ca6cec5b5a,0xa705992ceecf9c42, - 0xbd8430bd08277231,0x50c6ff782a838353, - 0xece53cec4a314ebd,0xa4f8bf5635246428, - 0x940f4613ae5ed136,0x871b7795e136be99, - 0xb913179899f68584,0x28e2557b59846e3f, - 0xe757dd7ec07426e5,0x331aeada2fe589cf, - 0x9096ea6f3848984f,0x3ff0d2c85def7621, - 0xb4bca50b065abe63,0xfed077a756b53a9, - 0xe1ebce4dc7f16dfb,0xd3e8495912c62894, - 0x8d3360f09cf6e4bd,0x64712dd7abbbd95c, - 0xb080392cc4349dec,0xbd8d794d96aacfb3, - 0xdca04777f541c567,0xecf0d7a0fc5583a0, - 0x89e42caaf9491b60,0xf41686c49db57244, - 0xac5d37d5b79b6239,0x311c2875c522ced5, - 0xd77485cb25823ac7,0x7d633293366b828b, - 0x86a8d39ef77164bc,0xae5dff9c02033197, - 0xa8530886b54dbdeb,0xd9f57f830283fdfc, - 0xd267caa862a12d66,0xd072df63c324fd7b, - 0x8380dea93da4bc60,0x4247cb9e59f71e6d, - 0xa46116538d0deb78,0x52d9be85f074e608, - 0xcd795be870516656,0x67902e276c921f8b, - 0x806bd9714632dff6,0xba1cd8a3db53b6, - 0xa086cfcd97bf97f3,0x80e8a40eccd228a4, - 0xc8a883c0fdaf7df0,0x6122cd128006b2cd, - 0xfad2a4b13d1b5d6c,0x796b805720085f81, - 0x9cc3a6eec6311a63,0xcbe3303674053bb0, - 0xc3f490aa77bd60fc,0xbedbfc4411068a9c, - 0xf4f1b4d515acb93b,0xee92fb5515482d44, - 0x991711052d8bf3c5,0x751bdd152d4d1c4a, - 0xbf5cd54678eef0b6,0xd262d45a78a0635d, - 0xef340a98172aace4,0x86fb897116c87c34, - 0x9580869f0e7aac0e,0xd45d35e6ae3d4da0, - 0xbae0a846d2195712,0x8974836059cca109, - 0xe998d258869facd7,0x2bd1a438703fc94b, - 0x91ff83775423cc06,0x7b6306a34627ddcf, - 0xb67f6455292cbf08,0x1a3bc84c17b1d542, - 0xe41f3d6a7377eeca,0x20caba5f1d9e4a93, - 0x8e938662882af53e,0x547eb47b7282ee9c, - 0xb23867fb2a35b28d,0xe99e619a4f23aa43, - 0xdec681f9f4c31f31,0x6405fa00e2ec94d4, - 0x8b3c113c38f9f37e,0xde83bc408dd3dd04, - 0xae0b158b4738705e,0x9624ab50b148d445, - 0xd98ddaee19068c76,0x3badd624dd9b0957, - 0x87f8a8d4cfa417c9,0xe54ca5d70a80e5d6, - 0xa9f6d30a038d1dbc,0x5e9fcf4ccd211f4c, - 0xd47487cc8470652b,0x7647c3200069671f, - 0x84c8d4dfd2c63f3b,0x29ecd9f40041e073, - 0xa5fb0a17c777cf09,0xf468107100525890, - 0xcf79cc9db955c2cc,0x7182148d4066eeb4, - 0x81ac1fe293d599bf,0xc6f14cd848405530, - 0xa21727db38cb002f,0xb8ada00e5a506a7c, - 0xca9cf1d206fdc03b,0xa6d90811f0e4851c, - 0xfd442e4688bd304a,0x908f4a166d1da663, - 0x9e4a9cec15763e2e,0x9a598e4e043287fe, - 0xc5dd44271ad3cdba,0x40eff1e1853f29fd, - 0xf7549530e188c128,0xd12bee59e68ef47c, - 0x9a94dd3e8cf578b9,0x82bb74f8301958ce, - 0xc13a148e3032d6e7,0xe36a52363c1faf01, - 0xf18899b1bc3f8ca1,0xdc44e6c3cb279ac1, - 0x96f5600f15a7b7e5,0x29ab103a5ef8c0b9, - 0xbcb2b812db11a5de,0x7415d448f6b6f0e7, - 0xebdf661791d60f56,0x111b495b3464ad21, - 0x936b9fcebb25c995,0xcab10dd900beec34, - 0xb84687c269ef3bfb,0x3d5d514f40eea742, - 0xe65829b3046b0afa,0xcb4a5a3112a5112, - 0x8ff71a0fe2c2e6dc,0x47f0e785eaba72ab, - 0xb3f4e093db73a093,0x59ed216765690f56, - 0xe0f218b8d25088b8,0x306869c13ec3532c, - 0x8c974f7383725573,0x1e414218c73a13fb, - 0xafbd2350644eeacf,0xe5d1929ef90898fa, - 0xdbac6c247d62a583,0xdf45f746b74abf39, - 0x894bc396ce5da772,0x6b8bba8c328eb783, - 0xab9eb47c81f5114f,0x66ea92f3f326564, - 0xd686619ba27255a2,0xc80a537b0efefebd, - 0x8613fd0145877585,0xbd06742ce95f5f36, - 0xa798fc4196e952e7,0x2c48113823b73704, - 0xd17f3b51fca3a7a0,0xf75a15862ca504c5, - 0x82ef85133de648c4,0x9a984d73dbe722fb, - 0xa3ab66580d5fdaf5,0xc13e60d0d2e0ebba, - 0xcc963fee10b7d1b3,0x318df905079926a8, - 0xffbbcfe994e5c61f,0xfdf17746497f7052, - 0x9fd561f1fd0f9bd3,0xfeb6ea8bedefa633, - 0xc7caba6e7c5382c8,0xfe64a52ee96b8fc0, - 0xf9bd690a1b68637b,0x3dfdce7aa3c673b0, - 0x9c1661a651213e2d,0x6bea10ca65c084e, - 0xc31bfa0fe5698db8,0x486e494fcff30a62, - 0xf3e2f893dec3f126,0x5a89dba3c3efccfa, - 0x986ddb5c6b3a76b7,0xf89629465a75e01c, - 0xbe89523386091465,0xf6bbb397f1135823, - 0xee2ba6c0678b597f,0x746aa07ded582e2c, - 0x94db483840b717ef,0xa8c2a44eb4571cdc, - 0xba121a4650e4ddeb,0x92f34d62616ce413, - 0xe896a0d7e51e1566,0x77b020baf9c81d17, - 0x915e2486ef32cd60,0xace1474dc1d122e, - 0xb5b5ada8aaff80b8,0xd819992132456ba, - 0xe3231912d5bf60e6,0x10e1fff697ed6c69, - 0x8df5efabc5979c8f,0xca8d3ffa1ef463c1, - 0xb1736b96b6fd83b3,0xbd308ff8a6b17cb2, - 0xddd0467c64bce4a0,0xac7cb3f6d05ddbde, - 0x8aa22c0dbef60ee4,0x6bcdf07a423aa96b, - 0xad4ab7112eb3929d,0x86c16c98d2c953c6, - 0xd89d64d57a607744,0xe871c7bf077ba8b7, - 0x87625f056c7c4a8b,0x11471cd764ad4972, - 0xa93af6c6c79b5d2d,0xd598e40d3dd89bcf, - 0xd389b47879823479,0x4aff1d108d4ec2c3, - 0x843610cb4bf160cb,0xcedf722a585139ba, - 0xa54394fe1eedb8fe,0xc2974eb4ee658828, - 0xce947a3da6a9273e,0x733d226229feea32, - 0x811ccc668829b887,0x806357d5a3f525f, - 0xa163ff802a3426a8,0xca07c2dcb0cf26f7, - 0xc9bcff6034c13052,0xfc89b393dd02f0b5, - 0xfc2c3f3841f17c67,0xbbac2078d443ace2, - 0x9d9ba7832936edc0,0xd54b944b84aa4c0d, - 0xc5029163f384a931,0xa9e795e65d4df11, - 0xf64335bcf065d37d,0x4d4617b5ff4a16d5, - 0x99ea0196163fa42e,0x504bced1bf8e4e45, - 0xc06481fb9bcf8d39,0xe45ec2862f71e1d6, - 0xf07da27a82c37088,0x5d767327bb4e5a4c, - 0x964e858c91ba2655,0x3a6a07f8d510f86f, - 0xbbe226efb628afea,0x890489f70a55368b, - 0xeadab0aba3b2dbe5,0x2b45ac74ccea842e, - 0x92c8ae6b464fc96f,0x3b0b8bc90012929d, - 0xb77ada0617e3bbcb,0x9ce6ebb40173744, - 0xe55990879ddcaabd,0xcc420a6a101d0515, - 0x8f57fa54c2a9eab6,0x9fa946824a12232d, - 0xb32df8e9f3546564,0x47939822dc96abf9, - 0xdff9772470297ebd,0x59787e2b93bc56f7, - 0x8bfbea76c619ef36,0x57eb4edb3c55b65a, - 0xaefae51477a06b03,0xede622920b6b23f1, - 0xdab99e59958885c4,0xe95fab368e45eced, - 0x88b402f7fd75539b,0x11dbcb0218ebb414, - 0xaae103b5fcd2a881,0xd652bdc29f26a119, - 0xd59944a37c0752a2,0x4be76d3346f0495f, - 0x857fcae62d8493a5,0x6f70a4400c562ddb, - 0xa6dfbd9fb8e5b88e,0xcb4ccd500f6bb952, - 0xd097ad07a71f26b2,0x7e2000a41346a7a7, - 0x825ecc24c873782f,0x8ed400668c0c28c8, - 0xa2f67f2dfa90563b,0x728900802f0f32fa, - 0xcbb41ef979346bca,0x4f2b40a03ad2ffb9, - 0xfea126b7d78186bc,0xe2f610c84987bfa8, - 0x9f24b832e6b0f436,0xdd9ca7d2df4d7c9, - 0xc6ede63fa05d3143,0x91503d1c79720dbb, - 0xf8a95fcf88747d94,0x75a44c6397ce912a, - 0x9b69dbe1b548ce7c,0xc986afbe3ee11aba, - 0xc24452da229b021b,0xfbe85badce996168, - 0xf2d56790ab41c2a2,0xfae27299423fb9c3, - 0x97c560ba6b0919a5,0xdccd879fc967d41a, - 0xbdb6b8e905cb600f,0x5400e987bbc1c920, - 0xed246723473e3813,0x290123e9aab23b68, - 0x9436c0760c86e30b,0xf9a0b6720aaf6521, - 0xb94470938fa89bce,0xf808e40e8d5b3e69, - 0xe7958cb87392c2c2,0xb60b1d1230b20e04, - 0x90bd77f3483bb9b9,0xb1c6f22b5e6f48c2, - 0xb4ecd5f01a4aa828,0x1e38aeb6360b1af3, - 0xe2280b6c20dd5232,0x25c6da63c38de1b0, - 0x8d590723948a535f,0x579c487e5a38ad0e, - 0xb0af48ec79ace837,0x2d835a9df0c6d851, - 0xdcdb1b2798182244,0xf8e431456cf88e65, - 0x8a08f0f8bf0f156b,0x1b8e9ecb641b58ff, - 0xac8b2d36eed2dac5,0xe272467e3d222f3f, - 0xd7adf884aa879177,0x5b0ed81dcc6abb0f, - 0x86ccbb52ea94baea,0x98e947129fc2b4e9, - 0xa87fea27a539e9a5,0x3f2398d747b36224, - 0xd29fe4b18e88640e,0x8eec7f0d19a03aad, - 0x83a3eeeef9153e89,0x1953cf68300424ac, - 0xa48ceaaab75a8e2b,0x5fa8c3423c052dd7, - 0xcdb02555653131b6,0x3792f412cb06794d, - 0x808e17555f3ebf11,0xe2bbd88bbee40bd0, - 0xa0b19d2ab70e6ed6,0x5b6aceaeae9d0ec4, - 0xc8de047564d20a8b,0xf245825a5a445275, - 0xfb158592be068d2e,0xeed6e2f0f0d56712, - 0x9ced737bb6c4183d,0x55464dd69685606b, - 0xc428d05aa4751e4c,0xaa97e14c3c26b886, - 0xf53304714d9265df,0xd53dd99f4b3066a8, - 0x993fe2c6d07b7fab,0xe546a8038efe4029, - 0xbf8fdb78849a5f96,0xde98520472bdd033, - 0xef73d256a5c0f77c,0x963e66858f6d4440, - 0x95a8637627989aad,0xdde7001379a44aa8, - 0xbb127c53b17ec159,0x5560c018580d5d52, - 0xe9d71b689dde71af,0xaab8f01e6e10b4a6, - 0x9226712162ab070d,0xcab3961304ca70e8, - 0xb6b00d69bb55c8d1,0x3d607b97c5fd0d22, - 0xe45c10c42a2b3b05,0x8cb89a7db77c506a, - 0x8eb98a7a9a5b04e3,0x77f3608e92adb242, - 0xb267ed1940f1c61c,0x55f038b237591ed3, - 0xdf01e85f912e37a3,0x6b6c46dec52f6688, - 0x8b61313bbabce2c6,0x2323ac4b3b3da015, - 0xae397d8aa96c1b77,0xabec975e0a0d081a, - 0xd9c7dced53c72255,0x96e7bd358c904a21, - 0x881cea14545c7575,0x7e50d64177da2e54, - 0xaa242499697392d2,0xdde50bd1d5d0b9e9, - 0xd4ad2dbfc3d07787,0x955e4ec64b44e864, - 0x84ec3c97da624ab4,0xbd5af13bef0b113e, - 0xa6274bbdd0fadd61,0xecb1ad8aeacdd58e, - 0xcfb11ead453994ba,0x67de18eda5814af2, - 0x81ceb32c4b43fcf4,0x80eacf948770ced7, - 0xa2425ff75e14fc31,0xa1258379a94d028d, - 0xcad2f7f5359a3b3e,0x96ee45813a04330, - 0xfd87b5f28300ca0d,0x8bca9d6e188853fc, - 0x9e74d1b791e07e48,0x775ea264cf55347e, - 0xc612062576589dda,0x95364afe032a819e, - 0xf79687aed3eec551,0x3a83ddbd83f52205, - 0x9abe14cd44753b52,0xc4926a9672793543, - 0xc16d9a0095928a27,0x75b7053c0f178294, - 0xf1c90080baf72cb1,0x5324c68b12dd6339, - 0x971da05074da7bee,0xd3f6fc16ebca5e04, - 0xbce5086492111aea,0x88f4bb1ca6bcf585, - 0xec1e4a7db69561a5,0x2b31e9e3d06c32e6, - 0x9392ee8e921d5d07,0x3aff322e62439fd0, - 0xb877aa3236a4b449,0x9befeb9fad487c3, - 0xe69594bec44de15b,0x4c2ebe687989a9b4, - 0x901d7cf73ab0acd9,0xf9d37014bf60a11, - 0xb424dc35095cd80f,0x538484c19ef38c95, - 0xe12e13424bb40e13,0x2865a5f206b06fba, - 0x8cbccc096f5088cb,0xf93f87b7442e45d4, - 0xafebff0bcb24aafe,0xf78f69a51539d749, - 0xdbe6fecebdedd5be,0xb573440e5a884d1c, - 0x89705f4136b4a597,0x31680a88f8953031, - 0xabcc77118461cefc,0xfdc20d2b36ba7c3e, - 0xd6bf94d5e57a42bc,0x3d32907604691b4d, - 0x8637bd05af6c69b5,0xa63f9a49c2c1b110, - 0xa7c5ac471b478423,0xfcf80dc33721d54, - 0xd1b71758e219652b,0xd3c36113404ea4a9, - 0x83126e978d4fdf3b,0x645a1cac083126ea, - 0xa3d70a3d70a3d70a,0x3d70a3d70a3d70a4, - 0xcccccccccccccccc,0xcccccccccccccccd, - 0x8000000000000000,0x0, - 0xa000000000000000,0x0, - 0xc800000000000000,0x0, - 0xfa00000000000000,0x0, - 0x9c40000000000000,0x0, - 0xc350000000000000,0x0, - 0xf424000000000000,0x0, - 0x9896800000000000,0x0, - 0xbebc200000000000,0x0, - 0xee6b280000000000,0x0, - 0x9502f90000000000,0x0, - 0xba43b74000000000,0x0, - 0xe8d4a51000000000,0x0, - 0x9184e72a00000000,0x0, - 0xb5e620f480000000,0x0, - 0xe35fa931a0000000,0x0, - 0x8e1bc9bf04000000,0x0, - 0xb1a2bc2ec5000000,0x0, - 0xde0b6b3a76400000,0x0, - 0x8ac7230489e80000,0x0, - 0xad78ebc5ac620000,0x0, - 0xd8d726b7177a8000,0x0, - 0x878678326eac9000,0x0, - 0xa968163f0a57b400,0x0, - 0xd3c21bcecceda100,0x0, - 0x84595161401484a0,0x0, - 0xa56fa5b99019a5c8,0x0, - 0xcecb8f27f4200f3a,0x0, - 0x813f3978f8940984,0x4000000000000000, - 0xa18f07d736b90be5,0x5000000000000000, - 0xc9f2c9cd04674ede,0xa400000000000000, - 0xfc6f7c4045812296,0x4d00000000000000, - 0x9dc5ada82b70b59d,0xf020000000000000, - 0xc5371912364ce305,0x6c28000000000000, - 0xf684df56c3e01bc6,0xc732000000000000, - 0x9a130b963a6c115c,0x3c7f400000000000, - 0xc097ce7bc90715b3,0x4b9f100000000000, - 0xf0bdc21abb48db20,0x1e86d40000000000, - 0x96769950b50d88f4,0x1314448000000000, - 0xbc143fa4e250eb31,0x17d955a000000000, - 0xeb194f8e1ae525fd,0x5dcfab0800000000, - 0x92efd1b8d0cf37be,0x5aa1cae500000000, - 0xb7abc627050305ad,0xf14a3d9e40000000, - 0xe596b7b0c643c719,0x6d9ccd05d0000000, - 0x8f7e32ce7bea5c6f,0xe4820023a2000000, - 0xb35dbf821ae4f38b,0xdda2802c8a800000, - 0xe0352f62a19e306e,0xd50b2037ad200000, - 0x8c213d9da502de45,0x4526f422cc340000, - 0xaf298d050e4395d6,0x9670b12b7f410000, - 0xdaf3f04651d47b4c,0x3c0cdd765f114000, - 0x88d8762bf324cd0f,0xa5880a69fb6ac800, - 0xab0e93b6efee0053,0x8eea0d047a457a00, - 0xd5d238a4abe98068,0x72a4904598d6d880, - 0x85a36366eb71f041,0x47a6da2b7f864750, - 0xa70c3c40a64e6c51,0x999090b65f67d924, - 0xd0cf4b50cfe20765,0xfff4b4e3f741cf6d, - 0x82818f1281ed449f,0xbff8f10e7a8921a4, - 0xa321f2d7226895c7,0xaff72d52192b6a0d, - 0xcbea6f8ceb02bb39,0x9bf4f8a69f764490, - 0xfee50b7025c36a08,0x2f236d04753d5b4, - 0x9f4f2726179a2245,0x1d762422c946590, - 0xc722f0ef9d80aad6,0x424d3ad2b7b97ef5, - 0xf8ebad2b84e0d58b,0xd2e0898765a7deb2, - 0x9b934c3b330c8577,0x63cc55f49f88eb2f, - 0xc2781f49ffcfa6d5,0x3cbf6b71c76b25fb, - 0xf316271c7fc3908a,0x8bef464e3945ef7a, - 0x97edd871cfda3a56,0x97758bf0e3cbb5ac, - 0xbde94e8e43d0c8ec,0x3d52eeed1cbea317, - 0xed63a231d4c4fb27,0x4ca7aaa863ee4bdd, - 0x945e455f24fb1cf8,0x8fe8caa93e74ef6a, - 0xb975d6b6ee39e436,0xb3e2fd538e122b44, - 0xe7d34c64a9c85d44,0x60dbbca87196b616, - 0x90e40fbeea1d3a4a,0xbc8955e946fe31cd, - 0xb51d13aea4a488dd,0x6babab6398bdbe41, - 0xe264589a4dcdab14,0xc696963c7eed2dd1, - 0x8d7eb76070a08aec,0xfc1e1de5cf543ca2, - 0xb0de65388cc8ada8,0x3b25a55f43294bcb, - 0xdd15fe86affad912,0x49ef0eb713f39ebe, - 0x8a2dbf142dfcc7ab,0x6e3569326c784337, - 0xacb92ed9397bf996,0x49c2c37f07965404, - 0xd7e77a8f87daf7fb,0xdc33745ec97be906, - 0x86f0ac99b4e8dafd,0x69a028bb3ded71a3, - 0xa8acd7c0222311bc,0xc40832ea0d68ce0c, - 0xd2d80db02aabd62b,0xf50a3fa490c30190, - 0x83c7088e1aab65db,0x792667c6da79e0fa, - 0xa4b8cab1a1563f52,0x577001b891185938, - 0xcde6fd5e09abcf26,0xed4c0226b55e6f86, - 0x80b05e5ac60b6178,0x544f8158315b05b4, - 0xa0dc75f1778e39d6,0x696361ae3db1c721, - 0xc913936dd571c84c,0x3bc3a19cd1e38e9, - 0xfb5878494ace3a5f,0x4ab48a04065c723, - 0x9d174b2dcec0e47b,0x62eb0d64283f9c76, - 0xc45d1df942711d9a,0x3ba5d0bd324f8394, - 0xf5746577930d6500,0xca8f44ec7ee36479, - 0x9968bf6abbe85f20,0x7e998b13cf4e1ecb, - 0xbfc2ef456ae276e8,0x9e3fedd8c321a67e, - 0xefb3ab16c59b14a2,0xc5cfe94ef3ea101e, - 0x95d04aee3b80ece5,0xbba1f1d158724a12, - 0xbb445da9ca61281f,0x2a8a6e45ae8edc97, - 0xea1575143cf97226,0xf52d09d71a3293bd, - 0x924d692ca61be758,0x593c2626705f9c56, - 0xb6e0c377cfa2e12e,0x6f8b2fb00c77836c, - 0xe498f455c38b997a,0xb6dfb9c0f956447, - 0x8edf98b59a373fec,0x4724bd4189bd5eac, - 0xb2977ee300c50fe7,0x58edec91ec2cb657, - 0xdf3d5e9bc0f653e1,0x2f2967b66737e3ed, - 0x8b865b215899f46c,0xbd79e0d20082ee74, - 0xae67f1e9aec07187,0xecd8590680a3aa11, - 0xda01ee641a708de9,0xe80e6f4820cc9495, - 0x884134fe908658b2,0x3109058d147fdcdd, - 0xaa51823e34a7eede,0xbd4b46f0599fd415, - 0xd4e5e2cdc1d1ea96,0x6c9e18ac7007c91a, - 0x850fadc09923329e,0x3e2cf6bc604ddb0, - 0xa6539930bf6bff45,0x84db8346b786151c, - 0xcfe87f7cef46ff16,0xe612641865679a63, - 0x81f14fae158c5f6e,0x4fcb7e8f3f60c07e, - 0xa26da3999aef7749,0xe3be5e330f38f09d, - 0xcb090c8001ab551c,0x5cadf5bfd3072cc5, - 0xfdcb4fa002162a63,0x73d9732fc7c8f7f6, - 0x9e9f11c4014dda7e,0x2867e7fddcdd9afa, - 0xc646d63501a1511d,0xb281e1fd541501b8, - 0xf7d88bc24209a565,0x1f225a7ca91a4226, - 0x9ae757596946075f,0x3375788de9b06958, - 0xc1a12d2fc3978937,0x52d6b1641c83ae, - 0xf209787bb47d6b84,0xc0678c5dbd23a49a, - 0x9745eb4d50ce6332,0xf840b7ba963646e0, - 0xbd176620a501fbff,0xb650e5a93bc3d898, - 0xec5d3fa8ce427aff,0xa3e51f138ab4cebe, - 0x93ba47c980e98cdf,0xc66f336c36b10137, - 0xb8a8d9bbe123f017,0xb80b0047445d4184, - 0xe6d3102ad96cec1d,0xa60dc059157491e5, - 0x9043ea1ac7e41392,0x87c89837ad68db2f, - 0xb454e4a179dd1877,0x29babe4598c311fb, - 0xe16a1dc9d8545e94,0xf4296dd6fef3d67a, - 0x8ce2529e2734bb1d,0x1899e4a65f58660c, - 0xb01ae745b101e9e4,0x5ec05dcff72e7f8f, - 0xdc21a1171d42645d,0x76707543f4fa1f73, - 0x899504ae72497eba,0x6a06494a791c53a8, - 0xabfa45da0edbde69,0x487db9d17636892, - 0xd6f8d7509292d603,0x45a9d2845d3c42b6, - 0x865b86925b9bc5c2,0xb8a2392ba45a9b2, - 0xa7f26836f282b732,0x8e6cac7768d7141e, - 0xd1ef0244af2364ff,0x3207d795430cd926, - 0x8335616aed761f1f,0x7f44e6bd49e807b8, - 0xa402b9c5a8d3a6e7,0x5f16206c9c6209a6, - 0xcd036837130890a1,0x36dba887c37a8c0f, - 0x802221226be55a64,0xc2494954da2c9789, - 0xa02aa96b06deb0fd,0xf2db9baa10b7bd6c, - 0xc83553c5c8965d3d,0x6f92829494e5acc7, - 0xfa42a8b73abbf48c,0xcb772339ba1f17f9, - 0x9c69a97284b578d7,0xff2a760414536efb, - 0xc38413cf25e2d70d,0xfef5138519684aba, - 0xf46518c2ef5b8cd1,0x7eb258665fc25d69, - 0x98bf2f79d5993802,0xef2f773ffbd97a61, - 0xbeeefb584aff8603,0xaafb550ffacfd8fa, - 0xeeaaba2e5dbf6784,0x95ba2a53f983cf38, - 0x952ab45cfa97a0b2,0xdd945a747bf26183, - 0xba756174393d88df,0x94f971119aeef9e4, - 0xe912b9d1478ceb17,0x7a37cd5601aab85d, - 0x91abb422ccb812ee,0xac62e055c10ab33a, - 0xb616a12b7fe617aa,0x577b986b314d6009, - 0xe39c49765fdf9d94,0xed5a7e85fda0b80b, - 0x8e41ade9fbebc27d,0x14588f13be847307, - 0xb1d219647ae6b31c,0x596eb2d8ae258fc8, - 0xde469fbd99a05fe3,0x6fca5f8ed9aef3bb, - 0x8aec23d680043bee,0x25de7bb9480d5854, - 0xada72ccc20054ae9,0xaf561aa79a10ae6a, - 0xd910f7ff28069da4,0x1b2ba1518094da04, - 0x87aa9aff79042286,0x90fb44d2f05d0842, - 0xa99541bf57452b28,0x353a1607ac744a53, - 0xd3fa922f2d1675f2,0x42889b8997915ce8, - 0x847c9b5d7c2e09b7,0x69956135febada11, - 0xa59bc234db398c25,0x43fab9837e699095, - 0xcf02b2c21207ef2e,0x94f967e45e03f4bb, - 0x8161afb94b44f57d,0x1d1be0eebac278f5, - 0xa1ba1ba79e1632dc,0x6462d92a69731732, - 0xca28a291859bbf93,0x7d7b8f7503cfdcfe, - 0xfcb2cb35e702af78,0x5cda735244c3d43e, - 0x9defbf01b061adab,0x3a0888136afa64a7, - 0xc56baec21c7a1916,0x88aaa1845b8fdd0, - 0xf6c69a72a3989f5b,0x8aad549e57273d45, - 0x9a3c2087a63f6399,0x36ac54e2f678864b, - 0xc0cb28a98fcf3c7f,0x84576a1bb416a7dd, - 0xf0fdf2d3f3c30b9f,0x656d44a2a11c51d5, - 0x969eb7c47859e743,0x9f644ae5a4b1b325, - 0xbc4665b596706114,0x873d5d9f0dde1fee, - 0xeb57ff22fc0c7959,0xa90cb506d155a7ea, - 0x9316ff75dd87cbd8,0x9a7f12442d588f2, - 0xb7dcbf5354e9bece,0xc11ed6d538aeb2f, - 0xe5d3ef282a242e81,0x8f1668c8a86da5fa, - 0x8fa475791a569d10,0xf96e017d694487bc, - 0xb38d92d760ec4455,0x37c981dcc395a9ac, - 0xe070f78d3927556a,0x85bbe253f47b1417, - 0x8c469ab843b89562,0x93956d7478ccec8e, - 0xaf58416654a6babb,0x387ac8d1970027b2, - 0xdb2e51bfe9d0696a,0x6997b05fcc0319e, - 0x88fcf317f22241e2,0x441fece3bdf81f03, - 0xab3c2fddeeaad25a,0xd527e81cad7626c3, - 0xd60b3bd56a5586f1,0x8a71e223d8d3b074, - 0x85c7056562757456,0xf6872d5667844e49, - 0xa738c6bebb12d16c,0xb428f8ac016561db, - 0xd106f86e69d785c7,0xe13336d701beba52, - 0x82a45b450226b39c,0xecc0024661173473, - 0xa34d721642b06084,0x27f002d7f95d0190, - 0xcc20ce9bd35c78a5,0x31ec038df7b441f4, - 0xff290242c83396ce,0x7e67047175a15271, - 0x9f79a169bd203e41,0xf0062c6e984d386, - 0xc75809c42c684dd1,0x52c07b78a3e60868, - 0xf92e0c3537826145,0xa7709a56ccdf8a82, - 0x9bbcc7a142b17ccb,0x88a66076400bb691, - 0xc2abf989935ddbfe,0x6acff893d00ea435, - 0xf356f7ebf83552fe,0x583f6b8c4124d43, - 0x98165af37b2153de,0xc3727a337a8b704a, - 0xbe1bf1b059e9a8d6,0x744f18c0592e4c5c, - 0xeda2ee1c7064130c,0x1162def06f79df73, - 0x9485d4d1c63e8be7,0x8addcb5645ac2ba8, - 0xb9a74a0637ce2ee1,0x6d953e2bd7173692, - 0xe8111c87c5c1ba99,0xc8fa8db6ccdd0437, - 0x910ab1d4db9914a0,0x1d9c9892400a22a2, - 0xb54d5e4a127f59c8,0x2503beb6d00cab4b, - 0xe2a0b5dc971f303a,0x2e44ae64840fd61d, - 0x8da471a9de737e24,0x5ceaecfed289e5d2, - 0xb10d8e1456105dad,0x7425a83e872c5f47, - 0xdd50f1996b947518,0xd12f124e28f77719, - 0x8a5296ffe33cc92f,0x82bd6b70d99aaa6f, - 0xace73cbfdc0bfb7b,0x636cc64d1001550b, - 0xd8210befd30efa5a,0x3c47f7e05401aa4e, - 0x8714a775e3e95c78,0x65acfaec34810a71, - 0xa8d9d1535ce3b396,0x7f1839a741a14d0d, - 0xd31045a8341ca07c,0x1ede48111209a050, - 0x83ea2b892091e44d,0x934aed0aab460432, - 0xa4e4b66b68b65d60,0xf81da84d5617853f, - 0xce1de40642e3f4b9,0x36251260ab9d668e, - 0x80d2ae83e9ce78f3,0xc1d72b7c6b426019, - 0xa1075a24e4421730,0xb24cf65b8612f81f, - 0xc94930ae1d529cfc,0xdee033f26797b627, - 0xfb9b7cd9a4a7443c,0x169840ef017da3b1, - 0x9d412e0806e88aa5,0x8e1f289560ee864e, - 0xc491798a08a2ad4e,0xf1a6f2bab92a27e2, - 0xf5b5d7ec8acb58a2,0xae10af696774b1db, - 0x9991a6f3d6bf1765,0xacca6da1e0a8ef29, - 0xbff610b0cc6edd3f,0x17fd090a58d32af3, - 0xeff394dcff8a948e,0xddfc4b4cef07f5b0, - 0x95f83d0a1fb69cd9,0x4abdaf101564f98e, - 0xbb764c4ca7a4440f,0x9d6d1ad41abe37f1, - 0xea53df5fd18d5513,0x84c86189216dc5ed, - 0x92746b9be2f8552c,0x32fd3cf5b4e49bb4, - 0xb7118682dbb66a77,0x3fbc8c33221dc2a1, - 0xe4d5e82392a40515,0xfabaf3feaa5334a, - 0x8f05b1163ba6832d,0x29cb4d87f2a7400e, - 0xb2c71d5bca9023f8,0x743e20e9ef511012, - 0xdf78e4b2bd342cf6,0x914da9246b255416, - 0x8bab8eefb6409c1a,0x1ad089b6c2f7548e, - 0xae9672aba3d0c320,0xa184ac2473b529b1, - 0xda3c0f568cc4f3e8,0xc9e5d72d90a2741e, - 0x8865899617fb1871,0x7e2fa67c7a658892, - 0xaa7eebfb9df9de8d,0xddbb901b98feeab7, - 0xd51ea6fa85785631,0x552a74227f3ea565, - 0x8533285c936b35de,0xd53a88958f87275f, - 0xa67ff273b8460356,0x8a892abaf368f137, - 0xd01fef10a657842c,0x2d2b7569b0432d85, - 0x8213f56a67f6b29b,0x9c3b29620e29fc73, - 0xa298f2c501f45f42,0x8349f3ba91b47b8f, - 0xcb3f2f7642717713,0x241c70a936219a73, - 0xfe0efb53d30dd4d7,0xed238cd383aa0110, - 0x9ec95d1463e8a506,0xf4363804324a40aa, - 0xc67bb4597ce2ce48,0xb143c6053edcd0d5, - 0xf81aa16fdc1b81da,0xdd94b7868e94050a, - 0x9b10a4e5e9913128,0xca7cf2b4191c8326, - 0xc1d4ce1f63f57d72,0xfd1c2f611f63a3f0, - 0xf24a01a73cf2dccf,0xbc633b39673c8cec, - 0x976e41088617ca01,0xd5be0503e085d813, - 0xbd49d14aa79dbc82,0x4b2d8644d8a74e18, - 0xec9c459d51852ba2,0xddf8e7d60ed1219e, - 0x93e1ab8252f33b45,0xcabb90e5c942b503, - 0xb8da1662e7b00a17,0x3d6a751f3b936243, - 0xe7109bfba19c0c9d,0xcc512670a783ad4, - 0x906a617d450187e2,0x27fb2b80668b24c5, - 0xb484f9dc9641e9da,0xb1f9f660802dedf6, - 0xe1a63853bbd26451,0x5e7873f8a0396973, - 0x8d07e33455637eb2,0xdb0b487b6423e1e8, - 0xb049dc016abc5e5f,0x91ce1a9a3d2cda62, - 0xdc5c5301c56b75f7,0x7641a140cc7810fb, - 0x89b9b3e11b6329ba,0xa9e904c87fcb0a9d, - 0xac2820d9623bf429,0x546345fa9fbdcd44, - 0xd732290fbacaf133,0xa97c177947ad4095, - 0x867f59a9d4bed6c0,0x49ed8eabcccc485d, - 0xa81f301449ee8c70,0x5c68f256bfff5a74, - 0xd226fc195c6a2f8c,0x73832eec6fff3111, - 0x83585d8fd9c25db7,0xc831fd53c5ff7eab, - 0xa42e74f3d032f525,0xba3e7ca8b77f5e55, - 0xcd3a1230c43fb26f,0x28ce1bd2e55f35eb, - 0x80444b5e7aa7cf85,0x7980d163cf5b81b3, - 0xa0555e361951c366,0xd7e105bcc332621f, - 0xc86ab5c39fa63440,0x8dd9472bf3fefaa7, - 0xfa856334878fc150,0xb14f98f6f0feb951, - 0x9c935e00d4b9d8d2,0x6ed1bf9a569f33d3, - 0xc3b8358109e84f07,0xa862f80ec4700c8, - 0xf4a642e14c6262c8,0xcd27bb612758c0fa, - 0x98e7e9cccfbd7dbd,0x8038d51cb897789c, - 0xbf21e44003acdd2c,0xe0470a63e6bd56c3, - 0xeeea5d5004981478,0x1858ccfce06cac74, - 0x95527a5202df0ccb,0xf37801e0c43ebc8, - 0xbaa718e68396cffd,0xd30560258f54e6ba, - 0xe950df20247c83fd,0x47c6b82ef32a2069, - 0x91d28b7416cdd27e,0x4cdc331d57fa5441, - 0xb6472e511c81471d,0xe0133fe4adf8e952, - 0xe3d8f9e563a198e5,0x58180fddd97723a6, - 0x8e679c2f5e44ff8f,0x570f09eaa7ea7648,}; -using powers = powers_template<>; - -} - -#endif - - -#ifndef FASTFLOAT_DECIMAL_TO_BINARY_H -#define FASTFLOAT_DECIMAL_TO_BINARY_H - -//included above: -//#include -#include -#include -//included above: -//#include -#include -//included above: -//#include - -namespace fast_float { - -// This will compute or rather approximate w * 5**q and return a pair of 64-bit words approximating -// the result, with the "high" part corresponding to the most significant bits and the -// low part corresponding to the least significant bits. -// -template -fastfloat_really_inline -value128 compute_product_approximation(int64_t q, uint64_t w) { - const int index = 2 * int(q - powers::smallest_power_of_five); - // For small values of q, e.g., q in [0,27], the answer is always exact because - // The line value128 firstproduct = full_multiplication(w, power_of_five_128[index]); - // gives the exact answer. - value128 firstproduct = full_multiplication(w, powers::power_of_five_128[index]); - static_assert((bit_precision >= 0) && (bit_precision <= 64), " precision should be in (0,64]"); - constexpr uint64_t precision_mask = (bit_precision < 64) ? - (uint64_t(0xFFFFFFFFFFFFFFFF) >> bit_precision) - : uint64_t(0xFFFFFFFFFFFFFFFF); - if((firstproduct.high & precision_mask) == precision_mask) { // could further guard with (lower + w < lower) - // regarding the second product, we only need secondproduct.high, but our expectation is that the compiler will optimize this extra work away if needed. - value128 secondproduct = full_multiplication(w, powers::power_of_five_128[index + 1]); - firstproduct.low += secondproduct.high; - if(secondproduct.high > firstproduct.low) { - firstproduct.high++; - } - } - return firstproduct; -} - -namespace detail { -/** - * For q in (0,350), we have that - * f = (((152170 + 65536) * q ) >> 16); - * is equal to - * floor(p) + q - * where - * p = log(5**q)/log(2) = q * log(5)/log(2) - * - * For negative values of q in (-400,0), we have that - * f = (((152170 + 65536) * q ) >> 16); - * is equal to - * -ceil(p) + q - * where - * p = log(5**-q)/log(2) = -q * log(5)/log(2) - */ - constexpr fastfloat_really_inline int32_t power(int32_t q) noexcept { - return (((152170 + 65536) * q) >> 16) + 63; - } -} // namespace detail - -// create an adjusted mantissa, biased by the invalid power2 -// for significant digits already multiplied by 10 ** q. -template -fastfloat_really_inline -adjusted_mantissa compute_error_scaled(int64_t q, uint64_t w, int lz) noexcept { - int hilz = int(w >> 63) ^ 1; - adjusted_mantissa answer; - answer.mantissa = w << hilz; - int bias = binary::mantissa_explicit_bits() - binary::minimum_exponent(); - answer.power2 = int32_t(detail::power(int32_t(q)) + bias - hilz - lz - 62 + invalid_am_bias); - return answer; -} - -// w * 10 ** q, without rounding the representation up. -// the power2 in the exponent will be adjusted by invalid_am_bias. -template -fastfloat_really_inline -adjusted_mantissa compute_error(int64_t q, uint64_t w) noexcept { - int lz = leading_zeroes(w); - w <<= lz; - value128 product = compute_product_approximation(q, w); - return compute_error_scaled(q, product.high, lz); -} - -// w * 10 ** q -// The returned value should be a valid ieee64 number that simply need to be packed. -// However, in some very rare cases, the computation will fail. In such cases, we -// return an adjusted_mantissa with a negative power of 2: the caller should recompute -// in such cases. -template -fastfloat_really_inline -adjusted_mantissa compute_float(int64_t q, uint64_t w) noexcept { - adjusted_mantissa answer; - if ((w == 0) || (q < binary::smallest_power_of_ten())) { - answer.power2 = 0; - answer.mantissa = 0; - // result should be zero - return answer; - } - if (q > binary::largest_power_of_ten()) { - // we want to get infinity: - answer.power2 = binary::infinite_power(); - answer.mantissa = 0; - return answer; - } - // At this point in time q is in [powers::smallest_power_of_five, powers::largest_power_of_five]. - - // We want the most significant bit of i to be 1. Shift if needed. - int lz = leading_zeroes(w); - w <<= lz; - - // The required precision is binary::mantissa_explicit_bits() + 3 because - // 1. We need the implicit bit - // 2. We need an extra bit for rounding purposes - // 3. We might lose a bit due to the "upperbit" routine (result too small, requiring a shift) - - value128 product = compute_product_approximation(q, w); - if(product.low == 0xFFFFFFFFFFFFFFFF) { // could guard it further - // In some very rare cases, this could happen, in which case we might need a more accurate - // computation that what we can provide cheaply. This is very, very unlikely. - // - const bool inside_safe_exponent = (q >= -27) && (q <= 55); // always good because 5**q <2**128 when q>=0, - // and otherwise, for q<0, we have 5**-q<2**64 and the 128-bit reciprocal allows for exact computation. - if(!inside_safe_exponent) { - return compute_error_scaled(q, product.high, lz); - } - } - // The "compute_product_approximation" function can be slightly slower than a branchless approach: - // value128 product = compute_product(q, w); - // but in practice, we can win big with the compute_product_approximation if its additional branch - // is easily predicted. Which is best is data specific. - int upperbit = int(product.high >> 63); - - answer.mantissa = product.high >> (upperbit + 64 - binary::mantissa_explicit_bits() - 3); - - answer.power2 = int32_t(detail::power(int32_t(q)) + upperbit - lz - binary::minimum_exponent()); - if (answer.power2 <= 0) { // we have a subnormal? - // Here have that answer.power2 <= 0 so -answer.power2 >= 0 - if(-answer.power2 + 1 >= 64) { // if we have more than 64 bits below the minimum exponent, you have a zero for sure. - answer.power2 = 0; - answer.mantissa = 0; - // result should be zero - return answer; - } - // next line is safe because -answer.power2 + 1 < 64 - answer.mantissa >>= -answer.power2 + 1; - // Thankfully, we can't have both "round-to-even" and subnormals because - // "round-to-even" only occurs for powers close to 0. - answer.mantissa += (answer.mantissa & 1); // round up - answer.mantissa >>= 1; - // There is a weird scenario where we don't have a subnormal but just. - // Suppose we start with 2.2250738585072013e-308, we end up - // with 0x3fffffffffffff x 2^-1023-53 which is technically subnormal - // whereas 0x40000000000000 x 2^-1023-53 is normal. Now, we need to round - // up 0x3fffffffffffff x 2^-1023-53 and once we do, we are no longer - // subnormal, but we can only know this after rounding. - // So we only declare a subnormal if we are smaller than the threshold. - answer.power2 = (answer.mantissa < (uint64_t(1) << binary::mantissa_explicit_bits())) ? 0 : 1; - return answer; - } - - // usually, we round *up*, but if we fall right in between and and we have an - // even basis, we need to round down - // We are only concerned with the cases where 5**q fits in single 64-bit word. - if ((product.low <= 1) && (q >= binary::min_exponent_round_to_even()) && (q <= binary::max_exponent_round_to_even()) && - ((answer.mantissa & 3) == 1) ) { // we may fall between two floats! - // To be in-between two floats we need that in doing - // answer.mantissa = product.high >> (upperbit + 64 - binary::mantissa_explicit_bits() - 3); - // ... we dropped out only zeroes. But if this happened, then we can go back!!! - if((answer.mantissa << (upperbit + 64 - binary::mantissa_explicit_bits() - 3)) == product.high) { - answer.mantissa &= ~uint64_t(1); // flip it so that we do not round up - } - } - - answer.mantissa += (answer.mantissa & 1); // round up - answer.mantissa >>= 1; - if (answer.mantissa >= (uint64_t(2) << binary::mantissa_explicit_bits())) { - answer.mantissa = (uint64_t(1) << binary::mantissa_explicit_bits()); - answer.power2++; // undo previous addition - } - - answer.mantissa &= ~(uint64_t(1) << binary::mantissa_explicit_bits()); - if (answer.power2 >= binary::infinite_power()) { // infinity - answer.power2 = binary::infinite_power(); - answer.mantissa = 0; - } - return answer; -} - -} // namespace fast_float - -#endif - - -#ifndef FASTFLOAT_BIGINT_H -#define FASTFLOAT_BIGINT_H - -#include -//included above: -//#include -//included above: -//#include -//included above: -//#include - - -namespace fast_float { - -// the limb width: we want efficient multiplication of double the bits in -// limb, or for 64-bit limbs, at least 64-bit multiplication where we can -// extract the high and low parts efficiently. this is every 64-bit -// architecture except for sparc, which emulates 128-bit multiplication. -// we might have platforms where `CHAR_BIT` is not 8, so let's avoid -// doing `8 * sizeof(limb)`. -#if defined(FASTFLOAT_64BIT) && !defined(__sparc) -#define FASTFLOAT_64BIT_LIMB -typedef uint64_t limb; -constexpr size_t limb_bits = 64; -#else -#define FASTFLOAT_32BIT_LIMB -typedef uint32_t limb; -constexpr size_t limb_bits = 32; -#endif - -typedef span limb_span; - -// number of bits in a bigint. this needs to be at least the number -// of bits required to store the largest bigint, which is -// `log2(10**(digits + max_exp))`, or `log2(10**(767 + 342))`, or -// ~3600 bits, so we round to 4000. -constexpr size_t bigint_bits = 4000; -constexpr size_t bigint_limbs = bigint_bits / limb_bits; - -// vector-like type that is allocated on the stack. the entire -// buffer is pre-allocated, and only the length changes. -template -struct stackvec { - limb data[size]; - // we never need more than 150 limbs - uint16_t length{0}; - - stackvec() = default; - stackvec(const stackvec &) = delete; - stackvec &operator=(const stackvec &) = delete; - stackvec(stackvec &&) = delete; - stackvec &operator=(stackvec &&other) = delete; - - // create stack vector from existing limb span. - stackvec(limb_span s) { - FASTFLOAT_ASSERT(try_extend(s)); - } - - limb& operator[](size_t index) noexcept { - FASTFLOAT_DEBUG_ASSERT(index < length); - return data[index]; - } - const limb& operator[](size_t index) const noexcept { - FASTFLOAT_DEBUG_ASSERT(index < length); - return data[index]; - } - // index from the end of the container - const limb& rindex(size_t index) const noexcept { - FASTFLOAT_DEBUG_ASSERT(index < length); - size_t rindex = length - index - 1; - return data[rindex]; - } - - // set the length, without bounds checking. - void set_len(size_t len) noexcept { - length = uint16_t(len); - } - constexpr size_t len() const noexcept { - return length; - } - constexpr bool is_empty() const noexcept { - return length == 0; - } - constexpr size_t capacity() const noexcept { - return size; - } - // append item to vector, without bounds checking - void push_unchecked(limb value) noexcept { - data[length] = value; - length++; - } - // append item to vector, returning if item was added - bool try_push(limb value) noexcept { - if (len() < capacity()) { - push_unchecked(value); - return true; - } else { - return false; - } - } - // add items to the vector, from a span, without bounds checking - void extend_unchecked(limb_span s) noexcept { - limb* ptr = data + length; - ::memcpy((void*)ptr, (const void*)s.ptr, sizeof(limb) * s.len()); - set_len(len() + s.len()); - } - // try to add items to the vector, returning if items were added - bool try_extend(limb_span s) noexcept { - if (len() + s.len() <= capacity()) { - extend_unchecked(s); - return true; - } else { - return false; - } - } - // resize the vector, without bounds checking - // if the new size is longer than the vector, assign value to each - // appended item. - void resize_unchecked(size_t new_len, limb value) noexcept { - if (new_len > len()) { - size_t count = new_len - len(); - limb* first = data + len(); - limb* last = first + count; - ::std::fill(first, last, value); - set_len(new_len); - } else { - set_len(new_len); - } - } - // try to resize the vector, returning if the vector was resized. - bool try_resize(size_t new_len, limb value) noexcept { - if (new_len > capacity()) { - return false; - } else { - resize_unchecked(new_len, value); - return true; - } - } - // check if any limbs are non-zero after the given index. - // this needs to be done in reverse order, since the index - // is relative to the most significant limbs. - bool nonzero(size_t index) const noexcept { - while (index < len()) { - if (rindex(index) != 0) { - return true; - } - index++; - } - return false; - } - // normalize the big integer, so most-significant zero limbs are removed. - void normalize() noexcept { - while (len() > 0 && rindex(0) == 0) { - length--; - } - } -}; - -fastfloat_really_inline -uint64_t empty_hi64(bool& truncated) noexcept { - truncated = false; - return 0; -} - -fastfloat_really_inline -uint64_t uint64_hi64(uint64_t r0, bool& truncated) noexcept { - truncated = false; - int shl = leading_zeroes(r0); - return r0 << shl; -} - -fastfloat_really_inline -uint64_t uint64_hi64(uint64_t r0, uint64_t r1, bool& truncated) noexcept { - int shl = leading_zeroes(r0); - if (shl == 0) { - truncated = r1 != 0; - return r0; - } else { - int shr = 64 - shl; - truncated = (r1 << shl) != 0; - return (r0 << shl) | (r1 >> shr); - } -} - -fastfloat_really_inline -uint64_t uint32_hi64(uint32_t r0, bool& truncated) noexcept { - return uint64_hi64(r0, truncated); -} - -fastfloat_really_inline -uint64_t uint32_hi64(uint32_t r0, uint32_t r1, bool& truncated) noexcept { - uint64_t x0 = r0; - uint64_t x1 = r1; - return uint64_hi64((x0 << 32) | x1, truncated); -} - -fastfloat_really_inline -uint64_t uint32_hi64(uint32_t r0, uint32_t r1, uint32_t r2, bool& truncated) noexcept { - uint64_t x0 = r0; - uint64_t x1 = r1; - uint64_t x2 = r2; - return uint64_hi64(x0, (x1 << 32) | x2, truncated); -} - -// add two small integers, checking for overflow. -// we want an efficient operation. for msvc, where -// we don't have built-in intrinsics, this is still -// pretty fast. -fastfloat_really_inline -limb scalar_add(limb x, limb y, bool& overflow) noexcept { - limb z; - -// gcc and clang -#if defined(__has_builtin) - #if __has_builtin(__builtin_add_overflow) - overflow = __builtin_add_overflow(x, y, &z); - return z; - #endif -#endif - - // generic, this still optimizes correctly on MSVC. - z = x + y; - overflow = z < x; - return z; -} - -// multiply two small integers, getting both the high and low bits. -fastfloat_really_inline -limb scalar_mul(limb x, limb y, limb& carry) noexcept { -#ifdef FASTFLOAT_64BIT_LIMB - #if defined(__SIZEOF_INT128__) - // GCC and clang both define it as an extension. - __uint128_t z = __uint128_t(x) * __uint128_t(y) + __uint128_t(carry); - carry = limb(z >> limb_bits); - return limb(z); - #else - // fallback, no native 128-bit integer multiplication with carry. - // on msvc, this optimizes identically, somehow. - value128 z = full_multiplication(x, y); - bool overflow; - z.low = scalar_add(z.low, carry, overflow); - z.high += uint64_t(overflow); // cannot overflow - carry = z.high; - return z.low; - #endif -#else - uint64_t z = uint64_t(x) * uint64_t(y) + uint64_t(carry); - carry = limb(z >> limb_bits); - return limb(z); -#endif -} - -// add scalar value to bigint starting from offset. -// used in grade school multiplication -template -inline bool small_add_from(stackvec& vec, limb y, size_t start) noexcept { - size_t index = start; - limb carry = y; - bool overflow; - while (carry != 0 && index < vec.len()) { - vec[index] = scalar_add(vec[index], carry, overflow); - carry = limb(overflow); - index += 1; - } - if (carry != 0) { - FASTFLOAT_TRY(vec.try_push(carry)); - } - return true; -} - -// add scalar value to bigint. -template -fastfloat_really_inline bool small_add(stackvec& vec, limb y) noexcept { - return small_add_from(vec, y, 0); -} - -// multiply bigint by scalar value. -template -inline bool small_mul(stackvec& vec, limb y) noexcept { - limb carry = 0; - for (size_t index = 0; index < vec.len(); index++) { - vec[index] = scalar_mul(vec[index], y, carry); - } - if (carry != 0) { - FASTFLOAT_TRY(vec.try_push(carry)); - } - return true; -} - -// add bigint to bigint starting from index. -// used in grade school multiplication -template -bool large_add_from(stackvec& x, limb_span y, size_t start) noexcept { - // the effective x buffer is from `xstart..x.len()`, so exit early - // if we can't get that current range. - if (x.len() < start || y.len() > x.len() - start) { - FASTFLOAT_TRY(x.try_resize(y.len() + start, 0)); - } - - bool carry = false; - for (size_t index = 0; index < y.len(); index++) { - limb xi = x[index + start]; - limb yi = y[index]; - bool c1 = false; - bool c2 = false; - xi = scalar_add(xi, yi, c1); - if (carry) { - xi = scalar_add(xi, 1, c2); - } - x[index + start] = xi; - carry = c1 | c2; - } - - // handle overflow - if (carry) { - FASTFLOAT_TRY(small_add_from(x, 1, y.len() + start)); - } - return true; -} - -// add bigint to bigint. -template -fastfloat_really_inline bool large_add_from(stackvec& x, limb_span y) noexcept { - return large_add_from(x, y, 0); -} - -// grade-school multiplication algorithm -template -bool long_mul(stackvec& x, limb_span y) noexcept { - limb_span xs = limb_span(x.data, x.len()); - stackvec z(xs); - limb_span zs = limb_span(z.data, z.len()); - - if (y.len() != 0) { - limb y0 = y[0]; - FASTFLOAT_TRY(small_mul(x, y0)); - for (size_t index = 1; index < y.len(); index++) { - limb yi = y[index]; - stackvec zi; - if (yi != 0) { - // re-use the same buffer throughout - zi.set_len(0); - FASTFLOAT_TRY(zi.try_extend(zs)); - FASTFLOAT_TRY(small_mul(zi, yi)); - limb_span zis = limb_span(zi.data, zi.len()); - FASTFLOAT_TRY(large_add_from(x, zis, index)); - } - } - } - - x.normalize(); - return true; -} - -// grade-school multiplication algorithm -template -bool large_mul(stackvec& x, limb_span y) noexcept { - if (y.len() == 1) { - FASTFLOAT_TRY(small_mul(x, y[0])); - } else { - FASTFLOAT_TRY(long_mul(x, y)); - } - return true; -} - -// big integer type. implements a small subset of big integer -// arithmetic, using simple algorithms since asymptotically -// faster algorithms are slower for a small number of limbs. -// all operations assume the big-integer is normalized. -struct bigint { - // storage of the limbs, in little-endian order. - stackvec vec; - - bigint(): vec() {} - bigint(const bigint &) = delete; - bigint &operator=(const bigint &) = delete; - bigint(bigint &&) = delete; - bigint &operator=(bigint &&other) = delete; - - bigint(uint64_t value): vec() { -#ifdef FASTFLOAT_64BIT_LIMB - vec.push_unchecked(value); -#else - vec.push_unchecked(uint32_t(value)); - vec.push_unchecked(uint32_t(value >> 32)); -#endif - vec.normalize(); - } - - // get the high 64 bits from the vector, and if bits were truncated. - // this is to get the significant digits for the float. - uint64_t hi64(bool& truncated) const noexcept { -#ifdef FASTFLOAT_64BIT_LIMB - if (vec.len() == 0) { - return empty_hi64(truncated); - } else if (vec.len() == 1) { - return uint64_hi64(vec.rindex(0), truncated); - } else { - uint64_t result = uint64_hi64(vec.rindex(0), vec.rindex(1), truncated); - truncated |= vec.nonzero(2); - return result; - } -#else - if (vec.len() == 0) { - return empty_hi64(truncated); - } else if (vec.len() == 1) { - return uint32_hi64(vec.rindex(0), truncated); - } else if (vec.len() == 2) { - return uint32_hi64(vec.rindex(0), vec.rindex(1), truncated); - } else { - uint64_t result = uint32_hi64(vec.rindex(0), vec.rindex(1), vec.rindex(2), truncated); - truncated |= vec.nonzero(3); - return result; - } -#endif - } - - // compare two big integers, returning the large value. - // assumes both are normalized. if the return value is - // negative, other is larger, if the return value is - // positive, this is larger, otherwise they are equal. - // the limbs are stored in little-endian order, so we - // must compare the limbs in ever order. - int compare(const bigint& other) const noexcept { - if (vec.len() > other.vec.len()) { - return 1; - } else if (vec.len() < other.vec.len()) { - return -1; - } else { - for (size_t index = vec.len(); index > 0; index--) { - limb xi = vec[index - 1]; - limb yi = other.vec[index - 1]; - if (xi > yi) { - return 1; - } else if (xi < yi) { - return -1; - } - } - return 0; - } - } - - // shift left each limb n bits, carrying over to the new limb - // returns true if we were able to shift all the digits. - bool shl_bits(size_t n) noexcept { - // Internally, for each item, we shift left by n, and add the previous - // right shifted limb-bits. - // For example, we transform (for u8) shifted left 2, to: - // b10100100 b01000010 - // b10 b10010001 b00001000 - FASTFLOAT_DEBUG_ASSERT(n != 0); - FASTFLOAT_DEBUG_ASSERT(n < sizeof(limb) * 8); - - size_t shl = n; - size_t shr = limb_bits - shl; - limb prev = 0; - for (size_t index = 0; index < vec.len(); index++) { - limb xi = vec[index]; - vec[index] = (xi << shl) | (prev >> shr); - prev = xi; - } - - limb carry = prev >> shr; - if (carry != 0) { - return vec.try_push(carry); - } - return true; - } - - // move the limbs left by `n` limbs. - bool shl_limbs(size_t n) noexcept { - FASTFLOAT_DEBUG_ASSERT(n != 0); - if (n + vec.len() > vec.capacity()) { - return false; - } else if (!vec.is_empty()) { - // move limbs - limb* dst = vec.data + n; - const limb* src = vec.data; - ::memmove(dst, src, sizeof(limb) * vec.len()); - // fill in empty limbs - limb* first = vec.data; - limb* last = first + n; - ::std::fill(first, last, 0); - vec.set_len(n + vec.len()); - return true; - } else { - return true; - } - } - - // move the limbs left by `n` bits. - bool shl(size_t n) noexcept { - size_t rem = n % limb_bits; - size_t div = n / limb_bits; - if (rem != 0) { - FASTFLOAT_TRY(shl_bits(rem)); - } - if (div != 0) { - FASTFLOAT_TRY(shl_limbs(div)); - } - return true; - } - - // get the number of leading zeros in the bigint. - int ctlz() const noexcept { - if (vec.is_empty()) { - return 0; - } else { -#ifdef FASTFLOAT_64BIT_LIMB - return leading_zeroes(vec.rindex(0)); -#else - // no use defining a specialized leading_zeroes for a 32-bit type. - uint64_t r0 = vec.rindex(0); - return leading_zeroes(r0 << 32); -#endif - } - } - - // get the number of bits in the bigint. - int bit_length() const noexcept { - int lz = ctlz(); - return int(limb_bits * vec.len()) - lz; - } - - bool mul(limb y) noexcept { - return small_mul(vec, y); - } - - bool add(limb y) noexcept { - return small_add(vec, y); - } - - // multiply as if by 2 raised to a power. - bool pow2(uint32_t exp) noexcept { - return shl(exp); - } - - // multiply as if by 5 raised to a power. - bool pow5(uint32_t exp) noexcept { - // multiply by a power of 5 - static constexpr uint32_t large_step = 135; - static constexpr uint64_t small_power_of_5[] = { - 1UL, 5UL, 25UL, 125UL, 625UL, 3125UL, 15625UL, 78125UL, 390625UL, - 1953125UL, 9765625UL, 48828125UL, 244140625UL, 1220703125UL, - 6103515625UL, 30517578125UL, 152587890625UL, 762939453125UL, - 3814697265625UL, 19073486328125UL, 95367431640625UL, 476837158203125UL, - 2384185791015625UL, 11920928955078125UL, 59604644775390625UL, - 298023223876953125UL, 1490116119384765625UL, 7450580596923828125UL, - }; -#ifdef FASTFLOAT_64BIT_LIMB - constexpr static limb large_power_of_5[] = { - 1414648277510068013UL, 9180637584431281687UL, 4539964771860779200UL, - 10482974169319127550UL, 198276706040285095UL}; -#else - constexpr static limb large_power_of_5[] = { - 4279965485U, 329373468U, 4020270615U, 2137533757U, 4287402176U, - 1057042919U, 1071430142U, 2440757623U, 381945767U, 46164893U}; -#endif - size_t large_length = sizeof(large_power_of_5) / sizeof(limb); - limb_span large = limb_span(large_power_of_5, large_length); - while (exp >= large_step) { - FASTFLOAT_TRY(large_mul(vec, large)); - exp -= large_step; - } -#ifdef FASTFLOAT_64BIT_LIMB - uint32_t small_step = 27; - limb max_native = 7450580596923828125UL; -#else - uint32_t small_step = 13; - limb max_native = 1220703125U; -#endif - while (exp >= small_step) { - FASTFLOAT_TRY(small_mul(vec, max_native)); - exp -= small_step; - } - if (exp != 0) { - FASTFLOAT_TRY(small_mul(vec, limb(small_power_of_5[exp]))); - } - - return true; - } - - // multiply as if by 10 raised to a power. - bool pow10(uint32_t exp) noexcept { - FASTFLOAT_TRY(pow5(exp)); - return pow2(exp); - } -}; - -} // namespace fast_float - -#endif - - -#ifndef FASTFLOAT_ASCII_NUMBER_H -#define FASTFLOAT_ASCII_NUMBER_H - -//included above: -//#include -//included above: -//#include -//included above: -//#include -//included above: -//#include - - -namespace fast_float { - -// Next function can be micro-optimized, but compilers are entirely -// able to optimize it well. -fastfloat_really_inline bool is_integer(char c) noexcept { return c >= '0' && c <= '9'; } - -fastfloat_really_inline uint64_t byteswap(uint64_t val) { - return (val & 0xFF00000000000000) >> 56 - | (val & 0x00FF000000000000) >> 40 - | (val & 0x0000FF0000000000) >> 24 - | (val & 0x000000FF00000000) >> 8 - | (val & 0x00000000FF000000) << 8 - | (val & 0x0000000000FF0000) << 24 - | (val & 0x000000000000FF00) << 40 - | (val & 0x00000000000000FF) << 56; -} - -fastfloat_really_inline uint64_t read_u64(const char *chars) { - uint64_t val; - ::memcpy(&val, chars, sizeof(uint64_t)); -#if FASTFLOAT_IS_BIG_ENDIAN == 1 - // Need to read as-if the number was in little-endian order. - val = byteswap(val); -#endif - return val; -} - -fastfloat_really_inline void write_u64(uint8_t *chars, uint64_t val) { -#if FASTFLOAT_IS_BIG_ENDIAN == 1 - // Need to read as-if the number was in little-endian order. - val = byteswap(val); -#endif - ::memcpy(chars, &val, sizeof(uint64_t)); -} - -// credit @aqrit -fastfloat_really_inline uint32_t parse_eight_digits_unrolled(uint64_t val) { - const uint64_t mask = 0x000000FF000000FF; - const uint64_t mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32) - const uint64_t mul2 = 0x0000271000000001; // 1 + (10000ULL << 32) - val -= 0x3030303030303030; - val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8; - val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32; - return uint32_t(val); -} - -fastfloat_really_inline uint32_t parse_eight_digits_unrolled(const char *chars) noexcept { - return parse_eight_digits_unrolled(read_u64(chars)); -} - -// credit @aqrit -fastfloat_really_inline bool is_made_of_eight_digits_fast(uint64_t val) noexcept { - return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) & - 0x8080808080808080)); -} - -fastfloat_really_inline bool is_made_of_eight_digits_fast(const char *chars) noexcept { - return is_made_of_eight_digits_fast(read_u64(chars)); -} - -typedef span byte_span; - -struct parsed_number_string { - int64_t exponent{0}; - uint64_t mantissa{0}; - const char *lastmatch{nullptr}; - bool negative{false}; - bool valid{false}; - bool too_many_digits{false}; - // contains the range of the significant digits - byte_span integer{}; // non-nullable - byte_span fraction{}; // nullable -}; - -// Assuming that you use no more than 19 digits, this will -// parse an ASCII string. -fastfloat_really_inline -parsed_number_string parse_number_string(const char *p, const char *pend, parse_options options) noexcept { - const chars_format fmt = options.format; - const char decimal_point = options.decimal_point; - - parsed_number_string answer; - answer.valid = false; - answer.too_many_digits = false; - answer.negative = (*p == '-'); - if (*p == '-') { // C++17 20.19.3.(7.1) explicitly forbids '+' sign here - ++p; - if (p == pend) { - return answer; - } - if (!is_integer(*p) && (*p != decimal_point)) { // a sign must be followed by an integer or the dot - return answer; - } - } - const char *const start_digits = p; - - uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad) - - while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(p)) { - i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok - p += 8; - } - while ((p != pend) && is_integer(*p)) { - // a multiplication by 10 is cheaper than an arbitrary integer - // multiplication - i = 10 * i + - uint64_t(*p - '0'); // might overflow, we will handle the overflow later - ++p; - } - const char *const end_of_integer_part = p; - int64_t digit_count = int64_t(end_of_integer_part - start_digits); - answer.integer = byte_span(start_digits, size_t(digit_count)); - int64_t exponent = 0; - if ((p != pend) && (*p == decimal_point)) { - ++p; - const char* before = p; - // can occur at most twice without overflowing, but let it occur more, since - // for integers with many digits, digit parsing is the primary bottleneck. - while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(p)) { - i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok - p += 8; - } - while ((p != pend) && is_integer(*p)) { - uint8_t digit = uint8_t(*p - '0'); - ++p; - i = i * 10 + digit; // in rare cases, this will overflow, but that's ok - } - exponent = before - p; - answer.fraction = byte_span(before, size_t(p - before)); - digit_count -= exponent; - } - // we must have encountered at least one integer! - if (digit_count == 0) { - return answer; - } - int64_t exp_number = 0; // explicit exponential part - if ((fmt & chars_format::scientific) && (p != pend) && (('e' == *p) || ('E' == *p))) { - const char * location_of_e = p; - ++p; - bool neg_exp = false; - if ((p != pend) && ('-' == *p)) { - neg_exp = true; - ++p; - } else if ((p != pend) && ('+' == *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1) - ++p; - } - if ((p == pend) || !is_integer(*p)) { - if(!(fmt & chars_format::fixed)) { - // We are in error. - return answer; - } - // Otherwise, we will be ignoring the 'e'. - p = location_of_e; - } else { - while ((p != pend) && is_integer(*p)) { - uint8_t digit = uint8_t(*p - '0'); - if (exp_number < 0x10000000) { - exp_number = 10 * exp_number + digit; - } - ++p; - } - if(neg_exp) { exp_number = - exp_number; } - exponent += exp_number; - } - } else { - // If it scientific and not fixed, we have to bail out. - if((fmt & chars_format::scientific) && !(fmt & chars_format::fixed)) { return answer; } - } - answer.lastmatch = p; - answer.valid = true; - - // If we frequently had to deal with long strings of digits, - // we could extend our code by using a 128-bit integer instead - // of a 64-bit integer. However, this is uncommon. - // - // We can deal with up to 19 digits. - if (digit_count > 19) { // this is uncommon - // It is possible that the integer had an overflow. - // We have to handle the case where we have 0.0000somenumber. - // We need to be mindful of the case where we only have zeroes... - // E.g., 0.000000000...000. - const char *start = start_digits; - while ((start != pend) && (*start == '0' || *start == decimal_point)) { - if(*start == '0') { digit_count --; } - start++; - } - if (digit_count > 19) { - answer.too_many_digits = true; - // Let us start again, this time, avoiding overflows. - // We don't need to check if is_integer, since we use the - // pre-tokenized spans from above. - i = 0; - p = answer.integer.ptr; - const char* int_end = p + answer.integer.len(); - const uint64_t minimal_nineteen_digit_integer{1000000000000000000}; - while((i < minimal_nineteen_digit_integer) && (p != int_end)) { - i = i * 10 + uint64_t(*p - '0'); - ++p; - } - if (i >= minimal_nineteen_digit_integer) { // We have a big integers - exponent = end_of_integer_part - p + exp_number; - } else { // We have a value with a fractional component. - p = answer.fraction.ptr; - const char* frac_end = p + answer.fraction.len(); - while((i < minimal_nineteen_digit_integer) && (p != frac_end)) { - i = i * 10 + uint64_t(*p - '0'); - ++p; - } - exponent = answer.fraction.ptr - p + exp_number; - } - // We have now corrected both exponent and i, to a truncated value - } - } - answer.exponent = exponent; - answer.mantissa = i; - return answer; -} - -} // namespace fast_float - -#endif - - -#ifndef FASTFLOAT_DIGIT_COMPARISON_H -#define FASTFLOAT_DIGIT_COMPARISON_H - -//included above: -//#include -//included above: -//#include -//included above: -//#include -//included above: -//#include - - -namespace fast_float { - -// 1e0 to 1e19 -constexpr static uint64_t powers_of_ten_uint64[] = { - 1UL, 10UL, 100UL, 1000UL, 10000UL, 100000UL, 1000000UL, 10000000UL, 100000000UL, - 1000000000UL, 10000000000UL, 100000000000UL, 1000000000000UL, 10000000000000UL, - 100000000000000UL, 1000000000000000UL, 10000000000000000UL, 100000000000000000UL, - 1000000000000000000UL, 10000000000000000000UL}; - -// calculate the exponent, in scientific notation, of the number. -// this algorithm is not even close to optimized, but it has no practical -// effect on performance: in order to have a faster algorithm, we'd need -// to slow down performance for faster algorithms, and this is still fast. -fastfloat_really_inline int32_t scientific_exponent(parsed_number_string& num) noexcept { - uint64_t mantissa = num.mantissa; - int32_t exponent = int32_t(num.exponent); - while (mantissa >= 10000) { - mantissa /= 10000; - exponent += 4; - } - while (mantissa >= 100) { - mantissa /= 100; - exponent += 2; - } - while (mantissa >= 10) { - mantissa /= 10; - exponent += 1; - } - return exponent; -} - -// this converts a native floating-point number to an extended-precision float. -template -fastfloat_really_inline adjusted_mantissa to_extended(T value) noexcept { - adjusted_mantissa am; - int32_t bias = binary_format::mantissa_explicit_bits() - binary_format::minimum_exponent(); - if (std::is_same::value) { - constexpr uint32_t exponent_mask = 0x7F800000; - constexpr uint32_t mantissa_mask = 0x007FFFFF; - constexpr uint64_t hidden_bit_mask = 0x00800000; - uint32_t bits; - ::memcpy(&bits, &value, sizeof(T)); - if ((bits & exponent_mask) == 0) { - // denormal - am.power2 = 1 - bias; - am.mantissa = bits & mantissa_mask; - } else { - // normal - am.power2 = int32_t((bits & exponent_mask) >> binary_format::mantissa_explicit_bits()); - am.power2 -= bias; - am.mantissa = (bits & mantissa_mask) | hidden_bit_mask; - } - } else { - constexpr uint64_t exponent_mask = 0x7FF0000000000000; - constexpr uint64_t mantissa_mask = 0x000FFFFFFFFFFFFF; - constexpr uint64_t hidden_bit_mask = 0x0010000000000000; - uint64_t bits; - ::memcpy(&bits, &value, sizeof(T)); - if ((bits & exponent_mask) == 0) { - // denormal - am.power2 = 1 - bias; - am.mantissa = bits & mantissa_mask; - } else { - // normal - am.power2 = int32_t((bits & exponent_mask) >> binary_format::mantissa_explicit_bits()); - am.power2 -= bias; - am.mantissa = (bits & mantissa_mask) | hidden_bit_mask; - } - } - - return am; -} - -// get the extended precision value of the halfway point between b and b+u. -// we are given a native float that represents b, so we need to adjust it -// halfway between b and b+u. -template -fastfloat_really_inline adjusted_mantissa to_extended_halfway(T value) noexcept { - adjusted_mantissa am = to_extended(value); - am.mantissa <<= 1; - am.mantissa += 1; - am.power2 -= 1; - return am; -} - -// round an extended-precision float to the nearest machine float. -template -fastfloat_really_inline void round(adjusted_mantissa& am, callback cb) noexcept { - int32_t mantissa_shift = 64 - binary_format::mantissa_explicit_bits() - 1; - if (-am.power2 >= mantissa_shift) { - // have a denormal float - int32_t shift = -am.power2 + 1; - cb(am, std::min(shift, 64)); - // check for round-up: if rounding-nearest carried us to the hidden bit. - am.power2 = (am.mantissa < (uint64_t(1) << binary_format::mantissa_explicit_bits())) ? 0 : 1; - return; - } - - // have a normal float, use the default shift. - cb(am, mantissa_shift); - - // check for carry - if (am.mantissa >= (uint64_t(2) << binary_format::mantissa_explicit_bits())) { - am.mantissa = (uint64_t(1) << binary_format::mantissa_explicit_bits()); - am.power2++; - } - - // check for infinite: we could have carried to an infinite power - am.mantissa &= ~(uint64_t(1) << binary_format::mantissa_explicit_bits()); - if (am.power2 >= binary_format::infinite_power()) { - am.power2 = binary_format::infinite_power(); - am.mantissa = 0; - } -} - -template -fastfloat_really_inline -void round_nearest_tie_even(adjusted_mantissa& am, int32_t shift, callback cb) noexcept { - uint64_t mask; - uint64_t halfway; - if (shift == 64) { - mask = UINT64_MAX; - } else { - mask = (uint64_t(1) << shift) - 1; - } - if (shift == 0) { - halfway = 0; - } else { - halfway = uint64_t(1) << (shift - 1); - } - uint64_t truncated_bits = am.mantissa & mask; - uint64_t is_above = truncated_bits > halfway; - uint64_t is_halfway = truncated_bits == halfway; - - // shift digits into position - if (shift == 64) { - am.mantissa = 0; - } else { - am.mantissa >>= shift; - } - am.power2 += shift; - - bool is_odd = (am.mantissa & 1) == 1; - am.mantissa += uint64_t(cb(is_odd, is_halfway, is_above)); -} - -fastfloat_really_inline void round_down(adjusted_mantissa& am, int32_t shift) noexcept { - if (shift == 64) { - am.mantissa = 0; - } else { - am.mantissa >>= shift; - } - am.power2 += shift; -} - -fastfloat_really_inline void skip_zeros(const char*& first, const char* last) noexcept { - uint64_t val; - while (std::distance(first, last) >= 8) { - ::memcpy(&val, first, sizeof(uint64_t)); - if (val != 0x3030303030303030) { - break; - } - first += 8; - } - while (first != last) { - if (*first != '0') { - break; - } - first++; - } -} - -// determine if any non-zero digits were truncated. -// all characters must be valid digits. -fastfloat_really_inline bool is_truncated(const char* first, const char* last) noexcept { - // do 8-bit optimizations, can just compare to 8 literal 0s. - uint64_t val; - while (std::distance(first, last) >= 8) { - ::memcpy(&val, first, sizeof(uint64_t)); - if (val != 0x3030303030303030) { - return true; - } - first += 8; - } - while (first != last) { - if (*first != '0') { - return true; - } - first++; - } - return false; -} - -fastfloat_really_inline bool is_truncated(byte_span s) noexcept { - return is_truncated(s.ptr, s.ptr + s.len()); -} - -fastfloat_really_inline -void parse_eight_digits(const char*& p, limb& value, size_t& counter, size_t& count) noexcept { - value = value * 100000000 + parse_eight_digits_unrolled(p); - p += 8; - counter += 8; - count += 8; -} - -fastfloat_really_inline -void parse_one_digit(const char*& p, limb& value, size_t& counter, size_t& count) noexcept { - value = value * 10 + limb(*p - '0'); - p++; - counter++; - count++; -} - -fastfloat_really_inline -void add_native(bigint& big, limb power, limb value) noexcept { - big.mul(power); - big.add(value); -} - -fastfloat_really_inline void round_up_bigint(bigint& big, size_t& count) noexcept { - // need to round-up the digits, but need to avoid rounding - // ....9999 to ...10000, which could cause a false halfway point. - add_native(big, 10, 1); - count++; -} - -// parse the significant digits into a big integer -inline void parse_mantissa(bigint& result, parsed_number_string& num, size_t max_digits, size_t& digits) noexcept { - // try to minimize the number of big integer and scalar multiplication. - // therefore, try to parse 8 digits at a time, and multiply by the largest - // scalar value (9 or 19 digits) for each step. - size_t counter = 0; - digits = 0; - limb value = 0; -#ifdef FASTFLOAT_64BIT_LIMB - size_t step = 19; -#else - size_t step = 9; -#endif - - // process all integer digits. - const char* p = num.integer.ptr; - const char* pend = p + num.integer.len(); - skip_zeros(p, pend); - // process all digits, in increments of step per loop - while (p != pend) { - while ((std::distance(p, pend) >= 8) && (step - counter >= 8) && (max_digits - digits >= 8)) { - parse_eight_digits(p, value, counter, digits); - } - while (counter < step && p != pend && digits < max_digits) { - parse_one_digit(p, value, counter, digits); - } - if (digits == max_digits) { - // add the temporary value, then check if we've truncated any digits - add_native(result, limb(powers_of_ten_uint64[counter]), value); - bool truncated = is_truncated(p, pend); - if (num.fraction.ptr != nullptr) { - truncated |= is_truncated(num.fraction); - } - if (truncated) { - round_up_bigint(result, digits); - } - return; - } else { - add_native(result, limb(powers_of_ten_uint64[counter]), value); - counter = 0; - value = 0; - } - } - - // add our fraction digits, if they're available. - if (num.fraction.ptr != nullptr) { - p = num.fraction.ptr; - pend = p + num.fraction.len(); - if (digits == 0) { - skip_zeros(p, pend); - } - // process all digits, in increments of step per loop - while (p != pend) { - while ((std::distance(p, pend) >= 8) && (step - counter >= 8) && (max_digits - digits >= 8)) { - parse_eight_digits(p, value, counter, digits); - } - while (counter < step && p != pend && digits < max_digits) { - parse_one_digit(p, value, counter, digits); - } - if (digits == max_digits) { - // add the temporary value, then check if we've truncated any digits - add_native(result, limb(powers_of_ten_uint64[counter]), value); - bool truncated = is_truncated(p, pend); - if (truncated) { - round_up_bigint(result, digits); - } - return; - } else { - add_native(result, limb(powers_of_ten_uint64[counter]), value); - counter = 0; - value = 0; - } - } - } - - if (counter != 0) { - add_native(result, limb(powers_of_ten_uint64[counter]), value); - } -} - -template -inline adjusted_mantissa positive_digit_comp(bigint& bigmant, int32_t exponent) noexcept { - FASTFLOAT_ASSERT(bigmant.pow10(uint32_t(exponent))); - adjusted_mantissa answer; - bool truncated; - answer.mantissa = bigmant.hi64(truncated); - int bias = binary_format::mantissa_explicit_bits() - binary_format::minimum_exponent(); - answer.power2 = bigmant.bit_length() - 64 + bias; - - round(answer, [truncated](adjusted_mantissa& a, int32_t shift) { - round_nearest_tie_even(a, shift, [truncated](bool is_odd, bool is_halfway, bool is_above) -> bool { - return is_above || (is_halfway && truncated) || (is_odd && is_halfway); - }); - }); - - return answer; -} - -// the scaling here is quite simple: we have, for the real digits `m * 10^e`, -// and for the theoretical digits `n * 2^f`. Since `e` is always negative, -// to scale them identically, we do `n * 2^f * 5^-f`, so we now have `m * 2^e`. -// we then need to scale by `2^(f- e)`, and then the two significant digits -// are of the same magnitude. -template -inline adjusted_mantissa negative_digit_comp(bigint& bigmant, adjusted_mantissa am, int32_t exponent) noexcept { - bigint& real_digits = bigmant; - int32_t real_exp = exponent; - - // get the value of `b`, rounded down, and get a bigint representation of b+h - adjusted_mantissa am_b = am; - // gcc7 buf: use a lambda to remove the noexcept qualifier bug with -Wnoexcept-type. - round(am_b, [](adjusted_mantissa&a, int32_t shift) { round_down(a, shift); }); - T b; - to_float(false, am_b, b); - adjusted_mantissa theor = to_extended_halfway(b); - bigint theor_digits(theor.mantissa); - int32_t theor_exp = theor.power2; - - // scale real digits and theor digits to be same power. - int32_t pow2_exp = theor_exp - real_exp; - uint32_t pow5_exp = uint32_t(-real_exp); - if (pow5_exp != 0) { - FASTFLOAT_ASSERT(theor_digits.pow5(pow5_exp)); - } - if (pow2_exp > 0) { - FASTFLOAT_ASSERT(theor_digits.pow2(uint32_t(pow2_exp))); - } else if (pow2_exp < 0) { - FASTFLOAT_ASSERT(real_digits.pow2(uint32_t(-pow2_exp))); - } - - // compare digits, and use it to director rounding - int ord = real_digits.compare(theor_digits); - adjusted_mantissa answer = am; - round(answer, [ord](adjusted_mantissa& a, int32_t shift) { - round_nearest_tie_even(a, shift, [ord](bool is_odd, bool _, bool __) -> bool { - (void)_; // not needed, since we've done our comparison - (void)__; // not needed, since we've done our comparison - if (ord > 0) { - return true; - } else if (ord < 0) { - return false; - } else { - return is_odd; - } - }); - }); - - return answer; -} - -// parse the significant digits as a big integer to unambiguously round the -// the significant digits. here, we are trying to determine how to round -// an extended float representation close to `b+h`, halfway between `b` -// (the float rounded-down) and `b+u`, the next positive float. this -// algorithm is always correct, and uses one of two approaches. when -// the exponent is positive relative to the significant digits (such as -// 1234), we create a big-integer representation, get the high 64-bits, -// determine if any lower bits are truncated, and use that to direct -// rounding. in case of a negative exponent relative to the significant -// digits (such as 1.2345), we create a theoretical representation of -// `b` as a big-integer type, scaled to the same binary exponent as -// the actual digits. we then compare the big integer representations -// of both, and use that to direct rounding. -template -inline adjusted_mantissa digit_comp(parsed_number_string& num, adjusted_mantissa am) noexcept { - // remove the invalid exponent bias - am.power2 -= invalid_am_bias; - - int32_t sci_exp = scientific_exponent(num); - size_t max_digits = binary_format::max_digits(); - size_t digits = 0; - bigint bigmant; - parse_mantissa(bigmant, num, max_digits, digits); - // can't underflow, since digits is at most max_digits. - int32_t exponent = sci_exp + 1 - int32_t(digits); - if (exponent >= 0) { - return positive_digit_comp(bigmant, exponent); - } else { - return negative_digit_comp(bigmant, am, exponent); - } -} - -} // namespace fast_float - -#endif - - -#ifndef FASTFLOAT_PARSE_NUMBER_H -#define FASTFLOAT_PARSE_NUMBER_H - - -//included above: -//#include -//included above: -//#include -//included above: -//#include -//included above: -//#include - -namespace fast_float { - - -namespace detail { -/** - * Special case +inf, -inf, nan, infinity, -infinity. - * The case comparisons could be made much faster given that we know that the - * strings a null-free and fixed. - **/ -template -from_chars_result parse_infnan(const char *first, const char *last, T &value) noexcept { - from_chars_result answer; - answer.ptr = first; - answer.ec = std::errc(); // be optimistic - bool minusSign = false; - if (*first == '-') { // assume first < last, so dereference without checks; C++17 20.19.3.(7.1) explicitly forbids '+' here - minusSign = true; - ++first; - } - if (last - first >= 3) { - if (fastfloat_strncasecmp(first, "nan", 3)) { - answer.ptr = (first += 3); - value = minusSign ? -std::numeric_limits::quiet_NaN() : std::numeric_limits::quiet_NaN(); - // Check for possible nan(n-char-seq-opt), C++17 20.19.3.7, C11 7.20.1.3.3. At least MSVC produces nan(ind) and nan(snan). - if(first != last && *first == '(') { - for(const char* ptr = first + 1; ptr != last; ++ptr) { - if (*ptr == ')') { - answer.ptr = ptr + 1; // valid nan(n-char-seq-opt) - break; - } - else if(!(('a' <= *ptr && *ptr <= 'z') || ('A' <= *ptr && *ptr <= 'Z') || ('0' <= *ptr && *ptr <= '9') || *ptr == '_')) - break; // forbidden char, not nan(n-char-seq-opt) - } - } - return answer; - } - if (fastfloat_strncasecmp(first, "inf", 3)) { - if ((last - first >= 8) && fastfloat_strncasecmp(first + 3, "inity", 5)) { - answer.ptr = first + 8; - } else { - answer.ptr = first + 3; - } - value = minusSign ? -std::numeric_limits::infinity() : std::numeric_limits::infinity(); - return answer; - } - } - answer.ec = std::errc::invalid_argument; - return answer; -} - -} // namespace detail - -template -from_chars_result from_chars(const char *first, const char *last, - T &value, chars_format fmt /*= chars_format::general*/) noexcept { - return from_chars_advanced(first, last, value, parse_options{fmt}); -} - -template -from_chars_result from_chars_advanced(const char *first, const char *last, - T &value, parse_options options) noexcept { - - static_assert (std::is_same::value || std::is_same::value, "only float and double are supported"); - - - from_chars_result answer; - if (first == last) { - answer.ec = std::errc::invalid_argument; - answer.ptr = first; - return answer; - } - parsed_number_string pns = parse_number_string(first, last, options); - if (!pns.valid) { - return detail::parse_infnan(first, last, value); - } - answer.ec = std::errc(); // be optimistic - answer.ptr = pns.lastmatch; - // Next is Clinger's fast path. - if (binary_format::min_exponent_fast_path() <= pns.exponent && pns.exponent <= binary_format::max_exponent_fast_path() && pns.mantissa <=binary_format::max_mantissa_fast_path() && !pns.too_many_digits) { - value = T(pns.mantissa); - if (pns.exponent < 0) { value = value / binary_format::exact_power_of_ten(-pns.exponent); } - else { value = value * binary_format::exact_power_of_ten(pns.exponent); } - if (pns.negative) { value = -value; } - return answer; - } - adjusted_mantissa am = compute_float>(pns.exponent, pns.mantissa); - if(pns.too_many_digits && am.power2 >= 0) { - if(am != compute_float>(pns.exponent, pns.mantissa + 1)) { - am = compute_error>(pns.exponent, pns.mantissa); - } - } - // If we called compute_float>(pns.exponent, pns.mantissa) and we have an invalid power (am.power2 < 0), - // then we need to go the long way around again. This is very uncommon. - if(am.power2 < 0) { am = digit_comp(pns, am); } - to_float(pns.negative, am, value); - return answer; -} - -} // namespace fast_float - -#endif - -#ifdef _MSC_VER -# pragma warning(pop) -#elif defined(__clang__) || defined(__APPLE_CC__) -# pragma clang diagnostic pop -#elif defined(__GNUC__) -# pragma GCC diagnostic pop -#endif - -#endif // _C4_EXT_FAST_FLOAT_HPP_ - - -// (end https://github.com/biojppm/c4core/src/c4/ext/fast_float.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/std/vector_fwd.hpp -// https://github.com/biojppm/c4core/src/c4/std/vector_fwd.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_STD_VECTOR_FWD_HPP_ -#define _C4_STD_VECTOR_FWD_HPP_ - -/** @file vector_fwd.hpp */ - -//included above: -//#include - -// forward declarations for std::vector -#if defined(__GLIBCXX__) || defined(__GLIBCPP__) || defined(_MSC_VER) -#if defined(_MSC_VER) -__pragma(warning(push)) -__pragma(warning(disable : 4643)) -#endif -namespace std { -template class allocator; -template class vector; -} // namespace std -#if defined(_MSC_VER) -__pragma(warning(pop)) -#endif -#elif defined(_LIBCPP_ABI_NAMESPACE) -namespace std { -inline namespace _LIBCPP_ABI_NAMESPACE { -template class allocator; -template class vector; -} // namespace _LIBCPP_ABI_NAMESPACE -} // namespace std -#else -#error "unknown standard library" -#endif - -#ifndef C4CORE_SINGLE_HEADER -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/substr_fwd.hpp -//#include "c4/substr_fwd.hpp" -#if !defined(C4_SUBSTR_FWD_HPP_) && !defined(_C4_SUBSTR_FWD_HPP_) -#error "amalgamate: file c4/substr_fwd.hpp must have been included at this point" -#endif /* C4_SUBSTR_FWD_HPP_ */ - -#endif - -namespace c4 { - -template c4::substr to_substr(std::vector &vec); -template c4::csubstr to_csubstr(std::vector const& vec); - -template bool operator!= (c4::csubstr ss, std::vector const& s); -template bool operator== (c4::csubstr ss, std::vector const& s); -template bool operator>= (c4::csubstr ss, std::vector const& s); -template bool operator> (c4::csubstr ss, std::vector const& s); -template bool operator<= (c4::csubstr ss, std::vector const& s); -template bool operator< (c4::csubstr ss, std::vector const& s); - -template bool operator!= (std::vector const& s, c4::csubstr ss); -template bool operator== (std::vector const& s, c4::csubstr ss); -template bool operator>= (std::vector const& s, c4::csubstr ss); -template bool operator> (std::vector const& s, c4::csubstr ss); -template bool operator<= (std::vector const& s, c4::csubstr ss); -template bool operator< (std::vector const& s, c4::csubstr ss); - -template size_t to_chars(c4::substr buf, std::vector const& s); -template bool from_chars(c4::csubstr buf, std::vector * s); - -} // namespace c4 - -#endif // _C4_STD_VECTOR_FWD_HPP_ - - -// (end https://github.com/biojppm/c4core/src/c4/std/vector_fwd.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/std/string_fwd.hpp -// https://github.com/biojppm/c4core/src/c4/std/string_fwd.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_STD_STRING_FWD_HPP_ -#define _C4_STD_STRING_FWD_HPP_ - -/** @file string_fwd.hpp */ - -#ifndef DOXYGEN - -#ifndef C4CORE_SINGLE_HEADER -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/substr_fwd.hpp -//#include "c4/substr_fwd.hpp" -#if !defined(C4_SUBSTR_FWD_HPP_) && !defined(_C4_SUBSTR_FWD_HPP_) -#error "amalgamate: file c4/substr_fwd.hpp must have been included at this point" -#endif /* C4_SUBSTR_FWD_HPP_ */ - -#endif - -//included above: -//#include - -// forward declarations for std::string -#if defined(__GLIBCXX__) || defined(__GLIBCPP__) -#include // use the fwd header in glibcxx -#elif defined(_LIBCPP_VERSION) || defined(__APPLE_CC__) -#include // use the fwd header in stdlibc++ -#elif defined(_MSC_VER) -//! @todo is there a fwd header in msvc? -namespace std { -template struct char_traits; -template class allocator; -template class basic_string; -using string = basic_string, allocator>; -} /* namespace std */ -#else -#error "unknown standard library" -#endif - -namespace c4 { - -C4_ALWAYS_INLINE c4::substr to_substr(std::string &s) noexcept; -C4_ALWAYS_INLINE c4::csubstr to_csubstr(std::string const& s) noexcept; - -bool operator== (c4::csubstr ss, std::string const& s); -bool operator!= (c4::csubstr ss, std::string const& s); -bool operator>= (c4::csubstr ss, std::string const& s); -bool operator> (c4::csubstr ss, std::string const& s); -bool operator<= (c4::csubstr ss, std::string const& s); -bool operator< (c4::csubstr ss, std::string const& s); - -bool operator== (std::string const& s, c4::csubstr ss); -bool operator!= (std::string const& s, c4::csubstr ss); -bool operator>= (std::string const& s, c4::csubstr ss); -bool operator> (std::string const& s, c4::csubstr ss); -bool operator<= (std::string const& s, c4::csubstr ss); -bool operator< (std::string const& s, c4::csubstr ss); - -size_t to_chars(c4::substr buf, std::string const& s); -bool from_chars(c4::csubstr buf, std::string * s); - -} // namespace c4 - -#endif // DOXYGEN -#endif // _C4_STD_STRING_FWD_HPP_ - - -// (end https://github.com/biojppm/c4core/src/c4/std/string_fwd.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/std/std_fwd.hpp -// https://github.com/biojppm/c4core/src/c4/std/std_fwd.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_STD_STD_FWD_HPP_ -#define _C4_STD_STD_FWD_HPP_ - -/** @file std_fwd.hpp includes all c4-std interop fwd files */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/std/vector_fwd.hpp -//#include "c4/std/vector_fwd.hpp" -#if !defined(C4_STD_VECTOR_FWD_HPP_) && !defined(_C4_STD_VECTOR_FWD_HPP_) -#error "amalgamate: file c4/std/vector_fwd.hpp must have been included at this point" -#endif /* C4_STD_VECTOR_FWD_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/std/string_fwd.hpp -//#include "c4/std/string_fwd.hpp" -#if !defined(C4_STD_STRING_FWD_HPP_) && !defined(_C4_STD_STRING_FWD_HPP_) -#error "amalgamate: file c4/std/string_fwd.hpp must have been included at this point" -#endif /* C4_STD_STRING_FWD_HPP_ */ - -//#include "c4/std/tuple_fwd.hpp" - -#endif // _C4_STD_STD_FWD_HPP_ - - -// (end https://github.com/biojppm/c4core/src/c4/std/std_fwd.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/charconv.hpp -// https://github.com/biojppm/c4core/src/c4/charconv.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_CHARCONV_HPP_ -#define _C4_CHARCONV_HPP_ - -/** @file charconv.hpp Lightweight generic type-safe wrappers for - * converting individual values to/from strings. - * - * These are the main functions: - * - * @code{.cpp} - * // Convert the given value, writing into the string. - * // The resulting string will NOT be null-terminated. - * // Return the number of characters needed. - * // This function is safe to call when the string is too small - - * // no writes will occur beyond the string's last character. - * template size_t c4::to_chars(substr buf, T const& C4_RESTRICT val); - * - * - * // Convert the given value to a string using to_chars(), and - * // return the resulting string, up to and including the last - * // written character. - * template substr c4::to_chars_sub(substr buf, T const& C4_RESTRICT val); - * - * - * // Read a value from the string, which must be - * // trimmed to the value (ie, no leading/trailing whitespace). - * // return true if the conversion succeeded. - * // There is no check for overflow; the value wraps around in a way similar - * // to the standard C/C++ overflow behavior. For example, - * // from_chars("128", &val) returns true and val will be - * // set tot 0. - * template bool c4::from_chars(csubstr buf, T * C4_RESTRICT val); - * - * - * // Read the first valid sequence of characters from the string, - * // skipping leading whitespace, and convert it using from_chars(). - * // Return the number of characters read for converting. - * template size_t c4::from_chars_first(csubstr buf, T * C4_RESTRICT val); - * @endcode - */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/language.hpp -//#include "c4/language.hpp" -#if !defined(C4_LANGUAGE_HPP_) && !defined(_C4_LANGUAGE_HPP_) -#error "amalgamate: file c4/language.hpp must have been included at this point" -#endif /* C4_LANGUAGE_HPP_ */ - -//included above: -//#include -//included above: -//#include -//included above: -//#include -//included above: -//#include -//included above: -//#include - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/config.hpp -//#include "c4/config.hpp" -#if !defined(C4_CONFIG_HPP_) && !defined(_C4_CONFIG_HPP_) -#error "amalgamate: file c4/config.hpp must have been included at this point" -#endif /* C4_CONFIG_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/substr.hpp -//#include "c4/substr.hpp" -#if !defined(C4_SUBSTR_HPP_) && !defined(_C4_SUBSTR_HPP_) -#error "amalgamate: file c4/substr.hpp must have been included at this point" -#endif /* C4_SUBSTR_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/std/std_fwd.hpp -//#include "c4/std/std_fwd.hpp" -#if !defined(C4_STD_STD_FWD_HPP_) && !defined(_C4_STD_STD_FWD_HPP_) -#error "amalgamate: file c4/std/std_fwd.hpp must have been included at this point" -#endif /* C4_STD_STD_FWD_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/memory_util.hpp -//#include "c4/memory_util.hpp" -#if !defined(C4_MEMORY_UTIL_HPP_) && !defined(_C4_MEMORY_UTIL_HPP_) -#error "amalgamate: file c4/memory_util.hpp must have been included at this point" -#endif /* C4_MEMORY_UTIL_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/szconv.hpp -//#include "c4/szconv.hpp" -#if !defined(C4_SZCONV_HPP_) && !defined(_C4_SZCONV_HPP_) -#error "amalgamate: file c4/szconv.hpp must have been included at this point" -#endif /* C4_SZCONV_HPP_ */ - - -#ifndef C4CORE_NO_FAST_FLOAT -# if (C4_CPP >= 17) -# if defined(_MSC_VER) -# if (C4_MSVC_VERSION >= C4_MSVC_VERSION_2019) // VS2017 and lower do not have these macros -# include -# define C4CORE_HAVE_STD_TOCHARS 1 -# define C4CORE_HAVE_STD_FROMCHARS 0 // prefer fast_float with MSVC -# define C4CORE_HAVE_FAST_FLOAT 1 -# else -# define C4CORE_HAVE_STD_TOCHARS 0 -# define C4CORE_HAVE_STD_FROMCHARS 0 -# define C4CORE_HAVE_FAST_FLOAT 1 -# endif -# else -# if __has_include() -//included above: -//# include -# if defined(__cpp_lib_to_chars) -# define C4CORE_HAVE_STD_TOCHARS 1 -# define C4CORE_HAVE_STD_FROMCHARS 0 // glibc uses fast_float internally -# define C4CORE_HAVE_FAST_FLOAT 1 -# else -# define C4CORE_HAVE_STD_TOCHARS 0 -# define C4CORE_HAVE_STD_FROMCHARS 0 -# define C4CORE_HAVE_FAST_FLOAT 1 -# endif -# else -# define C4CORE_HAVE_STD_TOCHARS 0 -# define C4CORE_HAVE_STD_FROMCHARS 0 -# define C4CORE_HAVE_FAST_FLOAT 1 -# endif -# endif -# else -# define C4CORE_HAVE_STD_TOCHARS 0 -# define C4CORE_HAVE_STD_FROMCHARS 0 -# define C4CORE_HAVE_FAST_FLOAT 1 -# endif -# if C4CORE_HAVE_FAST_FLOAT - C4_SUPPRESS_WARNING_GCC_WITH_PUSH("-Wsign-conversion") - C4_SUPPRESS_WARNING_GCC("-Warray-bounds") -# if __GNUC__ >= 5 - C4_SUPPRESS_WARNING_GCC("-Wshift-count-overflow") -# endif -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/ext/fast_float.hpp -//# include "c4/ext/fast_float.hpp" -#if !defined(C4_EXT_FAST_FLOAT_HPP_) && !defined(_C4_EXT_FAST_FLOAT_HPP_) -#error "amalgamate: file c4/ext/fast_float.hpp must have been included at this point" -#endif /* C4_EXT_FAST_FLOAT_HPP_ */ - - C4_SUPPRESS_WARNING_GCC_POP -# endif -#elif (C4_CPP >= 17) -# define C4CORE_HAVE_FAST_FLOAT 0 -# if defined(_MSC_VER) -# if (C4_MSVC_VERSION >= C4_MSVC_VERSION_2019) // VS2017 and lower do not have these macros -//included above: -//# include -# define C4CORE_HAVE_STD_TOCHARS 1 -# define C4CORE_HAVE_STD_FROMCHARS 1 -# else -# define C4CORE_HAVE_STD_TOCHARS 0 -# define C4CORE_HAVE_STD_FROMCHARS 0 -# endif -# else -# if __has_include() -//included above: -//# include -# if defined(__cpp_lib_to_chars) -# define C4CORE_HAVE_STD_TOCHARS 1 -# define C4CORE_HAVE_STD_FROMCHARS 1 // glibc uses fast_float internally -# else -# define C4CORE_HAVE_STD_TOCHARS 0 -# define C4CORE_HAVE_STD_FROMCHARS 0 -# endif -# else -# define C4CORE_HAVE_STD_TOCHARS 0 -# define C4CORE_HAVE_STD_FROMCHARS 0 -# endif -# endif -#else -# define C4CORE_HAVE_STD_TOCHARS 0 -# define C4CORE_HAVE_STD_FROMCHARS 0 -# define C4CORE_HAVE_FAST_FLOAT 0 -#endif - - -#if !C4CORE_HAVE_STD_FROMCHARS -#include -#endif - - -#ifdef _MSC_VER -# pragma warning(push) -# if C4_MSVC_VERSION != C4_MSVC_VERSION_2017 -# pragma warning(disable: 4800) //'int': forcing value to bool 'true' or 'false' (performance warning) -# endif -# pragma warning(disable: 4996) // snprintf/scanf: this function or variable may be unsafe -#elif defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wtautological-constant-out-of-range-compare" -# pragma clang diagnostic ignored "-Wformat-nonliteral" -# pragma clang diagnostic ignored "-Wdouble-promotion" // implicit conversion increases floating-point precision -#elif defined(__GNUC__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wformat-nonliteral" -# pragma GCC diagnostic ignored "-Wdouble-promotion" // implicit conversion increases floating-point precision -# pragma GCC diagnostic ignored "-Wuseless-cast" -#endif - - -namespace c4 { - -#if C4CORE_HAVE_STD_TOCHARS -/** @warning Use only the symbol. Do not rely on the type or naked value of this enum. */ -typedef enum : std::underlying_type::type { - /** print the real number in floating point format (like %f) */ - FTOA_FLOAT = static_cast::type>(std::chars_format::fixed), - /** print the real number in scientific format (like %e) */ - FTOA_SCIENT = static_cast::type>(std::chars_format::scientific), - /** print the real number in flexible format (like %g) */ - FTOA_FLEX = static_cast::type>(std::chars_format::general), - /** print the real number in hexadecimal format (like %a) */ - FTOA_HEXA = static_cast::type>(std::chars_format::hex), -} RealFormat_e; -#else -/** @warning Use only the symbol. Do not rely on the type or naked value of this enum. */ -typedef enum : char { - /** print the real number in floating point format (like %f) */ - FTOA_FLOAT = 'f', - /** print the real number in scientific format (like %e) */ - FTOA_SCIENT = 'e', - /** print the real number in flexible format (like %g) */ - FTOA_FLEX = 'g', - /** print the real number in hexadecimal format (like %a) */ - FTOA_HEXA = 'a', -} RealFormat_e; -#endif - - -/** in some platforms, int,unsigned int - * are not any of int8_t...int64_t and - * long,unsigned long are not any of uint8_t...uint64_t */ -template -struct is_fixed_length -{ - enum : bool { - /** true if T is one of the fixed length signed types */ - value_i = (std::is_integral::value - && (std::is_same::value - || std::is_same::value - || std::is_same::value - || std::is_same::value)), - /** true if T is one of the fixed length unsigned types */ - value_u = (std::is_integral::value - && (std::is_same::value - || std::is_same::value - || std::is_same::value - || std::is_same::value)), - /** true if T is one of the fixed length signed or unsigned types */ - value = value_i || value_u - }; -}; - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -#ifdef _MSC_VER -# pragma warning(push) -#elif defined(__clang__) -# pragma clang diagnostic push -#elif defined(__GNUC__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wconversion" -# if __GNUC__ >= 6 -# pragma GCC diagnostic ignored "-Wnull-dereference" -# endif -#endif - -namespace detail { - -/* python command to get the values below: -def dec(v): - return str(v) -for bits in (8, 16, 32, 64): - imin, imax, umax = (-(1 << (bits - 1))), (1 << (bits - 1)) - 1, (1 << bits) - 1 - for vname, v in (("imin", imin), ("imax", imax), ("umax", umax)): - for f in (bin, oct, dec, hex): - print(f"{bits}b: {vname}={v} {f.__name__}: len={len(f(v)):2d}: {v} {f(v)}") -*/ - -// do not use the type as the template argument because in some -// platforms long!=int32 and long!=int64. Just use the numbytes -// which is more generic and spares lengthy SFINAE code. -template struct charconv_digits_; -template using charconv_digits = charconv_digits_::value>; - -template<> struct charconv_digits_<1u, true> // int8_t -{ - enum : size_t { - maxdigits_bin = 1 + 2 + 8, // -128==-0b10000000 - maxdigits_oct = 1 + 2 + 3, // -128==-0o200 - maxdigits_dec = 1 + 3, // -128 - maxdigits_hex = 1 + 2 + 2, // -128==-0x80 - maxdigits_bin_nopfx = 8, // -128==-0b10000000 - maxdigits_oct_nopfx = 3, // -128==-0o200 - maxdigits_dec_nopfx = 3, // -128 - maxdigits_hex_nopfx = 2, // -128==-0x80 - }; - // min values without sign! - static constexpr csubstr min_value_dec() noexcept { return csubstr("128"); } - static constexpr csubstr min_value_hex() noexcept { return csubstr("80"); } - static constexpr csubstr min_value_oct() noexcept { return csubstr("200"); } - static constexpr csubstr min_value_bin() noexcept { return csubstr("10000000"); } - static constexpr csubstr max_value_dec() noexcept { return csubstr("127"); } - static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 3) || (str.len == 3 && str[0] <= '1')); } -}; -template<> struct charconv_digits_<1u, false> // uint8_t -{ - enum : size_t { - maxdigits_bin = 2 + 8, // 255 0b11111111 - maxdigits_oct = 2 + 3, // 255 0o377 - maxdigits_dec = 3, // 255 - maxdigits_hex = 2 + 2, // 255 0xff - maxdigits_bin_nopfx = 8, // 255 0b11111111 - maxdigits_oct_nopfx = 3, // 255 0o377 - maxdigits_dec_nopfx = 3, // 255 - maxdigits_hex_nopfx = 2, // 255 0xff - }; - static constexpr csubstr max_value_dec() noexcept { return csubstr("255"); } - static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 3) || (str.len == 3 && str[0] <= '3')); } -}; -template<> struct charconv_digits_<2u, true> // int16_t -{ - enum : size_t { - maxdigits_bin = 1 + 2 + 16, // -32768 -0b1000000000000000 - maxdigits_oct = 1 + 2 + 6, // -32768 -0o100000 - maxdigits_dec = 1 + 5, // -32768 -32768 - maxdigits_hex = 1 + 2 + 4, // -32768 -0x8000 - maxdigits_bin_nopfx = 16, // -32768 -0b1000000000000000 - maxdigits_oct_nopfx = 6, // -32768 -0o100000 - maxdigits_dec_nopfx = 5, // -32768 -32768 - maxdigits_hex_nopfx = 4, // -32768 -0x8000 - }; - // min values without sign! - static constexpr csubstr min_value_dec() noexcept { return csubstr("32768"); } - static constexpr csubstr min_value_hex() noexcept { return csubstr("8000"); } - static constexpr csubstr min_value_oct() noexcept { return csubstr("100000"); } - static constexpr csubstr min_value_bin() noexcept { return csubstr("1000000000000000"); } - static constexpr csubstr max_value_dec() noexcept { return csubstr("32767"); } - static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 6)); } -}; -template<> struct charconv_digits_<2u, false> // uint16_t -{ - enum : size_t { - maxdigits_bin = 2 + 16, // 65535 0b1111111111111111 - maxdigits_oct = 2 + 6, // 65535 0o177777 - maxdigits_dec = 6, // 65535 65535 - maxdigits_hex = 2 + 4, // 65535 0xffff - maxdigits_bin_nopfx = 16, // 65535 0b1111111111111111 - maxdigits_oct_nopfx = 6, // 65535 0o177777 - maxdigits_dec_nopfx = 6, // 65535 65535 - maxdigits_hex_nopfx = 4, // 65535 0xffff - }; - static constexpr csubstr max_value_dec() noexcept { return csubstr("65535"); } - static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 6) || (str.len == 6 && str[0] <= '1')); } -}; -template<> struct charconv_digits_<4u, true> // int32_t -{ - enum : size_t { - maxdigits_bin = 1 + 2 + 32, // len=35: -2147483648 -0b10000000000000000000000000000000 - maxdigits_oct = 1 + 2 + 11, // len=14: -2147483648 -0o20000000000 - maxdigits_dec = 1 + 10, // len=11: -2147483648 -2147483648 - maxdigits_hex = 1 + 2 + 8, // len=11: -2147483648 -0x80000000 - maxdigits_bin_nopfx = 32, // len=35: -2147483648 -0b10000000000000000000000000000000 - maxdigits_oct_nopfx = 11, // len=14: -2147483648 -0o20000000000 - maxdigits_dec_nopfx = 10, // len=11: -2147483648 -2147483648 - maxdigits_hex_nopfx = 8, // len=11: -2147483648 -0x80000000 - }; - // min values without sign! - static constexpr csubstr min_value_dec() noexcept { return csubstr("2147483648"); } - static constexpr csubstr min_value_hex() noexcept { return csubstr("80000000"); } - static constexpr csubstr min_value_oct() noexcept { return csubstr("20000000000"); } - static constexpr csubstr min_value_bin() noexcept { return csubstr("10000000000000000000000000000000"); } - static constexpr csubstr max_value_dec() noexcept { return csubstr("2147483647"); } - static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 11) || (str.len == 11 && str[0] <= '1')); } -}; -template<> struct charconv_digits_<4u, false> // uint32_t -{ - enum : size_t { - maxdigits_bin = 2 + 32, // len=34: 4294967295 0b11111111111111111111111111111111 - maxdigits_oct = 2 + 11, // len=13: 4294967295 0o37777777777 - maxdigits_dec = 10, // len=10: 4294967295 4294967295 - maxdigits_hex = 2 + 8, // len=10: 4294967295 0xffffffff - maxdigits_bin_nopfx = 32, // len=34: 4294967295 0b11111111111111111111111111111111 - maxdigits_oct_nopfx = 11, // len=13: 4294967295 0o37777777777 - maxdigits_dec_nopfx = 10, // len=10: 4294967295 4294967295 - maxdigits_hex_nopfx = 8, // len=10: 4294967295 0xffffffff - }; - static constexpr csubstr max_value_dec() noexcept { return csubstr("4294967295"); } - static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 11) || (str.len == 11 && str[0] <= '3')); } -}; -template<> struct charconv_digits_<8u, true> // int32_t -{ - enum : size_t { - maxdigits_bin = 1 + 2 + 64, // len=67: -9223372036854775808 -0b1000000000000000000000000000000000000000000000000000000000000000 - maxdigits_oct = 1 + 2 + 22, // len=25: -9223372036854775808 -0o1000000000000000000000 - maxdigits_dec = 1 + 19, // len=20: -9223372036854775808 -9223372036854775808 - maxdigits_hex = 1 + 2 + 16, // len=19: -9223372036854775808 -0x8000000000000000 - maxdigits_bin_nopfx = 64, // len=67: -9223372036854775808 -0b1000000000000000000000000000000000000000000000000000000000000000 - maxdigits_oct_nopfx = 22, // len=25: -9223372036854775808 -0o1000000000000000000000 - maxdigits_dec_nopfx = 19, // len=20: -9223372036854775808 -9223372036854775808 - maxdigits_hex_nopfx = 16, // len=19: -9223372036854775808 -0x8000000000000000 - }; - static constexpr csubstr min_value_dec() noexcept { return csubstr("9223372036854775808"); } - static constexpr csubstr min_value_hex() noexcept { return csubstr("8000000000000000"); } - static constexpr csubstr min_value_oct() noexcept { return csubstr("1000000000000000000000"); } - static constexpr csubstr min_value_bin() noexcept { return csubstr("1000000000000000000000000000000000000000000000000000000000000000"); } - static constexpr csubstr max_value_dec() noexcept { return csubstr("9223372036854775807"); } - static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 22)); } -}; -template<> struct charconv_digits_<8u, false> -{ - enum : size_t { - maxdigits_bin = 2 + 64, // len=66: 18446744073709551615 0b1111111111111111111111111111111111111111111111111111111111111111 - maxdigits_oct = 2 + 22, // len=24: 18446744073709551615 0o1777777777777777777777 - maxdigits_dec = 20, // len=20: 18446744073709551615 18446744073709551615 - maxdigits_hex = 2 + 16, // len=18: 18446744073709551615 0xffffffffffffffff - maxdigits_bin_nopfx = 64, // len=66: 18446744073709551615 0b1111111111111111111111111111111111111111111111111111111111111111 - maxdigits_oct_nopfx = 22, // len=24: 18446744073709551615 0o1777777777777777777777 - maxdigits_dec_nopfx = 20, // len=20: 18446744073709551615 18446744073709551615 - maxdigits_hex_nopfx = 16, // len=18: 18446744073709551615 0xffffffffffffffff - }; - static constexpr csubstr max_value_dec() noexcept { return csubstr("18446744073709551615"); } - static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 22) || (str.len == 22 && str[0] <= '1')); } -}; -} // namespace detail - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -// Helper macros, undefined below -#define _c4append(c) { if(C4_LIKELY(pos < buf.len)) { buf.str[pos++] = static_cast(c); } else { ++pos; } } -#define _c4appendhex(i) { if(C4_LIKELY(pos < buf.len)) { buf.str[pos++] = hexchars[i]; } else { ++pos; } } - -/** @name digits_dec return the number of digits required to encode a - * decimal number. - * - * @note At first sight this code may look heavily branchy and - * therefore inefficient. However, measurements revealed this to be - * the fastest among the alternatives. - * - * @see https://github.com/biojppm/c4core/pull/77 */ -/** @{ */ - -template -C4_CONSTEXPR14 C4_ALWAYS_INLINE -auto digits_dec(T v) noexcept - -> typename std::enable_if::type -{ - C4_STATIC_ASSERT(std::is_integral::value); - C4_ASSERT(v >= 0); - return ((v >= 100) ? 3u : ((v >= 10) ? 2u : 1u)); -} - -template -C4_CONSTEXPR14 C4_ALWAYS_INLINE -auto digits_dec(T v) noexcept - -> typename std::enable_if::type -{ - C4_STATIC_ASSERT(std::is_integral::value); - C4_ASSERT(v >= 0); - return ((v >= 10000) ? 5u : (v >= 1000) ? 4u : (v >= 100) ? 3u : (v >= 10) ? 2u : 1u); -} - -template -C4_CONSTEXPR14 C4_ALWAYS_INLINE -auto digits_dec(T v) noexcept - -> typename std::enable_if::type -{ - C4_STATIC_ASSERT(std::is_integral::value); - C4_ASSERT(v >= 0); - return ((v >= 1000000000) ? 10u : (v >= 100000000) ? 9u : (v >= 10000000) ? 8u : - (v >= 1000000) ? 7u : (v >= 100000) ? 6u : (v >= 10000) ? 5u : - (v >= 1000) ? 4u : (v >= 100) ? 3u : (v >= 10) ? 2u : 1u); -} - -template -C4_CONSTEXPR14 C4_ALWAYS_INLINE -auto digits_dec(T v) noexcept - -> typename std::enable_if::type -{ - // thanks @fargies!!! - // https://github.com/biojppm/c4core/pull/77#issuecomment-1063753568 - C4_STATIC_ASSERT(std::is_integral::value); - C4_ASSERT(v >= 0); - if(v >= 1000000000) // 10 - { - if(v >= 100000000000000) // 15 [15-20] range - { - if(v >= 100000000000000000) // 18 (15 + (20 - 15) / 2) - { - if((typename std::make_unsigned::type)v >= 10000000000000000000u) // 20 - return 20u; - else - return (v >= 1000000000000000000) ? 19u : 18u; - } - else if(v >= 10000000000000000) // 17 - return 17u; - else - return(v >= 1000000000000000) ? 16u : 15u; - } - else if(v >= 1000000000000) // 13 - return (v >= 10000000000000) ? 14u : 13u; - else if(v >= 100000000000) // 12 - return 12; - else - return(v >= 10000000000) ? 11u : 10u; - } - else if(v >= 10000) // 5 [5-9] range - { - if(v >= 10000000) // 8 - return (v >= 100000000) ? 9u : 8u; - else if(v >= 1000000) // 7 - return 7; - else - return (v >= 100000) ? 6u : 5u; - } - else if(v >= 100) - return (v >= 1000) ? 4u : 3u; - else - return (v >= 10) ? 2u : 1u; -} - -/** @} */ - - -template -C4_CONSTEXPR14 C4_ALWAYS_INLINE unsigned digits_hex(T v) noexcept -{ - C4_STATIC_ASSERT(std::is_integral::value); - C4_ASSERT(v >= 0); - return v ? 1u + (msb((typename std::make_unsigned::type)v) >> 2u) : 1u; -} - -template -C4_CONSTEXPR14 C4_ALWAYS_INLINE unsigned digits_bin(T v) noexcept -{ - C4_STATIC_ASSERT(std::is_integral::value); - C4_ASSERT(v >= 0); - return v ? 1u + msb((typename std::make_unsigned::type)v) : 1u; -} - -template -C4_CONSTEXPR14 C4_ALWAYS_INLINE unsigned digits_oct(T v_) noexcept -{ - // TODO: is there a better way? - C4_STATIC_ASSERT(std::is_integral::value); - C4_ASSERT(v_ >= 0); - using U = typename - std::conditional::type>::type; - U v = (U) v_; // safe because we require v_ >= 0 - unsigned __n = 1; - const unsigned __b2 = 64u; - const unsigned __b3 = __b2 * 8u; - const unsigned long __b4 = __b3 * 8u; - while(true) - { - if(v < 8u) - return __n; - if(v < __b2) - return __n + 1; - if(v < __b3) - return __n + 2; - if(v < __b4) - return __n + 3; - v /= (U) __b4; - __n += 4; - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -namespace detail { -C4_INLINE_CONSTEXPR const char hexchars[] = "0123456789abcdef"; -C4_INLINE_CONSTEXPR const char digits0099[] = - "0001020304050607080910111213141516171819" - "2021222324252627282930313233343536373839" - "4041424344454647484950515253545556575859" - "6061626364656667686970717273747576777879" - "8081828384858687888990919293949596979899"; -} // namespace detail - -C4_SUPPRESS_WARNING_GCC_PUSH -C4_SUPPRESS_WARNING_GCC("-Warray-bounds") // gcc has false positives here -#if (defined(__GNUC__) && (__GNUC__ >= 7)) -C4_SUPPRESS_WARNING_GCC("-Wstringop-overflow") // gcc has false positives here -#endif - -template -C4_HOT C4_ALWAYS_INLINE -void write_dec_unchecked(substr buf, T v, unsigned digits_v) noexcept -{ - C4_STATIC_ASSERT(std::is_integral::value); - C4_ASSERT(v >= 0); - C4_ASSERT(buf.len >= digits_v); - C4_XASSERT(digits_v == digits_dec(v)); - // in bm_xtoa: checkoncelog_singlediv_write2 - while(v >= T(100)) - { - const T quo = v / T(100); - const auto num = (v - quo * T(100)) << 1u; - v = quo; - buf.str[--digits_v] = detail::digits0099[num + 1]; - buf.str[--digits_v] = detail::digits0099[num]; - } - if(v >= T(10)) - { - C4_ASSERT(digits_v == 2); - const auto num = v << 1u; - buf.str[1] = detail::digits0099[num + 1]; - buf.str[0] = detail::digits0099[num]; - } - else - { - C4_ASSERT(digits_v == 1); - buf.str[0] = (char)('0' + v); - } -} - - -template -C4_HOT C4_ALWAYS_INLINE -void write_hex_unchecked(substr buf, T v, unsigned digits_v) noexcept -{ - C4_STATIC_ASSERT(std::is_integral::value); - C4_ASSERT(v >= 0); - C4_ASSERT(buf.len >= digits_v); - C4_XASSERT(digits_v == digits_hex(v)); - do { - buf.str[--digits_v] = detail::hexchars[v & T(15)]; - v >>= 4; - } while(v); - C4_ASSERT(digits_v == 0); -} - - -template -C4_HOT C4_ALWAYS_INLINE -void write_oct_unchecked(substr buf, T v, unsigned digits_v) noexcept -{ - C4_STATIC_ASSERT(std::is_integral::value); - C4_ASSERT(v >= 0); - C4_ASSERT(buf.len >= digits_v); - C4_XASSERT(digits_v == digits_oct(v)); - do { - buf.str[--digits_v] = (char)('0' + (v & T(7))); - v >>= 3; - } while(v); - C4_ASSERT(digits_v == 0); -} - - -template -C4_HOT C4_ALWAYS_INLINE -void write_bin_unchecked(substr buf, T v, unsigned digits_v) noexcept -{ - C4_STATIC_ASSERT(std::is_integral::value); - C4_ASSERT(v >= 0); - C4_ASSERT(buf.len >= digits_v); - C4_XASSERT(digits_v == digits_bin(v)); - do { - buf.str[--digits_v] = (char)('0' + (v & T(1))); - v >>= 1; - } while(v); - C4_ASSERT(digits_v == 0); -} - - -/** write an integer to a string in decimal format. This is the - * lowest level (and the fastest) function to do this task. - * @note does not accept negative numbers - * @note the resulting string is NOT zero-terminated. - * @note it is ok to call this with an empty or too-small buffer; - * no writes will occur, and the required size will be returned - * @return the number of characters required for the buffer. */ -template -C4_ALWAYS_INLINE size_t write_dec(substr buf, T v) noexcept -{ - C4_STATIC_ASSERT(std::is_integral::value); - C4_ASSERT(v >= 0); - unsigned digits = digits_dec(v); - if(C4_LIKELY(buf.len >= digits)) - write_dec_unchecked(buf, v, digits); - return digits; -} - -/** write an integer to a string in hexadecimal format. This is the - * lowest level (and the fastest) function to do this task. - * @note does not accept negative numbers - * @note does not prefix with 0x - * @note the resulting string is NOT zero-terminated. - * @note it is ok to call this with an empty or too-small buffer; - * no writes will occur, and the required size will be returned - * @return the number of characters required for the buffer. */ -template -C4_ALWAYS_INLINE size_t write_hex(substr buf, T v) noexcept -{ - C4_STATIC_ASSERT(std::is_integral::value); - C4_ASSERT(v >= 0); - unsigned digits = digits_hex(v); - if(C4_LIKELY(buf.len >= digits)) - write_hex_unchecked(buf, v, digits); - return digits; -} - -/** write an integer to a string in octal format. This is the - * lowest level (and the fastest) function to do this task. - * @note does not accept negative numbers - * @note does not prefix with 0o - * @note the resulting string is NOT zero-terminated. - * @note it is ok to call this with an empty or too-small buffer; - * no writes will occur, and the required size will be returned - * @return the number of characters required for the buffer. */ -template -C4_ALWAYS_INLINE size_t write_oct(substr buf, T v) noexcept -{ - C4_STATIC_ASSERT(std::is_integral::value); - C4_ASSERT(v >= 0); - unsigned digits = digits_oct(v); - if(C4_LIKELY(buf.len >= digits)) - write_oct_unchecked(buf, v, digits); - return digits; -} - -/** write an integer to a string in binary format. This is the - * lowest level (and the fastest) function to do this task. - * @note does not accept negative numbers - * @note does not prefix with 0b - * @note the resulting string is NOT zero-terminated. - * @note it is ok to call this with an empty or too-small buffer; - * no writes will occur, and the required size will be returned - * @return the number of characters required for the buffer. */ -template -C4_ALWAYS_INLINE size_t write_bin(substr buf, T v) noexcept -{ - C4_STATIC_ASSERT(std::is_integral::value); - C4_ASSERT(v >= 0); - unsigned digits = digits_bin(v); - C4_ASSERT(digits > 0); - if(C4_LIKELY(buf.len >= digits)) - write_bin_unchecked(buf, v, digits); - return digits; -} - - -namespace detail { -template using NumberWriter = size_t (*)(substr, U); -template writer> -size_t write_num_digits(substr buf, T v, size_t num_digits) noexcept -{ - C4_STATIC_ASSERT(std::is_integral::value); - size_t ret = writer(buf, v); - if(ret >= num_digits) - return ret; - else if(ret >= buf.len || num_digits > buf.len) - return num_digits; - C4_ASSERT(num_digits >= ret); - size_t delta = static_cast(num_digits - ret); - memmove(buf.str + delta, buf.str, ret); - memset(buf.str, '0', delta); - return num_digits; -} -} // namespace detail - - -/** same as c4::write_dec(), but pad with zeroes on the left - * such that the resulting string is @p num_digits wide. - * If the given number is requires more than num_digits, then the number prevails. */ -template -C4_ALWAYS_INLINE size_t write_dec(substr buf, T val, size_t num_digits) noexcept -{ - return detail::write_num_digits>(buf, val, num_digits); -} - -/** same as c4::write_hex(), but pad with zeroes on the left - * such that the resulting string is @p num_digits wide. - * If the given number is requires more than num_digits, then the number prevails. */ -template -C4_ALWAYS_INLINE size_t write_hex(substr buf, T val, size_t num_digits) noexcept -{ - return detail::write_num_digits>(buf, val, num_digits); -} - -/** same as c4::write_bin(), but pad with zeroes on the left - * such that the resulting string is @p num_digits wide. - * If the given number is requires more than num_digits, then the number prevails. */ -template -C4_ALWAYS_INLINE size_t write_bin(substr buf, T val, size_t num_digits) noexcept -{ - return detail::write_num_digits>(buf, val, num_digits); -} - -/** same as c4::write_oct(), but pad with zeroes on the left - * such that the resulting string is @p num_digits wide. - * If the given number is requires more than num_digits, then the number prevails. */ -template -C4_ALWAYS_INLINE size_t write_oct(substr buf, T val, size_t num_digits) noexcept -{ - return detail::write_num_digits>(buf, val, num_digits); -} - -C4_SUPPRESS_WARNING_GCC_POP - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -/** read a decimal integer from a string. This is the - * lowest level (and the fastest) function to do this task. - * @note does not accept negative numbers - * @note The string must be trimmed. Whitespace is not accepted. - * @note the string must not be empty - * @note there is no check for overflow; the value wraps around - * in a way similar to the standard C/C++ overflow behavior. - * For example, `read_dec("128", &val)` returns true - * and val will be set to 0 because 127 is the max i8 value. - * @see overflows() to find out if a number string overflows a type range - * @return true if the conversion was successful (no overflow check) */ -template -C4_ALWAYS_INLINE bool read_dec(csubstr s, I *C4_RESTRICT v) noexcept -{ - C4_STATIC_ASSERT(std::is_integral::value); - C4_ASSERT(!s.empty()); - *v = 0; - for(char c : s) - { - if(C4_UNLIKELY(c < '0' || c > '9')) - return false; - *v = (*v) * I(10) + (I(c) - I('0')); - } - return true; -} - -/** read an hexadecimal integer from a string. This is the - * lowest level (and the fastest) function to do this task. - * @note does not accept negative numbers - * @note does not accept leading 0x or 0X - * @note the string must not be empty - * @note the string must be trimmed. Whitespace is not accepted. - * @note there is no check for overflow; the value wraps around - * in a way similar to the standard C/C++ overflow behavior. - * For example, `read_hex("80", &val)` returns true - * and val will be set to 0 because 7f is the max i8 value. - * @see overflows() to find out if a number string overflows a type range - * @return true if the conversion was successful (no overflow check) */ -template -C4_ALWAYS_INLINE bool read_hex(csubstr s, I *C4_RESTRICT v) noexcept -{ - C4_STATIC_ASSERT(std::is_integral::value); - C4_ASSERT(!s.empty()); - *v = 0; - for(char c : s) - { - I cv; - if(c >= '0' && c <= '9') - cv = I(c) - I('0'); - else if(c >= 'a' && c <= 'f') - cv = I(10) + (I(c) - I('a')); - else if(c >= 'A' && c <= 'F') - cv = I(10) + (I(c) - I('A')); - else - return false; - *v = (*v) * I(16) + cv; - } - return true; -} - -/** read a binary integer from a string. This is the - * lowest level (and the fastest) function to do this task. - * @note does not accept negative numbers - * @note does not accept leading 0b or 0B - * @note the string must not be empty - * @note the string must be trimmed. Whitespace is not accepted. - * @note there is no check for overflow; the value wraps around - * in a way similar to the standard C/C++ overflow behavior. - * For example, `read_bin("10000000", &val)` returns true - * and val will be set to 0 because 1111111 is the max i8 value. - * @see overflows() to find out if a number string overflows a type range - * @return true if the conversion was successful (no overflow check) */ -template -C4_ALWAYS_INLINE bool read_bin(csubstr s, I *C4_RESTRICT v) noexcept -{ - C4_STATIC_ASSERT(std::is_integral::value); - C4_ASSERT(!s.empty()); - *v = 0; - for(char c : s) - { - *v <<= 1; - if(c == '1') - *v |= 1; - else if(c != '0') - return false; - } - return true; -} - -/** read an octal integer from a string. This is the - * lowest level (and the fastest) function to do this task. - * @note does not accept negative numbers - * @note does not accept leading 0o or 0O - * @note the string must not be empty - * @note the string must be trimmed. Whitespace is not accepted. - * @note there is no check for overflow; the value wraps around - * in a way similar to the standard C/C++ overflow behavior. - * For example, `read_oct("200", &val)` returns true - * and val will be set to 0 because 177 is the max i8 value. - * @see overflows() to find out if a number string overflows a type range - * @return true if the conversion was successful (no overflow check) */ -template -C4_ALWAYS_INLINE bool read_oct(csubstr s, I *C4_RESTRICT v) noexcept -{ - C4_STATIC_ASSERT(std::is_integral::value); - C4_ASSERT(!s.empty()); - *v = 0; - for(char c : s) - { - if(C4_UNLIKELY(c < '0' || c > '7')) - return false; - *v = (*v) * I(8) + (I(c) - I('0')); - } - return true; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -namespace detail { -inline size_t _itoa2buf(substr buf, size_t pos, csubstr val) noexcept -{ - C4_ASSERT(pos + val.len <= buf.len); - memcpy(buf.str + pos, val.str, val.len); - return pos + val.len; -} -inline size_t _itoa2bufwithdigits(substr buf, size_t pos, size_t num_digits, csubstr val) noexcept -{ - num_digits = num_digits > val.len ? num_digits - val.len : 0; - C4_ASSERT(num_digits + val.len <= buf.len); - for(size_t i = 0; i < num_digits; ++i) - _c4append('0'); - return detail::_itoa2buf(buf, pos, val); -} -template -C4_NO_INLINE size_t _itoadec2buf(substr buf) noexcept -{ - using digits_type = detail::charconv_digits; - if(C4_UNLIKELY(buf.len < digits_type::maxdigits_dec)) - return digits_type::maxdigits_dec; - buf.str[0] = '-'; - return detail::_itoa2buf(buf, 1, digits_type::min_value_dec()); -} -template -C4_NO_INLINE size_t _itoa2buf(substr buf, I radix) noexcept -{ - using digits_type = detail::charconv_digits; - size_t pos = 0; - if(C4_LIKELY(buf.len > 0)) - buf.str[pos++] = '-'; - switch(radix) - { - case I(10): - if(C4_UNLIKELY(buf.len < digits_type::maxdigits_dec)) - return digits_type::maxdigits_dec; - pos =_itoa2buf(buf, pos, digits_type::min_value_dec()); - break; - case I(16): - if(C4_UNLIKELY(buf.len < digits_type::maxdigits_hex)) - return digits_type::maxdigits_hex; - buf.str[pos++] = '0'; - buf.str[pos++] = 'x'; - pos = _itoa2buf(buf, pos, digits_type::min_value_hex()); - break; - case I( 2): - if(C4_UNLIKELY(buf.len < digits_type::maxdigits_bin)) - return digits_type::maxdigits_bin; - buf.str[pos++] = '0'; - buf.str[pos++] = 'b'; - pos = _itoa2buf(buf, pos, digits_type::min_value_bin()); - break; - case I( 8): - if(C4_UNLIKELY(buf.len < digits_type::maxdigits_oct)) - return digits_type::maxdigits_oct; - buf.str[pos++] = '0'; - buf.str[pos++] = 'o'; - pos = _itoa2buf(buf, pos, digits_type::min_value_oct()); - break; - } - return pos; -} -template -C4_NO_INLINE size_t _itoa2buf(substr buf, I radix, size_t num_digits) noexcept -{ - using digits_type = detail::charconv_digits; - size_t pos = 0; - size_t needed_digits = 0; - if(C4_LIKELY(buf.len > 0)) - buf.str[pos++] = '-'; - switch(radix) - { - case I(10): - // add 1 to account for - - needed_digits = num_digits+1 > digits_type::maxdigits_dec ? num_digits+1 : digits_type::maxdigits_dec; - if(C4_UNLIKELY(buf.len < needed_digits)) - return needed_digits; - pos = _itoa2bufwithdigits(buf, pos, num_digits, digits_type::min_value_dec()); - break; - case I(16): - // add 3 to account for -0x - needed_digits = num_digits+3 > digits_type::maxdigits_hex ? num_digits+3 : digits_type::maxdigits_hex; - if(C4_UNLIKELY(buf.len < needed_digits)) - return needed_digits; - buf.str[pos++] = '0'; - buf.str[pos++] = 'x'; - pos = _itoa2bufwithdigits(buf, pos, num_digits, digits_type::min_value_hex()); - break; - case I( 2): - // add 3 to account for -0b - needed_digits = num_digits+3 > digits_type::maxdigits_bin ? num_digits+3 : digits_type::maxdigits_bin; - if(C4_UNLIKELY(buf.len < needed_digits)) - return needed_digits; - C4_ASSERT(buf.len >= digits_type::maxdigits_bin); - buf.str[pos++] = '0'; - buf.str[pos++] = 'b'; - pos = _itoa2bufwithdigits(buf, pos, num_digits, digits_type::min_value_bin()); - break; - case I( 8): - // add 3 to account for -0o - needed_digits = num_digits+3 > digits_type::maxdigits_oct ? num_digits+3 : digits_type::maxdigits_oct; - if(C4_UNLIKELY(buf.len < needed_digits)) - return needed_digits; - C4_ASSERT(buf.len >= digits_type::maxdigits_oct); - buf.str[pos++] = '0'; - buf.str[pos++] = 'o'; - pos = _itoa2bufwithdigits(buf, pos, num_digits, digits_type::min_value_oct()); - break; - } - return pos; -} -} // namespace detail - - -/** convert an integral signed decimal to a string. - * @note the resulting string is NOT zero-terminated. - * @note it is ok to call this with an empty or too-small buffer; - * no writes will occur, and the needed size will be returned - * @return the number of characters required for the buffer. */ -template -C4_ALWAYS_INLINE size_t itoa(substr buf, T v) noexcept -{ - C4_STATIC_ASSERT(std::is_signed::value); - if(v >= T(0)) - { - // write_dec() checks the buffer size, so no need to check here - return write_dec(buf, v); - } - // when T is the min value (eg i8: -128), negating it - // will overflow, so treat the min as a special case - else if(C4_LIKELY(v != std::numeric_limits::min())) - { - v = -v; - unsigned digits = digits_dec(v); - if(C4_LIKELY(buf.len >= digits + 1u)) - { - buf.str[0] = '-'; - write_dec_unchecked(buf.sub(1), v, digits); - } - return digits + 1u; - } - return detail::_itoadec2buf(buf); -} - -/** convert an integral signed integer to a string, using a specific - * radix. The radix must be 2, 8, 10 or 16. - * - * @note the resulting string is NOT zero-terminated. - * @note it is ok to call this with an empty or too-small buffer; - * no writes will occur, and the needed size will be returned - * @return the number of characters required for the buffer. */ -template -C4_ALWAYS_INLINE size_t itoa(substr buf, T v, T radix) noexcept -{ - C4_STATIC_ASSERT(std::is_signed::value); - C4_ASSERT(radix == 2 || radix == 8 || radix == 10 || radix == 16); - C4_SUPPRESS_WARNING_GCC_PUSH - #if (defined(__GNUC__) && (__GNUC__ >= 7)) - C4_SUPPRESS_WARNING_GCC("-Wstringop-overflow") // gcc has a false positive here - #endif - // when T is the min value (eg i8: -128), negating it - // will overflow, so treat the min as a special case - if(C4_LIKELY(v != std::numeric_limits::min())) - { - unsigned pos = 0; - if(v < 0) - { - v = -v; - if(C4_LIKELY(buf.len > 0)) - buf.str[pos] = '-'; - ++pos; - } - unsigned digits = 0; - switch(radix) - { - case T(10): - digits = digits_dec(v); - if(C4_LIKELY(buf.len >= pos + digits)) - write_dec_unchecked(buf.sub(pos), v, digits); - break; - case T(16): - digits = digits_hex(v); - if(C4_LIKELY(buf.len >= pos + 2u + digits)) - { - buf.str[pos + 0] = '0'; - buf.str[pos + 1] = 'x'; - write_hex_unchecked(buf.sub(pos + 2), v, digits); - } - digits += 2u; - break; - case T(2): - digits = digits_bin(v); - if(C4_LIKELY(buf.len >= pos + 2u + digits)) - { - buf.str[pos + 0] = '0'; - buf.str[pos + 1] = 'b'; - write_bin_unchecked(buf.sub(pos + 2), v, digits); - } - digits += 2u; - break; - case T(8): - digits = digits_oct(v); - if(C4_LIKELY(buf.len >= pos + 2u + digits)) - { - buf.str[pos + 0] = '0'; - buf.str[pos + 1] = 'o'; - write_oct_unchecked(buf.sub(pos + 2), v, digits); - } - digits += 2u; - break; - } - return pos + digits; - } - C4_SUPPRESS_WARNING_GCC_POP - // when T is the min value (eg i8: -128), negating it - // will overflow - return detail::_itoa2buf(buf, radix); -} - - -/** same as c4::itoa(), but pad with zeroes on the left such that the - * resulting string is @p num_digits wide, not accounting for radix - * prefix (0x,0o,0b). The @p radix must be 2, 8, 10 or 16. - * - * @note the resulting string is NOT zero-terminated. - * @note it is ok to call this with an empty or too-small buffer; - * no writes will occur, and the needed size will be returned - * @return the number of characters required for the buffer. */ -template -C4_ALWAYS_INLINE size_t itoa(substr buf, T v, T radix, size_t num_digits) noexcept -{ - C4_STATIC_ASSERT(std::is_signed::value); - C4_ASSERT(radix == 2 || radix == 8 || radix == 10 || radix == 16); - C4_SUPPRESS_WARNING_GCC_PUSH - #if (defined(__GNUC__) && (__GNUC__ >= 7)) - C4_SUPPRESS_WARNING_GCC("-Wstringop-overflow") // gcc has a false positive here - #endif - // when T is the min value (eg i8: -128), negating it - // will overflow, so treat the min as a special case - if(C4_LIKELY(v != std::numeric_limits::min())) - { - unsigned pos = 0; - if(v < 0) - { - v = -v; - if(C4_LIKELY(buf.len > 0)) - buf.str[pos] = '-'; - ++pos; - } - unsigned total_digits = 0; - switch(radix) - { - case T(10): - total_digits = digits_dec(v); - total_digits = pos + (unsigned)(num_digits > total_digits ? num_digits : total_digits); - if(C4_LIKELY(buf.len >= total_digits)) - write_dec(buf.sub(pos), v, num_digits); - break; - case T(16): - total_digits = digits_hex(v); - total_digits = pos + 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits); - if(C4_LIKELY(buf.len >= total_digits)) - { - buf.str[pos + 0] = '0'; - buf.str[pos + 1] = 'x'; - write_hex(buf.sub(pos + 2), v, num_digits); - } - break; - case T(2): - total_digits = digits_bin(v); - total_digits = pos + 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits); - if(C4_LIKELY(buf.len >= total_digits)) - { - buf.str[pos + 0] = '0'; - buf.str[pos + 1] = 'b'; - write_bin(buf.sub(pos + 2), v, num_digits); - } - break; - case T(8): - total_digits = digits_oct(v); - total_digits = pos + 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits); - if(C4_LIKELY(buf.len >= total_digits)) - { - buf.str[pos + 0] = '0'; - buf.str[pos + 1] = 'o'; - write_oct(buf.sub(pos + 2), v, num_digits); - } - break; - } - return total_digits; - } - C4_SUPPRESS_WARNING_GCC_POP - // when T is the min value (eg i8: -128), negating it - // will overflow - return detail::_itoa2buf(buf, radix, num_digits); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -/** convert an integral unsigned decimal to a string. - * - * @note the resulting string is NOT zero-terminated. - * @note it is ok to call this with an empty or too-small buffer; - * no writes will occur, and the needed size will be returned - * @return the number of characters required for the buffer. */ -template -C4_ALWAYS_INLINE size_t utoa(substr buf, T v) noexcept -{ - C4_STATIC_ASSERT(std::is_unsigned::value); - // write_dec() does the buffer length check, so no need to check here - return write_dec(buf, v); -} - -/** convert an integral unsigned integer to a string, using a specific - * radix. The radix must be 2, 8, 10 or 16. - * - * @note the resulting string is NOT zero-terminated. - * @note it is ok to call this with an empty or too-small buffer; - * no writes will occur, and the needed size will be returned - * @return the number of characters required for the buffer. */ -template -C4_ALWAYS_INLINE size_t utoa(substr buf, T v, T radix) noexcept -{ - C4_STATIC_ASSERT(std::is_unsigned::value); - C4_ASSERT(radix == 10 || radix == 16 || radix == 2 || radix == 8); - unsigned digits = 0; - switch(radix) - { - case T(10): - digits = digits_dec(v); - if(C4_LIKELY(buf.len >= digits)) - write_dec_unchecked(buf, v, digits); - break; - case T(16): - digits = digits_hex(v); - if(C4_LIKELY(buf.len >= digits+2u)) - { - buf.str[0] = '0'; - buf.str[1] = 'x'; - write_hex_unchecked(buf.sub(2), v, digits); - } - digits += 2u; - break; - case T(2): - digits = digits_bin(v); - if(C4_LIKELY(buf.len >= digits+2u)) - { - buf.str[0] = '0'; - buf.str[1] = 'b'; - write_bin_unchecked(buf.sub(2), v, digits); - } - digits += 2u; - break; - case T(8): - digits = digits_oct(v); - if(C4_LIKELY(buf.len >= digits+2u)) - { - buf.str[0] = '0'; - buf.str[1] = 'o'; - write_oct_unchecked(buf.sub(2), v, digits); - } - digits += 2u; - break; - } - return digits; -} - -/** same as c4::utoa(), but pad with zeroes on the left such that the - * resulting string is @p num_digits wide. The @p radix must be 2, - * 8, 10 or 16. - * - * @note the resulting string is NOT zero-terminated. - * @note it is ok to call this with an empty or too-small buffer; - * no writes will occur, and the needed size will be returned - * @return the number of characters required for the buffer. */ -template -C4_ALWAYS_INLINE size_t utoa(substr buf, T v, T radix, size_t num_digits) noexcept -{ - C4_STATIC_ASSERT(std::is_unsigned::value); - C4_ASSERT(radix == 10 || radix == 16 || radix == 2 || radix == 8); - unsigned total_digits = 0; - switch(radix) - { - case T(10): - total_digits = digits_dec(v); - total_digits = (unsigned)(num_digits > total_digits ? num_digits : total_digits); - if(C4_LIKELY(buf.len >= total_digits)) - write_dec(buf, v, num_digits); - break; - case T(16): - total_digits = digits_hex(v); - total_digits = 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits); - if(C4_LIKELY(buf.len >= total_digits)) - { - buf.str[0] = '0'; - buf.str[1] = 'x'; - write_hex(buf.sub(2), v, num_digits); - } - break; - case T(2): - total_digits = digits_bin(v); - total_digits = 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits); - if(C4_LIKELY(buf.len >= total_digits)) - { - buf.str[0] = '0'; - buf.str[1] = 'b'; - write_bin(buf.sub(2), v, num_digits); - } - break; - case T(8): - total_digits = digits_oct(v); - total_digits = 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits); - if(C4_LIKELY(buf.len >= total_digits)) - { - buf.str[0] = '0'; - buf.str[1] = 'o'; - write_oct(buf.sub(2), v, num_digits); - } - break; - } - return total_digits; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -/** Convert a trimmed string to a signed integral value. The input - * string can be formatted as decimal, binary (prefix 0b or 0B), octal - * (prefix 0o or 0O) or hexadecimal (prefix 0x or 0X). Strings with - * leading zeroes are considered as decimal and not octal (unlike the - * C/C++ convention). Every character in the input string is read for - * the conversion; the input string must not contain any leading or - * trailing whitespace. - * - * @return true if the conversion was successful. - * - * @note overflow is not detected: the return status is true even if - * the conversion would return a value outside of the type's range, in - * which case the result will wrap around the type's range. - * This is similar to native behavior. - * - * @note a positive sign is not accepted. ie, the string must not - * start with '+' - * - * @see atoi_first() if the string is not trimmed to the value to read. */ -template -C4_ALWAYS_INLINE bool atoi(csubstr str, T * C4_RESTRICT v) noexcept -{ - C4_STATIC_ASSERT(std::is_integral::value); - C4_STATIC_ASSERT(std::is_signed::value); - - if(C4_UNLIKELY(str.len == 0)) - return false; - - C4_ASSERT(str.str[0] != '+'); - - T sign = 1; - size_t start = 0; - if(str.str[0] == '-') - { - if(C4_UNLIKELY(str.len == ++start)) - return false; - sign = -1; - } - - bool parsed_ok = true; - if(str.str[start] != '0') // this should be the common case, so put it first - { - parsed_ok = read_dec(str.sub(start), v); - } - else if(str.len > start + 1) - { - // starts with 0: is it 0x, 0o, 0b? - const char pfx = str.str[start + 1]; - if(pfx == 'x' || pfx == 'X') - parsed_ok = str.len > start + 2 && read_hex(str.sub(start + 2), v); - else if(pfx == 'b' || pfx == 'B') - parsed_ok = str.len > start + 2 && read_bin(str.sub(start + 2), v); - else if(pfx == 'o' || pfx == 'O') - parsed_ok = str.len > start + 2 && read_oct(str.sub(start + 2), v); - else - parsed_ok = read_dec(str.sub(start + 1), v); - } - else - { - parsed_ok = read_dec(str.sub(start), v); - } - if(C4_LIKELY(parsed_ok)) - *v *= sign; - return parsed_ok; -} - - -/** Select the next range of characters in the string that can be parsed - * as a signed integral value, and convert it using atoi(). Leading - * whitespace (space, newline, tabs) is skipped. - * @return the number of characters read for conversion, or csubstr::npos if the conversion failed - * @see atoi() if the string is already trimmed to the value to read. - * @see csubstr::first_int_span() */ -template -C4_ALWAYS_INLINE size_t atoi_first(csubstr str, T * C4_RESTRICT v) -{ - csubstr trimmed = str.first_int_span(); - if(trimmed.len == 0) - return csubstr::npos; - if(atoi(trimmed, v)) - return static_cast(trimmed.end() - str.begin()); - return csubstr::npos; -} - - -//----------------------------------------------------------------------------- - -/** Convert a trimmed string to an unsigned integral value. The string can be - * formatted as decimal, binary (prefix 0b or 0B), octal (prefix 0o or 0O) - * or hexadecimal (prefix 0x or 0X). Every character in the input string is read - * for the conversion; it must not contain any leading or trailing whitespace. - * - * @return true if the conversion was successful. - * - * @note overflow is not detected: the return status is true even if - * the conversion would return a value outside of the type's range, in - * which case the result will wrap around the type's range. - * - * @note If the string has a minus character, the return status - * will be false. - * - * @see atou_first() if the string is not trimmed to the value to read. */ -template -bool atou(csubstr str, T * C4_RESTRICT v) noexcept -{ - C4_STATIC_ASSERT(std::is_integral::value); - - if(C4_UNLIKELY(str.len == 0 || str.front() == '-')) - return false; - - bool parsed_ok = true; - if(str.str[0] != '0') - { - parsed_ok = read_dec(str, v); - } - else - { - if(str.len > 1) - { - const char pfx = str.str[1]; - if(pfx == 'x' || pfx == 'X') - parsed_ok = str.len > 2 && read_hex(str.sub(2), v); - else if(pfx == 'b' || pfx == 'B') - parsed_ok = str.len > 2 && read_bin(str.sub(2), v); - else if(pfx == 'o' || pfx == 'O') - parsed_ok = str.len > 2 && read_oct(str.sub(2), v); - else - parsed_ok = read_dec(str, v); - } - else - { - *v = 0; // we know the first character is 0 - } - } - return parsed_ok; -} - - -/** Select the next range of characters in the string that can be parsed - * as an unsigned integral value, and convert it using atou(). Leading - * whitespace (space, newline, tabs) is skipped. - * @return the number of characters read for conversion, or csubstr::npos if the conversion faileds - * @see atou() if the string is already trimmed to the value to read. - * @see csubstr::first_uint_span() */ -template -C4_ALWAYS_INLINE size_t atou_first(csubstr str, T *v) -{ - csubstr trimmed = str.first_uint_span(); - if(trimmed.len == 0) - return csubstr::npos; - if(atou(trimmed, v)) - return static_cast(trimmed.end() - str.begin()); - return csubstr::npos; -} - - -#ifdef _MSC_VER -# pragma warning(pop) -#elif defined(__clang__) -# pragma clang diagnostic pop -#elif defined(__GNUC__) -# pragma GCC diagnostic pop -#endif - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -namespace detail { -inline bool check_overflow(csubstr str, csubstr limit) noexcept -{ - if(str.len == limit.len) - { - for(size_t i = 0; i < limit.len; ++i) - { - if(str[i] < limit[i]) - return false; - else if(str[i] > limit[i]) - return true; - } - return false; - } - else - return str.len > limit.len; -} -} // namespace detail - - -/** Test if the following string would overflow when converted to associated - * types. - * @return true if number will overflow, false if it fits (or doesn't parse) - */ -template -auto overflows(csubstr str) noexcept - -> typename std::enable_if::value, bool>::type -{ - C4_STATIC_ASSERT(std::is_integral::value); - - if(C4_UNLIKELY(str.len == 0)) - { - return false; - } - else if(str.str[0] == '0') - { - if (str.len == 1) - return false; - switch (str.str[1]) - { - case 'x': - case 'X': - { - size_t fno = str.first_not_of('0', 2); - if (fno == csubstr::npos) - return false; - return !(str.len <= fno + (sizeof(T) * 2)); - } - case 'b': - case 'B': - { - size_t fno = str.first_not_of('0', 2); - if (fno == csubstr::npos) - return false; - return !(str.len <= fno +(sizeof(T) * 8)); - } - case 'o': - case 'O': - { - size_t fno = str.first_not_of('0', 2); - if(fno == csubstr::npos) - return false; - return detail::charconv_digits::is_oct_overflow(str.sub(fno)); - } - default: - { - size_t fno = str.first_not_of('0', 1); - if(fno == csubstr::npos) - return false; - return detail::check_overflow(str.sub(fno), detail::charconv_digits::max_value_dec()); - } - } - } - else if(C4_UNLIKELY(str[0] == '-')) - { - return true; - } - else - { - return detail::check_overflow(str, detail::charconv_digits::max_value_dec()); - } -} - - -/** Test if the following string would overflow when converted to associated - * types. - * @return true if number will overflow, false if it fits (or doesn't parse) - */ -template -auto overflows(csubstr str) - -> typename std::enable_if::value, bool>::type -{ - C4_STATIC_ASSERT(std::is_integral::value); - if(C4_UNLIKELY(str.len == 0)) - return false; - if(str.str[0] == '-') - { - if(str.str[1] == '0') - { - if(str.len == 2) - return false; - switch(str.str[2]) - { - case 'x': - case 'X': - { - size_t fno = str.first_not_of('0', 3); - if (fno == csubstr::npos) - return false; - return detail::check_overflow(str.sub(fno), detail::charconv_digits::min_value_hex()); - } - case 'b': - case 'B': - { - size_t fno = str.first_not_of('0', 3); - if (fno == csubstr::npos) - return false; - return detail::check_overflow(str.sub(fno), detail::charconv_digits::min_value_bin()); - } - case 'o': - case 'O': - { - size_t fno = str.first_not_of('0', 3); - if(fno == csubstr::npos) - return false; - return detail::check_overflow(str.sub(fno), detail::charconv_digits::min_value_oct()); - } - default: - { - size_t fno = str.first_not_of('0', 2); - if(fno == csubstr::npos) - return false; - return detail::check_overflow(str.sub(fno), detail::charconv_digits::min_value_dec()); - } - } - } - else - return detail::check_overflow(str.sub(1), detail::charconv_digits::min_value_dec()); - } - else if(str.str[0] == '0') - { - if (str.len == 1) - return false; - switch(str.str[1]) - { - case 'x': - case 'X': - { - size_t fno = str.first_not_of('0', 2); - if (fno == csubstr::npos) - return false; - const size_t len = str.len - fno; - return !((len < sizeof (T) * 2) || (len == sizeof(T) * 2 && str[fno] <= '7')); - } - case 'b': - case 'B': - { - size_t fno = str.first_not_of('0', 2); - if (fno == csubstr::npos) - return false; - return !(str.len <= fno + (sizeof(T) * 8 - 1)); - } - case 'o': - case 'O': - { - size_t fno = str.first_not_of('0', 2); - if(fno == csubstr::npos) - return false; - return detail::charconv_digits::is_oct_overflow(str.sub(fno)); - } - default: - { - size_t fno = str.first_not_of('0', 1); - if(fno == csubstr::npos) - return false; - return detail::check_overflow(str.sub(fno), detail::charconv_digits::max_value_dec()); - } - } - } - else - return detail::check_overflow(str, detail::charconv_digits::max_value_dec()); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -namespace detail { - - -#if (!C4CORE_HAVE_STD_FROMCHARS) -/** @see http://www.exploringbinary.com/ for many good examples on float-str conversion */ -template -void get_real_format_str(char (& C4_RESTRICT fmt)[N], int precision, RealFormat_e formatting, const char* length_modifier="") -{ - int iret; - if(precision == -1) - iret = snprintf(fmt, sizeof(fmt), "%%%s%c", length_modifier, formatting); - else if(precision == 0) - iret = snprintf(fmt, sizeof(fmt), "%%.%s%c", length_modifier, formatting); - else - iret = snprintf(fmt, sizeof(fmt), "%%.%d%s%c", precision, length_modifier, formatting); - C4_ASSERT(iret >= 2 && size_t(iret) < sizeof(fmt)); - C4_UNUSED(iret); -} - - -/** @todo we're depending on snprintf()/sscanf() for converting to/from - * floating point numbers. Apparently, this increases the binary size - * by a considerable amount. There are some lightweight printf - * implementations: - * - * @see http://www.sparetimelabs.com/tinyprintf/tinyprintf.php (BSD) - * @see https://github.com/weiss/c99-snprintf - * @see https://github.com/nothings/stb/blob/master/stb_sprintf.h - * @see http://www.exploringbinary.com/ - * @see https://blog.benoitblanchon.fr/lightweight-float-to-string/ - * @see http://www.ryanjuckett.com/programming/printing-floating-point-numbers/ - */ -template -size_t print_one(substr str, const char* full_fmt, T v) -{ -#ifdef _MSC_VER - /** use _snprintf() to prevent early termination of the output - * for writing the null character at the last position - * @see https://msdn.microsoft.com/en-us/library/2ts7cx93.aspx */ - int iret = _snprintf(str.str, str.len, full_fmt, v); - if(iret < 0) - { - /* when buf.len is not enough, VS returns a negative value. - * so call it again with a negative value for getting an - * actual length of the string */ - iret = snprintf(nullptr, 0, full_fmt, v); - C4_ASSERT(iret > 0); - } - size_t ret = (size_t) iret; - return ret; -#else - int iret = snprintf(str.str, str.len, full_fmt, v); - C4_ASSERT(iret >= 0); - size_t ret = (size_t) iret; - if(ret >= str.len) - ++ret; /* snprintf() reserves the last character to write \0 */ - return ret; -#endif -} -#endif // (!C4CORE_HAVE_STD_FROMCHARS) - - -#if (!C4CORE_HAVE_STD_FROMCHARS) && (!C4CORE_HAVE_FAST_FLOAT) -/** scans a string using the given type format, while at the same time - * allowing non-null-terminated strings AND guaranteeing that the given - * string length is strictly respected, so that no buffer overflows - * might occur. */ -template -inline size_t scan_one(csubstr str, const char *type_fmt, T *v) -{ - /* snscanf() is absolutely needed here as we must be sure that - * str.len is strictly respected, because substr is - * generally not null-terminated. - * - * Alas, there is no snscanf(). - * - * So we fake it by using a dynamic format with an explicit - * field size set to the length of the given span. - * This trick is taken from: - * https://stackoverflow.com/a/18368910/5875572 */ - - /* this is the actual format we'll use for scanning */ - char fmt[16]; - - /* write the length into it. Eg "%12f". - * Also, get the number of characters read from the string. - * So the final format ends up as "%12f%n"*/ - int iret = std::snprintf(fmt, sizeof(fmt), "%%" "%zu" "%s" "%%n", str.len, type_fmt); - /* no nasty surprises, please! */ - C4_ASSERT(iret >= 0 && size_t(iret) < C4_COUNTOF(fmt)); - - /* now we scan with confidence that the span length is respected */ - int num_chars; - iret = std::sscanf(str.str, fmt, v, &num_chars); - /* scanf returns the number of successful conversions */ - if(iret != 1) return csubstr::npos; - C4_ASSERT(num_chars >= 0); - return (size_t)(num_chars); -} -#endif // (!C4CORE_HAVE_STD_FROMCHARS) && (!C4CORE_HAVE_FAST_FLOAT) - - -#if C4CORE_HAVE_STD_TOCHARS -template -C4_ALWAYS_INLINE size_t rtoa(substr buf, T v, int precision=-1, RealFormat_e formatting=FTOA_FLEX) noexcept -{ - std::to_chars_result result; - size_t pos = 0; - if(formatting == FTOA_HEXA) - { - if(buf.len > size_t(2)) - { - buf.str[0] = '0'; - buf.str[1] = 'x'; - } - pos += size_t(2); - } - if(precision == -1) - result = std::to_chars(buf.str + pos, buf.str + buf.len, v, (std::chars_format)formatting); - else - result = std::to_chars(buf.str + pos, buf.str + buf.len, v, (std::chars_format)formatting, precision); - if(result.ec == std::errc()) - { - // all good, no errors. - C4_ASSERT(result.ptr >= buf.str); - ptrdiff_t delta = result.ptr - buf.str; - return static_cast(delta); - } - C4_ASSERT(result.ec == std::errc::value_too_large); - // This is unfortunate. - // - // When the result can't fit in the given buffer, - // std::to_chars() returns the end pointer it was originally - // given, which is useless because here we would like to know - // _exactly_ how many characters the buffer must have to fit - // the result. - // - // So we take the pessimistic view, and assume as many digits - // as could ever be required: - size_t ret = static_cast(std::numeric_limits::max_digits10); - return ret > buf.len ? ret : buf.len + 1; -} -#endif // C4CORE_HAVE_STD_TOCHARS - - -#if C4CORE_HAVE_FAST_FLOAT -template -C4_ALWAYS_INLINE bool scan_rhex(csubstr s, T *C4_RESTRICT val) noexcept -{ - C4_ASSERT(s.len > 0); - C4_ASSERT(s.str[0] != '-'); - C4_ASSERT(s.str[0] != '+'); - C4_ASSERT(!s.begins_with("0x")); - C4_ASSERT(!s.begins_with("0X")); - size_t pos = 0; - // integer part - for( ; pos < s.len; ++pos) - { - const char c = s.str[pos]; - if(c >= '0' && c <= '9') - *val = *val * T(16) + T(c - '0'); - else if(c >= 'a' && c <= 'f') - *val = *val * T(16) + T(c - 'a'); - else if(c >= 'A' && c <= 'F') - *val = *val * T(16) + T(c - 'A'); - else if(c == '.') - { - ++pos; - break; // follow on to mantissa - } - else if(c == 'p' || c == 'P') - { - ++pos; - goto power; // no mantissa given, jump to power - } - else - { - return false; - } - } - // mantissa - { - // 0.0625 == 1/16 == value of first digit after the comma - for(T digit = T(0.0625); pos < s.len; ++pos, digit /= T(16)) - { - const char c = s.str[pos]; - if(c >= '0' && c <= '9') - *val += digit * T(c - '0'); - else if(c >= 'a' && c <= 'f') - *val += digit * T(c - 'a'); - else if(c >= 'A' && c <= 'F') - *val += digit * T(c - 'A'); - else if(c == 'p' || c == 'P') - { - ++pos; - goto power; // mantissa finished, jump to power - } - else - { - return false; - } - } - } - return true; -power: - if(C4_LIKELY(pos < s.len)) - { - if(s.str[pos] == '+') // atoi() cannot handle a leading '+' - ++pos; - if(C4_LIKELY(pos < s.len)) - { - int16_t powval = {}; - if(C4_LIKELY(atoi(s.sub(pos), &powval))) - { - *val *= ipow(powval); - return true; - } - } - } - return false; -} -#endif - -} // namespace detail - - -#undef _c4appendhex -#undef _c4append - - -/** Convert a single-precision real number to string. The string will - * in general be NOT null-terminated. For FTOA_FLEX, \p precision is - * the number of significand digits. Otherwise \p precision is the - * number of decimals. It is safe to call this function with an empty - * or too-small buffer. - * - * @return the size of the buffer needed to write the number - */ -C4_ALWAYS_INLINE size_t ftoa(substr str, float v, int precision=-1, RealFormat_e formatting=FTOA_FLEX) noexcept -{ -#if C4CORE_HAVE_STD_TOCHARS - return detail::rtoa(str, v, precision, formatting); -#else - char fmt[16]; - detail::get_real_format_str(fmt, precision, formatting, /*length_modifier*/""); - return detail::print_one(str, fmt, v); -#endif -} - - -/** Convert a double-precision real number to string. The string will - * in general be NOT null-terminated. For FTOA_FLEX, \p precision is - * the number of significand digits. Otherwise \p precision is the - * number of decimals. It is safe to call this function with an empty - * or too-small buffer. - * - * @return the size of the buffer needed to write the number - */ -C4_ALWAYS_INLINE size_t dtoa(substr str, double v, int precision=-1, RealFormat_e formatting=FTOA_FLEX) noexcept -{ -#if C4CORE_HAVE_STD_TOCHARS - return detail::rtoa(str, v, precision, formatting); -#else - char fmt[16]; - detail::get_real_format_str(fmt, precision, formatting, /*length_modifier*/"l"); - return detail::print_one(str, fmt, v); -#endif -} - - -/** Convert a string to a single precision real number. - * The input string must be trimmed to the value, ie - * no leading or trailing whitespace can be present. - * @return true iff the conversion succeeded - * @see atof_first() if the string is not trimmed - */ -C4_ALWAYS_INLINE bool atof(csubstr str, float * C4_RESTRICT v) noexcept -{ - C4_ASSERT(str.len > 0); - C4_ASSERT(str.triml(" \r\t\n").len == str.len); -#if C4CORE_HAVE_FAST_FLOAT - // fastfloat cannot parse hexadecimal floats - bool isneg = (str.str[0] == '-'); - csubstr rem = str.sub(isneg || str.str[0] == '+'); - if(!(rem.len >= 2 && (rem.str[0] == '0' && (rem.str[1] == 'x' || rem.str[1] == 'X')))) - { - fast_float::from_chars_result result; - result = fast_float::from_chars(str.str, str.str + str.len, *v); - return result.ec == std::errc(); - } - else if(detail::scan_rhex(rem.sub(2), v)) - { - *v *= isneg ? -1.f : 1.f; - return true; - } - return false; -#elif C4CORE_HAVE_STD_FROMCHARS - std::from_chars_result result; - result = std::from_chars(str.str, str.str + str.len, *v); - return result.ec == std::errc(); -#else - csubstr rem = str.sub(str.str[0] == '-' || str.str[0] == '+'); - if(!(rem.len >= 2 && (rem.str[0] == '0' && (rem.str[1] == 'x' || rem.str[1] == 'X')))) - return detail::scan_one(str, "f", v) != csubstr::npos; - else - return detail::scan_one(str, "a", v) != csubstr::npos; -#endif -} - - -/** Convert a string to a double precision real number. - * The input string must be trimmed to the value, ie - * no leading or trailing whitespace can be present. - * @return true iff the conversion succeeded - * @see atod_first() if the string is not trimmed - */ -C4_ALWAYS_INLINE bool atod(csubstr str, double * C4_RESTRICT v) noexcept -{ - C4_ASSERT(str.triml(" \r\t\n").len == str.len); -#if C4CORE_HAVE_FAST_FLOAT - // fastfloat cannot parse hexadecimal floats - bool isneg = (str.str[0] == '-'); - csubstr rem = str.sub(isneg || str.str[0] == '+'); - if(!(rem.len >= 2 && (rem.str[0] == '0' && (rem.str[1] == 'x' || rem.str[1] == 'X')))) - { - fast_float::from_chars_result result; - result = fast_float::from_chars(str.str, str.str + str.len, *v); - return result.ec == std::errc(); - } - else if(detail::scan_rhex(rem.sub(2), v)) - { - *v *= isneg ? -1. : 1.; - return true; - } - return false; -#elif C4CORE_HAVE_STD_FROMCHARS - std::from_chars_result result; - result = std::from_chars(str.str, str.str + str.len, *v); - return result.ec == std::errc(); -#else - csubstr rem = str.sub(str.str[0] == '-' || str.str[0] == '+'); - if(!(rem.len >= 2 && (rem.str[0] == '0' && (rem.str[1] == 'x' || rem.str[1] == 'X')))) - return detail::scan_one(str, "lf", v) != csubstr::npos; - else - return detail::scan_one(str, "la", v) != csubstr::npos; -#endif -} - - -/** Convert a string to a single precision real number. - * Leading whitespace is skipped until valid characters are found. - * @return the number of characters read from the string, or npos if - * conversion was not successful or if the string was empty */ -inline size_t atof_first(csubstr str, float * C4_RESTRICT v) noexcept -{ - csubstr trimmed = str.first_real_span(); - if(trimmed.len == 0) - return csubstr::npos; - if(atof(trimmed, v)) - return static_cast(trimmed.end() - str.begin()); - return csubstr::npos; -} - - -/** Convert a string to a double precision real number. - * Leading whitespace is skipped until valid characters are found. - * @return the number of characters read from the string, or npos if - * conversion was not successful or if the string was empty */ -inline size_t atod_first(csubstr str, double * C4_RESTRICT v) noexcept -{ - csubstr trimmed = str.first_real_span(); - if(trimmed.len == 0) - return csubstr::npos; - if(atod(trimmed, v)) - return static_cast(trimmed.end() - str.begin()); - return csubstr::npos; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -// generic versions - -C4_ALWAYS_INLINE size_t xtoa(substr s, uint8_t v) noexcept { return write_dec(s, v); } -C4_ALWAYS_INLINE size_t xtoa(substr s, uint16_t v) noexcept { return write_dec(s, v); } -C4_ALWAYS_INLINE size_t xtoa(substr s, uint32_t v) noexcept { return write_dec(s, v); } -C4_ALWAYS_INLINE size_t xtoa(substr s, uint64_t v) noexcept { return write_dec(s, v); } -C4_ALWAYS_INLINE size_t xtoa(substr s, int8_t v) noexcept { return itoa(s, v); } -C4_ALWAYS_INLINE size_t xtoa(substr s, int16_t v) noexcept { return itoa(s, v); } -C4_ALWAYS_INLINE size_t xtoa(substr s, int32_t v) noexcept { return itoa(s, v); } -C4_ALWAYS_INLINE size_t xtoa(substr s, int64_t v) noexcept { return itoa(s, v); } -C4_ALWAYS_INLINE size_t xtoa(substr s, float v) noexcept { return ftoa(s, v); } -C4_ALWAYS_INLINE size_t xtoa(substr s, double v) noexcept { return dtoa(s, v); } - -C4_ALWAYS_INLINE size_t xtoa(substr s, uint8_t v, uint8_t radix) noexcept { return utoa(s, v, radix); } -C4_ALWAYS_INLINE size_t xtoa(substr s, uint16_t v, uint16_t radix) noexcept { return utoa(s, v, radix); } -C4_ALWAYS_INLINE size_t xtoa(substr s, uint32_t v, uint32_t radix) noexcept { return utoa(s, v, radix); } -C4_ALWAYS_INLINE size_t xtoa(substr s, uint64_t v, uint64_t radix) noexcept { return utoa(s, v, radix); } -C4_ALWAYS_INLINE size_t xtoa(substr s, int8_t v, int8_t radix) noexcept { return itoa(s, v, radix); } -C4_ALWAYS_INLINE size_t xtoa(substr s, int16_t v, int16_t radix) noexcept { return itoa(s, v, radix); } -C4_ALWAYS_INLINE size_t xtoa(substr s, int32_t v, int32_t radix) noexcept { return itoa(s, v, radix); } -C4_ALWAYS_INLINE size_t xtoa(substr s, int64_t v, int64_t radix) noexcept { return itoa(s, v, radix); } - -C4_ALWAYS_INLINE size_t xtoa(substr s, uint8_t v, uint8_t radix, size_t num_digits) noexcept { return utoa(s, v, radix, num_digits); } -C4_ALWAYS_INLINE size_t xtoa(substr s, uint16_t v, uint16_t radix, size_t num_digits) noexcept { return utoa(s, v, radix, num_digits); } -C4_ALWAYS_INLINE size_t xtoa(substr s, uint32_t v, uint32_t radix, size_t num_digits) noexcept { return utoa(s, v, radix, num_digits); } -C4_ALWAYS_INLINE size_t xtoa(substr s, uint64_t v, uint64_t radix, size_t num_digits) noexcept { return utoa(s, v, radix, num_digits); } -C4_ALWAYS_INLINE size_t xtoa(substr s, int8_t v, int8_t radix, size_t num_digits) noexcept { return itoa(s, v, radix, num_digits); } -C4_ALWAYS_INLINE size_t xtoa(substr s, int16_t v, int16_t radix, size_t num_digits) noexcept { return itoa(s, v, radix, num_digits); } -C4_ALWAYS_INLINE size_t xtoa(substr s, int32_t v, int32_t radix, size_t num_digits) noexcept { return itoa(s, v, radix, num_digits); } -C4_ALWAYS_INLINE size_t xtoa(substr s, int64_t v, int64_t radix, size_t num_digits) noexcept { return itoa(s, v, radix, num_digits); } - -C4_ALWAYS_INLINE size_t xtoa(substr s, float v, int precision, RealFormat_e formatting=FTOA_FLEX) noexcept { return ftoa(s, v, precision, formatting); } -C4_ALWAYS_INLINE size_t xtoa(substr s, double v, int precision, RealFormat_e formatting=FTOA_FLEX) noexcept { return dtoa(s, v, precision, formatting); } - -C4_ALWAYS_INLINE bool atox(csubstr s, uint8_t *C4_RESTRICT v) noexcept { return atou(s, v); } -C4_ALWAYS_INLINE bool atox(csubstr s, uint16_t *C4_RESTRICT v) noexcept { return atou(s, v); } -C4_ALWAYS_INLINE bool atox(csubstr s, uint32_t *C4_RESTRICT v) noexcept { return atou(s, v); } -C4_ALWAYS_INLINE bool atox(csubstr s, uint64_t *C4_RESTRICT v) noexcept { return atou(s, v); } -C4_ALWAYS_INLINE bool atox(csubstr s, int8_t *C4_RESTRICT v) noexcept { return atoi(s, v); } -C4_ALWAYS_INLINE bool atox(csubstr s, int16_t *C4_RESTRICT v) noexcept { return atoi(s, v); } -C4_ALWAYS_INLINE bool atox(csubstr s, int32_t *C4_RESTRICT v) noexcept { return atoi(s, v); } -C4_ALWAYS_INLINE bool atox(csubstr s, int64_t *C4_RESTRICT v) noexcept { return atoi(s, v); } -C4_ALWAYS_INLINE bool atox(csubstr s, float *C4_RESTRICT v) noexcept { return atof(s, v); } -C4_ALWAYS_INLINE bool atox(csubstr s, double *C4_RESTRICT v) noexcept { return atod(s, v); } - -C4_ALWAYS_INLINE size_t to_chars(substr buf, uint8_t v) noexcept { return write_dec(buf, v); } -C4_ALWAYS_INLINE size_t to_chars(substr buf, uint16_t v) noexcept { return write_dec(buf, v); } -C4_ALWAYS_INLINE size_t to_chars(substr buf, uint32_t v) noexcept { return write_dec(buf, v); } -C4_ALWAYS_INLINE size_t to_chars(substr buf, uint64_t v) noexcept { return write_dec(buf, v); } -C4_ALWAYS_INLINE size_t to_chars(substr buf, int8_t v) noexcept { return itoa(buf, v); } -C4_ALWAYS_INLINE size_t to_chars(substr buf, int16_t v) noexcept { return itoa(buf, v); } -C4_ALWAYS_INLINE size_t to_chars(substr buf, int32_t v) noexcept { return itoa(buf, v); } -C4_ALWAYS_INLINE size_t to_chars(substr buf, int64_t v) noexcept { return itoa(buf, v); } -C4_ALWAYS_INLINE size_t to_chars(substr buf, float v) noexcept { return ftoa(buf, v); } -C4_ALWAYS_INLINE size_t to_chars(substr buf, double v) noexcept { return dtoa(buf, v); } - -C4_ALWAYS_INLINE bool from_chars(csubstr buf, uint8_t *C4_RESTRICT v) noexcept { return atou(buf, v); } -C4_ALWAYS_INLINE bool from_chars(csubstr buf, uint16_t *C4_RESTRICT v) noexcept { return atou(buf, v); } -C4_ALWAYS_INLINE bool from_chars(csubstr buf, uint32_t *C4_RESTRICT v) noexcept { return atou(buf, v); } -C4_ALWAYS_INLINE bool from_chars(csubstr buf, uint64_t *C4_RESTRICT v) noexcept { return atou(buf, v); } -C4_ALWAYS_INLINE bool from_chars(csubstr buf, int8_t *C4_RESTRICT v) noexcept { return atoi(buf, v); } -C4_ALWAYS_INLINE bool from_chars(csubstr buf, int16_t *C4_RESTRICT v) noexcept { return atoi(buf, v); } -C4_ALWAYS_INLINE bool from_chars(csubstr buf, int32_t *C4_RESTRICT v) noexcept { return atoi(buf, v); } -C4_ALWAYS_INLINE bool from_chars(csubstr buf, int64_t *C4_RESTRICT v) noexcept { return atoi(buf, v); } -C4_ALWAYS_INLINE bool from_chars(csubstr buf, float *C4_RESTRICT v) noexcept { return atof(buf, v); } -C4_ALWAYS_INLINE bool from_chars(csubstr buf, double *C4_RESTRICT v) noexcept { return atod(buf, v); } - -C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, uint8_t *C4_RESTRICT v) noexcept { return atou_first(buf, v); } -C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, uint16_t *C4_RESTRICT v) noexcept { return atou_first(buf, v); } -C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, uint32_t *C4_RESTRICT v) noexcept { return atou_first(buf, v); } -C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, uint64_t *C4_RESTRICT v) noexcept { return atou_first(buf, v); } -C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, int8_t *C4_RESTRICT v) noexcept { return atoi_first(buf, v); } -C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, int16_t *C4_RESTRICT v) noexcept { return atoi_first(buf, v); } -C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, int32_t *C4_RESTRICT v) noexcept { return atoi_first(buf, v); } -C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, int64_t *C4_RESTRICT v) noexcept { return atoi_first(buf, v); } -C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, float *C4_RESTRICT v) noexcept { return atof_first(buf, v); } -C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, double *C4_RESTRICT v) noexcept { return atod_first(buf, v); } - - -//----------------------------------------------------------------------------- -// on some platforms, (unsigned) int and (unsigned) long -// are not any of the fixed length types above - -#define _C4_IF_NOT_FIXED_LENGTH_I(T, ty) C4_ALWAYS_INLINE typename std::enable_if::value && !is_fixed_length::value_i, ty> -#define _C4_IF_NOT_FIXED_LENGTH_U(T, ty) C4_ALWAYS_INLINE typename std::enable_if::value && !is_fixed_length::value_u, ty> - -template _C4_IF_NOT_FIXED_LENGTH_I(T, size_t)::type xtoa(substr buf, T v) noexcept { return itoa(buf, v); } -template _C4_IF_NOT_FIXED_LENGTH_U(T, size_t)::type xtoa(substr buf, T v) noexcept { return write_dec(buf, v); } - -template _C4_IF_NOT_FIXED_LENGTH_I(T, bool )::type atox(csubstr buf, T *C4_RESTRICT v) noexcept { return atoi(buf, v); } -template _C4_IF_NOT_FIXED_LENGTH_U(T, bool )::type atox(csubstr buf, T *C4_RESTRICT v) noexcept { return atou(buf, v); } - -template _C4_IF_NOT_FIXED_LENGTH_I(T, size_t)::type to_chars(substr buf, T v) noexcept { return itoa(buf, v); } -template _C4_IF_NOT_FIXED_LENGTH_U(T, size_t)::type to_chars(substr buf, T v) noexcept { return write_dec(buf, v); } - -template _C4_IF_NOT_FIXED_LENGTH_I(T, bool )::type from_chars(csubstr buf, T *C4_RESTRICT v) noexcept { return atoi(buf, v); } -template _C4_IF_NOT_FIXED_LENGTH_U(T, bool )::type from_chars(csubstr buf, T *C4_RESTRICT v) noexcept { return atou(buf, v); } - -template _C4_IF_NOT_FIXED_LENGTH_I(T, size_t)::type from_chars_first(csubstr buf, T *C4_RESTRICT v) noexcept { return atoi_first(buf, v); } -template _C4_IF_NOT_FIXED_LENGTH_U(T, size_t)::type from_chars_first(csubstr buf, T *C4_RESTRICT v) noexcept { return atou_first(buf, v); } - -#undef _C4_IF_NOT_FIXED_LENGTH_I -#undef _C4_IF_NOT_FIXED_LENGTH_U - - -//----------------------------------------------------------------------------- -// for pointers - -template C4_ALWAYS_INLINE size_t xtoa(substr s, T *v) noexcept { return itoa(s, (intptr_t)v, (intptr_t)16); } -template C4_ALWAYS_INLINE bool atox(csubstr s, T **v) noexcept { intptr_t tmp; bool ret = atox(s, &tmp); if(ret) { *v = (T*)tmp; } return ret; } -template C4_ALWAYS_INLINE size_t to_chars(substr s, T *v) noexcept { return itoa(s, (intptr_t)v, (intptr_t)16); } -template C4_ALWAYS_INLINE bool from_chars(csubstr buf, T **v) noexcept { intptr_t tmp; bool ret = from_chars(buf, &tmp); if(ret) { *v = (T*)tmp; } return ret; } -template C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, T **v) noexcept { intptr_t tmp; bool ret = from_chars_first(buf, &tmp); if(ret) { *v = (T*)tmp; } return ret; } - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -/** call to_chars() and return a substr consisting of the - * written portion of the input buffer. Ie, same as to_chars(), - * but return a substr instead of a size_t. - * - * @see to_chars() */ -template -C4_ALWAYS_INLINE substr to_chars_sub(substr buf, T const& C4_RESTRICT v) noexcept -{ - size_t sz = to_chars(buf, v); - return buf.left_of(sz <= buf.len ? sz : buf.len); -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -// bool implementation - -C4_ALWAYS_INLINE size_t to_chars(substr buf, bool v) noexcept -{ - int val = v; - return to_chars(buf, val); -} - -inline bool from_chars(csubstr buf, bool * C4_RESTRICT v) noexcept -{ - if(buf == '0') - { - *v = false; return true; - } - else if(buf == '1') - { - *v = true; return true; - } - else if(buf == "false") - { - *v = false; return true; - } - else if(buf == "true") - { - *v = true; return true; - } - else if(buf == "False") - { - *v = false; return true; - } - else if(buf == "True") - { - *v = true; return true; - } - else if(buf == "FALSE") - { - *v = false; return true; - } - else if(buf == "TRUE") - { - *v = true; return true; - } - // fallback to c-style int bools - int val = 0; - bool ret = from_chars(buf, &val); - if(C4_LIKELY(ret)) - { - *v = (val != 0); - } - return ret; -} - -inline size_t from_chars_first(csubstr buf, bool * C4_RESTRICT v) noexcept -{ - csubstr trimmed = buf.first_non_empty_span(); - if(trimmed.len == 0 || !from_chars(buf, v)) - return csubstr::npos; - return trimmed.len; -} - - -//----------------------------------------------------------------------------- -// single-char implementation - -inline size_t to_chars(substr buf, char v) noexcept -{ - if(buf.len > 0) - buf[0] = v; - return 1; -} - -/** extract a single character from a substring - * @note to extract a string instead and not just a single character, use the csubstr overload */ -inline bool from_chars(csubstr buf, char * C4_RESTRICT v) noexcept -{ - if(buf.len != 1) - return false; - *v = buf[0]; - return true; -} - -inline size_t from_chars_first(csubstr buf, char * C4_RESTRICT v) noexcept -{ - if(buf.len < 1) - return csubstr::npos; - *v = buf[0]; - return 1; -} - - -//----------------------------------------------------------------------------- -// csubstr implementation - -inline size_t to_chars(substr buf, csubstr v) noexcept -{ - C4_ASSERT(!buf.overlaps(v)); - size_t len = buf.len < v.len ? buf.len : v.len; - // calling memcpy with null strings is undefined behavior - // and will wreak havoc in calling code's branches. - // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637 - if(len) - { - C4_ASSERT(buf.str != nullptr); - C4_ASSERT(v.str != nullptr); - memcpy(buf.str, v.str, len); - } - return v.len; -} - -inline bool from_chars(csubstr buf, csubstr *C4_RESTRICT v) noexcept -{ - *v = buf; - return true; -} - -inline size_t from_chars_first(substr buf, csubstr * C4_RESTRICT v) noexcept -{ - csubstr trimmed = buf.first_non_empty_span(); - if(trimmed.len == 0) - return csubstr::npos; - *v = trimmed; - return static_cast(trimmed.end() - buf.begin()); -} - - -//----------------------------------------------------------------------------- -// substr - -inline size_t to_chars(substr buf, substr v) noexcept -{ - C4_ASSERT(!buf.overlaps(v)); - size_t len = buf.len < v.len ? buf.len : v.len; - // calling memcpy with null strings is undefined behavior - // and will wreak havoc in calling code's branches. - // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637 - if(len) - { - C4_ASSERT(buf.str != nullptr); - C4_ASSERT(v.str != nullptr); - memcpy(buf.str, v.str, len); - } - return v.len; -} - -inline bool from_chars(csubstr buf, substr * C4_RESTRICT v) noexcept -{ - C4_ASSERT(!buf.overlaps(*v)); - // is the destination buffer wide enough? - if(v->len >= buf.len) - { - // calling memcpy with null strings is undefined behavior - // and will wreak havoc in calling code's branches. - // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637 - if(buf.len) - { - C4_ASSERT(buf.str != nullptr); - C4_ASSERT(v->str != nullptr); - memcpy(v->str, buf.str, buf.len); - } - v->len = buf.len; - return true; - } - return false; -} - -inline size_t from_chars_first(csubstr buf, substr * C4_RESTRICT v) noexcept -{ - csubstr trimmed = buf.first_non_empty_span(); - C4_ASSERT(!trimmed.overlaps(*v)); - if(C4_UNLIKELY(trimmed.len == 0)) - return csubstr::npos; - size_t len = trimmed.len > v->len ? v->len : trimmed.len; - // calling memcpy with null strings is undefined behavior - // and will wreak havoc in calling code's branches. - // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637 - if(len) - { - C4_ASSERT(buf.str != nullptr); - C4_ASSERT(v->str != nullptr); - memcpy(v->str, trimmed.str, len); - } - if(C4_UNLIKELY(trimmed.len > v->len)) - return csubstr::npos; - return static_cast(trimmed.end() - buf.begin()); -} - - -//----------------------------------------------------------------------------- - -template -inline size_t to_chars(substr buf, const char (& C4_RESTRICT v)[N]) noexcept -{ - csubstr sp(v); - return to_chars(buf, sp); -} - -inline size_t to_chars(substr buf, const char * C4_RESTRICT v) noexcept -{ - return to_chars(buf, to_csubstr(v)); -} - -} // namespace c4 - -#ifdef _MSC_VER -# pragma warning(pop) -#elif defined(__clang__) -# pragma clang diagnostic pop -#elif defined(__GNUC__) -# pragma GCC diagnostic pop -#endif - -#endif /* _C4_CHARCONV_HPP_ */ - - -// (end https://github.com/biojppm/c4core/src/c4/charconv.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/utf.hpp -// https://github.com/biojppm/c4core/src/c4/utf.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef C4_UTF_HPP_ -#define C4_UTF_HPP_ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/language.hpp -//#include "c4/language.hpp" -#if !defined(C4_LANGUAGE_HPP_) && !defined(_C4_LANGUAGE_HPP_) -#error "amalgamate: file c4/language.hpp must have been included at this point" -#endif /* C4_LANGUAGE_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/substr_fwd.hpp -//#include "c4/substr_fwd.hpp" -#if !defined(C4_SUBSTR_FWD_HPP_) && !defined(_C4_SUBSTR_FWD_HPP_) -#error "amalgamate: file c4/substr_fwd.hpp must have been included at this point" -#endif /* C4_SUBSTR_FWD_HPP_ */ - -//included above: -//#include -//included above: -//#include - -namespace c4 { - -substr decode_code_point(substr out, csubstr code_point); -size_t decode_code_point(uint8_t *C4_RESTRICT buf, size_t buflen, const uint32_t code); - -} // namespace c4 - -#endif // C4_UTF_HPP_ - - -// (end https://github.com/biojppm/c4core/src/c4/utf.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/format.hpp -// https://github.com/biojppm/c4core/src/c4/format.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_FORMAT_HPP_ -#define _C4_FORMAT_HPP_ - -/** @file format.hpp provides type-safe facilities for formatting arguments - * to string buffers */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/charconv.hpp -//#include "c4/charconv.hpp" -#if !defined(C4_CHARCONV_HPP_) && !defined(_C4_CHARCONV_HPP_) -#error "amalgamate: file c4/charconv.hpp must have been included at this point" -#endif /* C4_CHARCONV_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/blob.hpp -//#include "c4/blob.hpp" -#if !defined(C4_BLOB_HPP_) && !defined(_C4_BLOB_HPP_) -#error "amalgamate: file c4/blob.hpp must have been included at this point" -#endif /* C4_BLOB_HPP_ */ - - - -#ifdef _MSC_VER -# pragma warning(push) -# if C4_MSVC_VERSION != C4_MSVC_VERSION_2017 -# pragma warning(disable: 4800) // forcing value to bool 'true' or 'false' (performance warning) -# endif -# pragma warning(disable: 4996) // snprintf/scanf: this function or variable may be unsafe -#elif defined(__clang__) -# pragma clang diagnostic push -#elif defined(__GNUC__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wuseless-cast" -#endif - -namespace c4 { - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -// formatting truthy types as booleans - -namespace fmt { - -/** write a variable as an alphabetic boolean, ie as either true or false - * @param strict_read */ -template -struct boolalpha_ -{ - boolalpha_(T val_, bool strict_read_=false) : val(val_ ? true : false), strict_read(strict_read_) {} - bool val; - bool strict_read; -}; - -template -boolalpha_ boolalpha(T const& val, bool strict_read=false) -{ - return boolalpha_(val, strict_read); -} - -} // namespace fmt - -/** write a variable as an alphabetic boolean, ie as either true or false */ -template -inline size_t to_chars(substr buf, fmt::boolalpha_ fmt) -{ - return to_chars(buf, fmt.val ? "true" : "false"); -} - - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -// formatting integral types - -namespace fmt { - -/** format an integral type with a custom radix */ -template -struct integral_ -{ - T val; - T radix; - C4_ALWAYS_INLINE integral_(T val_, T radix_) : val(val_), radix(radix_) {} -}; - -/** format an integral type with a custom radix, and pad with zeroes on the left */ -template -struct integral_padded_ -{ - T val; - T radix; - size_t num_digits; - C4_ALWAYS_INLINE integral_padded_(T val_, T radix_, size_t nd) : val(val_), radix(radix_), num_digits(nd) {} -}; - -/** format an integral type with a custom radix */ -template -C4_ALWAYS_INLINE integral_ integral(T val, T radix=10) -{ - return integral_(val, radix); -} -/** format an integral type with a custom radix */ -template -C4_ALWAYS_INLINE integral_ integral(T const* val, T radix=10) -{ - return integral_(reinterpret_cast(val), static_cast(radix)); -} -/** format an integral type with a custom radix */ -template -C4_ALWAYS_INLINE integral_ integral(std::nullptr_t, T radix=10) -{ - return integral_(intptr_t(0), static_cast(radix)); -} -/** pad the argument with zeroes on the left, with decimal radix */ -template -C4_ALWAYS_INLINE integral_padded_ zpad(T val, size_t num_digits) -{ - return integral_padded_(val, T(10), num_digits); -} -/** pad the argument with zeroes on the left */ -template -C4_ALWAYS_INLINE integral_padded_ zpad(integral_ val, size_t num_digits) -{ - return integral_padded_(val.val, val.radix, num_digits); -} -/** pad the argument with zeroes on the left */ -C4_ALWAYS_INLINE integral_padded_ zpad(std::nullptr_t, size_t num_digits) -{ - return integral_padded_(0, 16, num_digits); -} -/** pad the argument with zeroes on the left */ -template -C4_ALWAYS_INLINE integral_padded_ zpad(T const* val, size_t num_digits) -{ - return integral_padded_(reinterpret_cast(val), 16, num_digits); -} -template -C4_ALWAYS_INLINE integral_padded_ zpad(T * val, size_t num_digits) -{ - return integral_padded_(reinterpret_cast(val), 16, num_digits); -} - - -/** format the pointer as an hexadecimal value */ -template -inline integral_ hex(T * v) -{ - return integral_(reinterpret_cast(v), intptr_t(16)); -} -/** format the pointer as an hexadecimal value */ -template -inline integral_ hex(T const* v) -{ - return integral_(reinterpret_cast(v), intptr_t(16)); -} -/** format null as an hexadecimal value - * @overload hex */ -inline integral_ hex(std::nullptr_t) -{ - return integral_(0, intptr_t(16)); -} -/** format the integral_ argument as an hexadecimal value - * @overload hex */ -template -inline integral_ hex(T v) -{ - return integral_(v, T(16)); -} - -/** format the pointer as an octal value */ -template -inline integral_ oct(T const* v) -{ - return integral_(reinterpret_cast(v), intptr_t(8)); -} -/** format the pointer as an octal value */ -template -inline integral_ oct(T * v) -{ - return integral_(reinterpret_cast(v), intptr_t(8)); -} -/** format null as an octal value */ -inline integral_ oct(std::nullptr_t) -{ - return integral_(intptr_t(0), intptr_t(8)); -} -/** format the integral_ argument as an octal value */ -template -inline integral_ oct(T v) -{ - return integral_(v, T(8)); -} - -/** format the pointer as a binary 0-1 value - * @see c4::raw() if you want to use a binary memcpy instead of 0-1 formatting */ -template -inline integral_ bin(T const* v) -{ - return integral_(reinterpret_cast(v), intptr_t(2)); -} -/** format the pointer as a binary 0-1 value - * @see c4::raw() if you want to use a binary memcpy instead of 0-1 formatting */ -template -inline integral_ bin(T * v) -{ - return integral_(reinterpret_cast(v), intptr_t(2)); -} -/** format null as a binary 0-1 value - * @see c4::raw() if you want to use a binary memcpy instead of 0-1 formatting */ -inline integral_ bin(std::nullptr_t) -{ - return integral_(intptr_t(0), intptr_t(2)); -} -/** format the integral_ argument as a binary 0-1 value - * @see c4::raw() if you want to use a raw memcpy-based binary dump instead of 0-1 formatting */ -template -inline integral_ bin(T v) -{ - return integral_(v, T(2)); -} - - -template -struct overflow_checked_ -{ - static_assert(std::is_integral::value, "range checking only for integral types"); - C4_ALWAYS_INLINE overflow_checked_(T &val_) : val(&val_) {} - T *val; -}; -template -C4_ALWAYS_INLINE overflow_checked_ overflow_checked(T &val) -{ - return overflow_checked_(val); -} - -} // namespace fmt - -/** format an integral_ signed type */ -template -C4_ALWAYS_INLINE -typename std::enable_if::value, size_t>::type -to_chars(substr buf, fmt::integral_ fmt) -{ - return itoa(buf, fmt.val, fmt.radix); -} -/** format an integral_ signed type, pad with zeroes */ -template -C4_ALWAYS_INLINE -typename std::enable_if::value, size_t>::type -to_chars(substr buf, fmt::integral_padded_ fmt) -{ - return itoa(buf, fmt.val, fmt.radix, fmt.num_digits); -} - -/** format an integral_ unsigned type */ -template -C4_ALWAYS_INLINE -typename std::enable_if::value, size_t>::type -to_chars(substr buf, fmt::integral_ fmt) -{ - return utoa(buf, fmt.val, fmt.radix); -} -/** format an integral_ unsigned type, pad with zeroes */ -template -C4_ALWAYS_INLINE -typename std::enable_if::value, size_t>::type -to_chars(substr buf, fmt::integral_padded_ fmt) -{ - return utoa(buf, fmt.val, fmt.radix, fmt.num_digits); -} - -template -C4_ALWAYS_INLINE bool from_chars(csubstr s, fmt::overflow_checked_ wrapper) -{ - if(C4_LIKELY(!overflows(s))) - return atox(s, wrapper.val); - return false; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -// formatting real types - -namespace fmt { - -template -struct real_ -{ - T val; - int precision; - RealFormat_e fmt; - real_(T v, int prec=-1, RealFormat_e f=FTOA_FLOAT) : val(v), precision(prec), fmt(f) {} -}; - -template -real_ real(T val, int precision, RealFormat_e fmt=FTOA_FLOAT) -{ - return real_(val, precision, fmt); -} - -} // namespace fmt - -inline size_t to_chars(substr buf, fmt::real_< float> fmt) { return ftoa(buf, fmt.val, fmt.precision, fmt.fmt); } -inline size_t to_chars(substr buf, fmt::real_ fmt) { return dtoa(buf, fmt.val, fmt.precision, fmt.fmt); } - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -// writing raw binary data - -namespace fmt { - -/** @see blob_ */ -template -struct raw_wrapper_ : public blob_ -{ - size_t alignment; - - C4_ALWAYS_INLINE raw_wrapper_(blob_ data, size_t alignment_) noexcept - : - blob_(data), - alignment(alignment_) - { - C4_ASSERT_MSG(alignment > 0 && (alignment & (alignment - 1)) == 0, "alignment must be a power of two"); - } -}; - -using const_raw_wrapper = raw_wrapper_; -using raw_wrapper = raw_wrapper_; - -/** mark a variable to be written in raw binary format, using memcpy - * @see blob_ */ -inline const_raw_wrapper craw(cblob data, size_t alignment=alignof(max_align_t)) -{ - return const_raw_wrapper(data, alignment); -} -/** mark a variable to be written in raw binary format, using memcpy - * @see blob_ */ -inline const_raw_wrapper raw(cblob data, size_t alignment=alignof(max_align_t)) -{ - return const_raw_wrapper(data, alignment); -} -/** mark a variable to be written in raw binary format, using memcpy - * @see blob_ */ -template -inline const_raw_wrapper craw(T const& C4_RESTRICT data, size_t alignment=alignof(T)) -{ - return const_raw_wrapper(cblob(data), alignment); -} -/** mark a variable to be written in raw binary format, using memcpy - * @see blob_ */ -template -inline const_raw_wrapper raw(T const& C4_RESTRICT data, size_t alignment=alignof(T)) -{ - return const_raw_wrapper(cblob(data), alignment); -} - -/** mark a variable to be read in raw binary format, using memcpy */ -inline raw_wrapper raw(blob data, size_t alignment=alignof(max_align_t)) -{ - return raw_wrapper(data, alignment); -} -/** mark a variable to be read in raw binary format, using memcpy */ -template -inline raw_wrapper raw(T & C4_RESTRICT data, size_t alignment=alignof(T)) -{ - return raw_wrapper(blob(data), alignment); -} - -} // namespace fmt - - -/** write a variable in raw binary format, using memcpy */ -C4CORE_EXPORT size_t to_chars(substr buf, fmt::const_raw_wrapper r); - -/** read a variable in raw binary format, using memcpy */ -C4CORE_EXPORT bool from_chars(csubstr buf, fmt::raw_wrapper *r); -/** read a variable in raw binary format, using memcpy */ -inline bool from_chars(csubstr buf, fmt::raw_wrapper r) -{ - return from_chars(buf, &r); -} - -/** read a variable in raw binary format, using memcpy */ -inline size_t from_chars_first(csubstr buf, fmt::raw_wrapper *r) -{ - return from_chars(buf, r); -} -/** read a variable in raw binary format, using memcpy */ -inline size_t from_chars_first(csubstr buf, fmt::raw_wrapper r) -{ - return from_chars(buf, &r); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -// formatting aligned to left/right - -namespace fmt { - -template -struct left_ -{ - T val; - size_t width; - char pad; - left_(T v, size_t w, char p) : val(v), width(w), pad(p) {} -}; - -template -struct right_ -{ - T val; - size_t width; - char pad; - right_(T v, size_t w, char p) : val(v), width(w), pad(p) {} -}; - -/** mark an argument to be aligned left */ -template -left_ left(T val, size_t width, char padchar=' ') -{ - return left_(val, width, padchar); -} - -/** mark an argument to be aligned right */ -template -right_ right(T val, size_t width, char padchar=' ') -{ - return right_(val, width, padchar); -} - -} // namespace fmt - - -template -size_t to_chars(substr buf, fmt::left_ const& C4_RESTRICT align) -{ - size_t ret = to_chars(buf, align.val); - if(ret >= buf.len || ret >= align.width) - return ret > align.width ? ret : align.width; - buf.first(align.width).sub(ret).fill(align.pad); - to_chars(buf, align.val); - return align.width; -} - -template -size_t to_chars(substr buf, fmt::right_ const& C4_RESTRICT align) -{ - size_t ret = to_chars(buf, align.val); - if(ret >= buf.len || ret >= align.width) - return ret > align.width ? ret : align.width; - size_t rem = static_cast(align.width - ret); - buf.first(rem).fill(align.pad); - to_chars(buf.sub(rem), align.val); - return align.width; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -/// @cond dev -// terminates the variadic recursion -inline size_t cat(substr /*buf*/) -{ - return 0; -} -/// @endcond - - -/** serialize the arguments, concatenating them to the given fixed-size buffer. - * The buffer size is strictly respected: no writes will occur beyond its end. - * @return the number of characters needed to write all the arguments into the buffer. - * @see c4::catrs() if instead of a fixed-size buffer, a resizeable container is desired - * @see c4::uncat() for the inverse function - * @see c4::catsep() if a separator between each argument is to be used - * @see c4::format() if a format string is desired */ -template -size_t cat(substr buf, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more) -{ - size_t num = to_chars(buf, a); - buf = buf.len >= num ? buf.sub(num) : substr{}; - num += cat(buf, more...); - return num; -} - -/** like c4::cat() but return a substr instead of a size */ -template -substr cat_sub(substr buf, Args && ...args) -{ - size_t sz = cat(buf, std::forward(args)...); - C4_CHECK(sz <= buf.len); - return {buf.str, sz <= buf.len ? sz : buf.len}; -} - - -//----------------------------------------------------------------------------- - -/// @cond dev -// terminates the variadic recursion -inline size_t uncat(csubstr /*buf*/) -{ - return 0; -} -/// @endcond - - -/** deserialize the arguments from the given buffer. - * - * @return the number of characters read from the buffer, or csubstr::npos - * if a conversion was not successful. - * @see c4::cat(). c4::uncat() is the inverse of c4::cat(). */ -template -size_t uncat(csubstr buf, Arg & C4_RESTRICT a, Args & C4_RESTRICT ...more) -{ - size_t out = from_chars_first(buf, &a); - if(C4_UNLIKELY(out == csubstr::npos)) - return csubstr::npos; - buf = buf.len >= out ? buf.sub(out) : substr{}; - size_t num = uncat(buf, more...); - if(C4_UNLIKELY(num == csubstr::npos)) - return csubstr::npos; - return out + num; -} - - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -namespace detail { - -template -inline size_t catsep_more(substr /*buf*/, Sep const& C4_RESTRICT /*sep*/) -{ - return 0; -} - -template -size_t catsep_more(substr buf, Sep const& C4_RESTRICT sep, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more) -{ - size_t ret = to_chars(buf, sep), num = ret; - buf = buf.len >= ret ? buf.sub(ret) : substr{}; - ret = to_chars(buf, a); - num += ret; - buf = buf.len >= ret ? buf.sub(ret) : substr{}; - ret = catsep_more(buf, sep, more...); - num += ret; - return num; -} - -template -inline size_t uncatsep_more(csubstr /*buf*/, Sep & /*sep*/) -{ - return 0; -} - -template -size_t uncatsep_more(csubstr buf, Sep & C4_RESTRICT sep, Arg & C4_RESTRICT a, Args & C4_RESTRICT ...more) -{ - size_t ret = from_chars_first(buf, &sep), num = ret; - if(C4_UNLIKELY(ret == csubstr::npos)) - return csubstr::npos; - buf = buf.len >= ret ? buf.sub(ret) : substr{}; - ret = from_chars_first(buf, &a); - if(C4_UNLIKELY(ret == csubstr::npos)) - return csubstr::npos; - num += ret; - buf = buf.len >= ret ? buf.sub(ret) : substr{}; - ret = uncatsep_more(buf, sep, more...); - if(C4_UNLIKELY(ret == csubstr::npos)) - return csubstr::npos; - num += ret; - return num; -} - -} // namespace detail - - -/** serialize the arguments, concatenating them to the given fixed-size - * buffer, using a separator between each argument. - * The buffer size is strictly respected: no writes will occur beyond its end. - * @return the number of characters needed to write all the arguments into the buffer. - * @see c4::catseprs() if instead of a fixed-size buffer, a resizeable container is desired - * @see c4::uncatsep() for the inverse function (ie, reading instead of writing) - * @see c4::cat() if no separator is needed - * @see c4::format() if a format string is desired */ -template -size_t catsep(substr buf, Sep const& C4_RESTRICT sep, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more) -{ - size_t num = to_chars(buf, a); - buf = buf.len >= num ? buf.sub(num) : substr{}; - num += detail::catsep_more(buf, sep, more...); - return num; -} - -/** like c4::catsep() but return a substr instead of a size - * @see c4::catsep(). c4::uncatsep() is the inverse of c4::catsep(). */ -template -substr catsep_sub(substr buf, Args && ...args) -{ - size_t sz = catsep(buf, std::forward(args)...); - C4_CHECK(sz <= buf.len); - return {buf.str, sz <= buf.len ? sz : buf.len}; -} - -/** deserialize the arguments from the given buffer, using a separator. - * - * @return the number of characters read from the buffer, or csubstr::npos - * if a conversion was not successful - * @see c4::catsep(). c4::uncatsep() is the inverse of c4::catsep(). */ -template -size_t uncatsep(csubstr buf, Sep & C4_RESTRICT sep, Arg & C4_RESTRICT a, Args & C4_RESTRICT ...more) -{ - size_t ret = from_chars_first(buf, &a), num = ret; - if(C4_UNLIKELY(ret == csubstr::npos)) - return csubstr::npos; - buf = buf.len >= ret ? buf.sub(ret) : substr{}; - ret = detail::uncatsep_more(buf, sep, more...); - if(C4_UNLIKELY(ret == csubstr::npos)) - return csubstr::npos; - num += ret; - return num; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -/// @cond dev -// terminates the variadic recursion -inline size_t format(substr buf, csubstr fmt) -{ - return to_chars(buf, fmt); -} -/// @endcond - - -/** using a format string, serialize the arguments into the given - * fixed-size buffer. - * The buffer size is strictly respected: no writes will occur beyond its end. - * In the format string, each argument is marked with a compact - * curly-bracket pair: {}. Arguments beyond the last curly bracket pair - * are silently ignored. For example: - * @code{.cpp} - * c4::format(buf, "the {} drank {} {}", "partier", 5, "beers"); // the partier drank 5 beers - * c4::format(buf, "the {} drank {} {}", "programmer", 6, "coffees"); // the programmer drank 6 coffees - * @endcode - * @return the number of characters needed to write into the buffer. - * @see c4::formatrs() if instead of a fixed-size buffer, a resizeable container is desired - * @see c4::unformat() for the inverse function - * @see c4::cat() if no format or separator is needed - * @see c4::catsep() if no format is needed, but a separator must be used */ -template -size_t format(substr buf, csubstr fmt, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more) -{ - size_t pos = fmt.find("{}"); // @todo use _find_fmt() - if(C4_UNLIKELY(pos == csubstr::npos)) - return to_chars(buf, fmt); - size_t num = to_chars(buf, fmt.sub(0, pos)); - size_t out = num; - buf = buf.len >= num ? buf.sub(num) : substr{}; - num = to_chars(buf, a); - out += num; - buf = buf.len >= num ? buf.sub(num) : substr{}; - num = format(buf, fmt.sub(pos + 2), more...); - out += num; - return out; -} - -/** like c4::format() but return a substr instead of a size - * @see c4::format() - * @see c4::catsep(). uncatsep() is the inverse of catsep(). */ -template -substr format_sub(substr buf, csubstr fmt, Args const& C4_RESTRICT ...args) -{ - size_t sz = c4::format(buf, fmt, args...); - C4_CHECK(sz <= buf.len); - return {buf.str, sz <= buf.len ? sz : buf.len}; -} - - -//----------------------------------------------------------------------------- - -/// @cond dev -// terminates the variadic recursion -inline size_t unformat(csubstr /*buf*/, csubstr fmt) -{ - return fmt.len; -} -/// @endcond - - -/** using a format string, deserialize the arguments from the given - * buffer. - * @return the number of characters read from the buffer, or npos if a conversion failed. - * @see c4::format(). c4::unformat() is the inverse function to format(). */ -template -size_t unformat(csubstr buf, csubstr fmt, Arg & C4_RESTRICT a, Args & C4_RESTRICT ...more) -{ - const size_t pos = fmt.find("{}"); - if(C4_UNLIKELY(pos == csubstr::npos)) - return unformat(buf, fmt); - size_t num = pos; - size_t out = num; - buf = buf.len >= num ? buf.sub(num) : substr{}; - num = from_chars_first(buf, &a); - if(C4_UNLIKELY(num == csubstr::npos)) - return csubstr::npos; - out += num; - buf = buf.len >= num ? buf.sub(num) : substr{}; - num = unformat(buf, fmt.sub(pos + 2), more...); - if(C4_UNLIKELY(num == csubstr::npos)) - return csubstr::npos; - out += num; - return out; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -/** a tag type for marking append to container - * @see c4::catrs() */ -struct append_t {}; - -/** a tag variable - * @see c4::catrs() */ -constexpr const append_t append = {}; - - -//----------------------------------------------------------------------------- - -/** like c4::cat(), but receives a container, and resizes it as needed to contain - * the result. The container is overwritten. To append to it, use the append - * overload. - * @see c4::cat() */ -template -inline void catrs(CharOwningContainer * C4_RESTRICT cont, Args const& C4_RESTRICT ...args) -{ -retry: - substr buf = to_substr(*cont); - size_t ret = cat(buf, args...); - cont->resize(ret); - if(ret > buf.len) - goto retry; -} - -/** like c4::cat(), but creates and returns a new container sized as needed to contain - * the result. - * @see c4::cat() */ -template -inline CharOwningContainer catrs(Args const& C4_RESTRICT ...args) -{ - CharOwningContainer cont; - catrs(&cont, args...); - return cont; -} - -/** like c4::cat(), but receives a container, and appends to it instead of - * overwriting it. The container is resized as needed to contain the result. - * @return the region newly appended to the original container - * @see c4::cat() - * @see c4::catrs() */ -template -inline csubstr catrs(append_t, CharOwningContainer * C4_RESTRICT cont, Args const& C4_RESTRICT ...args) -{ - const size_t pos = cont->size(); -retry: - substr buf = to_substr(*cont).sub(pos); - size_t ret = cat(buf, args...); - cont->resize(pos + ret); - if(ret > buf.len) - goto retry; - return to_csubstr(*cont).range(pos, cont->size()); -} - - -//----------------------------------------------------------------------------- - -/// @cond dev -// terminates the recursion -template -inline void catseprs(CharOwningContainer * C4_RESTRICT, Sep const& C4_RESTRICT) -{ - return; -} -/// @end cond - - -/** like c4::catsep(), but receives a container, and resizes it as needed to contain the result. - * The container is overwritten. To append to the container use the append overload. - * @see c4::catsep() */ -template -inline void catseprs(CharOwningContainer * C4_RESTRICT cont, Sep const& C4_RESTRICT sep, Args const& C4_RESTRICT ...args) -{ -retry: - substr buf = to_substr(*cont); - size_t ret = catsep(buf, sep, args...); - cont->resize(ret); - if(ret > buf.len) - goto retry; -} - -/** like c4::catsep(), but create a new container with the result. - * @return the requested container */ -template -inline CharOwningContainer catseprs(Sep const& C4_RESTRICT sep, Args const& C4_RESTRICT ...args) -{ - CharOwningContainer cont; - catseprs(&cont, sep, args...); - return cont; -} - - -/// @cond dev -// terminates the recursion -template -inline csubstr catseprs(append_t, CharOwningContainer * C4_RESTRICT, Sep const& C4_RESTRICT) -{ - csubstr s; - return s; -} -/// @endcond - -/** like catsep(), but receives a container, and appends the arguments, resizing the - * container as needed to contain the result. The buffer is appended to. - * @return a csubstr of the appended part - * @ingroup formatting_functions */ -template -inline csubstr catseprs(append_t, CharOwningContainer * C4_RESTRICT cont, Sep const& C4_RESTRICT sep, Args const& C4_RESTRICT ...args) -{ - const size_t pos = cont->size(); -retry: - substr buf = to_substr(*cont).sub(pos); - size_t ret = catsep(buf, sep, args...); - cont->resize(pos + ret); - if(ret > buf.len) - goto retry; - return to_csubstr(*cont).range(pos, cont->size()); -} - - -//----------------------------------------------------------------------------- - -/** like c4::format(), but receives a container, and resizes it as needed - * to contain the result. The container is overwritten. To append to - * the container use the append overload. - * @see c4::format() */ -template -inline void formatrs(CharOwningContainer * C4_RESTRICT cont, csubstr fmt, Args const& C4_RESTRICT ...args) -{ -retry: - substr buf = to_substr(*cont); - size_t ret = format(buf, fmt, args...); - cont->resize(ret); - if(ret > buf.len) - goto retry; -} - -/** like c4::format(), but create a new container with the result. - * @return the requested container */ -template -inline CharOwningContainer formatrs(csubstr fmt, Args const& C4_RESTRICT ...args) -{ - CharOwningContainer cont; - formatrs(&cont, fmt, args...); - return cont; -} - -/** like format(), but receives a container, and appends the - * arguments, resizing the container as needed to contain the - * result. The buffer is appended to. - * @return the region newly appended to the original container - * @ingroup formatting_functions */ -template -inline csubstr formatrs(append_t, CharOwningContainer * C4_RESTRICT cont, csubstr fmt, Args const& C4_RESTRICT ...args) -{ - const size_t pos = cont->size(); -retry: - substr buf = to_substr(*cont).sub(pos); - size_t ret = format(buf, fmt, args...); - cont->resize(pos + ret); - if(ret > buf.len) - goto retry; - return to_csubstr(*cont).range(pos, cont->size()); -} - -} // namespace c4 - -#ifdef _MSC_VER -# pragma warning(pop) -#elif defined(__clang__) -# pragma clang diagnostic pop -#elif defined(__GNUC__) -# pragma GCC diagnostic pop -#endif - -#endif /* _C4_FORMAT_HPP_ */ - - -// (end https://github.com/biojppm/c4core/src/c4/format.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/dump.hpp -// https://github.com/biojppm/c4core/src/c4/dump.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef C4_DUMP_HPP_ -#define C4_DUMP_HPP_ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/substr.hpp -//#include -#if !defined(C4_SUBSTR_HPP_) && !defined(_C4_SUBSTR_HPP_) -#error "amalgamate: file c4/substr.hpp must have been included at this point" -#endif /* C4_SUBSTR_HPP_ */ - - -namespace c4 { - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -/** type of the function to dump characters */ -using DumperPfn = void (*)(csubstr buf); - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -template -inline size_t dump(substr buf, Arg const& a) -{ - size_t sz = to_chars(buf, a); // need to serialize to the buffer - if(C4_LIKELY(sz <= buf.len)) - dumpfn(buf.first(sz)); - return sz; -} - -template -inline size_t dump(DumperFn &&dumpfn, substr buf, Arg const& a) -{ - size_t sz = to_chars(buf, a); // need to serialize to the buffer - if(C4_LIKELY(sz <= buf.len)) - dumpfn(buf.first(sz)); - return sz; -} - -template -inline size_t dump(substr buf, csubstr a) -{ - if(buf.len) - dumpfn(a); // dump directly, no need to serialize to the buffer - return 0; // no space was used in the buffer -} - -template -inline size_t dump(DumperFn &&dumpfn, substr buf, csubstr a) -{ - if(buf.len) - dumpfn(a); // dump directly, no need to serialize to the buffer - return 0; // no space was used in the buffer -} - -template -inline size_t dump(substr buf, const char (&a)[N]) -{ - if(buf.len) - dumpfn(csubstr(a)); // dump directly, no need to serialize to the buffer - return 0; // no space was used in the buffer -} - -template -inline size_t dump(DumperFn &&dumpfn, substr buf, const char (&a)[N]) -{ - if(buf.len) - dumpfn(csubstr(a)); // dump directly, no need to serialize to the buffer - return 0; // no space was used in the buffer -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -/** */ -struct DumpResults -{ - enum : size_t { noarg = (size_t)-1 }; - size_t bufsize = 0; - size_t lastok = noarg; - bool success_until(size_t expected) const { return lastok == noarg ? false : lastok >= expected; } - bool write_arg(size_t arg) const { return lastok == noarg || arg > lastok; } - size_t argfail() const { return lastok + 1; } -}; - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -/// @cond dev -// terminates the variadic recursion -template -size_t cat_dump(DumperFn &&, substr) -{ - return 0; -} - -// terminates the variadic recursion -template -size_t cat_dump(substr) -{ - return 0; -} -/// @endcond - -/** take the function pointer as a function argument */ -template -size_t cat_dump(DumperFn &&dumpfn, substr buf, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more) -{ - size_t size_for_a = dump(dumpfn, buf, a); - if(C4_UNLIKELY(size_for_a > buf.len)) - buf = buf.first(0); // ensure no more calls - size_t size_for_more = cat_dump(dumpfn, buf, more...); - return size_for_more > size_for_a ? size_for_more : size_for_a; -} - -/** take the function pointer as a template argument */ -template -size_t cat_dump(substr buf, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more) -{ - size_t size_for_a = dump(buf, a); - if(C4_LIKELY(size_for_a > buf.len)) - buf = buf.first(0); // ensure no more calls - size_t size_for_more = cat_dump(buf, more...); - return size_for_more > size_for_a ? size_for_more : size_for_a; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -/// @cond dev -namespace detail { - -// terminates the variadic recursion -template -DumpResults cat_dump_resume(size_t currarg, DumpResults results, substr buf, Arg const& C4_RESTRICT a) -{ - if(C4_LIKELY(results.write_arg(currarg))) - { - size_t sz = dump(buf, a); // yield to the specialized function - if(currarg == results.lastok + 1 && sz <= buf.len) - results.lastok = currarg; - results.bufsize = sz > results.bufsize ? sz : results.bufsize; - } - return results; -} - -// terminates the variadic recursion -template -DumpResults cat_dump_resume(size_t currarg, DumperFn &&dumpfn, DumpResults results, substr buf, Arg const& C4_RESTRICT a) -{ - if(C4_LIKELY(results.write_arg(currarg))) - { - size_t sz = dump(dumpfn, buf, a); // yield to the specialized function - if(currarg == results.lastok + 1 && sz <= buf.len) - results.lastok = currarg; - results.bufsize = sz > results.bufsize ? sz : results.bufsize; - } - return results; -} - -template -DumpResults cat_dump_resume(size_t currarg, DumpResults results, substr buf, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more) -{ - results = detail::cat_dump_resume(currarg, results, buf, a); - return detail::cat_dump_resume(currarg + 1u, results, buf, more...); -} - -template -DumpResults cat_dump_resume(size_t currarg, DumperFn &&dumpfn, DumpResults results, substr buf, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more) -{ - results = detail::cat_dump_resume(currarg, dumpfn, results, buf, a); - return detail::cat_dump_resume(currarg + 1u, dumpfn, results, buf, more...); -} -} // namespace detail -/// @endcond - - -template -C4_ALWAYS_INLINE DumpResults cat_dump_resume(DumpResults results, substr buf, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more) -{ - if(results.bufsize > buf.len) - return results; - return detail::cat_dump_resume(0u, results, buf, a, more...); -} - -template -C4_ALWAYS_INLINE DumpResults cat_dump_resume(DumperFn &&dumpfn, DumpResults results, substr buf, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more) -{ - if(results.bufsize > buf.len) - return results; - return detail::cat_dump_resume(0u, dumpfn, results, buf, a, more...); -} - -template -C4_ALWAYS_INLINE DumpResults cat_dump_resume(substr buf, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more) -{ - return detail::cat_dump_resume(0u, DumpResults{}, buf, a, more...); -} - -template -C4_ALWAYS_INLINE DumpResults cat_dump_resume(DumperFn &&dumpfn, substr buf, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more) -{ - return detail::cat_dump_resume(0u, dumpfn, DumpResults{}, buf, a, more...); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -/// @cond dev -// terminate the recursion -template -size_t catsep_dump(DumperFn &&, substr, Sep const& C4_RESTRICT) -{ - return 0; -} - -// terminate the recursion -template -size_t catsep_dump(substr, Sep const& C4_RESTRICT) -{ - return 0; -} -/// @endcond - -/** take the function pointer as a function argument */ -template -size_t catsep_dump(DumperFn &&dumpfn, substr buf, Sep const& C4_RESTRICT sep, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more) -{ - size_t sz = dump(dumpfn, buf, a); - if(C4_UNLIKELY(sz > buf.len)) - buf = buf.first(0); // ensure no more calls - if C4_IF_CONSTEXPR (sizeof...(more) > 0) - { - size_t szsep = dump(dumpfn, buf, sep); - if(C4_UNLIKELY(szsep > buf.len)) - buf = buf.first(0); // ensure no more calls - sz = sz > szsep ? sz : szsep; - } - size_t size_for_more = catsep_dump(dumpfn, buf, sep, more...); - return size_for_more > sz ? size_for_more : sz; -} - -/** take the function pointer as a template argument */ -template -size_t catsep_dump(substr buf, Sep const& C4_RESTRICT sep, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more) -{ - size_t sz = dump(buf, a); - if(C4_UNLIKELY(sz > buf.len)) - buf = buf.first(0); // ensure no more calls - if C4_IF_CONSTEXPR (sizeof...(more) > 0) - { - size_t szsep = dump(buf, sep); - if(C4_UNLIKELY(szsep > buf.len)) - buf = buf.first(0); // ensure no more calls - sz = sz > szsep ? sz : szsep; - } - size_t size_for_more = catsep_dump(buf, sep, more...); - return size_for_more > sz ? size_for_more : sz; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -/// @cond dev -namespace detail { -template -void catsep_dump_resume_(size_t currarg, DumpResults *C4_RESTRICT results, substr *C4_RESTRICT buf, Arg const& C4_RESTRICT a) -{ - if(C4_LIKELY(results->write_arg(currarg))) - { - size_t sz = dump(*buf, a); - results->bufsize = sz > results->bufsize ? sz : results->bufsize; - if(C4_LIKELY(sz <= buf->len)) - results->lastok = currarg; - else - buf->len = 0; - } -} - -template -void catsep_dump_resume_(size_t currarg, DumperFn &&dumpfn, DumpResults *C4_RESTRICT results, substr *C4_RESTRICT buf, Arg const& C4_RESTRICT a) -{ - if(C4_LIKELY(results->write_arg(currarg))) - { - size_t sz = dump(dumpfn, *buf, a); - results->bufsize = sz > results->bufsize ? sz : results->bufsize; - if(C4_LIKELY(sz <= buf->len)) - results->lastok = currarg; - else - buf->len = 0; - } -} - -template -C4_ALWAYS_INLINE void catsep_dump_resume(size_t currarg, DumpResults *C4_RESTRICT results, substr *C4_RESTRICT buf, Sep const& C4_RESTRICT, Arg const& C4_RESTRICT a) -{ - detail::catsep_dump_resume_(currarg, results, buf, a); -} - -template -C4_ALWAYS_INLINE void catsep_dump_resume(size_t currarg, DumperFn &&dumpfn, DumpResults *C4_RESTRICT results, substr *C4_RESTRICT buf, Sep const& C4_RESTRICT, Arg const& C4_RESTRICT a) -{ - detail::catsep_dump_resume_(currarg, dumpfn, results, buf, a); -} - -template -C4_ALWAYS_INLINE void catsep_dump_resume(size_t currarg, DumpResults *C4_RESTRICT results, substr *C4_RESTRICT buf, Sep const& C4_RESTRICT sep, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more) -{ - detail::catsep_dump_resume_(currarg , results, buf, a); - detail::catsep_dump_resume_(currarg + 1u, results, buf, sep); - detail::catsep_dump_resume (currarg + 2u, results, buf, sep, more...); -} - -template -C4_ALWAYS_INLINE void catsep_dump_resume(size_t currarg, DumperFn &&dumpfn, DumpResults *C4_RESTRICT results, substr *C4_RESTRICT buf, Sep const& C4_RESTRICT sep, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more) -{ - detail::catsep_dump_resume_(currarg , dumpfn, results, buf, a); - detail::catsep_dump_resume_(currarg + 1u, dumpfn, results, buf, sep); - detail::catsep_dump_resume (currarg + 2u, dumpfn, results, buf, sep, more...); -} -} // namespace detail -/// @endcond - - -template -C4_ALWAYS_INLINE DumpResults catsep_dump_resume(DumpResults results, substr buf, Sep const& C4_RESTRICT sep, Args const& C4_RESTRICT ...more) -{ - detail::catsep_dump_resume(0u, &results, &buf, sep, more...); - return results; -} - -template -C4_ALWAYS_INLINE DumpResults catsep_dump_resume(DumperFn &&dumpfn, DumpResults results, substr buf, Sep const& C4_RESTRICT sep, Args const& C4_RESTRICT ...more) -{ - detail::catsep_dump_resume(0u, dumpfn, &results, &buf, sep, more...); - return results; -} - -template -C4_ALWAYS_INLINE DumpResults catsep_dump_resume(substr buf, Sep const& C4_RESTRICT sep, Args const& C4_RESTRICT ...more) -{ - DumpResults results; - detail::catsep_dump_resume(0u, &results, &buf, sep, more...); - return results; -} - -template -C4_ALWAYS_INLINE DumpResults catsep_dump_resume(DumperFn &&dumpfn, substr buf, Sep const& C4_RESTRICT sep, Args const& C4_RESTRICT ...more) -{ - DumpResults results; - detail::catsep_dump_resume(0u, dumpfn, &results, &buf, sep, more...); - return results; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -/** take the function pointer as a function argument */ -template -C4_ALWAYS_INLINE size_t format_dump(DumperFn &&dumpfn, substr buf, csubstr fmt) -{ - // we can dump without using buf - // but we'll only dump if the buffer is ok - if(C4_LIKELY(buf.len > 0 && fmt.len)) - dumpfn(fmt); - return 0u; -} - -/** take the function pointer as a function argument */ -template -C4_ALWAYS_INLINE size_t format_dump(substr buf, csubstr fmt) -{ - // we can dump without using buf - // but we'll only dump if the buffer is ok - if(C4_LIKELY(buf.len > 0 && fmt.len > 0)) - dumpfn(fmt); - return 0u; -} - -/** take the function pointer as a function argument */ -template -size_t format_dump(DumperFn &&dumpfn, substr buf, csubstr fmt, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more) -{ - // we can dump without using buf - // but we'll only dump if the buffer is ok - size_t pos = fmt.find("{}"); // @todo use _find_fmt() - if(C4_UNLIKELY(pos == csubstr::npos)) - { - if(C4_LIKELY(buf.len > 0 && fmt.len > 0)) - dumpfn(fmt); - return 0u; - } - if(C4_LIKELY(buf.len > 0 && pos > 0)) - dumpfn(fmt.first(pos)); // we can dump without using buf - fmt = fmt.sub(pos + 2); // skip {} do this before assigning to pos again - pos = dump(dumpfn, buf, a); - if(C4_UNLIKELY(pos > buf.len)) - buf.len = 0; // ensure no more calls to dump - size_t size_for_more = format_dump(dumpfn, buf, fmt, more...); - return size_for_more > pos ? size_for_more : pos; -} - -/** take the function pointer as a template argument */ -template -size_t format_dump(substr buf, csubstr fmt, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more) -{ - // we can dump without using buf - // but we'll only dump if the buffer is ok - size_t pos = fmt.find("{}"); // @todo use _find_fmt() - if(C4_UNLIKELY(pos == csubstr::npos)) - { - if(C4_LIKELY(buf.len > 0 && fmt.len > 0)) - dumpfn(fmt); - return 0u; - } - if(C4_LIKELY(buf.len > 0 && pos > 0)) - dumpfn(fmt.first(pos)); // we can dump without using buf - fmt = fmt.sub(pos + 2); // skip {} do this before assigning to pos again - pos = dump(buf, a); - if(C4_UNLIKELY(pos > buf.len)) - buf.len = 0; // ensure no more calls to dump - size_t size_for_more = format_dump(buf, fmt, more...); - return size_for_more > pos ? size_for_more : pos; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -/// @cond dev -namespace detail { - -template -DumpResults format_dump_resume(size_t currarg, DumpResults results, substr buf, csubstr fmt) -{ - // we can dump without using buf - // but we'll only dump if the buffer is ok - if(C4_LIKELY(buf.len > 0)) - { - dumpfn(fmt); - results.lastok = currarg; - } - return results; -} - -template -DumpResults format_dump_resume(size_t currarg, DumperFn &&dumpfn, DumpResults results, substr buf, csubstr fmt) -{ - // we can dump without using buf - // but we'll only dump if the buffer is ok - if(C4_LIKELY(buf.len > 0)) - { - dumpfn(fmt); - results.lastok = currarg; - } - return results; -} - -template -DumpResults format_dump_resume(size_t currarg, DumpResults results, substr buf, csubstr fmt, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more) -{ - // we need to process the format even if we're not - // going to print the first arguments because we're resuming - size_t pos = fmt.find("{}"); // @todo use _find_fmt() - // we can dump without using buf - // but we'll only dump if the buffer is ok - if(C4_LIKELY(results.write_arg(currarg))) - { - if(C4_UNLIKELY(pos == csubstr::npos)) - { - if(C4_LIKELY(buf.len > 0)) - { - results.lastok = currarg; - dumpfn(fmt); - } - return results; - } - if(C4_LIKELY(buf.len > 0)) - { - results.lastok = currarg; - dumpfn(fmt.first(pos)); - } - } - fmt = fmt.sub(pos + 2); - if(C4_LIKELY(results.write_arg(currarg + 1))) - { - pos = dump(buf, a); - results.bufsize = pos > results.bufsize ? pos : results.bufsize; - if(C4_LIKELY(pos <= buf.len)) - results.lastok = currarg + 1; - else - buf.len = 0; - } - return detail::format_dump_resume(currarg + 2u, results, buf, fmt, more...); -} -/// @endcond - - -template -DumpResults format_dump_resume(size_t currarg, DumperFn &&dumpfn, DumpResults results, substr buf, csubstr fmt, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more) -{ - // we need to process the format even if we're not - // going to print the first arguments because we're resuming - size_t pos = fmt.find("{}"); // @todo use _find_fmt() - // we can dump without using buf - // but we'll only dump if the buffer is ok - if(C4_LIKELY(results.write_arg(currarg))) - { - if(C4_UNLIKELY(pos == csubstr::npos)) - { - if(C4_LIKELY(buf.len > 0)) - { - results.lastok = currarg; - dumpfn(fmt); - } - return results; - } - if(C4_LIKELY(buf.len > 0)) - { - results.lastok = currarg; - dumpfn(fmt.first(pos)); - } - } - fmt = fmt.sub(pos + 2); - if(C4_LIKELY(results.write_arg(currarg + 1))) - { - pos = dump(dumpfn, buf, a); - results.bufsize = pos > results.bufsize ? pos : results.bufsize; - if(C4_LIKELY(pos <= buf.len)) - results.lastok = currarg + 1; - else - buf.len = 0; - } - return detail::format_dump_resume(currarg + 2u, dumpfn, results, buf, fmt, more...); -} -} // namespace detail - - -template -C4_ALWAYS_INLINE DumpResults format_dump_resume(DumpResults results, substr buf, csubstr fmt, Args const& C4_RESTRICT ...more) -{ - return detail::format_dump_resume(0u, results, buf, fmt, more...); -} - -template -C4_ALWAYS_INLINE DumpResults format_dump_resume(DumperFn &&dumpfn, DumpResults results, substr buf, csubstr fmt, Args const& C4_RESTRICT ...more) -{ - return detail::format_dump_resume(0u, dumpfn, results, buf, fmt, more...); -} - - -template -C4_ALWAYS_INLINE DumpResults format_dump_resume(substr buf, csubstr fmt, Args const& C4_RESTRICT ...more) -{ - return detail::format_dump_resume(0u, DumpResults{}, buf, fmt, more...); -} - -template -C4_ALWAYS_INLINE DumpResults format_dump_resume(DumperFn &&dumpfn, substr buf, csubstr fmt, Args const& C4_RESTRICT ...more) -{ - return detail::format_dump_resume(0u, dumpfn, DumpResults{}, buf, fmt, more...); -} - - -} // namespace c4 - - -#endif /* C4_DUMP_HPP_ */ - - -// (end https://github.com/biojppm/c4core/src/c4/dump.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/enum.hpp -// https://github.com/biojppm/c4core/src/c4/enum.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_ENUM_HPP_ -#define _C4_ENUM_HPP_ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/error.hpp -//#include "c4/error.hpp" -#if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) -#error "amalgamate: file c4/error.hpp must have been included at this point" -#endif /* C4_ERROR_HPP_ */ - -//included above: -//#include - -/** @file enum.hpp utilities for enums: convert to/from string - */ - - -namespace c4 { - -//! taken from http://stackoverflow.com/questions/15586163/c11-type-trait-to-differentiate-between-enum-class-and-regular-enum -template -using is_scoped_enum = std::integral_constant::value && !std::is_convertible::value>; - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -typedef enum { - EOFFS_NONE = 0, ///< no offset - EOFFS_CLS = 1, ///< get the enum offset for the class name. @see eoffs_cls() - EOFFS_PFX = 2, ///< get the enum offset for the enum prefix. @see eoffs_pfx() - _EOFFS_LAST ///< reserved -} EnumOffsetType; - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -/** A simple (proxy) container for the value-name pairs of an enum type. - * Uses linear search for finds; this could be improved for time-critical - * code. */ -template -class EnumSymbols -{ -public: - - struct Sym - { - Enum value; - const char *name; - - bool cmp(const char *s) const; - bool cmp(const char *s, size_t len) const; - - const char *name_offs(EnumOffsetType t) const; - }; - - using const_iterator = Sym const*; - -public: - - template - EnumSymbols(Sym const (&p)[N]) : m_symbols(p), m_num(N) {} - - size_t size() const { return m_num; } - bool empty() const { return m_num == 0; } - - Sym const* get(Enum v) const { auto p = find(v); C4_CHECK_MSG(p != nullptr, "could not find symbol=%zd", (std::ptrdiff_t)v); return p; } - Sym const* get(const char *s) const { auto p = find(s); C4_CHECK_MSG(p != nullptr, "could not find symbol \"%s\"", s); return p; } - Sym const* get(const char *s, size_t len) const { auto p = find(s, len); C4_CHECK_MSG(p != nullptr, "could not find symbol \"%.*s\"", len, s); return p; } - - Sym const* find(Enum v) const; - Sym const* find(const char *s) const; - Sym const* find(const char *s, size_t len) const; - - Sym const& operator[] (size_t i) const { C4_CHECK(i < m_num); return m_symbols[i]; } - - Sym const* begin() const { return m_symbols; } - Sym const* end () const { return m_symbols + m_num; } - -private: - - Sym const* m_symbols; - size_t const m_num; - -}; - -//----------------------------------------------------------------------------- -/** return an EnumSymbols object for the enum type T - * - * @warning SPECIALIZE! This needs to be specialized for each enum - * type. Failure to provide a specialization will cause a linker - * error. */ -template -EnumSymbols const esyms(); - - -/** return the offset for an enum symbol class. For example, - * eoffs_cls() would be 13=strlen("MyEnumClass::"). - * - * With this function you can announce that the full prefix (including - * an eventual enclosing class or C++11 enum class) is of a certain - * length. - * - * @warning Needs to be specialized for each enum class type that - * wants to use this. When no specialization is given, will return - * 0. */ -template -size_t eoffs_cls() -{ - return 0; -} - - -/** return the offset for an enum symbol prefix. This includes - * eoffs_cls(). With this function you can announce that the full - * prefix (including an eventual enclosing class or C++11 enum class - * plus the string prefix) is of a certain length. - * - * @warning Needs to be specialized for each enum class type that - * wants to use this. When no specialization is given, will return - * 0. */ -template -size_t eoffs_pfx() -{ - return 0; -} - - -template -size_t eoffs(EnumOffsetType which) -{ - switch(which) - { - case EOFFS_NONE: - return 0; - case EOFFS_CLS: - return eoffs_cls(); - case EOFFS_PFX: - { - size_t pfx = eoffs_pfx(); - return pfx > 0 ? pfx : eoffs_cls(); - } - default: - C4_ERROR("unknown offset type %d", (int)which); - return 0; - } -} - - -//----------------------------------------------------------------------------- -/** get the enum value corresponding to a c-string */ - -#ifdef __clang__ -# pragma clang diagnostic push -#elif defined(__GNUC__) -# pragma GCC diagnostic push -# if __GNUC__ >= 6 -# pragma GCC diagnostic ignored "-Wnull-dereference" -# endif -#endif - -template -Enum str2e(const char* str) -{ - auto pairs = esyms(); - auto *p = pairs.get(str); - C4_CHECK_MSG(p != nullptr, "no valid enum pair name for '%s'", str); - return p->value; -} - -/** get the c-string corresponding to an enum value */ -template -const char* e2str(Enum e) -{ - auto es = esyms(); - auto *p = es.get(e); - C4_CHECK_MSG(p != nullptr, "no valid enum pair name"); - return p->name; -} - -/** like e2str(), but add an offset. */ -template -const char* e2stroffs(Enum e, EnumOffsetType ot=EOFFS_PFX) -{ - const char *s = e2str(e) + eoffs(ot); - return s; -} - -#ifdef __clang__ -# pragma clang diagnostic pop -#elif defined(__GNUC__) -# pragma GCC diagnostic pop -#endif - -//----------------------------------------------------------------------------- -/** Find a symbol by value. Returns nullptr when none is found */ -template -typename EnumSymbols::Sym const* EnumSymbols::find(Enum v) const -{ - for(Sym const* p = this->m_symbols, *e = p+this->m_num; p < e; ++p) - if(p->value == v) - return p; - return nullptr; -} - -/** Find a symbol by name. Returns nullptr when none is found */ -template -typename EnumSymbols::Sym const* EnumSymbols::find(const char *s) const -{ - for(Sym const* p = this->m_symbols, *e = p+this->m_num; p < e; ++p) - if(p->cmp(s)) - return p; - return nullptr; -} - -/** Find a symbol by name. Returns nullptr when none is found */ -template -typename EnumSymbols::Sym const* EnumSymbols::find(const char *s, size_t len) const -{ - for(Sym const* p = this->m_symbols, *e = p+this->m_num; p < e; ++p) - if(p->cmp(s, len)) - return p; - return nullptr; -} - -//----------------------------------------------------------------------------- -template -bool EnumSymbols::Sym::cmp(const char *s) const -{ - if(strcmp(name, s) == 0) - return true; - - for(int i = 1; i < _EOFFS_LAST; ++i) - { - auto o = eoffs((EnumOffsetType)i); - if(o > 0) - if(strcmp(name + o, s) == 0) - return true; - } - - return false; -} - -template -bool EnumSymbols::Sym::cmp(const char *s, size_t len) const -{ - if(strncmp(name, s, len) == 0) - return true; - - size_t nlen = 0; - for(int i = 1; i <_EOFFS_LAST; ++i) - { - auto o = eoffs((EnumOffsetType)i); - if(o > 0) - { - if(!nlen) - { - nlen = strlen(name); - } - C4_ASSERT(o < nlen); - size_t rem = nlen - o; - auto m = len > rem ? len : rem; - if(len >= m && strncmp(name + o, s, m) == 0) - return true; - } - } - - return false; -} - -//----------------------------------------------------------------------------- -template -const char* EnumSymbols::Sym::name_offs(EnumOffsetType t) const -{ - C4_ASSERT(eoffs(t) < strlen(name)); - return name + eoffs(t); -} - -} // namespace c4 - -#endif // _C4_ENUM_HPP_ - - -// (end https://github.com/biojppm/c4core/src/c4/enum.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/bitmask.hpp -// https://github.com/biojppm/c4core/src/c4/bitmask.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_BITMASK_HPP_ -#define _C4_BITMASK_HPP_ - -/** @file bitmask.hpp bitmask utilities */ - -//included above: -//#include -//included above: -//#include - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/enum.hpp -//#include "c4/enum.hpp" -#if !defined(C4_ENUM_HPP_) && !defined(_C4_ENUM_HPP_) -#error "amalgamate: file c4/enum.hpp must have been included at this point" -#endif /* C4_ENUM_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/format.hpp -//#include "c4/format.hpp" -#if !defined(C4_FORMAT_HPP_) && !defined(_C4_FORMAT_HPP_) -#error "amalgamate: file c4/format.hpp must have been included at this point" -#endif /* C4_FORMAT_HPP_ */ - - -#ifdef _MSC_VER -# pragma warning(push) -# pragma warning(disable : 4996) // 'strncpy', fopen, etc: This function or variable may be unsafe -#elif defined(__clang__) -#elif defined(__GNUC__) -# pragma GCC diagnostic push -# if __GNUC__ >= 8 -# pragma GCC diagnostic ignored "-Wstringop-truncation" -# pragma GCC diagnostic ignored "-Wstringop-overflow" -# endif -#endif - -namespace c4 { - -//----------------------------------------------------------------------------- -/** write a bitmask to a stream, formatted as a string */ - -template -Stream& bm2stream(Stream &s, typename std::underlying_type::type bits, EnumOffsetType offst=EOFFS_PFX) -{ - using I = typename std::underlying_type::type; - bool written = false; - - auto const& pairs = esyms(); - - // write non null value - if(bits) - { - // do reverse iteration to give preference to composite enum symbols, - // which are likely to appear at the end of the enum sequence - for(size_t i = pairs.size() - 1; i != size_t(-1); --i) - { - auto p = pairs[i]; - I b(static_cast(p.value)); - if(b && (bits & b) == b) - { - if(written) s << '|'; // append bit-or character - written = true; - s << p.name_offs(offst); // append bit string - bits &= ~b; - } - } - return s; - } - else - { - // write a null value - for(size_t i = pairs.size() - 1; i != size_t(-1); --i) - { - auto p = pairs[i]; - I b(static_cast(p.value)); - if(b == 0) - { - s << p.name_offs(offst); - written = true; - break; - } - } - } - if(!written) - { - s << '0'; - } - return s; -} - -template -typename std::enable_if::value, Stream&>::type -bm2stream(Stream &s, Enum value, EnumOffsetType offst=EOFFS_PFX) -{ - using I = typename std::underlying_type::type; - return bm2stream(s, static_cast(value), offst); -} - - -//----------------------------------------------------------------------------- - -// some utility macros, undefed below - -/// @cond dev - -/* Execute `code` if the `num` of characters is available in the str - * buffer. This macro simplifies the code for bm2str(). - * @todo improve performance by writing from the end and moving only once. */ -#define _c4prependchars(code, num) \ - if(str && (pos + num <= sz)) \ - { \ - /* move the current string to the right */ \ - memmove(str + num, str, pos); \ - /* now write in the beginning of the string */ \ - code; \ - } \ - else if(str && sz) \ - { \ - C4_ERROR("cannot write to string pos=%d num=%d sz=%d", \ - (int)pos, (int)num, (int)sz); \ - } \ - pos += num - -/* Execute `code` if the `num` of characters is available in the str - * buffer. This macro simplifies the code for bm2str(). */ -#define _c4appendchars(code, num) \ - if(str && (pos + num <= sz)) \ - { \ - code; \ - } \ - else if(str && sz) \ - { \ - C4_ERROR("cannot write to string pos=%d num=%d sz=%d", \ - (int)pos, (int)num, (int)sz); \ - } \ - pos += num - -/// @endcond - - -/** convert a bitmask to string. - * return the number of characters written. To find the needed size, - * call first with str=nullptr and sz=0 */ -template -size_t bm2str -( - typename std::underlying_type::type bits, - char *str=nullptr, - size_t sz=0, - EnumOffsetType offst=EOFFS_PFX -) -{ - using I = typename std::underlying_type::type; - C4_ASSERT((str == nullptr) == (sz == 0)); - - auto syms = esyms(); - size_t pos = 0; - typename EnumSymbols::Sym const* C4_RESTRICT zero = nullptr; - - // do reverse iteration to give preference to composite enum symbols, - // which are likely to appear later in the enum sequence - for(size_t i = syms.size()-1; i != size_t(-1); --i) - { - auto const &C4_RESTRICT p = syms[i]; // do not copy, we are assigning to `zero` - I b = static_cast(p.value); - if(b == 0) - { - zero = &p; // save this symbol for later - } - else if((bits & b) == b) - { - bits &= ~b; - // append bit-or character - if(pos > 0) - { - _c4prependchars(*str = '|', 1); - } - // append bit string - const char *pname = p.name_offs(offst); - size_t len = strlen(pname); - _c4prependchars(strncpy(str, pname, len), len); - } - } - - C4_CHECK_MSG(bits == 0, "could not find all bits"); - if(pos == 0) // make sure at least something is written - { - if(zero) // if we have a zero symbol, use that - { - const char *pname = zero->name_offs(offst); - size_t len = strlen(pname); - _c4prependchars(strncpy(str, pname, len), len); - } - else // otherwise just write an integer zero - { - _c4prependchars(*str = '0', 1); - } - } - _c4appendchars(str[pos] = '\0', 1); - - return pos; -} - - -// cleanup! -#undef _c4appendchars -#undef _c4prependchars - - -/** scoped enums do not convert automatically to their underlying type, - * so this SFINAE overload will accept scoped enum symbols and cast them - * to the underlying type */ -template -typename std::enable_if::value, size_t>::type -bm2str -( - Enum bits, - char *str=nullptr, - size_t sz=0, - EnumOffsetType offst=EOFFS_PFX -) -{ - using I = typename std::underlying_type::type; - return bm2str(static_cast(bits), str, sz, offst); -} - - -//----------------------------------------------------------------------------- - -namespace detail { - -#ifdef __clang__ -# pragma clang diagnostic push -#elif defined(__GNUC__) -# pragma GCC diagnostic push -# if __GNUC__ >= 6 -# pragma GCC diagnostic ignored "-Wnull-dereference" -# endif -#endif - -template -typename std::underlying_type::type str2bm_read_one(const char *str, size_t sz, bool alnum) -{ - using I = typename std::underlying_type::type; - auto pairs = esyms(); - if(alnum) - { - auto *p = pairs.find(str, sz); - C4_CHECK_MSG(p != nullptr, "no valid enum pair name for '%.*s'", (int)sz, str); - return static_cast(p->value); - } - I tmp; - size_t len = uncat(csubstr(str, sz), tmp); - C4_CHECK_MSG(len != csubstr::npos, "could not read string as an integral type: '%.*s'", (int)sz, str); - return tmp; -} - -#ifdef __clang__ -# pragma clang diagnostic pop -#elif defined(__GNUC__) -# pragma GCC diagnostic pop -#endif -} // namespace detail - -/** convert a string to a bitmask */ -template -typename std::underlying_type::type str2bm(const char *str, size_t sz) -{ - using I = typename std::underlying_type::type; - - I val = 0; - bool started = false; - bool alnum = false, num = false; - const char *f = nullptr, *pc = str; - for( ; pc < str+sz; ++pc) - { - const char c = *pc; - if((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_') - { - C4_CHECK(( ! num) || ((pc - f) == 1 && (c == 'x' || c == 'X'))); // accept hexadecimal numbers - if( ! started) - { - f = pc; - alnum = started = true; - } - } - else if(c >= '0' && c <= '9') - { - C4_CHECK( ! alnum); - if(!started) - { - f = pc; - num = started = true; - } - } - else if(c == ':' || c == ' ') - { - // skip this char - } - else if(c == '|' || c == '\0') - { - C4_ASSERT(num != alnum); - C4_ASSERT(pc >= f); - val |= detail::str2bm_read_one(f, static_cast(pc-f), alnum); - started = num = alnum = false; - if(c == '\0') - { - return val; - } - } - else - { - C4_ERROR("bad character '%c' in bitmask string", c); - } - } - - if(f) - { - C4_ASSERT(num != alnum); - C4_ASSERT(pc >= f); - val |= detail::str2bm_read_one(f, static_cast(pc-f), alnum); - } - - return val; -} - -/** convert a string to a bitmask */ -template -typename std::underlying_type::type str2bm(const char *str) -{ - return str2bm(str, strlen(str)); -} - -} // namespace c4 - -#ifdef _MSC_VER -# pragma warning(pop) -#elif defined(__clang__) -#elif defined(__GNUC__) -# pragma GCC diagnostic pop -#endif - -#endif // _C4_BITMASK_HPP_ - - -// (end https://github.com/biojppm/c4core/src/c4/bitmask.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/span.hpp -// https://github.com/biojppm/c4core/src/c4/span.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_SPAN_HPP_ -#define _C4_SPAN_HPP_ - -/** @file span.hpp Provides span classes. */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/config.hpp -//#include "c4/config.hpp" -#if !defined(C4_CONFIG_HPP_) && !defined(_C4_CONFIG_HPP_) -#error "amalgamate: file c4/config.hpp must have been included at this point" -#endif /* C4_CONFIG_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/error.hpp -//#include "c4/error.hpp" -#if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) -#error "amalgamate: file c4/error.hpp must have been included at this point" -#endif /* C4_ERROR_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/szconv.hpp -//#include "c4/szconv.hpp" -#if !defined(C4_SZCONV_HPP_) && !defined(_C4_SZCONV_HPP_) -#error "amalgamate: file c4/szconv.hpp must have been included at this point" -#endif /* C4_SZCONV_HPP_ */ - - -//included above: -//#include - -namespace c4 { - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -/** a crtp base for implementing span classes - * - * A span is a non-owning range of elements contiguously stored in memory. - * Unlike STL's array_view, the span allows write-access to its members. - * - * To obtain subspans from a span, the following const member functions - * are available: - * - subspan(first, num) - * - range(first, last) - * - first(num) - * - last(num) - * - * A span can also be resized via the following non-const member functions: - * - resize(sz) - * - ltrim(num) - * - rtrim(num) - * - * @see span - * @see cspan - * @see spanrs - * @see cspanrs - * @see spanrsl - * @see cspanrsl - */ -template -class span_crtp -{ -// some utility defines, undefined at the end of this class -#define _c4this ((SpanImpl *)this) -#define _c4cthis ((SpanImpl const*)this) -#define _c4ptr ((SpanImpl *)this)->m_ptr -#define _c4cptr ((SpanImpl const*)this)->m_ptr -#define _c4sz ((SpanImpl *)this)->m_size -#define _c4csz ((SpanImpl const*)this)->m_size - -public: - - _c4_DEFINE_ARRAY_TYPES(T, I); - -public: - - C4_ALWAYS_INLINE constexpr I value_size() const noexcept { return sizeof(T); } - C4_ALWAYS_INLINE constexpr I elm_size () const noexcept { return sizeof(T); } - C4_ALWAYS_INLINE constexpr I type_size () const noexcept { return sizeof(T); } - C4_ALWAYS_INLINE I byte_size () const noexcept { return _c4csz*sizeof(T); } - - C4_ALWAYS_INLINE bool empty() const noexcept { return _c4csz == 0; } - C4_ALWAYS_INLINE I size() const noexcept { return _c4csz; } - //C4_ALWAYS_INLINE I capacity() const noexcept { return _c4sz; } // this must be defined by impl classes - - C4_ALWAYS_INLINE void clear() noexcept { _c4sz = 0; } - - C4_ALWAYS_INLINE T * data() noexcept { return _c4ptr; } - C4_ALWAYS_INLINE T const* data() const noexcept { return _c4cptr; } - - C4_ALWAYS_INLINE iterator begin() noexcept { return _c4ptr; } - C4_ALWAYS_INLINE const_iterator begin() const noexcept { return _c4cptr; } - C4_ALWAYS_INLINE const_iterator cbegin() const noexcept { return _c4cptr; } - - C4_ALWAYS_INLINE iterator end() noexcept { return _c4ptr + _c4sz; } - C4_ALWAYS_INLINE const_iterator end() const noexcept { return _c4cptr + _c4csz; } - C4_ALWAYS_INLINE const_iterator cend() const noexcept { return _c4cptr + _c4csz; } - - C4_ALWAYS_INLINE reverse_iterator rbegin() noexcept { return reverse_iterator(_c4ptr + _c4sz); } - C4_ALWAYS_INLINE const_reverse_iterator rbegin() const noexcept { return reverse_iterator(_c4cptr + _c4sz); } - C4_ALWAYS_INLINE const_reverse_iterator crbegin() const noexcept { return reverse_iterator(_c4cptr + _c4sz); } - - C4_ALWAYS_INLINE reverse_iterator rend() noexcept { return const_reverse_iterator(_c4ptr); } - C4_ALWAYS_INLINE const_reverse_iterator rend() const noexcept { return const_reverse_iterator(_c4cptr); } - C4_ALWAYS_INLINE const_reverse_iterator crend() const noexcept { return const_reverse_iterator(_c4cptr); } - - C4_ALWAYS_INLINE T & front() C4_NOEXCEPT_X { C4_XASSERT(!empty()); return _c4ptr [0]; } - C4_ALWAYS_INLINE T const& front() const C4_NOEXCEPT_X { C4_XASSERT(!empty()); return _c4cptr[0]; } - - C4_ALWAYS_INLINE T & back() C4_NOEXCEPT_X { C4_XASSERT(!empty()); return _c4ptr [_c4sz - 1]; } - C4_ALWAYS_INLINE T const& back() const C4_NOEXCEPT_X { C4_XASSERT(!empty()); return _c4cptr[_c4csz - 1]; } - - C4_ALWAYS_INLINE T & operator[] (I i) C4_NOEXCEPT_X { C4_XASSERT(i >= 0 && i < _c4sz ); return _c4ptr [i]; } - C4_ALWAYS_INLINE T const& operator[] (I i) const C4_NOEXCEPT_X { C4_XASSERT(i >= 0 && i < _c4csz); return _c4cptr[i]; } - - C4_ALWAYS_INLINE SpanImpl subspan(I first, I num) const C4_NOEXCEPT_X - { - C4_XASSERT((first >= 0 && first < _c4csz) || (first == _c4csz && num == 0)); - C4_XASSERT((first + num >= 0) && (first + num <= _c4csz)); - return _c4cthis->_select(_c4cptr + first, num); - } - C4_ALWAYS_INLINE SpanImpl subspan(I first) const C4_NOEXCEPT_X ///< goes up until the end of the span - { - C4_XASSERT(first >= 0 && first <= _c4csz); - return _c4cthis->_select(_c4cptr + first, _c4csz - first); - } - - C4_ALWAYS_INLINE SpanImpl range(I first, I last) const C4_NOEXCEPT_X ///< last element is NOT included - { - C4_XASSERT(((first >= 0) && (first < _c4csz)) || (first == _c4csz && first == last)); - C4_XASSERT((last >= 0) && (last <= _c4csz)); - C4_XASSERT(last >= first); - return _c4cthis->_select(_c4cptr + first, last - first); - } - C4_ALWAYS_INLINE SpanImpl range(I first) const C4_NOEXCEPT_X ///< goes up until the end of the span - { - C4_XASSERT(((first >= 0) && (first <= _c4csz))); - return _c4cthis->_select(_c4cptr + first, _c4csz - first); - } - - C4_ALWAYS_INLINE SpanImpl first(I num) const C4_NOEXCEPT_X ///< get the first num elements, starting at 0 - { - C4_XASSERT((num >= 0) && (num <= _c4csz)); - return _c4cthis->_select(_c4cptr, num); - } - C4_ALWAYS_INLINE SpanImpl last(I num) const C4_NOEXCEPT_X ///< get the last num elements, starting at size()-num - { - C4_XASSERT((num >= 0) && (num <= _c4csz)); - return _c4cthis->_select(_c4cptr + _c4csz - num, num); - } - - bool is_subspan(span_crtp const& ss) const noexcept - { - if(_c4cptr == nullptr) return false; - auto *b = begin(), *e = end(); - auto *ssb = ss.begin(), *sse = ss.end(); - if(ssb >= b && sse <= e) - { - return true; - } - else - { - return false; - } - } - - /** COMPLement Left: return the complement to the left of the beginning of the given subspan. - * If ss does not begin inside this, returns an empty substring. */ - SpanImpl compll(span_crtp const& ss) const C4_NOEXCEPT_X - { - auto ssb = ss.begin(); - auto b = begin(); - auto e = end(); - if(ssb >= b && ssb <= e) - { - return subspan(0, static_cast(ssb - b)); - } - else - { - return subspan(0, 0); - } - } - - /** COMPLement Right: return the complement to the right of the end of the given subspan. - * If ss does not end inside this, returns an empty substring. */ - SpanImpl complr(span_crtp const& ss) const C4_NOEXCEPT_X - { - auto sse = ss.end(); - auto b = begin(); - auto e = end(); - if(sse >= b && sse <= e) - { - return subspan(static_cast(sse - b), static_cast(e - sse)); - } - else - { - return subspan(0, 0); - } - } - - C4_ALWAYS_INLINE bool same_span(span_crtp const& that) const noexcept - { - return size() == that.size() && data() == that.data(); - } - template - C4_ALWAYS_INLINE bool same_span(span_crtp const& that) const C4_NOEXCEPT_X - { - I tsz = szconv(that.size()); // x-asserts that the size does not overflow - return size() == tsz && data() == that.data(); - } - -#undef _c4this -#undef _c4cthis -#undef _c4ptr -#undef _c4cptr -#undef _c4sz -#undef _c4csz -}; - -//----------------------------------------------------------------------------- -template -inline constexpr bool operator== -( - span_crtp const& l, - span_crtp const& r -) -{ -#if C4_CPP >= 14 - return std::equal(l.begin(), l.end(), r.begin(), r.end()); -#else - return l.same_span(r) || std::equal(l.begin(), l.end(), r.begin()); -#endif -} - -template -inline constexpr bool operator!= -( - span_crtp const& l, - span_crtp const& r -) -{ - return ! (l == r); -} - -//----------------------------------------------------------------------------- -template -inline constexpr bool operator< -( - span_crtp const& l, - span_crtp const& r -) -{ - return std::lexicographical_compare(l.begin(), l.end(), r.begin(), r.end()); -} - -template -inline constexpr bool operator<= -( - span_crtp const& l, - span_crtp const& r -) -{ - return ! (l > r); -} - -//----------------------------------------------------------------------------- -template -inline constexpr bool operator> -( - span_crtp const& l, - span_crtp const& r -) -{ - return r < l; -} - -//----------------------------------------------------------------------------- -template -inline constexpr bool operator>= -( - span_crtp const& l, - span_crtp const& r -) -{ - return ! (l < r); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -/** A non-owning span of elements contiguously stored in memory. */ -template -class span : public span_crtp> -{ - friend class span_crtp>; - - T * C4_RESTRICT m_ptr; - I m_size; - - C4_ALWAYS_INLINE span _select(T *p, I sz) const { return span(p, sz); } - -public: - - _c4_DEFINE_ARRAY_TYPES(T, I); - using NCT = typename std::remove_const::type; //!< NCT=non const type - using CT = typename std::add_const::type; //!< CT=const type - using const_type = span; - - /// convert automatically to span of const T - operator span () const { span s(m_ptr, m_size); return s; } - -public: - - C4_ALWAYS_INLINE C4_CONSTEXPR14 span() noexcept : m_ptr{nullptr}, m_size{0} {} - - span(span const&) = default; - span(span &&) = default; - - span& operator= (span const&) = default; - span& operator= (span &&) = default; - -public: - - /** @name Construction and assignment from same type */ - /** @{ */ - - template C4_ALWAYS_INLINE C4_CONSTEXPR14 span (T (&arr)[N]) noexcept : m_ptr{arr}, m_size{N} {} - template C4_ALWAYS_INLINE C4_CONSTEXPR14 void assign(T (&arr)[N]) noexcept { m_ptr = arr; m_size = N; } - - C4_ALWAYS_INLINE C4_CONSTEXPR14 span(T *p, I sz) noexcept : m_ptr{p}, m_size{sz} {} - C4_ALWAYS_INLINE C4_CONSTEXPR14 void assign(T *p, I sz) noexcept { m_ptr = p; m_size = sz; } - - C4_ALWAYS_INLINE C4_CONSTEXPR14 span (c4::aggregate_t, std::initializer_list il) noexcept : m_ptr{&*il.begin()}, m_size{il.size()} {} - C4_ALWAYS_INLINE C4_CONSTEXPR14 void assign(c4::aggregate_t, std::initializer_list il) noexcept { m_ptr = &*il.begin(); m_size = il.size(); } - - /** @} */ - -public: - - C4_ALWAYS_INLINE I capacity() const noexcept { return m_size; } - - C4_ALWAYS_INLINE void resize(I sz) C4_NOEXCEPT_A { C4_ASSERT(sz <= m_size); m_size = sz; } - C4_ALWAYS_INLINE void rtrim (I n ) C4_NOEXCEPT_A { C4_ASSERT(n >= 0 && n < m_size); m_size -= n; } - C4_ALWAYS_INLINE void ltrim (I n ) C4_NOEXCEPT_A { C4_ASSERT(n >= 0 && n < m_size); m_size -= n; m_ptr += n; } - -}; -template using cspan = span; - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -/** A non-owning span resizeable up to a capacity. Subselection or resizing - * will keep the original provided it starts at begin(). If subselection or - * resizing change the pointer, then the original capacity information will - * be lost. - * - * Thus, resizing via resize() and ltrim() and subselecting via first() - * or any of subspan() or range() when starting from the beginning will keep - * the original capacity. OTOH, using last(), or any of subspan() or range() - * with an offset from the start will remove from capacity (shifting the - * pointer) by the corresponding offset. If this is undesired, then consider - * using spanrsl. - * - * @see spanrs for a span resizeable on the right - * @see spanrsl for a span resizeable on the right and left - */ - -template -class spanrs : public span_crtp> -{ - friend class span_crtp>; - - T * C4_RESTRICT m_ptr; - I m_size; - I m_capacity; - - C4_ALWAYS_INLINE spanrs _select(T *p, I sz) const noexcept - { - C4_ASSERT(p >= m_ptr); - size_t delta = static_cast(p - m_ptr); - C4_ASSERT(m_capacity >= delta); - return spanrs(p, sz, static_cast(m_capacity - delta)); - } - -public: - - _c4_DEFINE_ARRAY_TYPES(T, I); - using NCT = typename std::remove_const::type; //!< NCT=non const type - using CT = typename std::add_const::type; //!< CT=const type - using const_type = spanrs; - - /// convert automatically to span of T - C4_ALWAYS_INLINE operator span () const noexcept { return span(m_ptr, m_size); } - /// convert automatically to span of const T - //C4_ALWAYS_INLINE operator span () const noexcept { span s(m_ptr, m_size); return s; } - /// convert automatically to spanrs of const T - C4_ALWAYS_INLINE operator spanrs () const noexcept { spanrs s(m_ptr, m_size, m_capacity); return s; } - -public: - - C4_ALWAYS_INLINE spanrs() noexcept : m_ptr{nullptr}, m_size{0}, m_capacity{0} {} - - spanrs(spanrs const&) = default; - spanrs(spanrs &&) = default; - - spanrs& operator= (spanrs const&) = default; - spanrs& operator= (spanrs &&) = default; - -public: - - /** @name Construction and assignment from same type */ - /** @{ */ - - C4_ALWAYS_INLINE spanrs(T *p, I sz) noexcept : m_ptr{p}, m_size{sz}, m_capacity{sz} {} - /** @warning will reset the capacity to sz */ - C4_ALWAYS_INLINE void assign(T *p, I sz) noexcept { m_ptr = p; m_size = sz; m_capacity = sz; } - - C4_ALWAYS_INLINE spanrs(T *p, I sz, I cap) noexcept : m_ptr{p}, m_size{sz}, m_capacity{cap} {} - C4_ALWAYS_INLINE void assign(T *p, I sz, I cap) noexcept { m_ptr = p; m_size = sz; m_capacity = cap; } - - template C4_ALWAYS_INLINE spanrs(T (&arr)[N]) noexcept : m_ptr{arr}, m_size{N}, m_capacity{N} {} - template C4_ALWAYS_INLINE void assign(T (&arr)[N]) noexcept { m_ptr = arr; m_size = N; m_capacity = N; } - - C4_ALWAYS_INLINE spanrs(c4::aggregate_t, std::initializer_list il) noexcept : m_ptr{il.begin()}, m_size{il.size()}, m_capacity{il.size()} {} - C4_ALWAYS_INLINE void assign(c4::aggregate_t, std::initializer_list il) noexcept { m_ptr = il.begin(); m_size = il.size(); m_capacity = il.size(); } - - /** @} */ - -public: - - C4_ALWAYS_INLINE I capacity() const noexcept { return m_capacity; } - - C4_ALWAYS_INLINE void resize(I sz) C4_NOEXCEPT_A { C4_ASSERT(sz <= m_capacity); m_size = sz; } - C4_ALWAYS_INLINE void rtrim (I n ) C4_NOEXCEPT_A { C4_ASSERT(n >= 0 && n < m_size); m_size -= n; } - C4_ALWAYS_INLINE void ltrim (I n ) C4_NOEXCEPT_A { C4_ASSERT(n >= 0 && n < m_size); m_size -= n; m_ptr += n; m_capacity -= n; } - -}; -template using cspanrs = spanrs; - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -/** A non-owning span which always retains the capacity of the original - * range it was taken from (though it may loose its original size). - * The resizing methods resize(), ltrim(), rtrim() as well - * as the subselection methods subspan(), range(), first() and last() can be - * used at will without loosing the original capacity; the full capacity span - * can always be recovered by calling original(). - */ -template -class spanrsl : public span_crtp> -{ - friend class span_crtp>; - - T *C4_RESTRICT m_ptr; ///< the current ptr. the original ptr is (m_ptr - m_offset). - I m_size; ///< the current size. the original size is unrecoverable. - I m_capacity; ///< the current capacity. the original capacity is (m_capacity + m_offset). - I m_offset; ///< the offset of the current m_ptr to the start of the original memory block. - - C4_ALWAYS_INLINE spanrsl _select(T *p, I sz) const noexcept - { - C4_ASSERT(p >= m_ptr); - I delta = static_cast(p - m_ptr); - C4_ASSERT(m_capacity >= delta); - return spanrsl(p, sz, static_cast(m_capacity - delta), m_offset + delta); - } - -public: - - _c4_DEFINE_ARRAY_TYPES(T, I); - using NCT = typename std::remove_const::type; //!< NCT=non const type - using CT = typename std::add_const::type; //!< CT=const type - using const_type = spanrsl; - - C4_ALWAYS_INLINE operator span () const noexcept { return span(m_ptr, m_size); } - C4_ALWAYS_INLINE operator spanrs () const noexcept { return spanrs(m_ptr, m_size, m_capacity); } - C4_ALWAYS_INLINE operator spanrsl () const noexcept { return spanrsl(m_ptr, m_size, m_capacity, m_offset); } - -public: - - C4_ALWAYS_INLINE spanrsl() noexcept : m_ptr{nullptr}, m_size{0}, m_capacity{0}, m_offset{0} {} - - spanrsl(spanrsl const&) = default; - spanrsl(spanrsl &&) = default; - - spanrsl& operator= (spanrsl const&) = default; - spanrsl& operator= (spanrsl &&) = default; - -public: - - C4_ALWAYS_INLINE spanrsl(T *p, I sz) noexcept : m_ptr{p}, m_size{sz}, m_capacity{sz}, m_offset{0} {} - C4_ALWAYS_INLINE void assign(T *p, I sz) noexcept { m_ptr = p; m_size = sz; m_capacity = sz; m_offset = 0; } - - C4_ALWAYS_INLINE spanrsl(T *p, I sz, I cap) noexcept : m_ptr{p}, m_size{sz}, m_capacity{cap}, m_offset{0} {} - C4_ALWAYS_INLINE void assign(T *p, I sz, I cap) noexcept { m_ptr = p; m_size = sz; m_capacity = cap; m_offset = 0; } - - C4_ALWAYS_INLINE spanrsl(T *p, I sz, I cap, I offs) noexcept : m_ptr{p}, m_size{sz}, m_capacity{cap}, m_offset{offs} {} - C4_ALWAYS_INLINE void assign(T *p, I sz, I cap, I offs) noexcept { m_ptr = p; m_size = sz; m_capacity = cap; m_offset = offs; } - - template C4_ALWAYS_INLINE spanrsl(T (&arr)[N]) noexcept : m_ptr{arr}, m_size{N}, m_capacity{N}, m_offset{0} {} - template C4_ALWAYS_INLINE void assign(T (&arr)[N]) noexcept { m_ptr = arr; m_size = N; m_capacity = N; m_offset = 0; } - - C4_ALWAYS_INLINE spanrsl(c4::aggregate_t, std::initializer_list il) noexcept : m_ptr{il.begin()}, m_size{il.size()}, m_capacity{il.size()}, m_offset{0} {} - C4_ALWAYS_INLINE void assign (c4::aggregate_t, std::initializer_list il) noexcept { m_ptr = il.begin(); m_size = il.size(); m_capacity = il.size(); m_offset = 0; } - -public: - - C4_ALWAYS_INLINE I offset() const noexcept { return m_offset; } - C4_ALWAYS_INLINE I capacity() const noexcept { return m_capacity; } - - C4_ALWAYS_INLINE void resize(I sz) C4_NOEXCEPT_A { C4_ASSERT(sz <= m_capacity); m_size = sz; } - C4_ALWAYS_INLINE void rtrim (I n ) C4_NOEXCEPT_A { C4_ASSERT(n >= 0 && n < m_size); m_size -= n; } - C4_ALWAYS_INLINE void ltrim (I n ) C4_NOEXCEPT_A { C4_ASSERT(n >= 0 && n < m_size); m_size -= n; m_ptr += n; m_offset += n; m_capacity -= n; } - - /** recover the original span as an spanrsl */ - C4_ALWAYS_INLINE spanrsl original() const - { - return spanrsl(m_ptr - m_offset, m_capacity + m_offset, m_capacity + m_offset, 0); - } - /** recover the original span as a different span type. Example: spanrs<...> orig = s.original(); */ - template class OtherSpanType> - C4_ALWAYS_INLINE OtherSpanType original() - { - return OtherSpanType(m_ptr - m_offset, m_capacity + m_offset); - } -}; -template using cspanrsl = spanrsl; - - -} // namespace c4 - - -#endif /* _C4_SPAN_HPP_ */ - - -// (end https://github.com/biojppm/c4core/src/c4/span.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/type_name.hpp -// https://github.com/biojppm/c4core/src/c4/type_name.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_TYPENAME_HPP_ -#define _C4_TYPENAME_HPP_ - -/** @file type_name.hpp compile-time type name */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/span.hpp -//#include "c4/span.hpp" -#if !defined(C4_SPAN_HPP_) && !defined(_C4_SPAN_HPP_) -#error "amalgamate: file c4/span.hpp must have been included at this point" -#endif /* C4_SPAN_HPP_ */ - - -/// @cond dev -struct _c4t -{ - const char *str; - size_t sz; - template - constexpr _c4t(const char (&s)[N]) : str(s), sz(N-1) {} // take off the \0 -}; -// this is a more abbreviated way of getting the type name -// (if we used span in the return type, the name would involve -// templates and would create longer type name strings, -// as well as larger differences between compilers) -template -C4_CONSTEXPR14 C4_ALWAYS_INLINE -_c4t _c4tn() -{ - auto p = _c4t(C4_PRETTY_FUNC); - return p; -} -/// @endcond - - -namespace c4 { - -/** compile-time type name - * @see http://stackoverflow.com/a/20170989/5875572 */ -template -C4_CONSTEXPR14 cspan type_name() -{ - const _c4t p = _c4tn(); - -#if (0) // _C4_THIS_IS_A_DEBUG_SCAFFOLD - for(size_t index = 0; index < p.sz; ++index) - { - printf(" %2c", p.str[index]); - } - printf("\n"); - for(size_t index = 0; index < p.sz; ++index) - { - printf(" %2d", (int)index); - } - printf("\n"); -#endif - -#if defined(_MSC_VER) -# if defined(__clang__) // Visual Studio has the clang toolset - // example: - // ..........................xxx. - // _c4t __cdecl _c4tn() [T = int] - enum : size_t { tstart = 26, tend = 1}; - -# elif defined(C4_MSVC_2015) || defined(C4_MSVC_2017) || defined(C4_MSVC_2019) || defined(C4_MSVC_2022) - // Note: subtract 7 at the end because the function terminates with ">(void)" in VS2015+ - cspan::size_type tstart = 26, tend = 7; - - const char *s = p.str + tstart; // look at the start - - // we're not using strcmp() or memcmp() to spare the #include - - // does it start with 'class '? - if(p.sz > 6 && s[0] == 'c' && s[1] == 'l' && s[2] == 'a' && s[3] == 's' && s[4] == 's' && s[5] == ' ') - { - tstart += 6; - } - // does it start with 'struct '? - else if(p.sz > 7 && s[0] == 's' && s[1] == 't' && s[2] == 'r' && s[3] == 'u' && s[4] == 'c' && s[5] == 't' && s[6] == ' ') - { - tstart += 7; - } - -# else - C4_NOT_IMPLEMENTED(); -# endif - -#elif defined(__ICC) - // example: - // ........................xxx. - // "_c4t _c4tn() [with T = int]" - enum : size_t { tstart = 23, tend = 1}; - -#elif defined(__clang__) - // example: - // ...................xxx. - // "_c4t _c4tn() [T = int]" - enum : size_t { tstart = 18, tend = 1}; - -#elif defined(__GNUC__) - #if __GNUC__ >= 7 && C4_CPP >= 14 - // example: - // ..................................xxx. - // "constexpr _c4t _c4tn() [with T = int]" - enum : size_t { tstart = 33, tend = 1 }; - #else - // example: - // ........................xxx. - // "_c4t _c4tn() [with T = int]" - enum : size_t { tstart = 23, tend = 1 }; - #endif -#else - C4_NOT_IMPLEMENTED(); -#endif - - cspan o(p.str + tstart, p.sz - tstart - tend); - - return o; -} - -/** compile-time type name - * @overload */ -template -C4_CONSTEXPR14 C4_ALWAYS_INLINE cspan type_name(T const&) -{ - return type_name(); -} - -} // namespace c4 - -#endif //_C4_TYPENAME_HPP_ - - -// (end https://github.com/biojppm/c4core/src/c4/type_name.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/base64.hpp -// https://github.com/biojppm/c4core/src/c4/base64.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_BASE64_HPP_ -#define _C4_BASE64_HPP_ - -/** @file base64.hpp encoding/decoding for base64. - * @see https://en.wikipedia.org/wiki/Base64 - * @see https://www.base64encode.org/ - * */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/charconv.hpp -//#include "c4/charconv.hpp" -#if !defined(C4_CHARCONV_HPP_) && !defined(_C4_CHARCONV_HPP_) -#error "amalgamate: file c4/charconv.hpp must have been included at this point" -#endif /* C4_CHARCONV_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/blob.hpp -//#include "c4/blob.hpp" -#if !defined(C4_BLOB_HPP_) && !defined(_C4_BLOB_HPP_) -#error "amalgamate: file c4/blob.hpp must have been included at this point" -#endif /* C4_BLOB_HPP_ */ - - -namespace c4 { - -/** check that the given buffer is a valid base64 encoding - * @see https://en.wikipedia.org/wiki/Base64 */ -bool base64_valid(csubstr encoded); - -/** base64-encode binary data. - * @param encoded [out] output buffer for encoded data - * @param data [in] the input buffer with the binary data - * @return the number of bytes needed to return the output. No writes occur beyond the end of the output buffer. - * @see https://en.wikipedia.org/wiki/Base64 */ -size_t base64_encode(substr encoded, cblob data); - -/** decode the base64 encoding in the given buffer - * @param encoded [in] the encoded base64 - * @param data [out] the output buffer - * @return the number of bytes needed to return the output.. No writes occur beyond the end of the output buffer. - * @see https://en.wikipedia.org/wiki/Base64 */ -size_t base64_decode(csubstr encoded, blob data); - - -namespace fmt { - -template -struct base64_wrapper_ -{ - blob_ data; - base64_wrapper_() : data() {} - base64_wrapper_(blob_ blob) : data(blob) {} -}; -using const_base64_wrapper = base64_wrapper_; -using base64_wrapper = base64_wrapper_; - - -/** mark a variable to be written in base64 format */ -template -C4_ALWAYS_INLINE const_base64_wrapper cbase64(Args const& C4_RESTRICT ...args) -{ - return const_base64_wrapper(cblob(args...)); -} -/** mark a csubstr to be written in base64 format */ -C4_ALWAYS_INLINE const_base64_wrapper cbase64(csubstr s) -{ - return const_base64_wrapper(cblob(s.str, s.len)); -} -/** mark a variable to be written in base64 format */ -template -C4_ALWAYS_INLINE const_base64_wrapper base64(Args const& C4_RESTRICT ...args) -{ - return const_base64_wrapper(cblob(args...)); -} -/** mark a csubstr to be written in base64 format */ -C4_ALWAYS_INLINE const_base64_wrapper base64(csubstr s) -{ - return const_base64_wrapper(cblob(s.str, s.len)); -} - -/** mark a variable to be read in base64 format */ -template -C4_ALWAYS_INLINE base64_wrapper base64(Args &... args) -{ - return base64_wrapper(blob(args...)); -} -/** mark a variable to be read in base64 format */ -C4_ALWAYS_INLINE base64_wrapper base64(substr s) -{ - return base64_wrapper(blob(s.str, s.len)); -} - -} // namespace fmt - - -/** write a variable in base64 format */ -inline size_t to_chars(substr buf, fmt::const_base64_wrapper b) -{ - return base64_encode(buf, b.data); -} - -/** read a variable in base64 format */ -inline size_t from_chars(csubstr buf, fmt::base64_wrapper *b) -{ - return base64_decode(buf, b->data); -} - -} // namespace c4 - -#endif /* _C4_BASE64_HPP_ */ - - -// (end https://github.com/biojppm/c4core/src/c4/base64.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/std/string.hpp -// https://github.com/biojppm/c4core/src/c4/std/string.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_STD_STRING_HPP_ -#define _C4_STD_STRING_HPP_ - -/** @file string.hpp */ - -#ifndef C4CORE_SINGLE_HEADER -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/substr.hpp -//#include "c4/substr.hpp" -#if !defined(C4_SUBSTR_HPP_) && !defined(_C4_SUBSTR_HPP_) -#error "amalgamate: file c4/substr.hpp must have been included at this point" -#endif /* C4_SUBSTR_HPP_ */ - -#endif - -//included above: -//#include - -namespace c4 { - -//----------------------------------------------------------------------------- - -/** get a writeable view to an existing std::string. - * When the string is empty, the returned view will be pointing - * at the character with value '\0', but the size will be zero. - * @see https://en.cppreference.com/w/cpp/string/basic_string/operator_at - */ -C4_ALWAYS_INLINE c4::substr to_substr(std::string &s) noexcept -{ - #if C4_CPP < 11 - #error this function will do undefined behavior - #endif - // since c++11 it is legal to call s[s.size()]. - return c4::substr(&s[0], s.size()); -} - -/** get a readonly view to an existing std::string. - * When the string is empty, the returned view will be pointing - * at the character with value '\0', but the size will be zero. - * @see https://en.cppreference.com/w/cpp/string/basic_string/operator_at - */ -C4_ALWAYS_INLINE c4::csubstr to_csubstr(std::string const& s) noexcept -{ - #if C4_CPP < 11 - #error this function will do undefined behavior - #endif - // since c++11 it is legal to call s[s.size()]. - return c4::csubstr(&s[0], s.size()); -} - -/** get a readonly view to an existing std::string_view */ -C4_ALWAYS_INLINE c4::csubstr to_csubstr(std::string_view const s) -{ - const char* data = ! s.empty() ? &s[0] : nullptr; - return c4::csubstr(data, s.size()); -} - -//----------------------------------------------------------------------------- - -C4_ALWAYS_INLINE bool operator== (c4::csubstr ss, std::string const& s) { return ss.compare(to_csubstr(s)) == 0; } -C4_ALWAYS_INLINE bool operator!= (c4::csubstr ss, std::string const& s) { return ss.compare(to_csubstr(s)) != 0; } -C4_ALWAYS_INLINE bool operator>= (c4::csubstr ss, std::string const& s) { return ss.compare(to_csubstr(s)) >= 0; } -C4_ALWAYS_INLINE bool operator> (c4::csubstr ss, std::string const& s) { return ss.compare(to_csubstr(s)) > 0; } -C4_ALWAYS_INLINE bool operator<= (c4::csubstr ss, std::string const& s) { return ss.compare(to_csubstr(s)) <= 0; } -C4_ALWAYS_INLINE bool operator< (c4::csubstr ss, std::string const& s) { return ss.compare(to_csubstr(s)) < 0; } - -C4_ALWAYS_INLINE bool operator== (std::string const& s, c4::csubstr ss) { return ss.compare(to_csubstr(s)) == 0; } -C4_ALWAYS_INLINE bool operator!= (std::string const& s, c4::csubstr ss) { return ss.compare(to_csubstr(s)) != 0; } -C4_ALWAYS_INLINE bool operator>= (std::string const& s, c4::csubstr ss) { return ss.compare(to_csubstr(s)) <= 0; } -C4_ALWAYS_INLINE bool operator> (std::string const& s, c4::csubstr ss) { return ss.compare(to_csubstr(s)) < 0; } -C4_ALWAYS_INLINE bool operator<= (std::string const& s, c4::csubstr ss) { return ss.compare(to_csubstr(s)) >= 0; } -C4_ALWAYS_INLINE bool operator< (std::string const& s, c4::csubstr ss) { return ss.compare(to_csubstr(s)) > 0; } - -//----------------------------------------------------------------------------- - -/** copy an std::string to a writeable string view */ -inline size_t to_chars(c4::substr buf, std::string const& s) -{ - C4_ASSERT(!buf.overlaps(to_csubstr(s))); - size_t len = buf.len < s.size() ? buf.len : s.size(); - // calling memcpy with null strings is undefined behavior - // and will wreak havoc in calling code's branches. - // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637 - if(len) - { - C4_ASSERT(s.data() != nullptr); - C4_ASSERT(buf.str != nullptr); - memcpy(buf.str, s.data(), len); - } - return s.size(); // return the number of needed chars -} - -/** copy a string view to an existing std::string */ -inline bool from_chars(c4::csubstr buf, std::string * s) -{ - s->resize(buf.len); - C4_ASSERT(!buf.overlaps(to_csubstr(*s))); - // calling memcpy with null strings is undefined behavior - // and will wreak havoc in calling code's branches. - // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637 - if(buf.len) - { - C4_ASSERT(buf.str != nullptr); - memcpy(&(*s)[0], buf.str, buf.len); - } - return true; -} - -} // namespace c4 - -#endif // _C4_STD_STRING_HPP_ - - -// (end https://github.com/biojppm/c4core/src/c4/std/string.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/std/vector.hpp -// https://github.com/biojppm/c4core/src/c4/std/vector.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_STD_VECTOR_HPP_ -#define _C4_STD_VECTOR_HPP_ - -/** @file vector.hpp provides conversion and comparison facilities - * from/between std::vector to c4::substr and c4::csubstr. - * @todo add to_span() and friends - */ - -#ifndef C4CORE_SINGLE_HEADER -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/substr.hpp -//#include "c4/substr.hpp" -#if !defined(C4_SUBSTR_HPP_) && !defined(_C4_SUBSTR_HPP_) -#error "amalgamate: file c4/substr.hpp must have been included at this point" -#endif /* C4_SUBSTR_HPP_ */ - -#endif - -#include - -namespace c4 { - -//----------------------------------------------------------------------------- - -/** get a substr (writeable string view) of an existing std::vector */ -template -c4::substr to_substr(std::vector &vec) -{ - char *data = vec.empty() ? nullptr : vec.data(); // data() may or may not return a null pointer. - return c4::substr(data, vec.size()); -} - -/** get a csubstr (read-only string) view of an existing std::vector */ -template -c4::csubstr to_csubstr(std::vector const& vec) -{ - const char *data = vec.empty() ? nullptr : vec.data(); // data() may or may not return a null pointer. - return c4::csubstr(data, vec.size()); -} - -//----------------------------------------------------------------------------- -// comparisons between substrings and std::vector - -template C4_ALWAYS_INLINE bool operator!= (c4::csubstr ss, std::vector const& s) { return ss != to_csubstr(s); } -template C4_ALWAYS_INLINE bool operator== (c4::csubstr ss, std::vector const& s) { return ss == to_csubstr(s); } -template C4_ALWAYS_INLINE bool operator>= (c4::csubstr ss, std::vector const& s) { return ss >= to_csubstr(s); } -template C4_ALWAYS_INLINE bool operator> (c4::csubstr ss, std::vector const& s) { return ss > to_csubstr(s); } -template C4_ALWAYS_INLINE bool operator<= (c4::csubstr ss, std::vector const& s) { return ss <= to_csubstr(s); } -template C4_ALWAYS_INLINE bool operator< (c4::csubstr ss, std::vector const& s) { return ss < to_csubstr(s); } - -template C4_ALWAYS_INLINE bool operator!= (std::vector const& s, c4::csubstr ss) { return ss != to_csubstr(s); } -template C4_ALWAYS_INLINE bool operator== (std::vector const& s, c4::csubstr ss) { return ss == to_csubstr(s); } -template C4_ALWAYS_INLINE bool operator>= (std::vector const& s, c4::csubstr ss) { return ss <= to_csubstr(s); } -template C4_ALWAYS_INLINE bool operator> (std::vector const& s, c4::csubstr ss) { return ss < to_csubstr(s); } -template C4_ALWAYS_INLINE bool operator<= (std::vector const& s, c4::csubstr ss) { return ss >= to_csubstr(s); } -template C4_ALWAYS_INLINE bool operator< (std::vector const& s, c4::csubstr ss) { return ss > to_csubstr(s); } - -//----------------------------------------------------------------------------- - -/** copy a std::vector to a writeable string view */ -template -inline size_t to_chars(c4::substr buf, std::vector const& s) -{ - C4_ASSERT(!buf.overlaps(to_csubstr(s))); - size_t len = buf.len < s.size() ? buf.len : s.size(); - // calling memcpy with null strings is undefined behavior - // and will wreak havoc in calling code's branches. - // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637 - if(len > 0) - { - memcpy(buf.str, s.data(), len); - } - return s.size(); // return the number of needed chars -} - -/** copy a string view to an existing std::vector */ -template -inline bool from_chars(c4::csubstr buf, std::vector * s) -{ - s->resize(buf.len); - C4_ASSERT(!buf.overlaps(to_csubstr(*s))); - // calling memcpy with null strings is undefined behavior - // and will wreak havoc in calling code's branches. - // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637 - if(buf.len > 0) - { - memcpy(&(*s)[0], buf.str, buf.len); - } - return true; -} - -} // namespace c4 - -#endif // _C4_STD_VECTOR_HPP_ - - -// (end https://github.com/biojppm/c4core/src/c4/std/vector.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/std/tuple.hpp -// https://github.com/biojppm/c4core/src/c4/std/tuple.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_STD_TUPLE_HPP_ -#define _C4_STD_TUPLE_HPP_ - -/** @file tuple.hpp */ - -#ifndef C4CORE_SINGLE_HEADER -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/format.hpp -//#include "c4/format.hpp" -#if !defined(C4_FORMAT_HPP_) && !defined(_C4_FORMAT_HPP_) -#error "amalgamate: file c4/format.hpp must have been included at this point" -#endif /* C4_FORMAT_HPP_ */ - -#endif - -#include - -/** this is a work in progress */ -#undef C4_TUPLE_TO_CHARS - -namespace c4 { - -#ifdef C4_TUPLE_TO_CHARS -namespace detail { - -template< size_t Curr, class... Types > -struct tuple_helper -{ - static size_t do_cat(substr buf, std::tuple< Types... > const& tp) - { - size_t num = to_chars(buf, std::get(tp)); - buf = buf.len >= num ? buf.sub(num) : substr{}; - num += tuple_helper< Curr+1, Types... >::do_cat(buf, tp); - return num; - } - - static size_t do_uncat(csubstr buf, std::tuple< Types... > & tp) - { - size_t num = from_str_trim(buf, &std::get(tp)); - if(num == csubstr::npos) return csubstr::npos; - buf = buf.len >= num ? buf.sub(num) : substr{}; - num += tuple_helper< Curr+1, Types... >::do_uncat(buf, tp); - return num; - } - - template< class Sep > - static size_t do_catsep_more(substr buf, Sep const& sep, std::tuple< Types... > const& tp) - { - size_t ret = to_chars(buf, sep), num = ret; - buf = buf.len >= ret ? buf.sub(ret) : substr{}; - ret = to_chars(buf, std::get(tp)); - num += ret; - buf = buf.len >= ret ? buf.sub(ret) : substr{}; - ret = tuple_helper< Curr+1, Types... >::do_catsep_more(buf, sep, tp); - num += ret; - return num; - } - - template< class Sep > - static size_t do_uncatsep_more(csubstr buf, Sep & sep, std::tuple< Types... > & tp) - { - size_t ret = from_str_trim(buf, &sep), num = ret; - if(ret == csubstr::npos) return csubstr::npos; - buf = buf.len >= ret ? buf.sub(ret) : substr{}; - ret = from_str_trim(buf, &std::get(tp)); - if(ret == csubstr::npos) return csubstr::npos; - num += ret; - buf = buf.len >= ret ? buf.sub(ret) : substr{}; - ret = tuple_helper< Curr+1, Types... >::do_uncatsep_more(buf, sep, tp); - if(ret == csubstr::npos) return csubstr::npos; - num += ret; - return num; - } - - static size_t do_format(substr buf, csubstr fmt, std::tuple< Types... > const& tp) - { - auto pos = fmt.find("{}"); - if(pos != csubstr::npos) - { - size_t num = to_chars(buf, fmt.sub(0, pos)); - size_t out = num; - buf = buf.len >= num ? buf.sub(num) : substr{}; - num = to_chars(buf, std::get(tp)); - out += num; - buf = buf.len >= num ? buf.sub(num) : substr{}; - num = tuple_helper< Curr+1, Types... >::do_format(buf, fmt.sub(pos + 2), tp); - out += num; - return out; - } - else - { - return format(buf, fmt); - } - } - - static size_t do_unformat(csubstr buf, csubstr fmt, std::tuple< Types... > & tp) - { - auto pos = fmt.find("{}"); - if(pos != csubstr::npos) - { - size_t num = pos; - size_t out = num; - buf = buf.len >= num ? buf.sub(num) : substr{}; - num = from_str_trim(buf, &std::get(tp)); - out += num; - buf = buf.len >= num ? buf.sub(num) : substr{}; - num = tuple_helper< Curr+1, Types... >::do_unformat(buf, fmt.sub(pos + 2), tp); - out += num; - return out; - } - else - { - return tuple_helper< sizeof...(Types), Types... >::do_unformat(buf, fmt, tp); - } - } - -}; - -/** @todo VS compilation fails for this class */ -template< class... Types > -struct tuple_helper< sizeof...(Types), Types... > -{ - static size_t do_cat(substr /*buf*/, std::tuple const& /*tp*/) { return 0; } - static size_t do_uncat(csubstr /*buf*/, std::tuple & /*tp*/) { return 0; } - - template< class Sep > static size_t do_catsep_more(substr /*buf*/, Sep const& /*sep*/, std::tuple const& /*tp*/) { return 0; } - template< class Sep > static size_t do_uncatsep_more(csubstr /*buf*/, Sep & /*sep*/, std::tuple & /*tp*/) { return 0; } - - static size_t do_format(substr buf, csubstr fmt, std::tuple const& /*tp*/) - { - return to_chars(buf, fmt); - } - - static size_t do_unformat(csubstr buf, csubstr fmt, std::tuple const& /*tp*/) - { - return 0; - } -}; - -} // namespace detail - -template< class... Types > -inline size_t cat(substr buf, std::tuple< Types... > const& tp) -{ - return detail::tuple_helper< 0, Types... >::do_cat(buf, tp); -} - -template< class... Types > -inline size_t uncat(csubstr buf, std::tuple< Types... > & tp) -{ - return detail::tuple_helper< 0, Types... >::do_uncat(buf, tp); -} - -template< class Sep, class... Types > -inline size_t catsep(substr buf, Sep const& sep, std::tuple< Types... > const& tp) -{ - size_t num = to_chars(buf, std::cref(std::get<0>(tp))); - buf = buf.len >= num ? buf.sub(num) : substr{}; - num += detail::tuple_helper< 1, Types... >::do_catsep_more(buf, sep, tp); - return num; -} - -template< class Sep, class... Types > -inline size_t uncatsep(csubstr buf, Sep & sep, std::tuple< Types... > & tp) -{ - size_t ret = from_str_trim(buf, &std::get<0>(tp)), num = ret; - if(ret == csubstr::npos) return csubstr::npos; - buf = buf.len >= ret ? buf.sub(ret) : substr{}; - ret = detail::tuple_helper< 1, Types... >::do_uncatsep_more(buf, sep, tp); - if(ret == csubstr::npos) return csubstr::npos; - num += ret; - return num; -} - -template< class... Types > -inline size_t format(substr buf, csubstr fmt, std::tuple< Types... > const& tp) -{ - return detail::tuple_helper< 0, Types... >::do_format(buf, fmt, tp); -} - -template< class... Types > -inline size_t unformat(csubstr buf, csubstr fmt, std::tuple< Types... > & tp) -{ - return detail::tuple_helper< 0, Types... >::do_unformat(buf, fmt, tp); -} -#endif // C4_TUPLE_TO_CHARS - -} // namespace c4 - -#endif /* _C4_STD_TUPLE_HPP_ */ - - -// (end https://github.com/biojppm/c4core/src/c4/std/tuple.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/ext/rng/rng.hpp -// https://github.com/biojppm/c4core/src/c4/ext/rng/rng.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -/* Copyright (c) 2018 Arvid Gerstmann. - * - * https://arvid.io/2018/07/02/better-cxx-prng/ - * - * This code is licensed under MIT license. */ -#ifndef AG_RANDOM_H -#define AG_RANDOM_H - -//included above: -//#include -#include - - -namespace c4 { -namespace rng { - - -class splitmix -{ -public: - using result_type = uint32_t; - static constexpr result_type (min)() { return 0; } - static constexpr result_type (max)() { return UINT32_MAX; } - friend bool operator==(splitmix const &, splitmix const &); - friend bool operator!=(splitmix const &, splitmix const &); - - splitmix() : m_seed(1) {} - explicit splitmix(uint64_t s) : m_seed(s) {} - explicit splitmix(std::random_device &rd) - { - seed(rd); - } - - void seed(uint64_t s) { m_seed = s; } - void seed(std::random_device &rd) - { - m_seed = uint64_t(rd()) << 31 | uint64_t(rd()); - } - - result_type operator()() - { - uint64_t z = (m_seed += UINT64_C(0x9E3779B97F4A7C15)); - z = (z ^ (z >> 30)) * UINT64_C(0xBF58476D1CE4E5B9); - z = (z ^ (z >> 27)) * UINT64_C(0x94D049BB133111EB); - return result_type((z ^ (z >> 31)) >> 31); - } - - void discard(unsigned long long n) - { - for (unsigned long long i = 0; i < n; ++i) - operator()(); - } - -private: - uint64_t m_seed; -}; - -inline bool operator==(splitmix const &lhs, splitmix const &rhs) -{ - return lhs.m_seed == rhs.m_seed; -} -inline bool operator!=(splitmix const &lhs, splitmix const &rhs) -{ - return lhs.m_seed != rhs.m_seed; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -class xorshift -{ -public: - using result_type = uint32_t; - static constexpr result_type (min)() { return 0; } - static constexpr result_type (max)() { return UINT32_MAX; } - friend bool operator==(xorshift const &, xorshift const &); - friend bool operator!=(xorshift const &, xorshift const &); - - xorshift() : m_seed(0xc1f651c67c62c6e0ull) {} - explicit xorshift(std::random_device &rd) - { - seed(rd); - } - - void seed(uint64_t s) { m_seed = s; } - void seed(std::random_device &rd) - { - m_seed = uint64_t(rd()) << 31 | uint64_t(rd()); - } - - result_type operator()() - { - uint64_t result = m_seed * 0xd989bcacc137dcd5ull; - m_seed ^= m_seed >> 11; - m_seed ^= m_seed << 31; - m_seed ^= m_seed >> 18; - return uint32_t(result >> 32ull); - } - - void discard(unsigned long long n) - { - for (unsigned long long i = 0; i < n; ++i) - operator()(); - } - -private: - uint64_t m_seed; -}; - -inline bool operator==(xorshift const &lhs, xorshift const &rhs) -{ - return lhs.m_seed == rhs.m_seed; -} -inline bool operator!=(xorshift const &lhs, xorshift const &rhs) -{ - return lhs.m_seed != rhs.m_seed; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -class pcg -{ -public: - using result_type = uint32_t; - static constexpr result_type (min)() { return 0; } - static constexpr result_type (max)() { return UINT32_MAX; } - friend bool operator==(pcg const &, pcg const &); - friend bool operator!=(pcg const &, pcg const &); - - pcg() - : m_state(0x853c49e6748fea9bULL) - , m_inc(0xda3e39cb94b95bdbULL) - {} - explicit pcg(uint64_t s) { m_state = s; m_inc = m_state << 1; } - explicit pcg(std::random_device &rd) - { - seed(rd); - } - - void seed(uint64_t s) { m_state = s; } - void seed(std::random_device &rd) - { - uint64_t s0 = uint64_t(rd()) << 31 | uint64_t(rd()); - uint64_t s1 = uint64_t(rd()) << 31 | uint64_t(rd()); - - m_state = 0; - m_inc = (s1 << 1) | 1; - (void)operator()(); - m_state += s0; - (void)operator()(); - } - - result_type operator()() - { - uint64_t oldstate = m_state; - m_state = oldstate * 6364136223846793005ULL + m_inc; - uint32_t xorshifted = uint32_t(((oldstate >> 18u) ^ oldstate) >> 27u); - //int rot = oldstate >> 59u; // the original. error? - int64_t rot = (int64_t)oldstate >> 59u; // error? - return (xorshifted >> rot) | (xorshifted << ((uint64_t)(-rot) & 31)); - } - - void discard(unsigned long long n) - { - for (unsigned long long i = 0; i < n; ++i) - operator()(); - } - -private: - uint64_t m_state; - uint64_t m_inc; -}; - -inline bool operator==(pcg const &lhs, pcg const &rhs) -{ - return lhs.m_state == rhs.m_state - && lhs.m_inc == rhs.m_inc; -} -inline bool operator!=(pcg const &lhs, pcg const &rhs) -{ - return lhs.m_state != rhs.m_state - || lhs.m_inc != rhs.m_inc; -} - -} // namespace rng -} // namespace c4 - -#endif /* AG_RANDOM_H */ - - -// (end https://github.com/biojppm/c4core/src/c4/ext/rng/rng.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/ext/sg14/inplace_function.h -// https://github.com/biojppm/c4core/src/c4/ext/sg14/inplace_function.h -//-------------------------------------------------------------------------------- -//******************************************************************************** - -/* - * Boost Software License - Version 1.0 - August 17th, 2003 - * - * Permission is hereby granted, free of charge, to any person or organization - * obtaining a copy of the software and accompanying documentation covered by - * this license (the "Software") to use, reproduce, display, distribute, - * execute, and transmit the Software, and to prepare derivative works of the - * Software, and to permit third-parties to whom the Software is furnished to - * do so, all subject to the following: - * - * The copyright notices in the Software and this entire statement, including - * the above license grant, this restriction and the following disclaimer, - * must be included in all copies of the Software, in whole or in part, and - * all derivative works of the Software, unless such copies or derivative - * works are solely in the form of machine-executable object code generated by - * a source language processor. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT - * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE - * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ -#ifndef _C4_EXT_SG14_INPLACE_FUNCTION_H_ -#define _C4_EXT_SG14_INPLACE_FUNCTION_H_ - -//included above: -//#include -//included above: -//#include -#include - -namespace stdext { - -namespace inplace_function_detail { - -static constexpr size_t InplaceFunctionDefaultCapacity = 32; - -#if defined(__GLIBCXX__) // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61458 -template -union aligned_storage_helper { - struct double1 { double a; }; - struct double4 { double a[4]; }; - template using maybe = typename std::conditional<(Cap >= sizeof(T)), T, char>::type; - char real_data[Cap]; - maybe a; - maybe b; - maybe c; - maybe d; - maybe e; - maybe f; - maybe g; - maybe h; -}; - -template>::value> -struct aligned_storage { - using type = typename std::aligned_storage::type; -}; -#else -using std::aligned_storage; -#endif - -template struct wrapper -{ - using type = T; -}; - -template struct vtable -{ - using storage_ptr_t = void*; - - using invoke_ptr_t = R(*)(storage_ptr_t, Args&&...); - using process_ptr_t = void(*)(storage_ptr_t, storage_ptr_t); - using destructor_ptr_t = void(*)(storage_ptr_t); - - const invoke_ptr_t invoke_ptr; - const process_ptr_t copy_ptr; - const process_ptr_t move_ptr; - const destructor_ptr_t destructor_ptr; - - explicit constexpr vtable() noexcept : - invoke_ptr{ [](storage_ptr_t, Args&&...) -> R - { throw std::bad_function_call(); } - }, - copy_ptr{ [](storage_ptr_t, storage_ptr_t) noexcept -> void {} }, - move_ptr{ [](storage_ptr_t, storage_ptr_t) noexcept -> void {} }, - destructor_ptr{ [](storage_ptr_t) noexcept -> void {} } - {} - - template explicit constexpr vtable(wrapper) noexcept : - invoke_ptr{ [](storage_ptr_t storage_ptr, Args&&... args) - noexcept(noexcept(std::declval()(args...))) -> R - { return (*static_cast(storage_ptr))( - std::forward(args)... - ); } - }, - copy_ptr{ [](storage_ptr_t dst_ptr, storage_ptr_t src_ptr) - noexcept(std::is_nothrow_copy_constructible::value) -> void - { new (dst_ptr) C{ (*static_cast(src_ptr)) }; } - }, - move_ptr{ [](storage_ptr_t dst_ptr, storage_ptr_t src_ptr) - noexcept(std::is_nothrow_move_constructible::value) -> void - { new (dst_ptr) C{ std::move(*static_cast(src_ptr)) }; } - }, - destructor_ptr{ [](storage_ptr_t storage_ptr) - noexcept -> void - { static_cast(storage_ptr)->~C(); } - } - {} - - vtable(const vtable&) = delete; - vtable(vtable&&) = delete; - - vtable& operator= (const vtable&) = delete; - vtable& operator= (vtable&&) = delete; - - ~vtable() = default; -}; - -template -struct is_valid_inplace_dst : std::true_type -{ - static_assert(DstCap >= SrcCap, - "Can't squeeze larger inplace_function into a smaller one" - ); - - static_assert(DstAlign % SrcAlign == 0, - "Incompatible inplace_function alignments" - ); -}; - -} // namespace inplace_function_detail - -template< - typename Signature, - size_t Capacity = inplace_function_detail::InplaceFunctionDefaultCapacity, - size_t Alignment = std::alignment_of::type>::value -> -class inplace_function; // unspecified - -template< - typename R, - typename... Args, - size_t Capacity, - size_t Alignment -> -class inplace_function -{ - static const constexpr inplace_function_detail::vtable empty_vtable{}; -public: - using capacity = std::integral_constant; - using alignment = std::integral_constant; - - using storage_t = typename inplace_function_detail::aligned_storage::type; - using vtable_t = inplace_function_detail::vtable; - using vtable_ptr_t = const vtable_t*; - - template friend class inplace_function; - - inplace_function() noexcept : - vtable_ptr_{std::addressof(empty_vtable)} - {} - - template< - typename T, - typename C = typename std::decay::type, - typename = typename std::enable_if< - !(std::is_same::value - || std::is_convertible::value) - >::type - > - inplace_function(T&& closure) - { -#if __cplusplus >= 201703L - static_assert(std::is_invocable_r::value, - "inplace_function cannot be constructed from non-callable type" - ); -#endif - static_assert(std::is_copy_constructible::value, - "inplace_function cannot be constructed from non-copyable type" - ); - - static_assert(sizeof(C) <= Capacity, - "inplace_function cannot be constructed from object with this (large) size" - ); - - static_assert(Alignment % std::alignment_of::value == 0, - "inplace_function cannot be constructed from object with this (large) alignment" - ); - - static const vtable_t vt{inplace_function_detail::wrapper{}}; - vtable_ptr_ = std::addressof(vt); - - new (std::addressof(storage_)) C{std::forward(closure)}; - } - - inplace_function(std::nullptr_t) noexcept : - vtable_ptr_{std::addressof(empty_vtable)} - {} - - inplace_function(const inplace_function& other) : - vtable_ptr_{other.vtable_ptr_} - { - vtable_ptr_->copy_ptr( - std::addressof(storage_), - std::addressof(other.storage_) - ); - } - - inplace_function(inplace_function&& other) : - vtable_ptr_{other.vtable_ptr_} - { - vtable_ptr_->move_ptr( - std::addressof(storage_), - std::addressof(other.storage_) - ); - } - - inplace_function& operator= (std::nullptr_t) noexcept - { - vtable_ptr_->destructor_ptr(std::addressof(storage_)); - vtable_ptr_ = std::addressof(empty_vtable); - return *this; - } - - inplace_function& operator= (const inplace_function& other) - { - if(this != std::addressof(other)) - { - vtable_ptr_->destructor_ptr(std::addressof(storage_)); - - vtable_ptr_ = other.vtable_ptr_; - vtable_ptr_->copy_ptr( - std::addressof(storage_), - std::addressof(other.storage_) - ); - } - return *this; - } - - inplace_function& operator= (inplace_function&& other) - { - if(this != std::addressof(other)) - { - vtable_ptr_->destructor_ptr(std::addressof(storage_)); - - vtable_ptr_ = other.vtable_ptr_; - vtable_ptr_->move_ptr( - std::addressof(storage_), - std::addressof(other.storage_) - ); - } - return *this; - } - - ~inplace_function() - { - vtable_ptr_->destructor_ptr(std::addressof(storage_)); - } - - R operator() (Args... args) const - { - return vtable_ptr_->invoke_ptr( - std::addressof(storage_), - std::forward(args)... - ); - } - - constexpr bool operator== (std::nullptr_t) const noexcept - { - return !operator bool(); - } - - constexpr bool operator!= (std::nullptr_t) const noexcept - { - return operator bool(); - } - - explicit constexpr operator bool() const noexcept - { - return vtable_ptr_ != std::addressof(empty_vtable); - } - - template - operator inplace_function() const& - { - static_assert(inplace_function_detail::is_valid_inplace_dst< - Cap, Align, Capacity, Alignment - >::value, "conversion not allowed"); - - return {vtable_ptr_, vtable_ptr_->copy_ptr, std::addressof(storage_)}; - } - - template - operator inplace_function() && - { - static_assert(inplace_function_detail::is_valid_inplace_dst< - Cap, Align, Capacity, Alignment - >::value, "conversion not allowed"); - - return {vtable_ptr_, vtable_ptr_->move_ptr, std::addressof(storage_)}; - } - - void swap(inplace_function& other) - { - if (this == std::addressof(other)) return; - - storage_t tmp; - vtable_ptr_->move_ptr( - std::addressof(tmp), - std::addressof(storage_) - ); - vtable_ptr_->destructor_ptr(std::addressof(storage_)); - - other.vtable_ptr_->move_ptr( - std::addressof(storage_), - std::addressof(other.storage_) - ); - other.vtable_ptr_->destructor_ptr(std::addressof(other.storage_)); - - vtable_ptr_->move_ptr( - std::addressof(other.storage_), - std::addressof(tmp) - ); - vtable_ptr_->destructor_ptr(std::addressof(tmp)); - - std::swap(vtable_ptr_, other.vtable_ptr_); - } - -private: - vtable_ptr_t vtable_ptr_; - mutable storage_t storage_; - - inplace_function( - vtable_ptr_t vtable_ptr, - typename vtable_t::process_ptr_t process_ptr, - typename vtable_t::storage_ptr_t storage_ptr - ) : vtable_ptr_{vtable_ptr} - { - process_ptr(std::addressof(storage_), storage_ptr); - } -}; - -} // namespace stdext - -#endif /* _C4_EXT_SG14_INPLACE_FUNCTION_H_ */ - - -// (end https://github.com/biojppm/c4core/src/c4/ext/sg14/inplace_function.h) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/language.cpp -// https://github.com/biojppm/c4core/src/c4/language.cpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifdef C4CORE_SINGLE_HDR_DEFINE_NOW -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/language.hpp -//#include "c4/language.hpp" -#if !defined(C4_LANGUAGE_HPP_) && !defined(_C4_LANGUAGE_HPP_) -#error "amalgamate: file c4/language.hpp must have been included at this point" -#endif /* C4_LANGUAGE_HPP_ */ - - -namespace c4 { -namespace detail { - -#ifndef __GNUC__ -void use_char_pointer(char const volatile* v) -{ - C4_UNUSED(v); -} -#else -void foo() {} // to avoid empty file warning from the linker -#endif - -} // namespace detail -} // namespace c4 - -#endif /* C4CORE_SINGLE_HDR_DEFINE_NOW */ - - -// (end https://github.com/biojppm/c4core/src/c4/language.cpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/format.cpp -// https://github.com/biojppm/c4core/src/c4/format.cpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifdef C4CORE_SINGLE_HDR_DEFINE_NOW -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/format.hpp -//#include "c4/format.hpp" -#if !defined(C4_FORMAT_HPP_) && !defined(_C4_FORMAT_HPP_) -#error "amalgamate: file c4/format.hpp must have been included at this point" -#endif /* C4_FORMAT_HPP_ */ - - -//included above: -//#include // for std::align - -#ifdef __clang__ -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wformat-nonliteral" -#elif defined(__GNUC__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wformat-nonliteral" -#endif - -namespace c4 { - - -size_t to_chars(substr buf, fmt::const_raw_wrapper r) -{ - void * vptr = buf.str; - size_t space = buf.len; - auto ptr = (decltype(buf.str)) std::align(r.alignment, r.len, vptr, space); - if(ptr == nullptr) - { - // if it was not possible to align, return a conservative estimate - // of the required space - return r.alignment + r.len; - } - C4_CHECK(ptr >= buf.begin() && ptr <= buf.end()); - size_t sz = static_cast(ptr - buf.str) + r.len; - if(sz <= buf.len) - { - memcpy(ptr, r.buf, r.len); - } - return sz; -} - - -bool from_chars(csubstr buf, fmt::raw_wrapper *r) -{ - void * vptr = (void*)buf.str; - size_t space = buf.len; - auto ptr = (decltype(buf.str)) std::align(r->alignment, r->len, vptr, space); - C4_CHECK(ptr != nullptr); - C4_CHECK(ptr >= buf.begin() && ptr <= buf.end()); - //size_t dim = (ptr - buf.str) + r->len; - memcpy(r->buf, ptr, r->len); - return true; -} - - -} // namespace c4 - -#ifdef __clang__ -# pragma clang diagnostic pop -#elif defined(__GNUC__) -# pragma GCC diagnostic pop -#endif - -#endif /* C4CORE_SINGLE_HDR_DEFINE_NOW */ - - -// (end https://github.com/biojppm/c4core/src/c4/format.cpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/memory_util.cpp -// https://github.com/biojppm/c4core/src/c4/memory_util.cpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifdef C4CORE_SINGLE_HDR_DEFINE_NOW -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/memory_util.hpp -//#include "c4/memory_util.hpp" -#if !defined(C4_MEMORY_UTIL_HPP_) && !defined(_C4_MEMORY_UTIL_HPP_) -#error "amalgamate: file c4/memory_util.hpp must have been included at this point" -#endif /* C4_MEMORY_UTIL_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/error.hpp -//#include "c4/error.hpp" -#if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) -#error "amalgamate: file c4/error.hpp must have been included at this point" -#endif /* C4_ERROR_HPP_ */ - - -namespace c4 { - -/** Fills 'dest' with the first 'pattern_size' bytes at 'pattern', 'num_times'. */ -void mem_repeat(void* dest, void const* pattern, size_t pattern_size, size_t num_times) -{ - if(C4_UNLIKELY(num_times == 0)) - return; - C4_ASSERT( ! mem_overlaps(dest, pattern, num_times*pattern_size, pattern_size)); - char *begin = (char*)dest; - char *end = begin + num_times * pattern_size; - // copy the pattern once - ::memcpy(begin, pattern, pattern_size); - // now copy from dest to itself, doubling up every time - size_t n = pattern_size; - while(begin + 2*n < end) - { - ::memcpy(begin + n, begin, n); - n <<= 1; // double n - } - // copy the missing part - if(begin + n < end) - { - ::memcpy(begin + n, begin, static_cast(end - (begin + n))); - } -} - -} // namespace c4 - -#endif /* C4CORE_SINGLE_HDR_DEFINE_NOW */ - - -// (end https://github.com/biojppm/c4core/src/c4/memory_util.cpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/char_traits.cpp -// https://github.com/biojppm/c4core/src/c4/char_traits.cpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifdef C4CORE_SINGLE_HDR_DEFINE_NOW -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/char_traits.hpp -//#include "c4/char_traits.hpp" -#if !defined(C4_CHAR_TRAITS_HPP_) && !defined(_C4_CHAR_TRAITS_HPP_) -#error "amalgamate: file c4/char_traits.hpp must have been included at this point" -#endif /* C4_CHAR_TRAITS_HPP_ */ - - -namespace c4 { - -constexpr const char char_traits< char >::whitespace_chars[]; -constexpr const size_t char_traits< char >::num_whitespace_chars; -constexpr const wchar_t char_traits< wchar_t >::whitespace_chars[]; -constexpr const size_t char_traits< wchar_t >::num_whitespace_chars; - -} // namespace c4 - -#endif /* C4CORE_SINGLE_HDR_DEFINE_NOW */ - - -// (end https://github.com/biojppm/c4core/src/c4/char_traits.cpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/memory_resource.cpp -// https://github.com/biojppm/c4core/src/c4/memory_resource.cpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifdef C4CORE_SINGLE_HDR_DEFINE_NOW -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/memory_resource.hpp -//#include "c4/memory_resource.hpp" -#if !defined(C4_MEMORY_RESOURCE_HPP_) && !defined(_C4_MEMORY_RESOURCE_HPP_) -#error "amalgamate: file c4/memory_resource.hpp must have been included at this point" -#endif /* C4_MEMORY_RESOURCE_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/memory_util.hpp -//#include "c4/memory_util.hpp" -#if !defined(C4_MEMORY_UTIL_HPP_) && !defined(_C4_MEMORY_UTIL_HPP_) -#error "amalgamate: file c4/memory_util.hpp must have been included at this point" -#endif /* C4_MEMORY_UTIL_HPP_ */ - - -//included above: -//#include -//included above: -//#include -#if defined(C4_POSIX) || defined(C4_IOS) || defined(C4_MACOS) || defined(C4_ARM) -# include -#endif -#if defined(C4_ARM) -# include -#endif - -//included above: -//#include - -namespace c4 { - -namespace detail { - - -#ifdef C4_NO_ALLOC_DEFAULTS -aalloc_pfn s_aalloc = nullptr; -free_pfn s_afree = nullptr; -arealloc_pfn s_arealloc = nullptr; -#else - - -void afree_impl(void *ptr) -{ -#if defined(C4_WIN) || defined(C4_XBOX) - ::_aligned_free(ptr); -#else - ::free(ptr); -#endif -} - - -void* aalloc_impl(size_t size, size_t alignment) -{ - void *mem; -#if defined(C4_WIN) || defined(C4_XBOX) - mem = ::_aligned_malloc(size, alignment); - C4_CHECK(mem != nullptr || size == 0); -#elif defined(C4_ARM) - // https://stackoverflow.com/questions/53614538/undefined-reference-to-posix-memalign-in-arm-gcc - // https://electronics.stackexchange.com/questions/467382/e2-studio-undefined-reference-to-posix-memalign/467753 - mem = memalign(alignment, size); - C4_CHECK(mem != nullptr || size == 0); -#elif defined(C4_POSIX) || defined(C4_IOS) || defined(C4_MACOS) - // NOTE: alignment needs to be sized in multiples of sizeof(void*) - size_t amult = alignment; - if(C4_UNLIKELY(alignment < sizeof(void*))) - { - amult = sizeof(void*); - } - int ret = ::posix_memalign(&mem, amult, size); - if(C4_UNLIKELY(ret)) - { - if(ret == EINVAL) - { - C4_ERROR("The alignment argument %zu was not a power of two, " - "or was not a multiple of sizeof(void*)", alignment); - } - else if(ret == ENOMEM) - { - C4_ERROR("There was insufficient memory to fulfill the " - "allocation request of %zu bytes (alignment=%lu)", size, size); - } - return nullptr; - } -#else - C4_NOT_IMPLEMENTED_MSG("need to implement an aligned allocation for this platform"); -#endif - C4_ASSERT_MSG((uintptr_t(mem) & (alignment-1)) == 0, "address %p is not aligned to %zu boundary", mem, alignment); - return mem; -} - - -void* arealloc_impl(void* ptr, size_t oldsz, size_t newsz, size_t alignment) -{ - /** @todo make this more efficient - * @see https://stackoverflow.com/questions/9078259/does-realloc-keep-the-memory-alignment-of-posix-memalign - * @see look for qReallocAligned() in http://code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/global/qmalloc.cpp - */ - void *tmp = aalloc(newsz, alignment); - size_t min = newsz < oldsz ? newsz : oldsz; - if(mem_overlaps(ptr, tmp, oldsz, newsz)) - { - ::memmove(tmp, ptr, min); - } - else - { - ::memcpy(tmp, ptr, min); - } - afree(ptr); - return tmp; -} - -aalloc_pfn s_aalloc = aalloc_impl; -afree_pfn s_afree = afree_impl; -arealloc_pfn s_arealloc = arealloc_impl; - -#endif // C4_NO_ALLOC_DEFAULTS - -} // namespace detail - - -aalloc_pfn get_aalloc() -{ - return detail::s_aalloc; -} -void set_aalloc(aalloc_pfn fn) -{ - detail::s_aalloc = fn; -} - -afree_pfn get_afree() -{ - return detail::s_afree; -} -void set_afree(afree_pfn fn) -{ - detail::s_afree = fn; -} - -arealloc_pfn get_arealloc() -{ - return detail::s_arealloc; -} -void set_arealloc(arealloc_pfn fn) -{ - detail::s_arealloc = fn; -} - - -void* aalloc(size_t sz, size_t alignment) -{ - C4_ASSERT_MSG(c4::get_aalloc() != nullptr, "did you forget to call set_aalloc()?"); - auto fn = c4::get_aalloc(); - void* ptr = fn(sz, alignment); - return ptr; -} - -void afree(void* ptr) -{ - C4_ASSERT_MSG(c4::get_afree() != nullptr, "did you forget to call set_afree()?"); - auto fn = c4::get_afree(); - fn(ptr); -} - -void* arealloc(void *ptr, size_t oldsz, size_t newsz, size_t alignment) -{ - C4_ASSERT_MSG(c4::get_arealloc() != nullptr, "did you forget to call set_arealloc()?"); - auto fn = c4::get_arealloc(); - void* nptr = fn(ptr, oldsz, newsz, alignment); - return nptr; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -void detail::_MemoryResourceSingleChunk::release() -{ - if(m_mem && m_owner) - { - impl_type::deallocate(m_mem, m_size); - } - m_mem = nullptr; - m_size = 0; - m_owner = false; - m_pos = 0; -} - -void detail::_MemoryResourceSingleChunk::acquire(size_t sz) -{ - clear(); - m_owner = true; - m_mem = (char*) impl_type::allocate(sz, alignof(max_align_t)); - m_size = sz; - m_pos = 0; -} - -void detail::_MemoryResourceSingleChunk::acquire(void *mem, size_t sz) -{ - clear(); - m_owner = false; - m_mem = (char*) mem; - m_size = sz; - m_pos = 0; -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -void* MemoryResourceLinear::do_allocate(size_t sz, size_t alignment, void *hint) -{ - C4_UNUSED(hint); - if(sz == 0) return nullptr; - // make sure there's enough room to allocate - if(m_pos + sz > m_size) - { - C4_ERROR("out of memory"); - return nullptr; - } - void *mem = m_mem + m_pos; - size_t space = m_size - m_pos; - if(std::align(alignment, sz, mem, space)) - { - C4_ASSERT(m_pos <= m_size); - C4_ASSERT(m_size - m_pos >= space); - m_pos += (m_size - m_pos) - space; - m_pos += sz; - C4_ASSERT(m_pos <= m_size); - } - else - { - C4_ERROR("could not align memory"); - mem = nullptr; - } - return mem; -} - -void MemoryResourceLinear::do_deallocate(void* ptr, size_t sz, size_t alignment) -{ - C4_UNUSED(ptr); - C4_UNUSED(sz); - C4_UNUSED(alignment); - // nothing to do!! -} - -void* MemoryResourceLinear::do_reallocate(void* ptr, size_t oldsz, size_t newsz, size_t alignment) -{ - if(newsz == oldsz) return ptr; - // is ptr the most recently allocated (MRA) block? - char *cptr = (char*)ptr; - bool same_pos = (m_mem + m_pos == cptr + oldsz); - // no need to get more memory when shrinking - if(newsz < oldsz) - { - // if this is the MRA, we can safely shrink the position - if(same_pos) - { - m_pos -= oldsz - newsz; - } - return ptr; - } - // we're growing the block, and it fits in size - else if(same_pos && cptr + newsz <= m_mem + m_size) - { - // if this is the MRA, we can safely shrink the position - m_pos += newsz - oldsz; - return ptr; - } - // we're growing the block or it doesn't fit - - // delegate any of these situations to do_deallocate() - return do_allocate(newsz, alignment, ptr); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -/** @todo add a free list allocator. A good candidate because of its - * small size is TLSF. - * - * @see https://github.com/mattconte/tlsf - * - * Comparisons: - * - * @see https://www.researchgate.net/publication/262375150_A_Comparative_Study_on_Memory_Allocators_in_Multicore_and_Multithreaded_Applications_-_SBESC_2011_-_Presentation_Slides - * @see http://webkit.sed.hu/blog/20100324/war-allocators-tlsf-action - * @see https://github.com/emeryberger/Malloc-Implementations/tree/master/allocators - * - * */ - -} // namespace c4 - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -#ifdef C4_REDEFINE_CPPNEW -#include -void* operator new(size_t size) -{ - auto *mr = ::c4::get_memory_resource(); - return mr->allocate(size); -} -void operator delete(void *p) noexcept -{ - C4_NEVER_REACH(); -} -void operator delete(void *p, size_t size) -{ - auto *mr = ::c4::get_memory_resource(); - mr->deallocate(p, size); -} -void* operator new[](size_t size) -{ - return operator new(size); -} -void operator delete[](void *p) noexcept -{ - operator delete(p); -} -void operator delete[](void *p, size_t size) -{ - operator delete(p, size); -} -void* operator new(size_t size, std::nothrow_t) -{ - return operator new(size); -} -void operator delete(void *p, std::nothrow_t) -{ - operator delete(p); -} -void operator delete(void *p, size_t size, std::nothrow_t) -{ - operator delete(p, size); -} -void* operator new[](size_t size, std::nothrow_t) -{ - return operator new(size); -} -void operator delete[](void *p, std::nothrow_t) -{ - operator delete(p); -} -void operator delete[](void *p, size_t, std::nothrow_t) -{ - operator delete(p, size); -} -#endif // C4_REDEFINE_CPPNEW - -#endif /* C4CORE_SINGLE_HDR_DEFINE_NOW */ - - -// (end https://github.com/biojppm/c4core/src/c4/memory_resource.cpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/utf.cpp -// https://github.com/biojppm/c4core/src/c4/utf.cpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifdef C4CORE_SINGLE_HDR_DEFINE_NOW -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/utf.hpp -//#include "c4/utf.hpp" -#if !defined(C4_UTF_HPP_) && !defined(_C4_UTF_HPP_) -#error "amalgamate: file c4/utf.hpp must have been included at this point" -#endif /* C4_UTF_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/charconv.hpp -//#include "c4/charconv.hpp" -#if !defined(C4_CHARCONV_HPP_) && !defined(_C4_CHARCONV_HPP_) -#error "amalgamate: file c4/charconv.hpp must have been included at this point" -#endif /* C4_CHARCONV_HPP_ */ - - -namespace c4 { - -size_t decode_code_point(uint8_t *C4_RESTRICT buf, size_t buflen, const uint32_t code) -{ - C4_UNUSED(buflen); - C4_ASSERT(buflen >= 4); - if (code <= UINT32_C(0x7f)) - { - buf[0] = (uint8_t)code; - return 1u; - } - else if(code <= UINT32_C(0x7ff)) - { - buf[0] = (uint8_t)(UINT32_C(0xc0) | (code >> 6)); /* 110xxxxx */ - buf[1] = (uint8_t)(UINT32_C(0x80) | (code & UINT32_C(0x3f))); /* 10xxxxxx */ - return 2u; - } - else if(code <= UINT32_C(0xffff)) - { - buf[0] = (uint8_t)(UINT32_C(0xe0) | ((code >> 12))); /* 1110xxxx */ - buf[1] = (uint8_t)(UINT32_C(0x80) | ((code >> 6) & UINT32_C(0x3f))); /* 10xxxxxx */ - buf[2] = (uint8_t)(UINT32_C(0x80) | ((code ) & UINT32_C(0x3f))); /* 10xxxxxx */ - return 3u; - } - else if(code <= UINT32_C(0x10ffff)) - { - buf[0] = (uint8_t)(UINT32_C(0xf0) | ((code >> 18))); /* 11110xxx */ - buf[1] = (uint8_t)(UINT32_C(0x80) | ((code >> 12) & UINT32_C(0x3f))); /* 10xxxxxx */ - buf[2] = (uint8_t)(UINT32_C(0x80) | ((code >> 6) & UINT32_C(0x3f))); /* 10xxxxxx */ - buf[3] = (uint8_t)(UINT32_C(0x80) | ((code ) & UINT32_C(0x3f))); /* 10xxxxxx */ - return 4u; - } - return 0; -} - -substr decode_code_point(substr out, csubstr code_point) -{ - C4_ASSERT(out.len >= 4); - C4_ASSERT(!code_point.begins_with("U+")); - C4_ASSERT(!code_point.begins_with("\\x")); - C4_ASSERT(!code_point.begins_with("\\u")); - C4_ASSERT(!code_point.begins_with("\\U")); - C4_ASSERT(!code_point.begins_with('0')); - C4_ASSERT(code_point.len <= 8); - C4_ASSERT(code_point.len > 0); - uint32_t code_point_val; - C4_CHECK(read_hex(code_point, &code_point_val)); - size_t ret = decode_code_point((uint8_t*)out.str, out.len, code_point_val); - C4_ASSERT(ret <= 4); - return out.first(ret); -} - -} // namespace c4 - -#endif /* C4CORE_SINGLE_HDR_DEFINE_NOW */ - - -// (end https://github.com/biojppm/c4core/src/c4/utf.cpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/base64.cpp -// https://github.com/biojppm/c4core/src/c4/base64.cpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifdef C4CORE_SINGLE_HDR_DEFINE_NOW -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/base64.hpp -//#include "c4/base64.hpp" -#if !defined(C4_BASE64_HPP_) && !defined(_C4_BASE64_HPP_) -#error "amalgamate: file c4/base64.hpp must have been included at this point" -#endif /* C4_BASE64_HPP_ */ - - -#ifdef __clang__ -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wchar-subscripts" // array subscript is of type 'char' -#elif defined(__GNUC__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wchar-subscripts" -# pragma GCC diagnostic ignored "-Wtype-limits" -#endif - -namespace c4 { - -namespace detail { - -constexpr static const char base64_sextet_to_char_[64] = { - /* 0/ 65*/ 'A', /* 1/ 66*/ 'B', /* 2/ 67*/ 'C', /* 3/ 68*/ 'D', - /* 4/ 69*/ 'E', /* 5/ 70*/ 'F', /* 6/ 71*/ 'G', /* 7/ 72*/ 'H', - /* 8/ 73*/ 'I', /* 9/ 74*/ 'J', /*10/ 75*/ 'K', /*11/ 74*/ 'L', - /*12/ 77*/ 'M', /*13/ 78*/ 'N', /*14/ 79*/ 'O', /*15/ 78*/ 'P', - /*16/ 81*/ 'Q', /*17/ 82*/ 'R', /*18/ 83*/ 'S', /*19/ 82*/ 'T', - /*20/ 85*/ 'U', /*21/ 86*/ 'V', /*22/ 87*/ 'W', /*23/ 88*/ 'X', - /*24/ 89*/ 'Y', /*25/ 90*/ 'Z', /*26/ 97*/ 'a', /*27/ 98*/ 'b', - /*28/ 99*/ 'c', /*29/100*/ 'd', /*30/101*/ 'e', /*31/102*/ 'f', - /*32/103*/ 'g', /*33/104*/ 'h', /*34/105*/ 'i', /*35/106*/ 'j', - /*36/107*/ 'k', /*37/108*/ 'l', /*38/109*/ 'm', /*39/110*/ 'n', - /*40/111*/ 'o', /*41/112*/ 'p', /*42/113*/ 'q', /*43/114*/ 'r', - /*44/115*/ 's', /*45/116*/ 't', /*46/117*/ 'u', /*47/118*/ 'v', - /*48/119*/ 'w', /*49/120*/ 'x', /*50/121*/ 'y', /*51/122*/ 'z', - /*52/ 48*/ '0', /*53/ 49*/ '1', /*54/ 50*/ '2', /*55/ 51*/ '3', - /*56/ 52*/ '4', /*57/ 53*/ '5', /*58/ 54*/ '6', /*59/ 55*/ '7', - /*60/ 56*/ '8', /*61/ 57*/ '9', /*62/ 43*/ '+', /*63/ 47*/ '/', -}; - -// https://www.cs.cmu.edu/~pattis/15-1XX/common/handouts/ascii.html -constexpr static const char base64_char_to_sextet_[128] = { - #define __ char(-1) // undefined below - /* 0 NUL*/ __, /* 1 SOH*/ __, /* 2 STX*/ __, /* 3 ETX*/ __, - /* 4 EOT*/ __, /* 5 ENQ*/ __, /* 6 ACK*/ __, /* 7 BEL*/ __, - /* 8 BS */ __, /* 9 TAB*/ __, /* 10 LF */ __, /* 11 VT */ __, - /* 12 FF */ __, /* 13 CR */ __, /* 14 SO */ __, /* 15 SI */ __, - /* 16 DLE*/ __, /* 17 DC1*/ __, /* 18 DC2*/ __, /* 19 DC3*/ __, - /* 20 DC4*/ __, /* 21 NAK*/ __, /* 22 SYN*/ __, /* 23 ETB*/ __, - /* 24 CAN*/ __, /* 25 EM */ __, /* 26 SUB*/ __, /* 27 ESC*/ __, - /* 28 FS */ __, /* 29 GS */ __, /* 30 RS */ __, /* 31 US */ __, - /* 32 SPC*/ __, /* 33 ! */ __, /* 34 " */ __, /* 35 # */ __, - /* 36 $ */ __, /* 37 % */ __, /* 38 & */ __, /* 39 ' */ __, - /* 40 ( */ __, /* 41 ) */ __, /* 42 * */ __, /* 43 + */ 62, - /* 44 , */ __, /* 45 - */ __, /* 46 . */ __, /* 47 / */ 63, - /* 48 0 */ 52, /* 49 1 */ 53, /* 50 2 */ 54, /* 51 3 */ 55, - /* 52 4 */ 56, /* 53 5 */ 57, /* 54 6 */ 58, /* 55 7 */ 59, - /* 56 8 */ 60, /* 57 9 */ 61, /* 58 : */ __, /* 59 ; */ __, - /* 60 < */ __, /* 61 = */ __, /* 62 > */ __, /* 63 ? */ __, - /* 64 @ */ __, /* 65 A */ 0, /* 66 B */ 1, /* 67 C */ 2, - /* 68 D */ 3, /* 69 E */ 4, /* 70 F */ 5, /* 71 G */ 6, - /* 72 H */ 7, /* 73 I */ 8, /* 74 J */ 9, /* 75 K */ 10, - /* 76 L */ 11, /* 77 M */ 12, /* 78 N */ 13, /* 79 O */ 14, - /* 80 P */ 15, /* 81 Q */ 16, /* 82 R */ 17, /* 83 S */ 18, - /* 84 T */ 19, /* 85 U */ 20, /* 86 V */ 21, /* 87 W */ 22, - /* 88 X */ 23, /* 89 Y */ 24, /* 90 Z */ 25, /* 91 [ */ __, - /* 92 \ */ __, /* 93 ] */ __, /* 94 ^ */ __, /* 95 _ */ __, - /* 96 ` */ __, /* 97 a */ 26, /* 98 b */ 27, /* 99 c */ 28, - /*100 d */ 29, /*101 e */ 30, /*102 f */ 31, /*103 g */ 32, - /*104 h */ 33, /*105 i */ 34, /*106 j */ 35, /*107 k */ 36, - /*108 l */ 37, /*109 m */ 38, /*110 n */ 39, /*111 o */ 40, - /*112 p */ 41, /*113 q */ 42, /*114 r */ 43, /*115 s */ 44, - /*116 t */ 45, /*117 u */ 46, /*118 v */ 47, /*119 w */ 48, - /*120 x */ 49, /*121 y */ 50, /*122 z */ 51, /*123 { */ __, - /*124 | */ __, /*125 } */ __, /*126 ~ */ __, /*127 DEL*/ __, - #undef __ -}; - -#ifndef NDEBUG -void base64_test_tables() -{ - for(size_t i = 0; i < C4_COUNTOF(detail::base64_sextet_to_char_); ++i) - { - char s2c = base64_sextet_to_char_[i]; - char c2s = base64_char_to_sextet_[(int)s2c]; - C4_CHECK((size_t)c2s == i); - } - for(size_t i = 0; i < C4_COUNTOF(detail::base64_char_to_sextet_); ++i) - { - char c2s = base64_char_to_sextet_[i]; - if(c2s == char(-1)) - continue; - char s2c = base64_sextet_to_char_[(int)c2s]; - C4_CHECK((size_t)s2c == i); - } -} -#endif -} // namespace detail - - -bool base64_valid(csubstr encoded) -{ - if(encoded.len % 4) return false; - for(const char c : encoded) - { - if(c < 0/* || c >= 128*/) - return false; - if(c == '=') - continue; - if(detail::base64_char_to_sextet_[c] == char(-1)) - return false; - } - return true; -} - - -size_t base64_encode(substr buf, cblob data) -{ - #define c4append_(c) { if(pos < buf.len) { buf.str[pos] = (c); } ++pos; } - #define c4append_idx_(char_idx) \ - {\ - C4_XASSERT((char_idx) < sizeof(detail::base64_sextet_to_char_));\ - c4append_(detail::base64_sextet_to_char_[(char_idx)]);\ - } - - size_t rem, pos = 0; - constexpr const uint32_t sextet_mask = uint32_t(1 << 6) - 1; - const unsigned char *C4_RESTRICT d = (unsigned char *) data.buf; // cast to unsigned to avoid wrapping high-bits - for(rem = data.len; rem >= 3; rem -= 3, d += 3) - { - const uint32_t val = ((uint32_t(d[0]) << 16) | (uint32_t(d[1]) << 8) | (uint32_t(d[2]))); - c4append_idx_((val >> 18) & sextet_mask); - c4append_idx_((val >> 12) & sextet_mask); - c4append_idx_((val >> 6) & sextet_mask); - c4append_idx_((val ) & sextet_mask); - } - C4_ASSERT(rem < 3); - if(rem == 2) - { - const uint32_t val = ((uint32_t(d[0]) << 16) | (uint32_t(d[1]) << 8)); - c4append_idx_((val >> 18) & sextet_mask); - c4append_idx_((val >> 12) & sextet_mask); - c4append_idx_((val >> 6) & sextet_mask); - c4append_('='); - } - else if(rem == 1) - { - const uint32_t val = ((uint32_t(d[0]) << 16)); - c4append_idx_((val >> 18) & sextet_mask); - c4append_idx_((val >> 12) & sextet_mask); - c4append_('='); - c4append_('='); - } - return pos; - - #undef c4append_ - #undef c4append_idx_ -} - - -size_t base64_decode(csubstr encoded, blob data) -{ - #define c4append_(c) { if(wpos < data.len) { data.buf[wpos] = static_cast(c); } ++wpos; } - #define c4appendval_(c, shift)\ - {\ - C4_XASSERT(c >= 0);\ - C4_XASSERT(size_t(c) < sizeof(detail::base64_char_to_sextet_));\ - val |= static_cast(detail::base64_char_to_sextet_[(c)]) << ((shift) * 6);\ - } - - C4_ASSERT(base64_valid(encoded)); - C4_CHECK(encoded.len % 4 == 0); - size_t wpos = 0; // the write position - const char *C4_RESTRICT d = encoded.str; - constexpr const uint32_t full_byte = 0xff; - // process every quartet of input 6 bits --> triplet of output bytes - for(size_t rpos = 0; rpos < encoded.len; rpos += 4, d += 4) - { - if(d[2] == '=' || d[3] == '=') // skip the last quartet if it is padded - { - C4_ASSERT(d + 4 == encoded.str + encoded.len); - break; - } - uint32_t val = 0; - c4appendval_(d[3], 0); - c4appendval_(d[2], 1); - c4appendval_(d[1], 2); - c4appendval_(d[0], 3); - c4append_((val >> (2 * 8)) & full_byte); - c4append_((val >> (1 * 8)) & full_byte); - c4append_((val ) & full_byte); - } - // deal with the last quartet when it is padded - if(d == encoded.str + encoded.len) - return wpos; - if(d[2] == '=') // 2 padding chars - { - C4_ASSERT(d + 4 == encoded.str + encoded.len); - C4_ASSERT(d[3] == '='); - uint32_t val = 0; - c4appendval_(d[1], 2); - c4appendval_(d[0], 3); - c4append_((val >> (2 * 8)) & full_byte); - } - else if(d[3] == '=') // 1 padding char - { - C4_ASSERT(d + 4 == encoded.str + encoded.len); - uint32_t val = 0; - c4appendval_(d[2], 1); - c4appendval_(d[1], 2); - c4appendval_(d[0], 3); - c4append_((val >> (2 * 8)) & full_byte); - c4append_((val >> (1 * 8)) & full_byte); - } - return wpos; - #undef c4append_ - #undef c4appendval_ -} - -} // namespace c4 - -#ifdef __clang__ -# pragma clang diagnostic pop -#elif defined(__GNUC__) -# pragma GCC diagnostic pop -#endif - -#endif /* C4CORE_SINGLE_HDR_DEFINE_NOW */ - - -// (end https://github.com/biojppm/c4core/src/c4/base64.cpp) - -#define C4_WINDOWS_POP_HPP_ - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/windows_push.hpp -// https://github.com/biojppm/c4core/src/c4/windows_push.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_WINDOWS_PUSH_HPP_ -#define _C4_WINDOWS_PUSH_HPP_ - -/** @file windows_push.hpp sets up macros to include windows header files - * without pulling in all of - * - * @see #include windows_pop.hpp to undefine these macros - * - * @see https://aras-p.info/blog/2018/01/12/Minimizing-windows.h/ */ - - -#if defined(_WIN64) || defined(_WIN32) - -#if defined(_M_AMD64) -# ifndef _AMD64_ -# define _c4_AMD64_ -# define _AMD64_ -# endif -#elif defined(_M_IX86) -# ifndef _X86_ -# define _c4_X86_ -# define _X86_ -# endif -#elif defined(_M_ARM64) -# ifndef _ARM64_ -# define _c4_ARM64_ -# define _ARM64_ -# endif -#elif defined(_M_ARM) -# ifndef _ARM_ -# define _c4_ARM_ -# define _ARM_ -# endif -#endif - -#ifndef NOMINMAX -# define _c4_NOMINMAX -# define NOMINMAX -#endif - -#ifndef NOGDI -# define _c4_NOGDI -# define NOGDI -#endif - -#ifndef VC_EXTRALEAN -# define _c4_VC_EXTRALEAN -# define VC_EXTRALEAN -#endif - -#ifndef WIN32_LEAN_AND_MEAN -# define _c4_WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -#endif - -/* If defined, the following flags inhibit definition - * of the indicated items. - * - * NOGDICAPMASKS - CC_*, LC_*, PC_*, CP_*, TC_*, RC_ - * NOVIRTUALKEYCODES - VK_* - * NOWINMESSAGES - WM_*, EM_*, LB_*, CB_* - * NOWINSTYLES - WS_*, CS_*, ES_*, LBS_*, SBS_*, CBS_* - * NOSYSMETRICS - SM_* - * NOMENUS - MF_* - * NOICONS - IDI_* - * NOKEYSTATES - MK_* - * NOSYSCOMMANDS - SC_* - * NORASTEROPS - Binary and Tertiary raster ops - * NOSHOWWINDOW - SW_* - * OEMRESOURCE - OEM Resource values - * NOATOM - Atom Manager routines - * NOCLIPBOARD - Clipboard routines - * NOCOLOR - Screen colors - * NOCTLMGR - Control and Dialog routines - * NODRAWTEXT - DrawText() and DT_* - * NOGDI - All GDI defines and routines - * NOKERNEL - All KERNEL defines and routines - * NOUSER - All USER defines and routines - * NONLS - All NLS defines and routines - * NOMB - MB_* and MessageBox() - * NOMEMMGR - GMEM_*, LMEM_*, GHND, LHND, associated routines - * NOMETAFILE - typedef METAFILEPICT - * NOMINMAX - Macros min(a,b) and max(a,b) - * NOMSG - typedef MSG and associated routines - * NOOPENFILE - OpenFile(), OemToAnsi, AnsiToOem, and OF_* - * NOSCROLL - SB_* and scrolling routines - * NOSERVICE - All Service Controller routines, SERVICE_ equates, etc. - * NOSOUND - Sound driver routines - * NOTEXTMETRIC - typedef TEXTMETRIC and associated routines - * NOWH - SetWindowsHook and WH_* - * NOWINOFFSETS - GWL_*, GCL_*, associated routines - * NOCOMM - COMM driver routines - * NOKANJI - Kanji support stuff. - * NOHELP - Help engine interface. - * NOPROFILER - Profiler interface. - * NODEFERWINDOWPOS - DeferWindowPos routines - * NOMCX - Modem Configuration Extensions - */ - -#endif /* defined(_WIN64) || defined(_WIN32) */ - -#endif /* _C4_WINDOWS_PUSH_HPP_ */ - - -// (end https://github.com/biojppm/c4core/src/c4/windows_push.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/windows.hpp -// https://github.com/biojppm/c4core/src/c4/windows.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_WINDOWS_HPP_ -#define _C4_WINDOWS_HPP_ - -#if defined(_WIN64) || defined(_WIN32) -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/windows_push.hpp -//#include "c4/windows_push.hpp" -#if !defined(C4_WINDOWS_PUSH_HPP_) && !defined(_C4_WINDOWS_PUSH_HPP_) -#error "amalgamate: file c4/windows_push.hpp must have been included at this point" -#endif /* C4_WINDOWS_PUSH_HPP_ */ - -#include -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/windows_pop.hpp -//#include "c4/windows_pop.hpp" -#if !defined(C4_WINDOWS_POP_HPP_) && !defined(_C4_WINDOWS_POP_HPP_) -#error "amalgamate: file c4/windows_pop.hpp must have been included at this point" -#endif /* C4_WINDOWS_POP_HPP_ */ - -#endif - -#endif /* _C4_WINDOWS_HPP_ */ - - -// (end https://github.com/biojppm/c4core/src/c4/windows.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/windows_pop.hpp -// https://github.com/biojppm/c4core/src/c4/windows_pop.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_WINDOWS_POP_HPP_ -#define _C4_WINDOWS_POP_HPP_ - -#if defined(_WIN64) || defined(_WIN32) - -#ifdef _c4_AMD64_ -# undef _c4_AMD64_ -# undef _AMD64_ -#endif -#ifdef _c4_X86_ -# undef _c4_X86_ -# undef _X86_ -#endif -#ifdef _c4_ARM_ -# undef _c4_ARM_ -# undef _ARM_ -#endif - -#ifdef _c4_NOMINMAX -# undef _c4_NOMINMAX -# undef NOMINMAX -#endif - -#ifdef NOGDI -# undef _c4_NOGDI -# undef NOGDI -#endif - -#ifdef VC_EXTRALEAN -# undef _c4_VC_EXTRALEAN -# undef VC_EXTRALEAN -#endif - -#ifdef WIN32_LEAN_AND_MEAN -# undef _c4_WIN32_LEAN_AND_MEAN -# undef WIN32_LEAN_AND_MEAN -#endif - -#endif /* defined(_WIN64) || defined(_WIN32) */ - -#endif /* _C4_WINDOWS_POP_HPP_ */ - - -// (end https://github.com/biojppm/c4core/src/c4/windows_pop.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/error.cpp -// https://github.com/biojppm/c4core/src/c4/error.cpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifdef C4CORE_SINGLE_HDR_DEFINE_NOW -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/error.hpp -//#include "c4/error.hpp" -#if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) -#error "amalgamate: file c4/error.hpp must have been included at this point" -#endif /* C4_ERROR_HPP_ */ - - -//included above: -//#include -//included above: -//#include -//included above: -//#include - -#define C4_LOGF_ERR(...) fprintf(stderr, __VA_ARGS__); fflush(stderr) -#define C4_LOGF_WARN(...) fprintf(stderr, __VA_ARGS__); fflush(stderr) -#define C4_LOGP(msg, ...) printf(msg) - -#if defined(C4_XBOX) || (defined(C4_WIN) && defined(C4_MSVC)) -// amalgamate: removed include of -// https://github.com/biojppm/c4core/src/c4/windows.hpp -//# include "c4/windows.hpp" -#if !defined(C4_WINDOWS_HPP_) && !defined(_C4_WINDOWS_HPP_) -#error "amalgamate: file c4/windows.hpp must have been included at this point" -#endif /* C4_WINDOWS_HPP_ */ - -#elif defined(C4_PS4) -# include -#elif defined(C4_UNIX) || defined(C4_LINUX) -# include -//included above: -//# include -# include -#elif defined(C4_MACOS) || defined(C4_IOS) -//included above: -//# include -# include -# include -# include -#endif -// the amalgamation tool is dumb and was omitting this include under MACOS. -// So do it only once: -#if defined(C4_UNIX) || defined(C4_LINUX) || defined(C4_MACOS) || defined(C4_IOS) -# include -#endif - -#if defined(C4_EXCEPTIONS_ENABLED) && defined(C4_ERROR_THROWS_EXCEPTION) -# include -#endif - -#ifdef __clang__ -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wformat-nonliteral" -#elif defined(__GNUC__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wformat-nonliteral" -#endif - - -//----------------------------------------------------------------------------- -namespace c4 { - -static error_flags s_error_flags = ON_ERROR_DEFAULTS; -static error_callback_type s_error_callback = nullptr; - -//----------------------------------------------------------------------------- - -error_flags get_error_flags() -{ - return s_error_flags; -} -void set_error_flags(error_flags flags) -{ - s_error_flags = flags; -} - -error_callback_type get_error_callback() -{ - return s_error_callback; -} -/** Set the function which is called when an error occurs. */ -void set_error_callback(error_callback_type cb) -{ - s_error_callback = cb; -} - -//----------------------------------------------------------------------------- - -void handle_error(srcloc where, const char *fmt, ...) -{ - char buf[1024]; - size_t msglen = 0; - if(s_error_flags & (ON_ERROR_LOG|ON_ERROR_CALLBACK)) - { - va_list args; - va_start(args, fmt); - int ilen = vsnprintf(buf, sizeof(buf), fmt, args); // ss.vprintf(fmt, args); - va_end(args); - msglen = ilen >= 0 && ilen < (int)sizeof(buf) ? static_cast(ilen) : sizeof(buf)-1; - } - - if(s_error_flags & ON_ERROR_LOG) - { - C4_LOGF_ERR("\n"); -#if defined(C4_ERROR_SHOWS_FILELINE) && defined(C4_ERROR_SHOWS_FUNC) - C4_LOGF_ERR("%s:%d: ERROR: %s\n", where.file, where.line, buf); - C4_LOGF_ERR("%s:%d: ERROR here: %s\n", where.file, where.line, where.func); -#elif defined(C4_ERROR_SHOWS_FILELINE) - C4_LOGF_ERR("%s:%d: ERROR: %s\n", where.file, where.line, buf); -#elif ! defined(C4_ERROR_SHOWS_FUNC) - C4_LOGF_ERR("ERROR: %s\n", buf); -#endif - } - - if(s_error_flags & ON_ERROR_CALLBACK) - { - if(s_error_callback) - { - s_error_callback(buf, msglen/*ss.c_strp(), ss.tellp()*/); - } - } - - if(s_error_flags & ON_ERROR_ABORT) - { - abort(); - } - - if(s_error_flags & ON_ERROR_THROW) - { -#if defined(C4_EXCEPTIONS_ENABLED) && defined(C4_ERROR_THROWS_EXCEPTION) - throw Exception(buf); -#else - abort(); -#endif - } -} - -//----------------------------------------------------------------------------- - -void handle_warning(srcloc where, const char *fmt, ...) -{ - va_list args; - char buf[1024]; //sstream ss; - va_start(args, fmt); - vsnprintf(buf, sizeof(buf), fmt, args); - va_end(args); - C4_LOGF_WARN("\n"); -#if defined(C4_ERROR_SHOWS_FILELINE) && defined(C4_ERROR_SHOWS_FUNC) - C4_LOGF_WARN("%s:%d: WARNING: %s\n", where.file, where.line, buf/*ss.c_strp()*/); - C4_LOGF_WARN("%s:%d: WARNING: here: %s\n", where.file, where.line, where.func); -#elif defined(C4_ERROR_SHOWS_FILELINE) - C4_LOGF_WARN("%s:%d: WARNING: %s\n", where.file, where.line, buf/*ss.c_strp()*/); -#elif ! defined(C4_ERROR_SHOWS_FUNC) - C4_LOGF_WARN("WARNING: %s\n", buf/*ss.c_strp()*/); -#endif - //c4::log.flush(); -} - -//----------------------------------------------------------------------------- -bool is_debugger_attached() -{ -#if defined(C4_UNIX) || defined(C4_LINUX) - static bool first_call = true; - static bool first_call_result = false; - if(first_call) - { - first_call = false; - //! @see http://stackoverflow.com/questions/3596781/how-to-detect-if-the-current-process-is-being-run-by-gdb - //! (this answer: http://stackoverflow.com/a/24969863/3968589 ) - char buf[1024] = ""; - - int status_fd = open("/proc/self/status", O_RDONLY); - if (status_fd == -1) - { - return 0; - } - - ssize_t num_read = ::read(status_fd, buf, sizeof(buf)); - - if (num_read > 0) - { - static const char TracerPid[] = "TracerPid:"; - char *tracer_pid; - - if(num_read < 1024) - { - buf[num_read] = 0; - } - tracer_pid = strstr(buf, TracerPid); - if (tracer_pid) - { - first_call_result = !!::atoi(tracer_pid + sizeof(TracerPid) - 1); - } - } - } - return first_call_result; -#elif defined(C4_PS4) - return (sceDbgIsDebuggerAttached() != 0); -#elif defined(C4_XBOX) || (defined(C4_WIN) && defined(C4_MSVC)) - return IsDebuggerPresent() != 0; -#elif defined(C4_MACOS) || defined(C4_IOS) - // https://stackoverflow.com/questions/2200277/detecting-debugger-on-mac-os-x - // Returns true if the current process is being debugged (either - // running under the debugger or has a debugger attached post facto). - int junk; - int mib[4]; - struct kinfo_proc info; - size_t size; - - // Initialize the flags so that, if sysctl fails for some bizarre - // reason, we get a predictable result. - - info.kp_proc.p_flag = 0; - - // Initialize mib, which tells sysctl the info we want, in this case - // we're looking for information about a specific process ID. - - mib[0] = CTL_KERN; - mib[1] = KERN_PROC; - mib[2] = KERN_PROC_PID; - mib[3] = getpid(); - - // Call sysctl. - - size = sizeof(info); - junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0); - assert(junk == 0); - - // We're being debugged if the P_TRACED flag is set. - return ((info.kp_proc.p_flag & P_TRACED) != 0); -#else - return false; -#endif -} // is_debugger_attached() - -} // namespace c4 - - -#ifdef __clang__ -# pragma clang diagnostic pop -#elif defined(__GNUC__) -# pragma GCC diagnostic pop -#endif - -#endif /* C4CORE_SINGLE_HDR_DEFINE_NOW */ - - -// (end https://github.com/biojppm/c4core/src/c4/error.cpp) - -#endif /* _C4CORE_SINGLE_HEADER_AMALGAMATED_HPP_ */ - - - -// (end https://github.com/biojppm/rapidyaml/src/c4/c4core_all.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/yml/export.hpp -// https://github.com/biojppm/rapidyaml/src/c4/yml/export.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef C4_YML_EXPORT_HPP_ -#define C4_YML_EXPORT_HPP_ - -#ifdef _WIN32 - #ifdef RYML_SHARED - #ifdef RYML_EXPORTS - #define RYML_EXPORT __declspec(dllexport) - #else - #define RYML_EXPORT __declspec(dllimport) - #endif - #else - #define RYML_EXPORT - #endif -#else - #define RYML_EXPORT -#endif - -#endif /* C4_YML_EXPORT_HPP_ */ - - -// (end https://github.com/biojppm/rapidyaml/src/c4/yml/export.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/yml/common.hpp -// https://github.com/biojppm/rapidyaml/src/c4/yml/common.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_YML_COMMON_HPP_ -#define _C4_YML_COMMON_HPP_ - -//included above: -//#include -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/substr.hpp -//#include -#if !defined(C4_SUBSTR_HPP_) && !defined(_C4_SUBSTR_HPP_) -#error "amalgamate: file c4/substr.hpp must have been included at this point" -#endif /* C4_SUBSTR_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/yml/export.hpp -//#include -#if !defined(C4_YML_EXPORT_HPP_) && !defined(_C4_YML_EXPORT_HPP_) -#error "amalgamate: file c4/yml/export.hpp must have been included at this point" -#endif /* C4_YML_EXPORT_HPP_ */ - - - -#ifndef RYML_USE_ASSERT -# define RYML_USE_ASSERT C4_USE_ASSERT -#endif - - -#if RYML_USE_ASSERT -# define RYML_ASSERT(cond) RYML_CHECK(cond) -# define RYML_ASSERT_MSG(cond, msg) RYML_CHECK_MSG(cond, msg) -#else -# define RYML_ASSERT(cond) -# define RYML_ASSERT_MSG(cond, msg) -#endif - - -#if defined(NDEBUG) || defined(C4_NO_DEBUG_BREAK) -# define RYML_DEBUG_BREAK() -#else -# define RYML_DEBUG_BREAK() \ - { \ - if(c4::get_error_flags() & c4::ON_ERROR_DEBUGBREAK) \ - { \ - C4_DEBUG_BREAK(); \ - } \ - } -#endif - - -#define RYML_CHECK(cond) \ - do { \ - if(!(cond)) \ - { \ - RYML_DEBUG_BREAK() \ - c4::yml::error("check failed: " #cond, c4::yml::Location(__FILE__, __LINE__, 0)); \ - } \ - } while(0) - -#define RYML_CHECK_MSG(cond, msg) \ - do \ - { \ - if(!(cond)) \ - { \ - RYML_DEBUG_BREAK() \ - c4::yml::error(msg ": check failed: " #cond, c4::yml::Location(__FILE__, __LINE__, 0)); \ - } \ - } while(0) - - -#if C4_CPP >= 14 -# define RYML_DEPRECATED(msg) [[deprecated(msg)]] -#else -# if defined(_MSC_VER) -# define RYML_DEPRECATED(msg) __declspec(deprecated(msg)) -# else // defined(__GNUC__) || defined(__clang__) -# define RYML_DEPRECATED(msg) __attribute__((deprecated(msg))) -# endif -#endif - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -namespace c4 { -namespace yml { - -enum : size_t { - /** a null position */ - npos = size_t(-1), - /** an index to none */ - NONE = size_t(-1) -}; - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -//! holds a position into a source buffer -struct RYML_EXPORT LineCol -{ - //! number of bytes from the beginning of the source buffer - size_t offset; - //! line - size_t line; - //! column - size_t col; - - LineCol() : offset(), line(), col() {} - //! construct from line and column - LineCol(size_t l, size_t c) : offset(0), line(l), col(c) {} - //! construct from offset, line and column - LineCol(size_t o, size_t l, size_t c) : offset(o), line(l), col(c) {} -}; - - -//! a source file position -struct RYML_EXPORT Location : public LineCol -{ - csubstr name; - - operator bool () const { return !name.empty() || line != 0 || offset != 0; } - - Location() : LineCol(), name() {} - Location( size_t l, size_t c) : LineCol{ l, c}, name( ) {} - Location( csubstr n, size_t l, size_t c) : LineCol{ l, c}, name(n) {} - Location( csubstr n, size_t b, size_t l, size_t c) : LineCol{b, l, c}, name(n) {} - Location(const char *n, size_t l, size_t c) : LineCol{ l, c}, name(to_csubstr(n)) {} - Location(const char *n, size_t b, size_t l, size_t c) : LineCol{b, l, c}, name(to_csubstr(n)) {} -}; - - -//----------------------------------------------------------------------------- - -/** the type of the function used to report errors. This function must - * interrupt execution, either by raising an exception or calling - * std::abort(). - * - * @warning the error callback must never return: it must either abort - * or throw an exception. Otherwise, the parser will enter into an - * infinite loop, or the program may crash. */ -using pfn_error = void (*)(const char* msg, size_t msg_len, Location location, void *user_data); -/** the type of the function used to allocate memory */ -using pfn_allocate = void* (*)(size_t len, void* hint, void *user_data); -/** the type of the function used to free memory */ -using pfn_free = void (*)(void* mem, size_t size, void *user_data); - -/** trigger an error: call the current error callback. */ -RYML_EXPORT void error(const char *msg, size_t msg_len, Location loc); -/** @overload error */ -inline void error(const char *msg, size_t msg_len) -{ - error(msg, msg_len, Location{}); -} -/** @overload error */ -template -inline void error(const char (&msg)[N], Location loc) -{ - error(msg, N-1, loc); -} -/** @overload error */ -template -inline void error(const char (&msg)[N]) -{ - error(msg, N-1, Location{}); -} - -//----------------------------------------------------------------------------- - -/** a c-style callbacks class - * - * @warning the error callback must never return: it must either abort - * or throw an exception. Otherwise, the parser will enter into an - * infinite loop, or the program may crash. */ -struct RYML_EXPORT Callbacks -{ - void * m_user_data; - pfn_allocate m_allocate; - pfn_free m_free; - pfn_error m_error; - - Callbacks(); - Callbacks(void *user_data, pfn_allocate alloc, pfn_free free, pfn_error error_); - - bool operator!= (Callbacks const& that) const { return !operator==(that); } - bool operator== (Callbacks const& that) const - { - return (m_user_data == that.m_user_data && - m_allocate == that.m_allocate && - m_free == that.m_free && - m_error == that.m_error); - } -}; - -/** set the global callbacks. - * - * @warning the error callback must never return: it must either abort - * or throw an exception. Otherwise, the parser will enter into an - * infinite loop, or the program may crash. */ -RYML_EXPORT void set_callbacks(Callbacks const& c); -/// get the global callbacks -RYML_EXPORT Callbacks const& get_callbacks(); -/// set the global callbacks back to their defaults -RYML_EXPORT void reset_callbacks(); - -/// @cond dev -#define _RYML_CB_ERR(cb, msg_literal) \ -do \ -{ \ - const char msg[] = msg_literal; \ - RYML_DEBUG_BREAK() \ - (cb).m_error(msg, sizeof(msg), c4::yml::Location(__FILE__, 0, __LINE__, 0), (cb).m_user_data); \ -} while(0) -#define _RYML_CB_CHECK(cb, cond) \ - do \ - { \ - if(!(cond)) \ - { \ - const char msg[] = "check failed: " #cond; \ - RYML_DEBUG_BREAK() \ - (cb).m_error(msg, sizeof(msg), c4::yml::Location(__FILE__, 0, __LINE__, 0), (cb).m_user_data); \ - } \ - } while(0) -#ifdef RYML_USE_ASSERT -#define _RYML_CB_ASSERT(cb, cond) _RYML_CB_CHECK((cb), (cond)) -#else -#define _RYML_CB_ASSERT(cb, cond) do {} while(0) -#endif -#define _RYML_CB_ALLOC_HINT(cb, T, num, hint) (T*) (cb).m_allocate((num) * sizeof(T), (hint), (cb).m_user_data) -#define _RYML_CB_ALLOC(cb, T, num) _RYML_CB_ALLOC_HINT((cb), (T), (num), nullptr) -#define _RYML_CB_FREE(cb, buf, T, num) \ - do { \ - (cb).m_free((buf), (num) * sizeof(T), (cb).m_user_data); \ - (buf) = nullptr; \ - } while(0) - - - -namespace detail { -template -struct _charconstant_t - : public std::conditional::value, - std::integral_constant, - std::integral_constant>::type -{}; -#define _RYML_CHCONST(signedval, unsignedval) ::c4::yml::detail::_charconstant_t::value -} // namespace detail - - -namespace detail { -struct _SubstrWriter -{ - substr buf; - size_t pos; - _SubstrWriter(substr buf_, size_t pos_=0) : buf(buf_), pos(pos_) {} - void append(csubstr s) - { - C4_ASSERT(!s.overlaps(buf)); - if(pos + s.len <= buf.len) - memcpy(buf.str + pos, s.str, s.len); - pos += s.len; - } - void append(char c) - { - if(pos < buf.len) - buf.str[pos] = c; - ++pos; - } - void append_n(char c, size_t numtimes) - { - if(pos + numtimes < buf.len) - memset(buf.str + pos, c, numtimes); - pos += numtimes; - } - size_t slack() const { return pos <= buf.len ? buf.len - pos : 0; } - size_t excess() const { return pos > buf.len ? pos - buf.len : 0; } - //! get the part written so far - csubstr curr() const { return pos <= buf.len ? buf.first(pos) : buf; } - //! get the part that is still free to write to (the remainder) - substr rem() { return pos < buf.len ? buf.sub(pos) : buf.last(0); } - - size_t advance(size_t more) { pos += more; return pos; } -}; -} // namespace detail - -/// @endcond - -} // namespace yml -} // namespace c4 - -#endif /* _C4_YML_COMMON_HPP_ */ - - -// (end https://github.com/biojppm/rapidyaml/src/c4/yml/common.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/yml/tree.hpp -// https://github.com/biojppm/rapidyaml/src/c4/yml/tree.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_YML_TREE_HPP_ -#define _C4_YML_TREE_HPP_ - - -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/error.hpp -//#include "c4/error.hpp" -#if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) -#error "amalgamate: file c4/error.hpp must have been included at this point" -#endif /* C4_ERROR_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/types.hpp -//#include "c4/types.hpp" -#if !defined(C4_TYPES_HPP_) && !defined(_C4_TYPES_HPP_) -#error "amalgamate: file c4/types.hpp must have been included at this point" -#endif /* C4_TYPES_HPP_ */ - -#ifndef _C4_YML_COMMON_HPP_ -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/yml/common.hpp -//#include "c4/yml/common.hpp" -#if !defined(C4_YML_COMMON_HPP_) && !defined(_C4_YML_COMMON_HPP_) -#error "amalgamate: file c4/yml/common.hpp must have been included at this point" -#endif /* C4_YML_COMMON_HPP_ */ - -#endif - -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/charconv.hpp -//#include -#if !defined(C4_CHARCONV_HPP_) && !defined(_C4_CHARCONV_HPP_) -#error "amalgamate: file c4/charconv.hpp must have been included at this point" -#endif /* C4_CHARCONV_HPP_ */ - -//included above: -//#include -//included above: -//#include - - -C4_SUPPRESS_WARNING_MSVC_PUSH -C4_SUPPRESS_WARNING_MSVC(4251) // needs to have dll-interface to be used by clients of struct -C4_SUPPRESS_WARNING_MSVC(4296) // expression is always 'boolean_value' -C4_SUPPRESS_WARNING_GCC_CLANG_PUSH -C4_SUPPRESS_WARNING_GCC("-Wtype-limits") - - -namespace c4 { -namespace yml { - -struct NodeScalar; -struct NodeInit; -struct NodeData; -class NodeRef; -class ConstNodeRef; -class Tree; - - -/** encode a floating point value to a string. */ -template -size_t to_chars_float(substr buf, T val) -{ - C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wfloat-equal"); - static_assert(std::is_floating_point::value, "must be floating point"); - if(C4_UNLIKELY(std::isnan(val))) - return to_chars(buf, csubstr(".nan")); - else if(C4_UNLIKELY(val == std::numeric_limits::infinity())) - return to_chars(buf, csubstr(".inf")); - else if(C4_UNLIKELY(val == -std::numeric_limits::infinity())) - return to_chars(buf, csubstr("-.inf")); - return to_chars(buf, val); - C4_SUPPRESS_WARNING_GCC_CLANG_POP -} - - -/** decode a floating point from string. Accepts special values: .nan, - * .inf, -.inf */ -template -bool from_chars_float(csubstr buf, T *C4_RESTRICT val) -{ - static_assert(std::is_floating_point::value, "must be floating point"); - if(C4_LIKELY(from_chars(buf, val))) - { - return true; - } - else if(C4_UNLIKELY(buf == ".nan" || buf == ".NaN" || buf == ".NAN")) - { - *val = std::numeric_limits::quiet_NaN(); - return true; - } - else if(C4_UNLIKELY(buf == ".inf" || buf == ".Inf" || buf == ".INF")) - { - *val = std::numeric_limits::infinity(); - return true; - } - else if(C4_UNLIKELY(buf == "-.inf" || buf == "-.Inf" || buf == "-.INF")) - { - *val = -std::numeric_limits::infinity(); - return true; - } - else - { - return false; - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -/** the integral type necessary to cover all the bits marking node tags */ -using tag_bits = uint16_t; - -/** a bit mask for marking tags for types */ -typedef enum : tag_bits { - // container types - TAG_NONE = 0, - TAG_MAP = 1, /**< !!map Unordered set of key: value pairs without duplicates. @see https://yaml.org/type/map.html */ - TAG_OMAP = 2, /**< !!omap Ordered sequence of key: value pairs without duplicates. @see https://yaml.org/type/omap.html */ - TAG_PAIRS = 3, /**< !!pairs Ordered sequence of key: value pairs allowing duplicates. @see https://yaml.org/type/pairs.html */ - TAG_SET = 4, /**< !!set Unordered set of non-equal values. @see https://yaml.org/type/set.html */ - TAG_SEQ = 5, /**< !!seq Sequence of arbitrary values. @see https://yaml.org/type/seq.html */ - // scalar types - TAG_BINARY = 6, /**< !!binary A sequence of zero or more octets (8 bit values). @see https://yaml.org/type/binary.html */ - TAG_BOOL = 7, /**< !!bool Mathematical Booleans. @see https://yaml.org/type/bool.html */ - TAG_FLOAT = 8, /**< !!float Floating-point approximation to real numbers. https://yaml.org/type/float.html */ - TAG_INT = 9, /**< !!float Mathematical integers. https://yaml.org/type/int.html */ - TAG_MERGE = 10, /**< !!merge Specify one or more mapping to be merged with the current one. https://yaml.org/type/merge.html */ - TAG_NULL = 11, /**< !!null Devoid of value. https://yaml.org/type/null.html */ - TAG_STR = 12, /**< !!str A sequence of zero or more Unicode characters. https://yaml.org/type/str.html */ - TAG_TIMESTAMP = 13, /**< !!timestamp A point in time https://yaml.org/type/timestamp.html */ - TAG_VALUE = 14, /**< !!value Specify the default value of a mapping https://yaml.org/type/value.html */ - TAG_YAML = 15, /**< !!yaml Specify the default value of a mapping https://yaml.org/type/yaml.html */ -} YamlTag_e; - -YamlTag_e to_tag(csubstr tag); -csubstr from_tag(YamlTag_e tag); -csubstr from_tag_long(YamlTag_e tag); -csubstr normalize_tag(csubstr tag); -csubstr normalize_tag_long(csubstr tag); - -struct TagDirective -{ - /** Eg `!e!` in `%TAG !e! tag:example.com,2000:app/` */ - csubstr handle; - /** Eg `tag:example.com,2000:app/` in `%TAG !e! tag:example.com,2000:app/` */ - csubstr prefix; - /** The next node to which this tag directive applies */ - size_t next_node_id; -}; - -#ifndef RYML_MAX_TAG_DIRECTIVES -/** the maximum number of tag directives in a Tree */ -#define RYML_MAX_TAG_DIRECTIVES 4 -#endif - - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - - -/** the integral type necessary to cover all the bits marking node types */ -using type_bits = uint64_t; - - -/** a bit mask for marking node types */ -typedef enum : type_bits { - // a convenience define, undefined below - #define c4bit(v) (type_bits(1) << v) - NOTYPE = 0, ///< no node type is set - VAL = c4bit(0), ///< a leaf node, has a (possibly empty) value - KEY = c4bit(1), ///< is member of a map, must have non-empty key - MAP = c4bit(2), ///< a map: a parent of keyvals - SEQ = c4bit(3), ///< a seq: a parent of vals - DOC = c4bit(4), ///< a document - STREAM = c4bit(5)|SEQ, ///< a stream: a seq of docs - KEYREF = c4bit(6), ///< a *reference: the key references an &anchor - VALREF = c4bit(7), ///< a *reference: the val references an &anchor - KEYANCH = c4bit(8), ///< the key has an &anchor - VALANCH = c4bit(9), ///< the val has an &anchor - KEYTAG = c4bit(10), ///< the key has an explicit tag/type - VALTAG = c4bit(11), ///< the val has an explicit tag/type - _TYMASK = c4bit(12)-1, // all the bits up to here - VALQUO = c4bit(12), ///< the val is quoted by '', "", > or | - KEYQUO = c4bit(13), ///< the key is quoted by '', "", > or | - KEYVAL = KEY|VAL, - KEYSEQ = KEY|SEQ, - KEYMAP = KEY|MAP, - DOCMAP = DOC|MAP, - DOCSEQ = DOC|SEQ, - DOCVAL = DOC|VAL, - _KEYMASK = KEY | KEYQUO | KEYANCH | KEYREF | KEYTAG, - _VALMASK = VAL | VALQUO | VALANCH | VALREF | VALTAG, - // these flags are from a work in progress and should not be used yet - _WIP_STYLE_FLOW_SL = c4bit(14), ///< mark container with single-line flow format (seqs as '[val1,val2], maps as '{key: val, key2: val2}') - _WIP_STYLE_FLOW_ML = c4bit(15), ///< mark container with multi-line flow format (seqs as '[val1,\nval2], maps as '{key: val,\nkey2: val2}') - _WIP_STYLE_BLOCK = c4bit(16), ///< mark container with block format (seqs as '- val\n', maps as 'key: val') - _WIP_KEY_LITERAL = c4bit(17), ///< mark key scalar as multiline, block literal | - _WIP_VAL_LITERAL = c4bit(18), ///< mark val scalar as multiline, block literal | - _WIP_KEY_FOLDED = c4bit(19), ///< mark key scalar as multiline, block folded > - _WIP_VAL_FOLDED = c4bit(20), ///< mark val scalar as multiline, block folded > - _WIP_KEY_SQUO = c4bit(21), ///< mark key scalar as single quoted - _WIP_VAL_SQUO = c4bit(22), ///< mark val scalar as single quoted - _WIP_KEY_DQUO = c4bit(23), ///< mark key scalar as double quoted - _WIP_VAL_DQUO = c4bit(24), ///< mark val scalar as double quoted - _WIP_KEY_PLAIN = c4bit(25), ///< mark key scalar as plain scalar (unquoted, even when multiline) - _WIP_VAL_PLAIN = c4bit(26), ///< mark val scalar as plain scalar (unquoted, even when multiline) - _WIP_KEY_STYLE = _WIP_KEY_LITERAL|_WIP_KEY_FOLDED|_WIP_KEY_SQUO|_WIP_KEY_DQUO|_WIP_KEY_PLAIN, - _WIP_VAL_STYLE = _WIP_VAL_LITERAL|_WIP_VAL_FOLDED|_WIP_VAL_SQUO|_WIP_VAL_DQUO|_WIP_VAL_PLAIN, - _WIP_KEY_FT_NL = c4bit(27), ///< features: mark key scalar as having \n in its contents - _WIP_VAL_FT_NL = c4bit(28), ///< features: mark val scalar as having \n in its contents - _WIP_KEY_FT_SQ = c4bit(29), ///< features: mark key scalar as having single quotes in its contents - _WIP_VAL_FT_SQ = c4bit(30), ///< features: mark val scalar as having single quotes in its contents - _WIP_KEY_FT_DQ = c4bit(31), ///< features: mark key scalar as having double quotes in its contents - _WIP_VAL_FT_DQ = c4bit(32), ///< features: mark val scalar as having double quotes in its contents - #undef c4bit -} NodeType_e; - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -/** wraps a NodeType_e element with some syntactic sugar and predicates */ -struct NodeType -{ -public: - - NodeType_e type; - -public: - - C4_ALWAYS_INLINE NodeType() : type(NOTYPE) {} - C4_ALWAYS_INLINE NodeType(NodeType_e t) : type(t) {} - C4_ALWAYS_INLINE NodeType(type_bits t) : type((NodeType_e)t) {} - - C4_ALWAYS_INLINE const char *type_str() const { return type_str(type); } - static const char* type_str(NodeType_e t); - - C4_ALWAYS_INLINE void set(NodeType_e t) { type = t; } - C4_ALWAYS_INLINE void set(type_bits t) { type = (NodeType_e)t; } - - C4_ALWAYS_INLINE void add(NodeType_e t) { type = (NodeType_e)(type|t); } - C4_ALWAYS_INLINE void add(type_bits t) { type = (NodeType_e)(type|t); } - - C4_ALWAYS_INLINE void rem(NodeType_e t) { type = (NodeType_e)(type & ~t); } - C4_ALWAYS_INLINE void rem(type_bits t) { type = (NodeType_e)(type & ~t); } - - C4_ALWAYS_INLINE void clear() { type = NOTYPE; } - -public: - - C4_ALWAYS_INLINE operator NodeType_e & C4_RESTRICT () { return type; } - C4_ALWAYS_INLINE operator NodeType_e const& C4_RESTRICT () const { return type; } - - C4_ALWAYS_INLINE bool operator== (NodeType_e t) const { return type == t; } - C4_ALWAYS_INLINE bool operator!= (NodeType_e t) const { return type != t; } - -public: - - #if defined(__clang__) - # pragma clang diagnostic push - # pragma clang diagnostic ignored "-Wnull-dereference" - #elif defined(__GNUC__) - # pragma GCC diagnostic push - # if __GNUC__ >= 6 - # pragma GCC diagnostic ignored "-Wnull-dereference" - # endif - #endif - - C4_ALWAYS_INLINE bool is_notype() const { return type == NOTYPE; } - C4_ALWAYS_INLINE bool is_stream() const { return ((type & STREAM) == STREAM) != 0; } - C4_ALWAYS_INLINE bool is_doc() const { return (type & DOC) != 0; } - C4_ALWAYS_INLINE bool is_container() const { return (type & (MAP|SEQ|STREAM)) != 0; } - C4_ALWAYS_INLINE bool is_map() const { return (type & MAP) != 0; } - C4_ALWAYS_INLINE bool is_seq() const { return (type & SEQ) != 0; } - C4_ALWAYS_INLINE bool has_key() const { return (type & KEY) != 0; } - C4_ALWAYS_INLINE bool has_val() const { return (type & VAL) != 0; } - C4_ALWAYS_INLINE bool is_val() const { return (type & KEYVAL) == VAL; } - C4_ALWAYS_INLINE bool is_keyval() const { return (type & KEYVAL) == KEYVAL; } - C4_ALWAYS_INLINE bool has_key_tag() const { return (type & (KEY|KEYTAG)) == (KEY|KEYTAG); } - C4_ALWAYS_INLINE bool has_val_tag() const { return ((type & VALTAG) && (type & (VAL|MAP|SEQ))); } - C4_ALWAYS_INLINE bool has_key_anchor() const { return (type & (KEY|KEYANCH)) == (KEY|KEYANCH); } - C4_ALWAYS_INLINE bool is_key_anchor() const { return (type & (KEY|KEYANCH)) == (KEY|KEYANCH); } - C4_ALWAYS_INLINE bool has_val_anchor() const { return (type & VALANCH) != 0 && (type & (VAL|SEQ|MAP)) != 0; } - C4_ALWAYS_INLINE bool is_val_anchor() const { return (type & VALANCH) != 0 && (type & (VAL|SEQ|MAP)) != 0; } - C4_ALWAYS_INLINE bool has_anchor() const { return (type & (KEYANCH|VALANCH)) != 0; } - C4_ALWAYS_INLINE bool is_anchor() const { return (type & (KEYANCH|VALANCH)) != 0; } - C4_ALWAYS_INLINE bool is_key_ref() const { return (type & KEYREF) != 0; } - C4_ALWAYS_INLINE bool is_val_ref() const { return (type & VALREF) != 0; } - C4_ALWAYS_INLINE bool is_ref() const { return (type & (KEYREF|VALREF)) != 0; } - C4_ALWAYS_INLINE bool is_anchor_or_ref() const { return (type & (KEYANCH|VALANCH|KEYREF|VALREF)) != 0; } - C4_ALWAYS_INLINE bool is_key_quoted() const { return (type & (KEY|KEYQUO)) == (KEY|KEYQUO); } - C4_ALWAYS_INLINE bool is_val_quoted() const { return (type & (VAL|VALQUO)) == (VAL|VALQUO); } - C4_ALWAYS_INLINE bool is_quoted() const { return (type & (KEY|KEYQUO)) == (KEY|KEYQUO) || (type & (VAL|VALQUO)) == (VAL|VALQUO); } - - // these predicates are a work in progress and subject to change. Don't use yet. - C4_ALWAYS_INLINE bool default_block() const { return (type & (_WIP_STYLE_BLOCK|_WIP_STYLE_FLOW_ML|_WIP_STYLE_FLOW_SL)) == 0; } - C4_ALWAYS_INLINE bool marked_block() const { return (type & (_WIP_STYLE_BLOCK)) != 0; } - C4_ALWAYS_INLINE bool marked_flow_sl() const { return (type & (_WIP_STYLE_FLOW_SL)) != 0; } - C4_ALWAYS_INLINE bool marked_flow_ml() const { return (type & (_WIP_STYLE_FLOW_ML)) != 0; } - C4_ALWAYS_INLINE bool marked_flow() const { return (type & (_WIP_STYLE_FLOW_ML|_WIP_STYLE_FLOW_SL)) != 0; } - C4_ALWAYS_INLINE bool key_marked_literal() const { return (type & (_WIP_KEY_LITERAL)) != 0; } - C4_ALWAYS_INLINE bool val_marked_literal() const { return (type & (_WIP_VAL_LITERAL)) != 0; } - C4_ALWAYS_INLINE bool key_marked_folded() const { return (type & (_WIP_KEY_FOLDED)) != 0; } - C4_ALWAYS_INLINE bool val_marked_folded() const { return (type & (_WIP_VAL_FOLDED)) != 0; } - C4_ALWAYS_INLINE bool key_marked_squo() const { return (type & (_WIP_KEY_SQUO)) != 0; } - C4_ALWAYS_INLINE bool val_marked_squo() const { return (type & (_WIP_VAL_SQUO)) != 0; } - C4_ALWAYS_INLINE bool key_marked_dquo() const { return (type & (_WIP_KEY_DQUO)) != 0; } - C4_ALWAYS_INLINE bool val_marked_dquo() const { return (type & (_WIP_VAL_DQUO)) != 0; } - C4_ALWAYS_INLINE bool key_marked_plain() const { return (type & (_WIP_KEY_PLAIN)) != 0; } - C4_ALWAYS_INLINE bool val_marked_plain() const { return (type & (_WIP_VAL_PLAIN)) != 0; } - - #if defined(__clang__) - # pragma clang diagnostic pop - #elif defined(__GNUC__) - # pragma GCC diagnostic pop - #endif - -}; - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -/** a node scalar is a csubstr, which may be tagged and anchored. */ -struct NodeScalar -{ - csubstr tag; - csubstr scalar; - csubstr anchor; - -public: - - /// initialize as an empty scalar - inline NodeScalar() noexcept : tag(), scalar(), anchor() {} - - /// initialize as an untagged scalar - template - inline NodeScalar(const char (&s)[N]) noexcept : tag(), scalar(s), anchor() {} - inline NodeScalar(csubstr s ) noexcept : tag(), scalar(s), anchor() {} - - /// initialize as a tagged scalar - template - inline NodeScalar(const char (&t)[N], const char (&s)[N]) noexcept : tag(t), scalar(s), anchor() {} - inline NodeScalar(csubstr t , csubstr s ) noexcept : tag(t), scalar(s), anchor() {} - -public: - - ~NodeScalar() noexcept = default; - NodeScalar(NodeScalar &&) noexcept = default; - NodeScalar(NodeScalar const&) noexcept = default; - NodeScalar& operator= (NodeScalar &&) noexcept = default; - NodeScalar& operator= (NodeScalar const&) noexcept = default; - -public: - - bool empty() const noexcept { return tag.empty() && scalar.empty() && anchor.empty(); } - - void clear() noexcept { tag.clear(); scalar.clear(); anchor.clear(); } - - void set_ref_maybe_replacing_scalar(csubstr ref, bool has_scalar) noexcept - { - csubstr trimmed = ref.begins_with('*') ? ref.sub(1) : ref; - anchor = trimmed; - if((!has_scalar) || !scalar.ends_with(trimmed)) - scalar = ref; - } -}; -C4_MUST_BE_TRIVIAL_COPY(NodeScalar); - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -/** convenience class to initialize nodes */ -struct NodeInit -{ - - NodeType type; - NodeScalar key; - NodeScalar val; - -public: - - /// initialize as an empty node - NodeInit() : type(NOTYPE), key(), val() {} - /// initialize as a typed node - NodeInit(NodeType_e t) : type(t), key(), val() {} - /// initialize as a sequence member - NodeInit(NodeScalar const& v) : type(VAL), key(), val(v) { _add_flags(); } - /// initialize as a mapping member - NodeInit( NodeScalar const& k, NodeScalar const& v) : type(KEYVAL), key(k.tag, k.scalar), val(v.tag, v.scalar) { _add_flags(); } - /// initialize as a mapping member with explicit type - NodeInit(NodeType_e t, NodeScalar const& k, NodeScalar const& v) : type(t ), key(k.tag, k.scalar), val(v.tag, v.scalar) { _add_flags(); } - /// initialize as a mapping member with explicit type (eg SEQ or MAP) - NodeInit(NodeType_e t, NodeScalar const& k ) : type(t ), key(k.tag, k.scalar), val( ) { _add_flags(KEY); } - -public: - - void clear() - { - type.clear(); - key.clear(); - val.clear(); - } - - void _add_flags(type_bits more_flags=0) - { - type = (type|more_flags); - if( ! key.tag.empty()) - type = (type|KEYTAG); - if( ! val.tag.empty()) - type = (type|VALTAG); - if( ! key.anchor.empty()) - type = (type|KEYANCH); - if( ! val.anchor.empty()) - type = (type|VALANCH); - } - - bool _check() const - { - // key cannot be empty - RYML_ASSERT(key.scalar.empty() == ((type & KEY) == 0)); - // key tag cannot be empty - RYML_ASSERT(key.tag.empty() == ((type & KEYTAG) == 0)); - // val may be empty even though VAL is set. But when VAL is not set, val must be empty - RYML_ASSERT(((type & VAL) != 0) || val.scalar.empty()); - // val tag cannot be empty - RYML_ASSERT(val.tag.empty() == ((type & VALTAG) == 0)); - return true; - } -}; - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -/** contains the data for each YAML node. */ -struct NodeData -{ - NodeType m_type; - - NodeScalar m_key; - NodeScalar m_val; - - size_t m_parent; - size_t m_first_child; - size_t m_last_child; - size_t m_next_sibling; - size_t m_prev_sibling; -}; -C4_MUST_BE_TRIVIAL_COPY(NodeData); - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -class RYML_EXPORT Tree -{ -public: - - /** @name construction and assignment */ - /** @{ */ - - Tree() : Tree(get_callbacks()) {} - Tree(Callbacks const& cb); - Tree(size_t node_capacity, size_t arena_capacity=0) : Tree(node_capacity, arena_capacity, get_callbacks()) {} - Tree(size_t node_capacity, size_t arena_capacity, Callbacks const& cb); - - ~Tree(); - - Tree(Tree const& that) noexcept; - Tree(Tree && that) noexcept; - - Tree& operator= (Tree const& that) noexcept; - Tree& operator= (Tree && that) noexcept; - - /** @} */ - -public: - - /** @name memory and sizing */ - /** @{ */ - - void reserve(size_t node_capacity); - - /** clear the tree and zero every node - * @note does NOT clear the arena - * @see clear_arena() */ - void clear(); - inline void clear_arena() { m_arena_pos = 0; } - - inline bool empty() const { return m_size == 0; } - - inline size_t size() const { return m_size; } - inline size_t capacity() const { return m_cap; } - inline size_t slack() const { RYML_ASSERT(m_cap >= m_size); return m_cap - m_size; } - - Callbacks const& callbacks() const { return m_callbacks; } - void callbacks(Callbacks const& cb) { m_callbacks = cb; } - - /** @} */ - -public: - - /** @name node getters */ - /** @{ */ - - //! get the index of a node belonging to this tree. - //! @p n can be nullptr, in which case a - size_t id(NodeData const* n) const - { - if( ! n) - { - return NONE; - } - RYML_ASSERT(n >= m_buf && n < m_buf + m_cap); - return static_cast(n - m_buf); - } - - //! get a pointer to a node's NodeData. - //! i can be NONE, in which case a nullptr is returned - inline NodeData *get(size_t i) - { - if(i == NONE) - return nullptr; - RYML_ASSERT(i >= 0 && i < m_cap); - return m_buf + i; - } - //! get a pointer to a node's NodeData. - //! i can be NONE, in which case a nullptr is returned. - inline NodeData const *get(size_t i) const - { - if(i == NONE) - return nullptr; - RYML_ASSERT(i >= 0 && i < m_cap); - return m_buf + i; - } - - //! An if-less form of get() that demands a valid node index. - //! This function is implementation only; use at your own risk. - inline NodeData * _p(size_t i) { RYML_ASSERT(i != NONE && i >= 0 && i < m_cap); return m_buf + i; } - //! An if-less form of get() that demands a valid node index. - //! This function is implementation only; use at your own risk. - inline NodeData const * _p(size_t i) const { RYML_ASSERT(i != NONE && i >= 0 && i < m_cap); return m_buf + i; } - - //! Get the id of the root node - size_t root_id() { if(m_cap == 0) { reserve(16); } RYML_ASSERT(m_cap > 0 && m_size > 0); return 0; } - //! Get the id of the root node - size_t root_id() const { RYML_ASSERT(m_cap > 0 && m_size > 0); return 0; } - - //! Get a NodeRef of a node by id - NodeRef ref(size_t id); - //! Get a NodeRef of a node by id - ConstNodeRef ref(size_t id) const; - //! Get a NodeRef of a node by id - ConstNodeRef cref(size_t id); - //! Get a NodeRef of a node by id - ConstNodeRef cref(size_t id) const; - - //! Get the root as a NodeRef - NodeRef rootref(); - //! Get the root as a NodeRef - ConstNodeRef rootref() const; - //! Get the root as a NodeRef - ConstNodeRef crootref(); - //! Get the root as a NodeRef - ConstNodeRef crootref() const; - - //! find a root child by name, return it as a NodeRef - //! @note requires the root to be a map. - NodeRef operator[] (csubstr key); - //! find a root child by name, return it as a NodeRef - //! @note requires the root to be a map. - ConstNodeRef operator[] (csubstr key) const; - - //! find a root child by index: return the root node's @p i-th child as a NodeRef - //! @note @i is NOT the node id, but the child's position - NodeRef operator[] (size_t i); - //! find a root child by index: return the root node's @p i-th child as a NodeRef - //! @note @i is NOT the node id, but the child's position - ConstNodeRef operator[] (size_t i) const; - - //! get the i-th document of the stream - //! @note @i is NOT the node id, but the doc position within the stream - NodeRef docref(size_t i); - //! get the i-th document of the stream - //! @note @i is NOT the node id, but the doc position within the stream - ConstNodeRef docref(size_t i) const; - - /** @} */ - -public: - - /** @name node property getters */ - /** @{ */ - - NodeType type(size_t node) const { return _p(node)->m_type; } - const char* type_str(size_t node) const { return NodeType::type_str(_p(node)->m_type); } - - csubstr const& key (size_t node) const { RYML_ASSERT(has_key(node)); return _p(node)->m_key.scalar; } - csubstr const& key_tag (size_t node) const { RYML_ASSERT(has_key_tag(node)); return _p(node)->m_key.tag; } - csubstr const& key_ref (size_t node) const { RYML_ASSERT(is_key_ref(node) && ! has_key_anchor(node)); return _p(node)->m_key.anchor; } - csubstr const& key_anchor(size_t node) const { RYML_ASSERT( ! is_key_ref(node) && has_key_anchor(node)); return _p(node)->m_key.anchor; } - NodeScalar const& keysc (size_t node) const { RYML_ASSERT(has_key(node)); return _p(node)->m_key; } - - csubstr const& val (size_t node) const { RYML_ASSERT(has_val(node)); return _p(node)->m_val.scalar; } - csubstr const& val_tag (size_t node) const { RYML_ASSERT(has_val_tag(node)); return _p(node)->m_val.tag; } - csubstr const& val_ref (size_t node) const { RYML_ASSERT(is_val_ref(node) && ! has_val_anchor(node)); return _p(node)->m_val.anchor; } - csubstr const& val_anchor(size_t node) const { RYML_ASSERT( ! is_val_ref(node) && has_val_anchor(node)); return _p(node)->m_val.anchor; } - NodeScalar const& valsc (size_t node) const { RYML_ASSERT(has_val(node)); return _p(node)->m_val; } - - /** @} */ - -public: - - /** @name node predicates */ - /** @{ */ - - C4_ALWAYS_INLINE bool is_stream(size_t node) const { return _p(node)->m_type.is_stream(); } - C4_ALWAYS_INLINE bool is_doc(size_t node) const { return _p(node)->m_type.is_doc(); } - C4_ALWAYS_INLINE bool is_container(size_t node) const { return _p(node)->m_type.is_container(); } - C4_ALWAYS_INLINE bool is_map(size_t node) const { return _p(node)->m_type.is_map(); } - C4_ALWAYS_INLINE bool is_seq(size_t node) const { return _p(node)->m_type.is_seq(); } - C4_ALWAYS_INLINE bool has_key(size_t node) const { return _p(node)->m_type.has_key(); } - C4_ALWAYS_INLINE bool has_val(size_t node) const { return _p(node)->m_type.has_val(); } - C4_ALWAYS_INLINE bool is_val(size_t node) const { return _p(node)->m_type.is_val(); } - C4_ALWAYS_INLINE bool is_keyval(size_t node) const { return _p(node)->m_type.is_keyval(); } - C4_ALWAYS_INLINE bool has_key_tag(size_t node) const { return _p(node)->m_type.has_key_tag(); } - C4_ALWAYS_INLINE bool has_val_tag(size_t node) const { return _p(node)->m_type.has_val_tag(); } - C4_ALWAYS_INLINE bool has_key_anchor(size_t node) const { return _p(node)->m_type.has_key_anchor(); } - C4_ALWAYS_INLINE bool is_key_anchor(size_t node) const { return _p(node)->m_type.is_key_anchor(); } - C4_ALWAYS_INLINE bool has_val_anchor(size_t node) const { return _p(node)->m_type.has_val_anchor(); } - C4_ALWAYS_INLINE bool is_val_anchor(size_t node) const { return _p(node)->m_type.is_val_anchor(); } - C4_ALWAYS_INLINE bool has_anchor(size_t node) const { return _p(node)->m_type.has_anchor(); } - C4_ALWAYS_INLINE bool is_anchor(size_t node) const { return _p(node)->m_type.is_anchor(); } - C4_ALWAYS_INLINE bool is_key_ref(size_t node) const { return _p(node)->m_type.is_key_ref(); } - C4_ALWAYS_INLINE bool is_val_ref(size_t node) const { return _p(node)->m_type.is_val_ref(); } - C4_ALWAYS_INLINE bool is_ref(size_t node) const { return _p(node)->m_type.is_ref(); } - C4_ALWAYS_INLINE bool is_anchor_or_ref(size_t node) const { return _p(node)->m_type.is_anchor_or_ref(); } - C4_ALWAYS_INLINE bool is_key_quoted(size_t node) const { return _p(node)->m_type.is_key_quoted(); } - C4_ALWAYS_INLINE bool is_val_quoted(size_t node) const { return _p(node)->m_type.is_val_quoted(); } - C4_ALWAYS_INLINE bool is_quoted(size_t node) const { return _p(node)->m_type.is_quoted(); } - - C4_ALWAYS_INLINE bool parent_is_seq(size_t node) const { RYML_ASSERT(has_parent(node)); return is_seq(_p(node)->m_parent); } - C4_ALWAYS_INLINE bool parent_is_map(size_t node) const { RYML_ASSERT(has_parent(node)); return is_map(_p(node)->m_parent); } - - /** true when key and val are empty, and has no children */ - C4_ALWAYS_INLINE bool empty(size_t node) const { return ! has_children(node) && _p(node)->m_key.empty() && (( ! (_p(node)->m_type & VAL)) || _p(node)->m_val.empty()); } - /** true when the node has an anchor named a */ - C4_ALWAYS_INLINE bool has_anchor(size_t node, csubstr a) const { return _p(node)->m_key.anchor == a || _p(node)->m_val.anchor == a; } - - C4_ALWAYS_INLINE bool key_is_null(size_t node) const { RYML_ASSERT(has_key(node)); NodeData const* C4_RESTRICT n = _p(node); return !n->m_type.is_key_quoted() && _is_null(n->m_key.scalar); } - C4_ALWAYS_INLINE bool val_is_null(size_t node) const { RYML_ASSERT(has_val(node)); NodeData const* C4_RESTRICT n = _p(node); return !n->m_type.is_val_quoted() && _is_null(n->m_val.scalar); } - static bool _is_null(csubstr s) noexcept - { - return s.str == nullptr || - s == "~" || - s == "null" || - s == "Null" || - s == "NULL"; - } - - /** @} */ - -public: - - /** @name hierarchy predicates */ - /** @{ */ - - bool is_root(size_t node) const { RYML_ASSERT(_p(node)->m_parent != NONE || node == 0); return _p(node)->m_parent == NONE; } - - bool has_parent(size_t node) const { return _p(node)->m_parent != NONE; } - - /** true if @p node has a child with id @p ch */ - bool has_child(size_t node, size_t ch) const { return _p(ch)->m_parent == node; } - /** true if @p node has a child with key @p key */ - bool has_child(size_t node, csubstr key) const { return find_child(node, key) != npos; } - /** true if @p node has any children key */ - bool has_children(size_t node) const { return _p(node)->m_first_child != NONE; } - - /** true if @p node has a sibling with id @p sib */ - bool has_sibling(size_t node, size_t sib) const { return _p(node)->m_parent == _p(sib)->m_parent; } - /** true if one of the node's siblings has the given key */ - bool has_sibling(size_t node, csubstr key) const { return find_sibling(node, key) != npos; } - /** true if node is not a single child */ - bool has_other_siblings(size_t node) const - { - NodeData const *n = _p(node); - if(C4_LIKELY(n->m_parent != NONE)) - { - n = _p(n->m_parent); - return n->m_first_child != n->m_last_child; - } - return false; - } - - RYML_DEPRECATED("use has_other_siblings()") bool has_siblings(size_t /*node*/) const { return true; } - - /** @} */ - -public: - - /** @name hierarchy getters */ - /** @{ */ - - size_t parent(size_t node) const { return _p(node)->m_parent; } - - size_t prev_sibling(size_t node) const { return _p(node)->m_prev_sibling; } - size_t next_sibling(size_t node) const { return _p(node)->m_next_sibling; } - - /** O(#num_children) */ - size_t num_children(size_t node) const; - size_t child_pos(size_t node, size_t ch) const; - size_t first_child(size_t node) const { return _p(node)->m_first_child; } - size_t last_child(size_t node) const { return _p(node)->m_last_child; } - size_t child(size_t node, size_t pos) const; - size_t find_child(size_t node, csubstr const& key) const; - - /** O(#num_siblings) */ - /** counts with this */ - size_t num_siblings(size_t node) const { return is_root(node) ? 1 : num_children(_p(node)->m_parent); } - /** does not count with this */ - size_t num_other_siblings(size_t node) const { size_t ns = num_siblings(node); RYML_ASSERT(ns > 0); return ns-1; } - size_t sibling_pos(size_t node, size_t sib) const { RYML_ASSERT( ! is_root(node) || node == root_id()); return child_pos(_p(node)->m_parent, sib); } - size_t first_sibling(size_t node) const { return is_root(node) ? node : _p(_p(node)->m_parent)->m_first_child; } - size_t last_sibling(size_t node) const { return is_root(node) ? node : _p(_p(node)->m_parent)->m_last_child; } - size_t sibling(size_t node, size_t pos) const { return child(_p(node)->m_parent, pos); } - size_t find_sibling(size_t node, csubstr const& key) const { return find_child(_p(node)->m_parent, key); } - - size_t doc(size_t i) const { size_t rid = root_id(); RYML_ASSERT(is_stream(rid)); return child(rid, i); } //!< gets the @p i document node index. requires that the root node is a stream. - - /** @} */ - -public: - - /** @name node modifiers */ - /** @{ */ - - void to_keyval(size_t node, csubstr key, csubstr val, type_bits more_flags=0); - void to_map(size_t node, csubstr key, type_bits more_flags=0); - void to_seq(size_t node, csubstr key, type_bits more_flags=0); - void to_val(size_t node, csubstr val, type_bits more_flags=0); - void to_map(size_t node, type_bits more_flags=0); - void to_seq(size_t node, type_bits more_flags=0); - void to_doc(size_t node, type_bits more_flags=0); - void to_stream(size_t node, type_bits more_flags=0); - - void set_key(size_t node, csubstr key) { RYML_ASSERT(has_key(node)); _p(node)->m_key.scalar = key; } - void set_val(size_t node, csubstr val) { RYML_ASSERT(has_val(node)); _p(node)->m_val.scalar = val; } - - void set_key_tag(size_t node, csubstr tag) { RYML_ASSERT(has_key(node)); _p(node)->m_key.tag = tag; _add_flags(node, KEYTAG); } - void set_val_tag(size_t node, csubstr tag) { RYML_ASSERT(has_val(node) || is_container(node)); _p(node)->m_val.tag = tag; _add_flags(node, VALTAG); } - - void set_key_anchor(size_t node, csubstr anchor) { RYML_ASSERT( ! is_key_ref(node)); _p(node)->m_key.anchor = anchor.triml('&'); _add_flags(node, KEYANCH); } - void set_val_anchor(size_t node, csubstr anchor) { RYML_ASSERT( ! is_val_ref(node)); _p(node)->m_val.anchor = anchor.triml('&'); _add_flags(node, VALANCH); } - void set_key_ref (size_t node, csubstr ref ) { RYML_ASSERT( ! has_key_anchor(node)); NodeData* C4_RESTRICT n = _p(node); n->m_key.set_ref_maybe_replacing_scalar(ref, n->m_type.has_key()); _add_flags(node, KEY|KEYREF); } - void set_val_ref (size_t node, csubstr ref ) { RYML_ASSERT( ! has_val_anchor(node)); NodeData* C4_RESTRICT n = _p(node); n->m_val.set_ref_maybe_replacing_scalar(ref, n->m_type.has_val()); _add_flags(node, VAL|VALREF); } - - void rem_key_anchor(size_t node) { _p(node)->m_key.anchor.clear(); _rem_flags(node, KEYANCH); } - void rem_val_anchor(size_t node) { _p(node)->m_val.anchor.clear(); _rem_flags(node, VALANCH); } - void rem_key_ref (size_t node) { _p(node)->m_key.anchor.clear(); _rem_flags(node, KEYREF); } - void rem_val_ref (size_t node) { _p(node)->m_val.anchor.clear(); _rem_flags(node, VALREF); } - void rem_anchor_ref(size_t node) { _p(node)->m_key.anchor.clear(); _p(node)->m_val.anchor.clear(); _rem_flags(node, KEYANCH|VALANCH|KEYREF|VALREF); } - - /** @} */ - -public: - - /** @name tree modifiers */ - /** @{ */ - - /** reorder the tree in memory so that all the nodes are stored - * in a linear sequence when visited in depth-first order. - * This will invalidate existing ids, since the node id is its - * position in the node array. */ - void reorder(); - - /** Resolve references (aliases <- anchors) in the tree. - * - * Dereferencing is opt-in; after parsing, Tree::resolve() - * has to be called explicitly for obtaining resolved references in the - * tree. This method will resolve all references and substitute the - * anchored values in place of the reference. - * - * This method first does a full traversal of the tree to gather all - * anchors and references in a separate collection, then it goes through - * that collection to locate the names, which it does by obeying the YAML - * standard diktat that "an alias node refers to the most recent node in - * the serialization having the specified anchor" - * - * So, depending on the number of anchor/alias nodes, this is a - * potentially expensive operation, with a best-case linear complexity - * (from the initial traversal). This potential cost is the reason for - * requiring an explicit call. - */ - void resolve(); - - /** @} */ - -public: - - /** @name tag directives */ - /** @{ */ - - void resolve_tags(); - - size_t num_tag_directives() const; - size_t add_tag_directive(TagDirective const& td); - void clear_tag_directives(); - - size_t resolve_tag(substr output, csubstr tag, size_t node_id) const; - csubstr resolve_tag_sub(substr output, csubstr tag, size_t node_id) const - { - size_t needed = resolve_tag(output, tag, node_id); - return needed <= output.len ? output.first(needed) : output; - } - - using tag_directive_const_iterator = TagDirective const*; - tag_directive_const_iterator begin_tag_directives() const { return m_tag_directives; } - tag_directive_const_iterator end_tag_directives() const { return m_tag_directives + num_tag_directives(); } - - struct TagDirectiveProxy - { - tag_directive_const_iterator b, e; - tag_directive_const_iterator begin() const { return b; } - tag_directive_const_iterator end() const { return e; } - }; - - TagDirectiveProxy tag_directives() const { return TagDirectiveProxy{begin_tag_directives(), end_tag_directives()}; } - - /** @} */ - -public: - - /** @name modifying hierarchy */ - /** @{ */ - - /** create and insert a new child of @p parent. insert after the (to-be) - * sibling @p after, which must be a child of @p parent. To insert as the - * first child, set after to NONE */ - C4_ALWAYS_INLINE size_t insert_child(size_t parent, size_t after) - { - RYML_ASSERT(parent != NONE); - RYML_ASSERT(is_container(parent) || is_root(parent)); - RYML_ASSERT(after == NONE || (_p(after)->m_parent == parent)); - size_t child = _claim(); - _set_hierarchy(child, parent, after); - return child; - } - /** create and insert a node as the first child of @p parent */ - C4_ALWAYS_INLINE size_t prepend_child(size_t parent) { return insert_child(parent, NONE); } - /** create and insert a node as the last child of @p parent */ - C4_ALWAYS_INLINE size_t append_child(size_t parent) { return insert_child(parent, _p(parent)->m_last_child); } - -public: - - #if defined(__clang__) - # pragma clang diagnostic push - # pragma clang diagnostic ignored "-Wnull-dereference" - #elif defined(__GNUC__) - # pragma GCC diagnostic push - # if __GNUC__ >= 6 - # pragma GCC diagnostic ignored "-Wnull-dereference" - # endif - #endif - - //! create and insert a new sibling of n. insert after "after" - C4_ALWAYS_INLINE size_t insert_sibling(size_t node, size_t after) - { - return insert_child(_p(node)->m_parent, after); - } - /** create and insert a node as the first node of @p parent */ - C4_ALWAYS_INLINE size_t prepend_sibling(size_t node) { return prepend_child(_p(node)->m_parent); } - C4_ALWAYS_INLINE size_t append_sibling(size_t node) { return append_child(_p(node)->m_parent); } - -public: - - /** remove an entire branch at once: ie remove the children and the node itself */ - inline void remove(size_t node) - { - remove_children(node); - _release(node); - } - - /** remove all the node's children, but keep the node itself */ - void remove_children(size_t node); - - /** change the @p type of the node to one of MAP, SEQ or VAL. @p - * type must have one and only one of MAP,SEQ,VAL; @p type may - * possibly have KEY, but if it does, then the @p node must also - * have KEY. Changing to the same type is a no-op. Otherwise, - * changing to a different type will initialize the node with an - * empty value of the desired type: changing to VAL will - * initialize with a null scalar (~), changing to MAP will - * initialize with an empty map ({}), and changing to SEQ will - * initialize with an empty seq ([]). */ - bool change_type(size_t node, NodeType type); - - bool change_type(size_t node, type_bits type) - { - return change_type(node, (NodeType)type); - } - - #if defined(__clang__) - # pragma clang diagnostic pop - #elif defined(__GNUC__) - # pragma GCC diagnostic pop - #endif - -public: - - /** change the node's position in the parent */ - void move(size_t node, size_t after); - - /** change the node's parent and position */ - void move(size_t node, size_t new_parent, size_t after); - - /** change the node's parent and position to a different tree - * @return the index of the new node in the destination tree */ - size_t move(Tree * src, size_t node, size_t new_parent, size_t after); - - /** ensure the first node is a stream. Eg, change this tree - * - * DOCMAP - * MAP - * KEYVAL - * KEYVAL - * SEQ - * VAL - * - * to - * - * STREAM - * DOCMAP - * MAP - * KEYVAL - * KEYVAL - * SEQ - * VAL - * - * If the root is already a stream, this is a no-op. - */ - void set_root_as_stream(); - -public: - - /** recursively duplicate a node from this tree into a new parent, - * placing it after one of its children - * @return the index of the copy */ - size_t duplicate(size_t node, size_t new_parent, size_t after); - /** recursively duplicate a node from a different tree into a new parent, - * placing it after one of its children - * @return the index of the copy */ - size_t duplicate(Tree const* src, size_t node, size_t new_parent, size_t after); - - /** recursively duplicate the node's children (but not the node) - * @return the index of the last duplicated child */ - size_t duplicate_children(size_t node, size_t parent, size_t after); - /** recursively duplicate the node's children (but not the node), where - * the node is from a different tree - * @return the index of the last duplicated child */ - size_t duplicate_children(Tree const* src, size_t node, size_t parent, size_t after); - - void duplicate_contents(size_t node, size_t where); - void duplicate_contents(Tree const* src, size_t node, size_t where); - - /** duplicate the node's children (but not the node) in a new parent, but - * omit repetitions where a duplicated node has the same key (in maps) or - * value (in seqs). If one of the duplicated children has the same key - * (in maps) or value (in seqs) as one of the parent's children, the one - * that is placed closest to the end will prevail. */ - size_t duplicate_children_no_rep(size_t node, size_t parent, size_t after); - size_t duplicate_children_no_rep(Tree const* src, size_t node, size_t parent, size_t after); - -public: - - void merge_with(Tree const* src, size_t src_node=NONE, size_t dst_root=NONE); - - /** @} */ - -public: - - /** @name internal string arena */ - /** @{ */ - - /** get the current size of the tree's internal arena */ - RYML_DEPRECATED("use arena_size() instead") size_t arena_pos() const { return m_arena_pos; } - /** get the current size of the tree's internal arena */ - inline size_t arena_size() const { return m_arena_pos; } - /** get the current capacity of the tree's internal arena */ - inline size_t arena_capacity() const { return m_arena.len; } - /** get the current slack of the tree's internal arena */ - inline size_t arena_slack() const { RYML_ASSERT(m_arena.len >= m_arena_pos); return m_arena.len - m_arena_pos; } - - /** get the current arena */ - substr arena() const { return m_arena.first(m_arena_pos); } - - /** return true if the given substring is part of the tree's string arena */ - bool in_arena(csubstr s) const - { - return m_arena.is_super(s); - } - - /** serialize the given floating-point variable to the tree's - * arena, growing it as needed to accomodate the serialization. - * - * @note Growing the arena may cause relocation of the entire - * existing arena, and thus change the contents of individual - * nodes, and thus cost O(numnodes)+O(arenasize). To avoid this - * cost, ensure that the arena is reserved to an appropriate size - * using .reserve_arena() - * - * @see alloc_arena() */ - template - typename std::enable_if::value, csubstr>::type - to_arena(T const& C4_RESTRICT a) - { - substr rem(m_arena.sub(m_arena_pos)); - size_t num = to_chars_float(rem, a); - if(num > rem.len) - { - rem = _grow_arena(num); - num = to_chars_float(rem, a); - RYML_ASSERT(num <= rem.len); - } - rem = _request_span(num); - return rem; - } - - /** serialize the given non-floating-point variable to the tree's - * arena, growing it as needed to accomodate the serialization. - * - * @note Growing the arena may cause relocation of the entire - * existing arena, and thus change the contents of individual - * nodes, and thus cost O(numnodes)+O(arenasize). To avoid this - * cost, ensure that the arena is reserved to an appropriate size - * using .reserve_arena() - * - * @see alloc_arena() */ - template - typename std::enable_if::value, csubstr>::type - to_arena(T const& C4_RESTRICT a) - { - substr rem(m_arena.sub(m_arena_pos)); - size_t num = to_chars(rem, a); - if(num > rem.len) - { - rem = _grow_arena(num); - num = to_chars(rem, a); - RYML_ASSERT(num <= rem.len); - } - rem = _request_span(num); - return rem; - } - - /** serialize the given csubstr to the tree's arena, growing the - * arena as needed to accomodate the serialization. - * - * @note Growing the arena may cause relocation of the entire - * existing arena, and thus change the contents of individual - * nodes, and thus cost O(numnodes)+O(arenasize). To avoid this - * cost, ensure that the arena is reserved to an appropriate size - * using .reserve_arena() - * - * @see alloc_arena() */ - csubstr to_arena(csubstr a) - { - if(a.len > 0) - { - substr rem(m_arena.sub(m_arena_pos)); - size_t num = to_chars(rem, a); - if(num > rem.len) - { - rem = _grow_arena(num); - num = to_chars(rem, a); - RYML_ASSERT(num <= rem.len); - } - return _request_span(num); - } - else - { - if(a.str == nullptr) - { - return csubstr{}; - } - else if(m_arena.str == nullptr) - { - // Arena is empty and we want to store a non-null - // zero-length string. - // Even though the string has zero length, we need - // some "memory" to store a non-nullptr string - _grow_arena(1); - } - return _request_span(0); - } - } - C4_ALWAYS_INLINE csubstr to_arena(const char *s) - { - return to_arena(to_csubstr(s)); - } - C4_ALWAYS_INLINE csubstr to_arena(std::nullptr_t) - { - return csubstr{}; - } - - /** copy the given substr to the tree's arena, growing it by the - * required size - * - * @note Growing the arena may cause relocation of the entire - * existing arena, and thus change the contents of individual - * nodes, and thus cost O(numnodes)+O(arenasize). To avoid this - * cost, ensure that the arena is reserved to an appropriate size - * using .reserve_arena() - * - * @see alloc_arena() */ - substr copy_to_arena(csubstr s) - { - substr cp = alloc_arena(s.len); - RYML_ASSERT(cp.len == s.len); - RYML_ASSERT(!s.overlaps(cp)); - #if (!defined(__clang__)) && (defined(__GNUC__) && __GNUC__ >= 10) - C4_SUPPRESS_WARNING_GCC_PUSH - C4_SUPPRESS_WARNING_GCC("-Wstringop-overflow=") // no need for terminating \0 - C4_SUPPRESS_WARNING_GCC( "-Wrestrict") // there's an assert to ensure no violation of restrict behavior - #endif - if(s.len) - memcpy(cp.str, s.str, s.len); - #if (!defined(__clang__)) && (defined(__GNUC__) && __GNUC__ >= 10) - C4_SUPPRESS_WARNING_GCC_POP - #endif - return cp; - } - - /** grow the tree's string arena by the given size and return a substr - * of the added portion - * - * @note Growing the arena may cause relocation of the entire - * existing arena, and thus change the contents of individual - * nodes, and thus cost O(numnodes)+O(arenasize). To avoid this - * cost, ensure that the arena is reserved to an appropriate size - * using .reserve_arena(). - * - * @see reserve_arena() */ - substr alloc_arena(size_t sz) - { - if(sz > arena_slack()) - _grow_arena(sz - arena_slack()); - substr s = _request_span(sz); - return s; - } - - /** ensure the tree's internal string arena is at least the given capacity - * @note This operation has a potential complexity of O(numNodes)+O(arenasize). - * Growing the arena may cause relocation of the entire - * existing arena, and thus change the contents of individual nodes. */ - void reserve_arena(size_t arena_cap) - { - if(arena_cap > m_arena.len) - { - substr buf; - buf.str = (char*) m_callbacks.m_allocate(arena_cap, m_arena.str, m_callbacks.m_user_data); - buf.len = arena_cap; - if(m_arena.str) - { - RYML_ASSERT(m_arena.len >= 0); - _relocate(buf); // does a memcpy and changes nodes using the arena - m_callbacks.m_free(m_arena.str, m_arena.len, m_callbacks.m_user_data); - } - m_arena = buf; - } - } - - /** @} */ - -private: - - substr _grow_arena(size_t more) - { - size_t cap = m_arena.len + more; - cap = cap < 2 * m_arena.len ? 2 * m_arena.len : cap; - cap = cap < 64 ? 64 : cap; - reserve_arena(cap); - return m_arena.sub(m_arena_pos); - } - - substr _request_span(size_t sz) - { - substr s; - s = m_arena.sub(m_arena_pos, sz); - m_arena_pos += sz; - return s; - } - - substr _relocated(csubstr s, substr next_arena) const - { - RYML_ASSERT(m_arena.is_super(s)); - RYML_ASSERT(m_arena.sub(0, m_arena_pos).is_super(s)); - auto pos = (s.str - m_arena.str); - substr r(next_arena.str + pos, s.len); - RYML_ASSERT(r.str - next_arena.str == pos); - RYML_ASSERT(next_arena.sub(0, m_arena_pos).is_super(r)); - return r; - } - -public: - - /** @name lookup */ - /** @{ */ - - struct lookup_result - { - size_t target; - size_t closest; - size_t path_pos; - csubstr path; - - inline operator bool() const { return target != NONE; } - - lookup_result() : target(NONE), closest(NONE), path_pos(0), path() {} - lookup_result(csubstr path_, size_t start) : target(NONE), closest(start), path_pos(0), path(path_) {} - - /** get the part ot the input path that was resolved */ - csubstr resolved() const; - /** get the part ot the input path that was unresolved */ - csubstr unresolved() const; - }; - - /** for example foo.bar[0].baz */ - lookup_result lookup_path(csubstr path, size_t start=NONE) const; - - /** defaulted lookup: lookup @p path; if the lookup fails, recursively modify - * the tree so that the corresponding lookup_path() would return the - * default value. - * @see lookup_path() */ - size_t lookup_path_or_modify(csubstr default_value, csubstr path, size_t start=NONE); - - /** defaulted lookup: lookup @p path; if the lookup fails, recursively modify - * the tree so that the corresponding lookup_path() would return the - * branch @p src_node (from the tree @p src). - * @see lookup_path() */ - size_t lookup_path_or_modify(Tree const *src, size_t src_node, csubstr path, size_t start=NONE); - - /** @} */ - -private: - - struct _lookup_path_token - { - csubstr value; - NodeType type; - _lookup_path_token() : value(), type() {} - _lookup_path_token(csubstr v, NodeType t) : value(v), type(t) {} - inline operator bool() const { return type != NOTYPE; } - bool is_index() const { return value.begins_with('[') && value.ends_with(']'); } - }; - - size_t _lookup_path_or_create(csubstr path, size_t start); - - void _lookup_path (lookup_result *r) const; - void _lookup_path_modify(lookup_result *r); - - size_t _next_node (lookup_result *r, _lookup_path_token *parent) const; - size_t _next_node_modify(lookup_result *r, _lookup_path_token *parent); - - void _advance(lookup_result *r, size_t more) const; - - _lookup_path_token _next_token(lookup_result *r, _lookup_path_token const& parent) const; - -private: - - void _clear(); - void _free(); - void _copy(Tree const& that); - void _move(Tree & that); - - void _relocate(substr next_arena); - -public: - - #if ! RYML_USE_ASSERT - C4_ALWAYS_INLINE void _check_next_flags(size_t, type_bits) {} - #else - void _check_next_flags(size_t node, type_bits f) - { - auto n = _p(node); - type_bits o = n->m_type; // old - C4_UNUSED(o); - if(f & MAP) - { - RYML_ASSERT_MSG((f & SEQ) == 0, "cannot mark simultaneously as map and seq"); - RYML_ASSERT_MSG((f & VAL) == 0, "cannot mark simultaneously as map and val"); - RYML_ASSERT_MSG((o & SEQ) == 0, "cannot turn a seq into a map; clear first"); - RYML_ASSERT_MSG((o & VAL) == 0, "cannot turn a val into a map; clear first"); - } - else if(f & SEQ) - { - RYML_ASSERT_MSG((f & MAP) == 0, "cannot mark simultaneously as seq and map"); - RYML_ASSERT_MSG((f & VAL) == 0, "cannot mark simultaneously as seq and val"); - RYML_ASSERT_MSG((o & MAP) == 0, "cannot turn a map into a seq; clear first"); - RYML_ASSERT_MSG((o & VAL) == 0, "cannot turn a val into a seq; clear first"); - } - if(f & KEY) - { - RYML_ASSERT(!is_root(node)); - auto pid = parent(node); C4_UNUSED(pid); - RYML_ASSERT(is_map(pid)); - } - if((f & VAL) && !is_root(node)) - { - auto pid = parent(node); C4_UNUSED(pid); - RYML_ASSERT(is_map(pid) || is_seq(pid)); - } - } - #endif - - inline void _set_flags(size_t node, NodeType_e f) { _check_next_flags(node, f); _p(node)->m_type = f; } - inline void _set_flags(size_t node, type_bits f) { _check_next_flags(node, f); _p(node)->m_type = f; } - - inline void _add_flags(size_t node, NodeType_e f) { NodeData *d = _p(node); type_bits fb = f | d->m_type; _check_next_flags(node, fb); d->m_type = (NodeType_e) fb; } - inline void _add_flags(size_t node, type_bits f) { NodeData *d = _p(node); f |= d->m_type; _check_next_flags(node, f); d->m_type = f; } - - inline void _rem_flags(size_t node, NodeType_e f) { NodeData *d = _p(node); type_bits fb = d->m_type & ~f; _check_next_flags(node, fb); d->m_type = (NodeType_e) fb; } - inline void _rem_flags(size_t node, type_bits f) { NodeData *d = _p(node); f = d->m_type & ~f; _check_next_flags(node, f); d->m_type = f; } - - void _set_key(size_t node, csubstr key, type_bits more_flags=0) - { - _p(node)->m_key.scalar = key; - _add_flags(node, KEY|more_flags); - } - void _set_key(size_t node, NodeScalar const& key, type_bits more_flags=0) - { - _p(node)->m_key = key; - _add_flags(node, KEY|more_flags); - } - - void _set_val(size_t node, csubstr val, type_bits more_flags=0) - { - RYML_ASSERT(num_children(node) == 0); - RYML_ASSERT(!is_seq(node) && !is_map(node)); - _p(node)->m_val.scalar = val; - _add_flags(node, VAL|more_flags); - } - void _set_val(size_t node, NodeScalar const& val, type_bits more_flags=0) - { - RYML_ASSERT(num_children(node) == 0); - RYML_ASSERT( ! is_container(node)); - _p(node)->m_val = val; - _add_flags(node, VAL|more_flags); - } - - void _set(size_t node, NodeInit const& i) - { - RYML_ASSERT(i._check()); - NodeData *n = _p(node); - RYML_ASSERT(n->m_key.scalar.empty() || i.key.scalar.empty() || i.key.scalar == n->m_key.scalar); - _add_flags(node, i.type); - if(n->m_key.scalar.empty()) - { - if( ! i.key.scalar.empty()) - { - _set_key(node, i.key.scalar); - } - } - n->m_key.tag = i.key.tag; - n->m_val = i.val; - } - - void _set_parent_as_container_if_needed(size_t in) - { - NodeData const* n = _p(in); - size_t ip = parent(in); - if(ip != NONE) - { - if( ! (is_seq(ip) || is_map(ip))) - { - if((in == first_child(ip)) && (in == last_child(ip))) - { - if( ! n->m_key.empty() || has_key(in)) - { - _add_flags(ip, MAP); - } - else - { - _add_flags(ip, SEQ); - } - } - } - } - } - - void _seq2map(size_t node) - { - RYML_ASSERT(is_seq(node)); - for(size_t i = first_child(node); i != NONE; i = next_sibling(i)) - { - NodeData *C4_RESTRICT ch = _p(i); - if(ch->m_type.is_keyval()) - continue; - ch->m_type.add(KEY); - ch->m_key = ch->m_val; - } - auto *C4_RESTRICT n = _p(node); - n->m_type.rem(SEQ); - n->m_type.add(MAP); - } - - size_t _do_reorder(size_t *node, size_t count); - - void _swap(size_t n_, size_t m_); - void _swap_props(size_t n_, size_t m_); - void _swap_hierarchy(size_t n_, size_t m_); - void _copy_hierarchy(size_t dst_, size_t src_); - - inline void _copy_props(size_t dst_, size_t src_) - { - _copy_props(dst_, this, src_); - } - - inline void _copy_props_wo_key(size_t dst_, size_t src_) - { - _copy_props_wo_key(dst_, this, src_); - } - - void _copy_props(size_t dst_, Tree const* that_tree, size_t src_) - { - auto & C4_RESTRICT dst = *_p(dst_); - auto const& C4_RESTRICT src = *that_tree->_p(src_); - dst.m_type = src.m_type; - dst.m_key = src.m_key; - dst.m_val = src.m_val; - } - - void _copy_props_wo_key(size_t dst_, Tree const* that_tree, size_t src_) - { - auto & C4_RESTRICT dst = *_p(dst_); - auto const& C4_RESTRICT src = *that_tree->_p(src_); - dst.m_type = (src.m_type & ~_KEYMASK) | (dst.m_type & _KEYMASK); - dst.m_val = src.m_val; - } - - inline void _clear_type(size_t node) - { - _p(node)->m_type = NOTYPE; - } - - inline void _clear(size_t node) - { - auto *C4_RESTRICT n = _p(node); - n->m_type = NOTYPE; - n->m_key.clear(); - n->m_val.clear(); - n->m_parent = NONE; - n->m_first_child = NONE; - n->m_last_child = NONE; - } - - inline void _clear_key(size_t node) - { - _p(node)->m_key.clear(); - _rem_flags(node, KEY); - } - - inline void _clear_val(size_t node) - { - _p(node)->m_val.clear(); - _rem_flags(node, VAL); - } - -private: - - void _clear_range(size_t first, size_t num); - - size_t _claim(); - void _claim_root(); - void _release(size_t node); - void _free_list_add(size_t node); - void _free_list_rem(size_t node); - - void _set_hierarchy(size_t node, size_t parent, size_t after_sibling); - void _rem_hierarchy(size_t node); - -public: - - // members are exposed, but you should NOT access them directly - - NodeData * m_buf; - size_t m_cap; - - size_t m_size; - - size_t m_free_head; - size_t m_free_tail; - - substr m_arena; - size_t m_arena_pos; - - Callbacks m_callbacks; - - TagDirective m_tag_directives[RYML_MAX_TAG_DIRECTIVES]; - -}; - -} // namespace yml -} // namespace c4 - - -C4_SUPPRESS_WARNING_MSVC_POP -C4_SUPPRESS_WARNING_GCC_CLANG_POP - - -#endif /* _C4_YML_TREE_HPP_ */ - - -// (end https://github.com/biojppm/rapidyaml/src/c4/yml/tree.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/yml/node.hpp -// https://github.com/biojppm/rapidyaml/src/c4/yml/node.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_YML_NODE_HPP_ -#define _C4_YML_NODE_HPP_ - -/** @file node.hpp - * @see NodeRef */ - -//included above: -//#include - -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/yml/tree.hpp -//#include "c4/yml/tree.hpp" -#if !defined(C4_YML_TREE_HPP_) && !defined(_C4_YML_TREE_HPP_) -#error "amalgamate: file c4/yml/tree.hpp must have been included at this point" -#endif /* C4_YML_TREE_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/base64.hpp -//#include "c4/base64.hpp" -#if !defined(C4_BASE64_HPP_) && !defined(_C4_BASE64_HPP_) -#error "amalgamate: file c4/base64.hpp must have been included at this point" -#endif /* C4_BASE64_HPP_ */ - - -#ifdef __GNUC__ -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wtype-limits" -#endif - -#if defined(_MSC_VER) -# pragma warning(push) -# pragma warning(disable: 4251/*needs to have dll-interface to be used by clients of struct*/) -# pragma warning(disable: 4296/*expression is always 'boolean_value'*/) -#endif - -namespace c4 { -namespace yml { - -template struct Key { K & k; }; -template<> struct Key { fmt::const_base64_wrapper wrapper; }; -template<> struct Key { fmt::base64_wrapper wrapper; }; - -template C4_ALWAYS_INLINE Key key(K & k) { return Key{k}; } -C4_ALWAYS_INLINE Key key(fmt::const_base64_wrapper w) { return {w}; } -C4_ALWAYS_INLINE Key key(fmt::base64_wrapper w) { return {w}; } - -template void write(NodeRef *n, T const& v); - -template -typename std::enable_if< ! std::is_floating_point::value, bool>::type -read(NodeRef const& n, T *v); - -template -typename std::enable_if< std::is_floating_point::value, bool>::type -read(NodeRef const& n, T *v); - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -// forward decls -class NodeRef; -class ConstNodeRef; - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -namespace detail { - -template -struct child_iterator -{ - using value_type = NodeRefType; - using tree_type = typename NodeRefType::tree_type; - - tree_type * C4_RESTRICT m_tree; - size_t m_child_id; - - child_iterator(tree_type * t, size_t id) : m_tree(t), m_child_id(id) {} - - child_iterator& operator++ () { RYML_ASSERT(m_child_id != NONE); m_child_id = m_tree->next_sibling(m_child_id); return *this; } - child_iterator& operator-- () { RYML_ASSERT(m_child_id != NONE); m_child_id = m_tree->prev_sibling(m_child_id); return *this; } - - NodeRefType operator* () const { return NodeRefType(m_tree, m_child_id); } - NodeRefType operator-> () const { return NodeRefType(m_tree, m_child_id); } - - bool operator!= (child_iterator that) const { RYML_ASSERT(m_tree == that.m_tree); return m_child_id != that.m_child_id; } - bool operator== (child_iterator that) const { RYML_ASSERT(m_tree == that.m_tree); return m_child_id == that.m_child_id; } -}; - -template -struct children_view_ -{ - using n_iterator = child_iterator; - - n_iterator b, e; - - inline children_view_(n_iterator const& C4_RESTRICT b_, - n_iterator const& C4_RESTRICT e_) : b(b_), e(e_) {} - - inline n_iterator begin() const { return b; } - inline n_iterator end () const { return e; } -}; - -template -bool _visit(NodeRefType &node, Visitor fn, size_t indentation_level, bool skip_root=false) -{ - size_t increment = 0; - if( ! (node.is_root() && skip_root)) - { - if(fn(node, indentation_level)) - return true; - ++increment; - } - if(node.has_children()) - { - for(auto ch : node.children()) - { - if(_visit(ch, fn, indentation_level + increment, false)) // no need to forward skip_root as it won't be root - { - return true; - } - } - } - return false; -} - -template -bool _visit_stacked(NodeRefType &node, Visitor fn, size_t indentation_level, bool skip_root=false) -{ - size_t increment = 0; - if( ! (node.is_root() && skip_root)) - { - if(fn(node, indentation_level)) - { - return true; - } - ++increment; - } - if(node.has_children()) - { - fn.push(node, indentation_level); - for(auto ch : node.children()) - { - if(_visit_stacked(ch, fn, indentation_level + increment, false)) // no need to forward skip_root as it won't be root - { - fn.pop(node, indentation_level); - return true; - } - } - fn.pop(node, indentation_level); - } - return false; -} - - -//----------------------------------------------------------------------------- - -/** a CRTP base for read-only node methods */ -template -struct RoNodeMethods -{ - C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wcast-align") - // helper CRTP macros, undefined at the end - #define tree_ ((ConstImpl const* C4_RESTRICT)this)->m_tree - #define id_ ((ConstImpl const* C4_RESTRICT)this)->m_id - #define tree__ ((Impl const* C4_RESTRICT)this)->m_tree - #define id__ ((Impl const* C4_RESTRICT)this)->m_id - // require valid - #define _C4RV() \ - RYML_ASSERT(tree_ != nullptr); \ - _RYML_CB_ASSERT(tree_->m_callbacks, id_ != NONE) - #define _C4_IF_MUTABLE(ty) typename std::enable_if::value, ty>::type - -public: - - /** @name node property getters */ - /** @{ */ - - /** returns the data or null when the id is NONE */ - C4_ALWAYS_INLINE C4_PURE NodeData const* get() const noexcept { RYML_ASSERT(tree_ != nullptr); return tree_->get(id_); } - /** returns the data or null when the id is NONE */ - template - C4_ALWAYS_INLINE C4_PURE auto get() noexcept -> _C4_IF_MUTABLE(NodeData*) { RYML_ASSERT(tree_ != nullptr); return tree__->get(id__); } - - C4_ALWAYS_INLINE C4_PURE NodeType type() const noexcept { _C4RV(); return tree_->type(id_); } - C4_ALWAYS_INLINE C4_PURE const char* type_str() const noexcept { return tree_->type_str(id_); } - - C4_ALWAYS_INLINE C4_PURE csubstr key() const noexcept { _C4RV(); return tree_->key(id_); } - C4_ALWAYS_INLINE C4_PURE csubstr key_tag() const noexcept { _C4RV(); return tree_->key_tag(id_); } - C4_ALWAYS_INLINE C4_PURE csubstr key_ref() const noexcept { _C4RV(); return tree_->key_ref(id_); } - C4_ALWAYS_INLINE C4_PURE csubstr key_anchor() const noexcept { _C4RV(); return tree_->key_anchor(id_); } - - C4_ALWAYS_INLINE C4_PURE csubstr val() const noexcept { _C4RV(); return tree_->val(id_); } - C4_ALWAYS_INLINE C4_PURE csubstr val_tag() const noexcept { _C4RV(); return tree_->val_tag(id_); } - C4_ALWAYS_INLINE C4_PURE csubstr val_ref() const noexcept { _C4RV(); return tree_->val_ref(id_); } - C4_ALWAYS_INLINE C4_PURE csubstr val_anchor() const noexcept { _C4RV(); return tree_->val_anchor(id_); } - - C4_ALWAYS_INLINE C4_PURE NodeScalar const& keysc() const noexcept { _C4RV(); return tree_->keysc(id_); } - C4_ALWAYS_INLINE C4_PURE NodeScalar const& valsc() const noexcept { _C4RV(); return tree_->valsc(id_); } - - C4_ALWAYS_INLINE C4_PURE bool key_is_null() const noexcept { _C4RV(); return tree_->key_is_null(id_); } - C4_ALWAYS_INLINE C4_PURE bool val_is_null() const noexcept { _C4RV(); return tree_->val_is_null(id_); } - - /** @} */ - -public: - - /** @name node property predicates */ - /** @{ */ - - C4_ALWAYS_INLINE C4_PURE bool empty() const noexcept { _C4RV(); return tree_->empty(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_stream() const noexcept { _C4RV(); return tree_->is_stream(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_doc() const noexcept { _C4RV(); return tree_->is_doc(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_container() const noexcept { _C4RV(); return tree_->is_container(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_map() const noexcept { _C4RV(); return tree_->is_map(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_seq() const noexcept { _C4RV(); return tree_->is_seq(id_); } - C4_ALWAYS_INLINE C4_PURE bool has_val() const noexcept { _C4RV(); return tree_->has_val(id_); } - C4_ALWAYS_INLINE C4_PURE bool has_key() const noexcept { _C4RV(); return tree_->has_key(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_val() const noexcept { _C4RV(); return tree_->is_val(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_keyval() const noexcept { _C4RV(); return tree_->is_keyval(id_); } - C4_ALWAYS_INLINE C4_PURE bool has_key_tag() const noexcept { _C4RV(); return tree_->has_key_tag(id_); } - C4_ALWAYS_INLINE C4_PURE bool has_val_tag() const noexcept { _C4RV(); return tree_->has_val_tag(id_); } - C4_ALWAYS_INLINE C4_PURE bool has_key_anchor() const noexcept { _C4RV(); return tree_->has_key_anchor(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_key_anchor() const noexcept { _C4RV(); return tree_->is_key_anchor(id_); } - C4_ALWAYS_INLINE C4_PURE bool has_val_anchor() const noexcept { _C4RV(); return tree_->has_val_anchor(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_val_anchor() const noexcept { _C4RV(); return tree_->is_val_anchor(id_); } - C4_ALWAYS_INLINE C4_PURE bool has_anchor() const noexcept { _C4RV(); return tree_->has_anchor(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_anchor() const noexcept { _C4RV(); return tree_->is_anchor(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_key_ref() const noexcept { _C4RV(); return tree_->is_key_ref(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_val_ref() const noexcept { _C4RV(); return tree_->is_val_ref(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_ref() const noexcept { _C4RV(); return tree_->is_ref(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_anchor_or_ref() const noexcept { _C4RV(); return tree_->is_anchor_or_ref(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_key_quoted() const noexcept { _C4RV(); return tree_->is_key_quoted(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_val_quoted() const noexcept { _C4RV(); return tree_->is_val_quoted(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_quoted() const noexcept { _C4RV(); return tree_->is_quoted(id_); } - C4_ALWAYS_INLINE C4_PURE bool parent_is_seq() const noexcept { _C4RV(); return tree_->parent_is_seq(id_); } - C4_ALWAYS_INLINE C4_PURE bool parent_is_map() const noexcept { _C4RV(); return tree_->parent_is_map(id_); } - - /** @} */ - -public: - - /** @name hierarchy predicates */ - /** @{ */ - - C4_ALWAYS_INLINE C4_PURE bool is_root() const noexcept { _C4RV(); return tree_->is_root(id_); } - C4_ALWAYS_INLINE C4_PURE bool has_parent() const noexcept { _C4RV(); return tree_->has_parent(id_); } - - C4_ALWAYS_INLINE C4_PURE bool has_child(ConstImpl const& ch) const noexcept { _C4RV(); return tree_->has_child(id_, ch.m_id); } - C4_ALWAYS_INLINE C4_PURE bool has_child(csubstr name) const noexcept { _C4RV(); return tree_->has_child(id_, name); } - C4_ALWAYS_INLINE C4_PURE bool has_children() const noexcept { _C4RV(); return tree_->has_children(id_); } - - C4_ALWAYS_INLINE C4_PURE bool has_sibling(ConstImpl const& n) const noexcept { _C4RV(); return tree_->has_sibling(id_, n.m_id); } - C4_ALWAYS_INLINE C4_PURE bool has_sibling(csubstr name) const noexcept { _C4RV(); return tree_->has_sibling(id_, name); } - /** counts with this */ - C4_ALWAYS_INLINE C4_PURE bool has_siblings() const noexcept { _C4RV(); return tree_->has_siblings(id_); } - /** does not count with this */ - C4_ALWAYS_INLINE C4_PURE bool has_other_siblings() const noexcept { _C4RV(); return tree_->has_other_siblings(id_); } - - /** @} */ - -public: - - /** @name hierarchy getters */ - /** @{ */ - - - template - C4_ALWAYS_INLINE C4_PURE auto doc(size_t num) noexcept -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->doc(num)}; } - C4_ALWAYS_INLINE C4_PURE ConstImpl doc(size_t num) const noexcept { _C4RV(); return {tree_, tree_->doc(num)}; } - - - template - C4_ALWAYS_INLINE C4_PURE auto parent() noexcept -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->parent(id__)}; } - C4_ALWAYS_INLINE C4_PURE ConstImpl parent() const noexcept { _C4RV(); return {tree_, tree_->parent(id_)}; } - - - /** O(#num_children) */ - C4_ALWAYS_INLINE C4_PURE size_t child_pos(ConstImpl const& n) const noexcept { _C4RV(); return tree_->child_pos(id_, n.m_id); } - C4_ALWAYS_INLINE C4_PURE size_t num_children() const noexcept { _C4RV(); return tree_->num_children(id_); } - - template - C4_ALWAYS_INLINE C4_PURE auto first_child() noexcept -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->first_child(id__)}; } - C4_ALWAYS_INLINE C4_PURE ConstImpl first_child() const noexcept { _C4RV(); return {tree_, tree_->first_child(id_)}; } - - template - C4_ALWAYS_INLINE C4_PURE auto last_child() noexcept -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->last_child(id__)}; } - C4_ALWAYS_INLINE C4_PURE ConstImpl last_child () const noexcept { _C4RV(); return {tree_, tree_->last_child (id_)}; } - - template - C4_ALWAYS_INLINE C4_PURE auto child(size_t pos) noexcept -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->child(id__, pos)}; } - C4_ALWAYS_INLINE C4_PURE ConstImpl child(size_t pos) const noexcept { _C4RV(); return {tree_, tree_->child(id_, pos)}; } - - template - C4_ALWAYS_INLINE C4_PURE auto find_child(csubstr name) noexcept -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->find_child(id__, name)}; } - C4_ALWAYS_INLINE C4_PURE ConstImpl find_child(csubstr name) const noexcept { _C4RV(); return {tree_, tree_->find_child(id_, name)}; } - - - /** O(#num_siblings) */ - C4_ALWAYS_INLINE C4_PURE size_t num_siblings() const noexcept { _C4RV(); return tree_->num_siblings(id_); } - C4_ALWAYS_INLINE C4_PURE size_t num_other_siblings() const noexcept { _C4RV(); return tree_->num_other_siblings(id_); } - C4_ALWAYS_INLINE C4_PURE size_t sibling_pos(ConstImpl const& n) const noexcept { _C4RV(); return tree_->child_pos(tree_->parent(id_), n.m_id); } - - template - C4_ALWAYS_INLINE C4_PURE auto prev_sibling() noexcept -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->prev_sibling(id__)}; } - C4_ALWAYS_INLINE C4_PURE ConstImpl prev_sibling() const noexcept { _C4RV(); return {tree_, tree_->prev_sibling(id_)}; } - - template - C4_ALWAYS_INLINE C4_PURE auto next_sibling() noexcept -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->next_sibling(id__)}; } - C4_ALWAYS_INLINE C4_PURE ConstImpl next_sibling() const noexcept { _C4RV(); return {tree_, tree_->next_sibling(id_)}; } - - template - C4_ALWAYS_INLINE C4_PURE auto first_sibling() noexcept -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->first_sibling(id__)}; } - C4_ALWAYS_INLINE C4_PURE ConstImpl first_sibling() const noexcept { _C4RV(); return {tree_, tree_->first_sibling(id_)}; } - - template - C4_ALWAYS_INLINE C4_PURE auto last_sibling() noexcept -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->last_sibling(id__)}; } - C4_ALWAYS_INLINE C4_PURE ConstImpl last_sibling () const noexcept { _C4RV(); return {tree_, tree_->last_sibling(id_)}; } - - template - C4_ALWAYS_INLINE C4_PURE auto sibling(size_t pos) noexcept -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->sibling(id__, pos)}; } - C4_ALWAYS_INLINE C4_PURE ConstImpl sibling(size_t pos) const noexcept { _C4RV(); return {tree_, tree_->sibling(id_, pos)}; } - - template - C4_ALWAYS_INLINE C4_PURE auto find_sibling(csubstr name) noexcept -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->find_sibling(id__, name)}; } - C4_ALWAYS_INLINE C4_PURE ConstImpl find_sibling(csubstr name) const noexcept { _C4RV(); return {tree_, tree_->find_sibling(id_, name)}; } - - - /** O(num_children) */ - C4_ALWAYS_INLINE C4_PURE ConstImpl operator[] (csubstr k) const noexcept - { - _C4RV(); - size_t ch = tree_->find_child(id_, k); - _RYML_CB_ASSERT(tree_->m_callbacks, ch != NONE); - return {tree_, ch}; - } - /** Find child by key. O(num_children). returns a seed node if no such child is found. */ - template - C4_ALWAYS_INLINE C4_PURE auto operator[] (csubstr k) noexcept -> _C4_IF_MUTABLE(Impl) - { - _C4RV(); - size_t ch = tree__->find_child(id__, k); - return ch != NONE ? Impl(tree__, ch) : NodeRef(tree__, id__, k); - } - - /** O(num_children) */ - C4_ALWAYS_INLINE C4_PURE ConstImpl operator[] (size_t pos) const noexcept - { - _C4RV(); - size_t ch = tree_->child(id_, pos); - _RYML_CB_ASSERT(tree_->m_callbacks, ch != NONE); - return {tree_, ch}; - } - - /** Find child by position. O(pos). returns a seed node if no such child is found. */ - template - C4_ALWAYS_INLINE C4_PURE auto operator[] (size_t pos) noexcept -> _C4_IF_MUTABLE(Impl) - { - _C4RV(); - size_t ch = tree__->child(id__, pos); - return ch != NONE ? Impl(tree__, ch) : NodeRef(tree__, id__, pos); - } - - /** @} */ - -public: - - /** deserialization */ - /** @{ */ - - template - ConstImpl const& operator>> (T &v) const - { - _C4RV(); - if( ! read((ConstImpl const&)*this, &v)) - _RYML_CB_ERR(tree_->m_callbacks, "could not deserialize value"); - return *((ConstImpl const*)this); - } - - /** deserialize the node's key to the given variable */ - template - ConstImpl const& operator>> (Key v) const - { - _C4RV(); - if( ! from_chars(key(), &v.k)) - _RYML_CB_ERR(tree_->m_callbacks, "could not deserialize key"); - return *((ConstImpl const*)this); - } - - /** deserialize the node's key as base64 */ - ConstImpl const& operator>> (Key w) const - { - deserialize_key(w.wrapper); - return *((ConstImpl const*)this); - } - - /** deserialize the node's val as base64 */ - ConstImpl const& operator>> (fmt::base64_wrapper w) const - { - deserialize_val(w); - return *((ConstImpl const*)this); - } - - /** decode the base64-encoded key and assign the - * decoded blob to the given buffer/ - * @return the size of base64-decoded blob */ - size_t deserialize_key(fmt::base64_wrapper v) const - { - _C4RV(); - return from_chars(key(), &v); - } - /** decode the base64-encoded key and assign the - * decoded blob to the given buffer/ - * @return the size of base64-decoded blob */ - size_t deserialize_val(fmt::base64_wrapper v) const - { - _C4RV(); - return from_chars(val(), &v); - }; - - template - bool get_if(csubstr name, T *var) const - { - auto ch = find_child(name); - if(!ch.valid()) - return false; - ch >> *var; - return true; - } - - template - bool get_if(csubstr name, T *var, T const& fallback) const - { - auto ch = find_child(name); - if(ch.valid()) - { - ch >> *var; - return true; - } - else - { - *var = fallback; - return false; - } - } - - /** @} */ - -public: - - #if defined(__clang__) - # pragma clang diagnostic push - # pragma clang diagnostic ignored "-Wnull-dereference" - #elif defined(__GNUC__) - # pragma GCC diagnostic push - # if __GNUC__ >= 6 - # pragma GCC diagnostic ignored "-Wnull-dereference" - # endif - #endif - - /** @name iteration */ - /** @{ */ - - using iterator = detail::child_iterator; - using const_iterator = detail::child_iterator; - using children_view = detail::children_view_; - using const_children_view = detail::children_view_; - - template - C4_ALWAYS_INLINE C4_PURE auto begin() noexcept -> _C4_IF_MUTABLE(iterator) { _C4RV(); return iterator(tree__, tree__->first_child(id__)); } - C4_ALWAYS_INLINE C4_PURE const_iterator begin() const noexcept { _C4RV(); return const_iterator(tree_, tree_->first_child(id_)); } - C4_ALWAYS_INLINE C4_PURE const_iterator cbegin() const noexcept { _C4RV(); return const_iterator(tree_, tree_->first_child(id_)); } - - template - C4_ALWAYS_INLINE C4_PURE auto end() noexcept -> _C4_IF_MUTABLE(iterator) { _C4RV(); return iterator(tree__, NONE); } - C4_ALWAYS_INLINE C4_PURE const_iterator end() const noexcept { _C4RV(); return const_iterator(tree_, NONE); } - C4_ALWAYS_INLINE C4_PURE const_iterator cend() const noexcept { _C4RV(); return const_iterator(tree_, tree_->first_child(id_)); } - - /** get an iterable view over children */ - template - C4_ALWAYS_INLINE C4_PURE auto children() noexcept -> _C4_IF_MUTABLE(children_view) { _C4RV(); return children_view(begin(), end()); } - /** get an iterable view over children */ - C4_ALWAYS_INLINE C4_PURE const_children_view children() const noexcept { _C4RV(); return const_children_view(begin(), end()); } - /** get an iterable view over children */ - C4_ALWAYS_INLINE C4_PURE const_children_view cchildren() const noexcept { _C4RV(); return const_children_view(begin(), end()); } - - /** get an iterable view over all siblings (including the calling node) */ - template - C4_ALWAYS_INLINE C4_PURE auto siblings() noexcept -> _C4_IF_MUTABLE(children_view) - { - _C4RV(); - NodeData const *nd = tree__->get(id__); - return (nd->m_parent != NONE) ? // does it have a parent? - children_view(iterator(tree__, tree_->get(nd->m_parent)->m_first_child), iterator(tree__, NONE)) - : - children_view(end(), end()); - } - /** get an iterable view over all siblings (including the calling node) */ - C4_ALWAYS_INLINE C4_PURE const_children_view siblings() const noexcept - { - _C4RV(); - NodeData const *nd = tree_->get(id_); - return (nd->m_parent != NONE) ? // does it have a parent? - const_children_view(const_iterator(tree_, tree_->get(nd->m_parent)->m_first_child), const_iterator(tree_, NONE)) - : - const_children_view(end(), end()); - } - /** get an iterable view over all siblings (including the calling node) */ - C4_ALWAYS_INLINE C4_PURE const_children_view csiblings() const noexcept { return siblings(); } - - /** visit every child node calling fn(node) */ - template - C4_ALWAYS_INLINE C4_PURE bool visit(Visitor fn, size_t indentation_level=0, bool skip_root=true) const noexcept - { - return detail::_visit(*(ConstImpl*)this, fn, indentation_level, skip_root); - } - /** visit every child node calling fn(node) */ - template - auto visit(Visitor fn, size_t indentation_level=0, bool skip_root=true) noexcept - -> _C4_IF_MUTABLE(bool) - { - return detail::_visit(*(Impl*)this, fn, indentation_level, skip_root); - } - - /** visit every child node calling fn(node, level) */ - template - C4_ALWAYS_INLINE C4_PURE bool visit_stacked(Visitor fn, size_t indentation_level=0, bool skip_root=true) const noexcept - { - return detail::_visit_stacked(*(ConstImpl*)this, fn, indentation_level, skip_root); - } - /** visit every child node calling fn(node, level) */ - template - auto visit_stacked(Visitor fn, size_t indentation_level=0, bool skip_root=true) noexcept - -> _C4_IF_MUTABLE(bool) - { - return detail::_visit_stacked(*(Impl*)this, fn, indentation_level, skip_root); - } - - /** @} */ - - #if defined(__clang__) - # pragma clang diagnostic pop - #elif defined(__GNUC__) - # pragma GCC diagnostic pop - #endif - - #undef _C4_IF_MUTABLE - #undef _C4RV - #undef tree_ - #undef tree__ - #undef id_ - #undef id__ - - C4_SUPPRESS_WARNING_GCC_CLANG_POP -}; - -} // namespace detail - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -class RYML_EXPORT ConstNodeRef : public detail::RoNodeMethods -{ -public: - - using tree_type = Tree const; - -public: - - Tree const* C4_RESTRICT m_tree; - size_t m_id; - - friend NodeRef; - friend struct detail::RoNodeMethods; - -public: - - /** @name construction */ - /** @{ */ - - ConstNodeRef() : m_tree(nullptr), m_id(NONE) {} - ConstNodeRef(Tree const &t) : m_tree(&t), m_id(t .root_id()) {} - ConstNodeRef(Tree const *t) : m_tree(t ), m_id(t->root_id()) {} - ConstNodeRef(Tree const *t, size_t id) : m_tree(t), m_id(id) {} - ConstNodeRef(std::nullptr_t) : m_tree(nullptr), m_id(NONE) {} - - ConstNodeRef(ConstNodeRef const&) = default; - ConstNodeRef(ConstNodeRef &&) = default; - - ConstNodeRef(NodeRef const&); - ConstNodeRef(NodeRef &&); - - /** @} */ - -public: - - /** @name assignment */ - /** @{ */ - - ConstNodeRef& operator= (std::nullptr_t) { m_tree = nullptr; m_id = NONE; return *this; } - - ConstNodeRef& operator= (ConstNodeRef const&) = default; - ConstNodeRef& operator= (ConstNodeRef &&) = default; - - ConstNodeRef& operator= (NodeRef const&); - ConstNodeRef& operator= (NodeRef &&); - - - /** @} */ - -public: - - /** @name state queries */ - /** @{ */ - - C4_ALWAYS_INLINE C4_PURE bool valid() const noexcept { return m_tree != nullptr && m_id != NONE; } - - /** @} */ - -public: - - /** @name member getters */ - /** @{ */ - - C4_ALWAYS_INLINE C4_PURE Tree const* tree() const noexcept { return m_tree; } - C4_ALWAYS_INLINE C4_PURE size_t id() const noexcept { return m_id; } - - /** @} */ - -public: - - /** @name comparisons */ - /** @{ */ - - C4_ALWAYS_INLINE C4_PURE bool operator== (ConstNodeRef const& that) const noexcept { RYML_ASSERT(that.m_tree == m_tree); return m_id == that.m_id; } - C4_ALWAYS_INLINE C4_PURE bool operator!= (ConstNodeRef const& that) const noexcept { RYML_ASSERT(that.m_tree == m_tree); return ! this->operator==(that); } - - C4_ALWAYS_INLINE C4_PURE bool operator== (std::nullptr_t) const noexcept { return m_tree == nullptr || m_id == NONE; } - C4_ALWAYS_INLINE C4_PURE bool operator!= (std::nullptr_t) const noexcept { return ! this->operator== (nullptr); } - - C4_ALWAYS_INLINE C4_PURE bool operator== (csubstr val) const noexcept { RYML_ASSERT(has_val()); return m_tree->val(m_id) == val; } - C4_ALWAYS_INLINE C4_PURE bool operator!= (csubstr val) const noexcept { RYML_ASSERT(has_val()); return m_tree->val(m_id) != val; } - - /** @} */ - -}; - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -/** a reference to a node in an existing yaml tree, offering a more - * convenient API than the index-based API used in the tree. */ -class RYML_EXPORT NodeRef : public detail::RoNodeMethods -{ -public: - - using tree_type = Tree; - using base_type = detail::RoNodeMethods; - -private: - - Tree *C4_RESTRICT m_tree; - size_t m_id; - - /** This member is used to enable lazy operator[] writing. When a child - * with a key or index is not found, m_id is set to the id of the parent - * and the asked-for key or index are stored in this member until a write - * does happen. Then it is given as key or index for creating the child. - * When a key is used, the csubstr stores it (so the csubstr's string is - * non-null and the csubstr's size is different from NONE). When an index is - * used instead, the csubstr's string is set to null, and only the csubstr's - * size is set to a value different from NONE. Otherwise, when operator[] - * does find the child then this member is empty: the string is null and - * the size is NONE. */ - csubstr m_seed; - - friend ConstNodeRef; - friend struct detail::RoNodeMethods; - - // require valid: a helper macro, undefined at the end - #define _C4RV() \ - RYML_ASSERT(m_tree != nullptr); \ - _RYML_CB_ASSERT(m_tree->m_callbacks, m_id != NONE && !is_seed()) - -public: - - /** @name construction */ - /** @{ */ - - NodeRef() : m_tree(nullptr), m_id(NONE), m_seed() { _clear_seed(); } - NodeRef(Tree &t) : m_tree(&t), m_id(t .root_id()), m_seed() { _clear_seed(); } - NodeRef(Tree *t) : m_tree(t ), m_id(t->root_id()), m_seed() { _clear_seed(); } - NodeRef(Tree *t, size_t id) : m_tree(t), m_id(id), m_seed() { _clear_seed(); } - NodeRef(Tree *t, size_t id, size_t seed_pos) : m_tree(t), m_id(id), m_seed() { m_seed.str = nullptr; m_seed.len = seed_pos; } - NodeRef(Tree *t, size_t id, csubstr seed_key) : m_tree(t), m_id(id), m_seed(seed_key) {} - NodeRef(std::nullptr_t) : m_tree(nullptr), m_id(NONE), m_seed() {} - - /** @} */ - -public: - - /** @name assignment */ - /** @{ */ - - NodeRef(NodeRef const&) = default; - NodeRef(NodeRef &&) = default; - - NodeRef& operator= (NodeRef const&) = default; - NodeRef& operator= (NodeRef &&) = default; - - /** @} */ - -public: - - /** @name state queries */ - /** @{ */ - - inline bool valid() const { return m_tree != nullptr && m_id != NONE; } - inline bool is_seed() const { return m_seed.str != nullptr || m_seed.len != NONE; } - - inline void _clear_seed() { /*do this manually or an assert is triggered*/ m_seed.str = nullptr; m_seed.len = NONE; } - - /** @} */ - -public: - - /** @name comparisons */ - /** @{ */ - - inline bool operator== (NodeRef const& that) const { _C4RV(); RYML_ASSERT(that.valid() && !that.is_seed()); RYML_ASSERT(that.m_tree == m_tree); return m_id == that.m_id; } - inline bool operator!= (NodeRef const& that) const { return ! this->operator==(that); } - - inline bool operator== (ConstNodeRef const& that) const { _C4RV(); RYML_ASSERT(that.valid()); RYML_ASSERT(that.m_tree == m_tree); return m_id == that.m_id; } - inline bool operator!= (ConstNodeRef const& that) const { return ! this->operator==(that); } - - inline bool operator== (std::nullptr_t) const { return m_tree == nullptr || m_id == NONE || is_seed(); } - inline bool operator!= (std::nullptr_t) const { return m_tree != nullptr && m_id != NONE && !is_seed(); } - - inline bool operator== (csubstr val) const { _C4RV(); RYML_ASSERT(has_val()); return m_tree->val(m_id) == val; } - inline bool operator!= (csubstr val) const { _C4RV(); RYML_ASSERT(has_val()); return m_tree->val(m_id) != val; } - - //inline operator bool () const { return m_tree == nullptr || m_id == NONE || is_seed(); } - - /** @} */ - -public: - - /** @name node property getters */ - /** @{ */ - - C4_ALWAYS_INLINE C4_PURE Tree * tree() noexcept { return m_tree; } - C4_ALWAYS_INLINE C4_PURE Tree const* tree() const noexcept { return m_tree; } - - C4_ALWAYS_INLINE C4_PURE size_t id() const noexcept { return m_id; } - - /** @} */ - -public: - - /** @name node modifiers */ - /** @{ */ - - void change_type(NodeType t) { _C4RV(); m_tree->change_type(m_id, t); } - - void set_type(NodeType t) { _C4RV(); m_tree->_set_flags(m_id, t); } - void set_key(csubstr key) { _C4RV(); m_tree->_set_key(m_id, key); } - void set_val(csubstr val) { _C4RV(); m_tree->_set_val(m_id, val); } - void set_key_tag(csubstr key_tag) { _C4RV(); m_tree->set_key_tag(m_id, key_tag); } - void set_val_tag(csubstr val_tag) { _C4RV(); m_tree->set_val_tag(m_id, val_tag); } - void set_key_anchor(csubstr key_anchor) { _C4RV(); m_tree->set_key_anchor(m_id, key_anchor); } - void set_val_anchor(csubstr val_anchor) { _C4RV(); m_tree->set_val_anchor(m_id, val_anchor); } - void set_key_ref(csubstr key_ref) { _C4RV(); m_tree->set_key_ref(m_id, key_ref); } - void set_val_ref(csubstr val_ref) { _C4RV(); m_tree->set_val_ref(m_id, val_ref); } - - template - size_t set_key_serialized(T const& C4_RESTRICT k) - { - _C4RV(); - csubstr s = m_tree->to_arena(k); - m_tree->_set_key(m_id, s); - return s.len; - } - template - size_t set_val_serialized(T const& C4_RESTRICT v) - { - _C4RV(); - csubstr s = m_tree->to_arena(v); - m_tree->_set_val(m_id, s); - return s.len; - } - size_t set_val_serialized(std::nullptr_t) - { - _C4RV(); - m_tree->_set_val(m_id, csubstr{}); - return 0; - } - - /** encode a blob as base64, then assign the result to the node's key - * @return the size of base64-encoded blob */ - size_t set_key_serialized(fmt::const_base64_wrapper w); - /** encode a blob as base64, then assign the result to the node's val - * @return the size of base64-encoded blob */ - size_t set_val_serialized(fmt::const_base64_wrapper w); - -public: - - inline void clear() - { - if(is_seed()) - return; - m_tree->remove_children(m_id); - m_tree->_clear(m_id); - } - - inline void clear_key() - { - if(is_seed()) - return; - m_tree->_clear_key(m_id); - } - - inline void clear_val() - { - if(is_seed()) - return; - m_tree->_clear_val(m_id); - } - - inline void clear_children() - { - if(is_seed()) - return; - m_tree->remove_children(m_id); - } - - void create() { _apply_seed(); } - - inline void operator= (NodeType_e t) - { - _apply_seed(); - m_tree->_add_flags(m_id, t); - } - - inline void operator|= (NodeType_e t) - { - _apply_seed(); - m_tree->_add_flags(m_id, t); - } - - inline void operator= (NodeInit const& v) - { - _apply_seed(); - _apply(v); - } - - inline void operator= (NodeScalar const& v) - { - _apply_seed(); - _apply(v); - } - - inline void operator= (std::nullptr_t) - { - _apply_seed(); - _apply(csubstr{}); - } - - inline void operator= (csubstr v) - { - _apply_seed(); - _apply(v); - } - - template - inline void operator= (const char (&v)[N]) - { - _apply_seed(); - csubstr sv; - sv.assign(v); - _apply(sv); - } - - /** @} */ - -public: - - /** @name serialization */ - /** @{ */ - - /** serialize a variable to the arena */ - template - inline csubstr to_arena(T const& C4_RESTRICT s) - { - _C4RV(); - return m_tree->to_arena(s); - } - - /** serialize a variable, then assign the result to the node's val */ - inline NodeRef& operator<< (csubstr s) - { - // this overload is needed to prevent ambiguity (there's also - // operator<< for writing a substr to a stream) - _apply_seed(); - write(this, s); - RYML_ASSERT(val() == s); - return *this; - } - - template - inline NodeRef& operator<< (T const& C4_RESTRICT v) - { - _apply_seed(); - write(this, v); - return *this; - } - - /** serialize a variable, then assign the result to the node's key */ - template - inline NodeRef& operator<< (Key const& C4_RESTRICT v) - { - _apply_seed(); - set_key_serialized(v.k); - return *this; - } - - /** serialize a variable, then assign the result to the node's key */ - template - inline NodeRef& operator<< (Key const& C4_RESTRICT v) - { - _apply_seed(); - set_key_serialized(v.k); - return *this; - } - - NodeRef& operator<< (Key w) - { - set_key_serialized(w.wrapper); - return *this; - } - - NodeRef& operator<< (fmt::const_base64_wrapper w) - { - set_val_serialized(w); - return *this; - } - - /** @} */ - -private: - - void _apply_seed() - { - if(m_seed.str) // we have a seed key: use it to create the new child - { - //RYML_ASSERT(i.key.scalar.empty() || m_key == i.key.scalar || m_key.empty()); - m_id = m_tree->append_child(m_id); - m_tree->_set_key(m_id, m_seed); - m_seed.str = nullptr; - m_seed.len = NONE; - } - else if(m_seed.len != NONE) // we have a seed index: create a child at that position - { - RYML_ASSERT(m_tree->num_children(m_id) == m_seed.len); - m_id = m_tree->append_child(m_id); - m_seed.str = nullptr; - m_seed.len = NONE; - } - else - { - RYML_ASSERT(valid()); - } - } - - inline void _apply(csubstr v) - { - m_tree->_set_val(m_id, v); - } - - inline void _apply(NodeScalar const& v) - { - m_tree->_set_val(m_id, v); - } - - inline void _apply(NodeInit const& i) - { - m_tree->_set(m_id, i); - } - -public: - - /** @name modification of hierarchy */ - /** @{ */ - - inline NodeRef insert_child(NodeRef after) - { - _C4RV(); - RYML_ASSERT(after.m_tree == m_tree); - NodeRef r(m_tree, m_tree->insert_child(m_id, after.m_id)); - return r; - } - - inline NodeRef insert_child(NodeInit const& i, NodeRef after) - { - _C4RV(); - RYML_ASSERT(after.m_tree == m_tree); - NodeRef r(m_tree, m_tree->insert_child(m_id, after.m_id)); - r._apply(i); - return r; - } - - inline NodeRef prepend_child() - { - _C4RV(); - NodeRef r(m_tree, m_tree->insert_child(m_id, NONE)); - return r; - } - - inline NodeRef prepend_child(NodeInit const& i) - { - _C4RV(); - NodeRef r(m_tree, m_tree->insert_child(m_id, NONE)); - r._apply(i); - return r; - } - - inline NodeRef append_child() - { - _C4RV(); - NodeRef r(m_tree, m_tree->append_child(m_id)); - return r; - } - - inline NodeRef append_child(NodeInit const& i) - { - _C4RV(); - NodeRef r(m_tree, m_tree->append_child(m_id)); - r._apply(i); - return r; - } - -public: - - inline NodeRef insert_sibling(ConstNodeRef const& after) - { - _C4RV(); - RYML_ASSERT(after.m_tree == m_tree); - NodeRef r(m_tree, m_tree->insert_sibling(m_id, after.m_id)); - return r; - } - - inline NodeRef insert_sibling(NodeInit const& i, ConstNodeRef const& after) - { - _C4RV(); - RYML_ASSERT(after.m_tree == m_tree); - NodeRef r(m_tree, m_tree->insert_sibling(m_id, after.m_id)); - r._apply(i); - return r; - } - - inline NodeRef prepend_sibling() - { - _C4RV(); - NodeRef r(m_tree, m_tree->prepend_sibling(m_id)); - return r; - } - - inline NodeRef prepend_sibling(NodeInit const& i) - { - _C4RV(); - NodeRef r(m_tree, m_tree->prepend_sibling(m_id)); - r._apply(i); - return r; - } - - inline NodeRef append_sibling() - { - _C4RV(); - NodeRef r(m_tree, m_tree->append_sibling(m_id)); - return r; - } - - inline NodeRef append_sibling(NodeInit const& i) - { - _C4RV(); - NodeRef r(m_tree, m_tree->append_sibling(m_id)); - r._apply(i); - return r; - } - -public: - - inline void remove_child(NodeRef & child) - { - _C4RV(); - RYML_ASSERT(has_child(child)); - RYML_ASSERT(child.parent().id() == id()); - m_tree->remove(child.id()); - child.clear(); - } - - //! remove the nth child of this node - inline void remove_child(size_t pos) - { - _C4RV(); - RYML_ASSERT(pos >= 0 && pos < num_children()); - size_t child = m_tree->child(m_id, pos); - RYML_ASSERT(child != NONE); - m_tree->remove(child); - } - - //! remove a child by name - inline void remove_child(csubstr key) - { - _C4RV(); - size_t child = m_tree->find_child(m_id, key); - RYML_ASSERT(child != NONE); - m_tree->remove(child); - } - -public: - - /** change the node's position within its parent, placing it after - * @p after. To move to the first position in the parent, simply - * pass an empty or default-constructed reference like this: - * `n.move({})`. */ - inline void move(ConstNodeRef const& after) - { - _C4RV(); - m_tree->move(m_id, after.m_id); - } - - /** move the node to a different @p parent (which may belong to a - * different tree), placing it after @p after. When the - * destination parent is in a new tree, then this node's tree - * pointer is reset to the tree of the parent node. */ - inline void move(NodeRef const& parent, ConstNodeRef const& after) - { - _C4RV(); - if(parent.m_tree == m_tree) - { - m_tree->move(m_id, parent.m_id, after.m_id); - } - else - { - parent.m_tree->move(m_tree, m_id, parent.m_id, after.m_id); - m_tree = parent.m_tree; - } - } - - /** duplicate the current node somewhere within its parent, and - * place it after the node @p after. To place into the first - * position of the parent, simply pass an empty or - * default-constructed reference like this: `n.move({})`. */ - inline NodeRef duplicate(ConstNodeRef const& after) const - { - _C4RV(); - RYML_ASSERT(m_tree == after.m_tree || after.m_id == NONE); - size_t dup = m_tree->duplicate(m_id, m_tree->parent(m_id), after.m_id); - NodeRef r(m_tree, dup); - return r; - } - - /** duplicate the current node somewhere into a different @p parent - * (possibly from a different tree), and place it after the node - * @p after. To place into the first position of the parent, - * simply pass an empty or default-constructed reference like - * this: `n.move({})`. */ - inline NodeRef duplicate(NodeRef const& parent, ConstNodeRef const& after) const - { - _C4RV(); - RYML_ASSERT(parent.m_tree == after.m_tree || after.m_id == NONE); - if(parent.m_tree == m_tree) - { - size_t dup = m_tree->duplicate(m_id, parent.m_id, after.m_id); - NodeRef r(m_tree, dup); - return r; - } - else - { - size_t dup = parent.m_tree->duplicate(m_tree, m_id, parent.m_id, after.m_id); - NodeRef r(parent.m_tree, dup); - return r; - } - } - - inline void duplicate_children(NodeRef const& parent, ConstNodeRef const& after) const - { - _C4RV(); - RYML_ASSERT(parent.m_tree == after.m_tree); - if(parent.m_tree == m_tree) - { - m_tree->duplicate_children(m_id, parent.m_id, after.m_id); - } - else - { - parent.m_tree->duplicate_children(m_tree, m_id, parent.m_id, after.m_id); - } - } - - /** @} */ - -#undef _C4RV -}; - - -//----------------------------------------------------------------------------- - -inline ConstNodeRef::ConstNodeRef(NodeRef const& that) - : m_tree(that.m_tree) - , m_id(!that.is_seed() ? that.id() : NONE) -{ -} - -inline ConstNodeRef::ConstNodeRef(NodeRef && that) - : m_tree(that.m_tree) - , m_id(!that.is_seed() ? that.id() : NONE) -{ -} - - -inline ConstNodeRef& ConstNodeRef::operator= (NodeRef const& that) -{ - m_tree = (that.m_tree); - m_id = (!that.is_seed() ? that.id() : NONE); - return *this; -} - -inline ConstNodeRef& ConstNodeRef::operator= (NodeRef && that) -{ - m_tree = (that.m_tree); - m_id = (!that.is_seed() ? that.id() : NONE); - return *this; -} - - -//----------------------------------------------------------------------------- - -template -inline void write(NodeRef *n, T const& v) -{ - n->set_val_serialized(v); -} - -template -typename std::enable_if< ! std::is_floating_point::value, bool>::type -inline read(NodeRef const& n, T *v) -{ - return from_chars(n.val(), v); -} -template -typename std::enable_if< ! std::is_floating_point::value, bool>::type -inline read(ConstNodeRef const& n, T *v) -{ - return from_chars(n.val(), v); -} - -template -typename std::enable_if::value, bool>::type -inline read(NodeRef const& n, T *v) -{ - return from_chars_float(n.val(), v); -} -template -typename std::enable_if::value, bool>::type -inline read(ConstNodeRef const& n, T *v) -{ - return from_chars_float(n.val(), v); -} - - -} // namespace yml -} // namespace c4 - - -#if defined(_MSC_VER) -# pragma warning(pop) -#endif - -#ifdef __GNUC__ -# pragma GCC diagnostic pop -#endif - -#endif /* _C4_YML_NODE_HPP_ */ - - -// (end https://github.com/biojppm/rapidyaml/src/c4/yml/node.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/yml/writer.hpp -// https://github.com/biojppm/rapidyaml/src/c4/yml/writer.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_YML_WRITER_HPP_ -#define _C4_YML_WRITER_HPP_ - -#ifndef _C4_YML_COMMON_HPP_ -#include "./common.hpp" -#endif - -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/substr.hpp -//#include -#if !defined(C4_SUBSTR_HPP_) && !defined(_C4_SUBSTR_HPP_) -#error "amalgamate: file c4/substr.hpp must have been included at this point" -#endif /* C4_SUBSTR_HPP_ */ - -//included above: -//#include // fwrite(), fputc() -//included above: -//#include // memcpy() - - -namespace c4 { -namespace yml { - - -/** Repeat-Character: a character to be written a number of times. */ -struct RepC -{ - char c; - size_t num_times; -}; -inline RepC indent_to(size_t num_levels) -{ - return {' ', size_t(2) * num_levels}; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -/** A writer that outputs to a file. Defaults to stdout. */ -struct WriterFile -{ - FILE * m_file; - size_t m_pos; - - WriterFile(FILE *f = nullptr) : m_file(f ? f : stdout), m_pos(0) {} - - inline substr _get(bool /*error_on_excess*/) - { - substr sp; - sp.str = nullptr; - sp.len = m_pos; - return sp; - } - - template - inline void _do_write(const char (&a)[N]) - { - fwrite(a, sizeof(char), N - 1, m_file); - m_pos += N - 1; - } - - inline void _do_write(csubstr sp) - { - #if defined(__clang__) - # pragma clang diagnostic push - # pragma GCC diagnostic ignored "-Wsign-conversion" - #elif defined(__GNUC__) - # pragma GCC diagnostic push - # pragma GCC diagnostic ignored "-Wsign-conversion" - #endif - if(sp.empty()) return; - fwrite(sp.str, sizeof(csubstr::char_type), sp.len, m_file); - m_pos += sp.len; - #if defined(__clang__) - # pragma clang diagnostic pop - #elif defined(__GNUC__) - # pragma GCC diagnostic pop - #endif - } - - inline void _do_write(const char c) - { - fputc(c, m_file); - ++m_pos; - } - - inline void _do_write(RepC const rc) - { - for(size_t i = 0; i < rc.num_times; ++i) - { - fputc(rc.c, m_file); - } - m_pos += rc.num_times; - } -}; - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -/** A writer that outputs to an STL-like ostream. */ -template -struct WriterOStream -{ - OStream& m_stream; - size_t m_pos; - - WriterOStream(OStream &s) : m_stream(s), m_pos(0) {} - - inline substr _get(bool /*error_on_excess*/) - { - substr sp; - sp.str = nullptr; - sp.len = m_pos; - return sp; - } - - template - inline void _do_write(const char (&a)[N]) - { - m_stream.write(a, N - 1); - m_pos += N - 1; - } - - inline void _do_write(csubstr sp) - { - #if defined(__clang__) - # pragma clang diagnostic push - # pragma GCC diagnostic ignored "-Wsign-conversion" - #elif defined(__GNUC__) - # pragma GCC diagnostic push - # pragma GCC diagnostic ignored "-Wsign-conversion" - #endif - if(sp.empty()) return; - m_stream.write(sp.str, sp.len); - m_pos += sp.len; - #if defined(__clang__) - # pragma clang diagnostic pop - #elif defined(__GNUC__) - # pragma GCC diagnostic pop - #endif - } - - inline void _do_write(const char c) - { - m_stream.put(c); - ++m_pos; - } - - inline void _do_write(RepC const rc) - { - for(size_t i = 0; i < rc.num_times; ++i) - { - m_stream.put(rc.c); - } - m_pos += rc.num_times; - } -}; - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -/** a writer to a substr */ -struct WriterBuf -{ - substr m_buf; - size_t m_pos; - - WriterBuf(substr sp) : m_buf(sp), m_pos(0) {} - - inline substr _get(bool error_on_excess) - { - if(m_pos <= m_buf.len) - { - return m_buf.first(m_pos); - } - if(error_on_excess) - { - c4::yml::error("not enough space in the given buffer"); - } - substr sp; - sp.str = nullptr; - sp.len = m_pos; - return sp; - } - - template - inline void _do_write(const char (&a)[N]) - { - RYML_ASSERT( ! m_buf.overlaps(a)); - if(m_pos + N-1 <= m_buf.len) - { - memcpy(&(m_buf[m_pos]), a, N-1); - } - m_pos += N-1; - } - - inline void _do_write(csubstr sp) - { - if(sp.empty()) return; - RYML_ASSERT( ! sp.overlaps(m_buf)); - if(m_pos + sp.len <= m_buf.len) - { - memcpy(&(m_buf[m_pos]), sp.str, sp.len); - } - m_pos += sp.len; - } - - inline void _do_write(const char c) - { - if(m_pos + 1 <= m_buf.len) - { - m_buf[m_pos] = c; - } - ++m_pos; - } - - inline void _do_write(RepC const rc) - { - if(m_pos + rc.num_times <= m_buf.len) - { - for(size_t i = 0; i < rc.num_times; ++i) - { - m_buf[m_pos + i] = rc.c; - } - } - m_pos += rc.num_times; - } -}; - - -} // namespace yml -} // namespace c4 - -#endif /* _C4_YML_WRITER_HPP_ */ - - -// (end https://github.com/biojppm/rapidyaml/src/c4/yml/writer.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/yml/detail/parser_dbg.hpp -// https://github.com/biojppm/rapidyaml/src/c4/yml/detail/parser_dbg.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_YML_DETAIL_PARSER_DBG_HPP_ -#define _C4_YML_DETAIL_PARSER_DBG_HPP_ - -#ifndef _C4_YML_COMMON_HPP_ -#include "../common.hpp" -#endif -//included above: -//#include - -//----------------------------------------------------------------------------- -// some debugging scaffolds - -#if defined(_MSC_VER) -# pragma warning(push) -# pragma warning(disable: 4068/*unknown pragma*/) -#endif - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunknown-pragmas" -//#pragma GCC diagnostic ignored "-Wpragma-system-header-outside-header" -#pragma GCC system_header - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Werror" -#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" - -// some debugging scaffolds -#ifdef RYML_DBG -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/dump.hpp -//#include -#if !defined(C4_DUMP_HPP_) && !defined(_C4_DUMP_HPP_) -#error "amalgamate: file c4/dump.hpp must have been included at this point" -#endif /* C4_DUMP_HPP_ */ - -namespace c4 { -inline void _dbg_dumper(csubstr s) { fwrite(s.str, 1, s.len, stdout); }; -template -void _dbg_printf(c4::csubstr fmt, Args&& ...args) -{ - static char writebuf[256]; - auto results = c4::format_dump_resume<&_dbg_dumper>(writebuf, fmt, std::forward(args)...); - // resume writing if the results failed to fit the buffer - if(C4_UNLIKELY(results.bufsize > sizeof(writebuf))) // bufsize will be that of the largest element serialized. Eg int(1), will require 1 byte. - { - results = format_dump_resume<&_dbg_dumper>(results, writebuf, fmt, std::forward(args)...); - if(C4_UNLIKELY(results.bufsize > sizeof(writebuf))) - { - results = format_dump_resume<&_dbg_dumper>(results, writebuf, fmt, std::forward(args)...); - } - } -} -} // namespace c4 - -# define _c4dbgt(fmt, ...) this->_dbg ("{}:{}: " fmt , __FILE__, __LINE__, ## __VA_ARGS__) -# define _c4dbgpf(fmt, ...) _dbg_printf("{}:{}: " fmt "\n", __FILE__, __LINE__, ## __VA_ARGS__) -# define _c4dbgp(msg) _dbg_printf("{}:{}: " msg "\n", __FILE__, __LINE__ ) -# define _c4dbgq(msg) _dbg_printf(msg "\n") -# define _c4err(fmt, ...) \ - do { if(c4::is_debugger_attached()) { C4_DEBUG_BREAK(); } \ - this->_err("ERROR:\n" "{}:{}: " fmt, __FILE__, __LINE__, ## __VA_ARGS__); } while(0) -#else -# define _c4dbgt(fmt, ...) -# define _c4dbgpf(fmt, ...) -# define _c4dbgp(msg) -# define _c4dbgq(msg) -# define _c4err(fmt, ...) \ - do { if(c4::is_debugger_attached()) { C4_DEBUG_BREAK(); } \ - this->_err("ERROR: " fmt, ## __VA_ARGS__); } while(0) -#endif - -#define _c4prsp(sp) sp -#define _c4presc(s) __c4presc(s.str, s.len) -inline c4::csubstr _c4prc(const char &C4_RESTRICT c) -{ - switch(c) - { - case '\n': return c4::csubstr("\\n"); - case '\t': return c4::csubstr("\\t"); - case '\0': return c4::csubstr("\\0"); - case '\r': return c4::csubstr("\\r"); - case '\f': return c4::csubstr("\\f"); - case '\b': return c4::csubstr("\\b"); - case '\v': return c4::csubstr("\\v"); - case '\a': return c4::csubstr("\\a"); - default: return c4::csubstr(&c, 1); - } -} -inline void __c4presc(const char *s, size_t len) -{ - size_t prev = 0; - for(size_t i = 0; i < len; ++i) - { - switch(s[i]) - { - case '\n' : fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('n'); putchar('\n'); prev = i+1; break; - case '\t' : fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('t'); prev = i+1; break; - case '\0' : fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('0'); prev = i+1; break; - case '\r' : fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('r'); prev = i+1; break; - case '\f' : fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('f'); prev = i+1; break; - case '\b' : fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('b'); prev = i+1; break; - case '\v' : fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('v'); prev = i+1; break; - case '\a' : fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('a'); prev = i+1; break; - case '\x1b': fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('e'); prev = i+1; break; - case -0x3e/*0xc2u*/: - if(i+1 < len) - { - if(s[i+1] == -0x60/*0xa0u*/) - { - fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('_'); prev = i+2; ++i; - } - else if(s[i+1] == -0x7b/*0x85u*/) - { - fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('N'); prev = i+2; ++i; - } - break; - } - case -0x1e/*0xe2u*/: - if(i+2 < len && s[i+1] == -0x80/*0x80u*/) - { - if(s[i+2] == -0x58/*0xa8u*/) - { - fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('L'); prev = i+3; i += 2; - } - else if(s[i+2] == -0x57/*0xa9u*/) - { - fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('P'); prev = i+3; i += 2; - } - break; - } - } - } - fwrite(s + prev, 1, len - prev, stdout); -} - -#pragma clang diagnostic pop -#pragma GCC diagnostic pop - -#if defined(_MSC_VER) -# pragma warning(pop) -#endif - - -#endif /* _C4_YML_DETAIL_PARSER_DBG_HPP_ */ - - -// (end https://github.com/biojppm/rapidyaml/src/c4/yml/detail/parser_dbg.hpp) - -#define C4_YML_EMIT_DEF_HPP_ - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/yml/emit.hpp -// https://github.com/biojppm/rapidyaml/src/c4/yml/emit.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_YML_EMIT_HPP_ -#define _C4_YML_EMIT_HPP_ - -#ifndef _C4_YML_WRITER_HPP_ -#include "./writer.hpp" -#endif - -#ifndef _C4_YML_TREE_HPP_ -#include "./tree.hpp" -#endif - -#ifndef _C4_YML_NODE_HPP_ -#include "./node.hpp" -#endif - - -#define RYML_DEPRECATE_EMIT \ - RYML_DEPRECATED("use emit_yaml() instead. See https://github.com/biojppm/rapidyaml/issues/120") -#ifdef emit -#error "emit is defined, likely from a Qt include. This will cause a compilation error. See https://github.com/biojppm/rapidyaml/issues/120" -#endif -#define RYML_DEPRECATE_EMITRS \ - RYML_DEPRECATED("use emitrs_yaml() instead. See https://github.com/biojppm/rapidyaml/issues/120") - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -namespace c4 { -namespace yml { - -template class Emitter; - -template -using EmitterOStream = Emitter>; -using EmitterFile = Emitter; -using EmitterBuf = Emitter; - -typedef enum { - EMIT_YAML = 0, - EMIT_JSON = 1 -} EmitType_e; - - -/** mark a tree or node to be emitted as json */ -struct as_json -{ - Tree const* tree; - size_t node; - as_json(Tree const& t) : tree(&t), node(t.empty() ? NONE : t.root_id()) {} - as_json(Tree const& t, size_t id) : tree(&t), node(id) {} - as_json(ConstNodeRef const& n) : tree(n.tree()), node(n.id()) {} -}; - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -template -class Emitter : public Writer -{ -public: - - using Writer::Writer; - - /** emit! - * - * When writing to a buffer, returns a substr of the emitted YAML. - * If the given buffer has insufficient space, the returned span will - * be null and its size will be the needed space. No writes are done - * after the end of the buffer. - * - * When writing to a file, the returned substr will be null, but its - * length will be set to the number of bytes written. */ - substr emit_as(EmitType_e type, Tree const& t, size_t id, bool error_on_excess); - /** emit starting at the root node */ - substr emit_as(EmitType_e type, Tree const& t, bool error_on_excess=true); - /** emit the given node */ - substr emit_as(EmitType_e type, ConstNodeRef const& n, bool error_on_excess=true); - -private: - - Tree const* C4_RESTRICT m_tree; - - void _emit_yaml(size_t id); - void _do_visit_flow_sl(size_t id, size_t ilevel=0); - void _do_visit_flow_ml(size_t id, size_t ilevel=0, size_t do_indent=1); - void _do_visit_block(size_t id, size_t ilevel=0, size_t do_indent=1); - void _do_visit_block_container(size_t id, size_t next_level, size_t do_indent); - void _do_visit_json(size_t id); - -private: - - void _write(NodeScalar const& C4_RESTRICT sc, NodeType flags, size_t level); - void _write_json(NodeScalar const& C4_RESTRICT sc, NodeType flags); - - void _write_doc(size_t id); - void _write_scalar(csubstr s, bool was_quoted); - void _write_scalar_json(csubstr s, bool as_key, bool was_quoted); - void _write_scalar_literal(csubstr s, size_t level, bool as_key, bool explicit_indentation=false); - void _write_scalar_folded(csubstr s, size_t level, bool as_key); - void _write_scalar_squo(csubstr s, size_t level); - void _write_scalar_dquo(csubstr s, size_t level); - void _write_scalar_plain(csubstr s, size_t level); - - void _write_tag(csubstr tag) - { - if(!tag.begins_with('!')) - this->Writer::_do_write('!'); - this->Writer::_do_write(tag); - } - - enum : type_bits { - _keysc = (KEY|KEYREF|KEYANCH|KEYQUO|_WIP_KEY_STYLE) | ~(VAL|VALREF|VALANCH|VALQUO|_WIP_VAL_STYLE), - _valsc = ~(KEY|KEYREF|KEYANCH|KEYQUO|_WIP_KEY_STYLE) | (VAL|VALREF|VALANCH|VALQUO|_WIP_VAL_STYLE), - _keysc_json = (KEY) | ~(VAL), - _valsc_json = ~(KEY) | (VAL), - }; - - C4_ALWAYS_INLINE void _writek(size_t id, size_t level) { _write(m_tree->keysc(id), m_tree->_p(id)->m_type.type & ~_valsc, level); } - C4_ALWAYS_INLINE void _writev(size_t id, size_t level) { _write(m_tree->valsc(id), m_tree->_p(id)->m_type.type & ~_keysc, level); } - - C4_ALWAYS_INLINE void _writek_json(size_t id) { _write_json(m_tree->keysc(id), m_tree->_p(id)->m_type.type & ~(VAL)); } - C4_ALWAYS_INLINE void _writev_json(size_t id) { _write_json(m_tree->valsc(id), m_tree->_p(id)->m_type.type & ~(KEY)); } - -}; - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -/** emit YAML to the given file. A null file defaults to stdout. - * Return the number of bytes written. */ -inline size_t emit_yaml(Tree const& t, size_t id, FILE *f) -{ - EmitterFile em(f); - return em.emit_as(EMIT_YAML, t, id, /*error_on_excess*/true).len; -} -RYML_DEPRECATE_EMIT inline size_t emit(Tree const& t, size_t id, FILE *f) -{ - return emit_yaml(t, id, f); -} - -/** emit JSON to the given file. A null file defaults to stdout. - * Return the number of bytes written. */ -inline size_t emit_json(Tree const& t, size_t id, FILE *f) -{ - EmitterFile em(f); - return em.emit_as(EMIT_JSON, t, id, /*error_on_excess*/true).len; -} - - -/** emit YAML to the given file. A null file defaults to stdout. - * Return the number of bytes written. - * @overload */ -inline size_t emit_yaml(Tree const& t, FILE *f=nullptr) -{ - EmitterFile em(f); - return em.emit_as(EMIT_YAML, t, /*error_on_excess*/true).len; -} -RYML_DEPRECATE_EMIT inline size_t emit(Tree const& t, FILE *f=nullptr) -{ - return emit_yaml(t, f); -} - -/** emit JSON to the given file. A null file defaults to stdout. - * Return the number of bytes written. - * @overload */ -inline size_t emit_json(Tree const& t, FILE *f=nullptr) -{ - EmitterFile em(f); - return em.emit_as(EMIT_JSON, t, /*error_on_excess*/true).len; -} - - -/** emit YAML to the given file. A null file defaults to stdout. - * Return the number of bytes written. - * @overload */ -inline size_t emit_yaml(ConstNodeRef const& r, FILE *f=nullptr) -{ - EmitterFile em(f); - return em.emit_as(EMIT_YAML, r, /*error_on_excess*/true).len; -} -RYML_DEPRECATE_EMIT inline size_t emit(ConstNodeRef const& r, FILE *f=nullptr) -{ - return emit_yaml(r, f); -} - -/** emit JSON to the given file. A null file defaults to stdout. - * Return the number of bytes written. - * @overload */ -inline size_t emit_json(ConstNodeRef const& r, FILE *f=nullptr) -{ - EmitterFile em(f); - return em.emit_as(EMIT_JSON, r, /*error_on_excess*/true).len; -} - - -//----------------------------------------------------------------------------- - -/** emit YAML to an STL-like ostream */ -template -inline OStream& operator<< (OStream& s, Tree const& t) -{ - EmitterOStream em(s); - em.emit_as(EMIT_YAML, t); - return s; -} - -/** emit YAML to an STL-like ostream - * @overload */ -template -inline OStream& operator<< (OStream& s, ConstNodeRef const& n) -{ - EmitterOStream em(s); - em.emit_as(EMIT_YAML, n); - return s; -} - -/** emit json to an STL-like stream */ -template -inline OStream& operator<< (OStream& s, as_json const& j) -{ - EmitterOStream em(s); - em.emit_as(EMIT_JSON, *j.tree, j.node, true); - return s; -} - - -//----------------------------------------------------------------------------- - - -/** emit YAML to the given buffer. Return a substr trimmed to the emitted YAML. - * @param error_on_excess Raise an error if the space in the buffer is insufficient. - * @overload */ -inline substr emit_yaml(Tree const& t, size_t id, substr buf, bool error_on_excess=true) -{ - EmitterBuf em(buf); - return em.emit_as(EMIT_YAML, t, id, error_on_excess); -} -RYML_DEPRECATE_EMIT inline substr emit(Tree const& t, size_t id, substr buf, bool error_on_excess=true) -{ - return emit_yaml(t, id, buf, error_on_excess); -} - -/** emit JSON to the given buffer. Return a substr trimmed to the emitted JSON. - * @param error_on_excess Raise an error if the space in the buffer is insufficient. - * @overload */ -inline substr emit_json(Tree const& t, size_t id, substr buf, bool error_on_excess=true) -{ - EmitterBuf em(buf); - return em.emit_as(EMIT_JSON, t, id, error_on_excess); -} - - -/** emit YAML to the given buffer. Return a substr trimmed to the emitted YAML. - * @param error_on_excess Raise an error if the space in the buffer is insufficient. - * @overload */ -inline substr emit_yaml(Tree const& t, substr buf, bool error_on_excess=true) -{ - EmitterBuf em(buf); - return em.emit_as(EMIT_YAML, t, error_on_excess); -} -RYML_DEPRECATE_EMIT inline substr emit(Tree const& t, substr buf, bool error_on_excess=true) -{ - return emit_yaml(t, buf, error_on_excess); -} - -/** emit JSON to the given buffer. Return a substr trimmed to the emitted JSON. - * @param error_on_excess Raise an error if the space in the buffer is insufficient. - * @overload */ -inline substr emit_json(Tree const& t, substr buf, bool error_on_excess=true) -{ - EmitterBuf em(buf); - return em.emit_as(EMIT_JSON, t, error_on_excess); -} - - -/** emit YAML to the given buffer. Return a substr trimmed to the emitted YAML. - * @param error_on_excess Raise an error if the space in the buffer is insufficient. - * @overload - */ -inline substr emit_yaml(ConstNodeRef const& r, substr buf, bool error_on_excess=true) -{ - EmitterBuf em(buf); - return em.emit_as(EMIT_YAML, r, error_on_excess); -} -RYML_DEPRECATE_EMIT inline substr emit(ConstNodeRef const& r, substr buf, bool error_on_excess=true) -{ - return emit_yaml(r, buf, error_on_excess); -} - -/** emit JSON to the given buffer. Return a substr trimmed to the emitted JSON. - * @param error_on_excess Raise an error if the space in the buffer is insufficient. - * @overload - */ -inline substr emit_json(ConstNodeRef const& r, substr buf, bool error_on_excess=true) -{ - EmitterBuf em(buf); - return em.emit_as(EMIT_JSON, r, error_on_excess); -} - - -//----------------------------------------------------------------------------- - -/** emit+resize: emit YAML to the given std::string/std::vector-like - * container, resizing it as needed to fit the emitted YAML. */ -template -substr emitrs_yaml(Tree const& t, size_t id, CharOwningContainer * cont) -{ - substr buf = to_substr(*cont); - substr ret = emit_yaml(t, id, buf, /*error_on_excess*/false); - if(ret.str == nullptr && ret.len > 0) - { - cont->resize(ret.len); - buf = to_substr(*cont); - ret = emit_yaml(t, id, buf, /*error_on_excess*/true); - } - return ret; -} -template -RYML_DEPRECATE_EMITRS substr emitrs(Tree const& t, size_t id, CharOwningContainer * cont) -{ - return emitrs_yaml(t, id, cont); -} - -/** emit+resize: emit JSON to the given std::string/std::vector-like - * container, resizing it as needed to fit the emitted JSON. */ -template -substr emitrs_json(Tree const& t, size_t id, CharOwningContainer * cont) -{ - substr buf = to_substr(*cont); - substr ret = emit_json(t, id, buf, /*error_on_excess*/false); - if(ret.str == nullptr && ret.len > 0) - { - cont->resize(ret.len); - buf = to_substr(*cont); - ret = emit_json(t, id, buf, /*error_on_excess*/true); - } - return ret; -} - - -/** emit+resize: emit YAML to the given std::string/std::vector-like - * container, resizing it as needed to fit the emitted YAML. */ -template -CharOwningContainer emitrs_yaml(Tree const& t, size_t id) -{ - CharOwningContainer c; - emitrs_yaml(t, id, &c); - return c; -} -template -RYML_DEPRECATE_EMITRS CharOwningContainer emitrs(Tree const& t, size_t id) -{ - CharOwningContainer c; - emitrs_yaml(t, id, &c); - return c; -} - -/** emit+resize: emit JSON to the given std::string/std::vector-like - * container, resizing it as needed to fit the emitted JSON. */ -template -CharOwningContainer emitrs_json(Tree const& t, size_t id) -{ - CharOwningContainer c; - emitrs_json(t, id, &c); - return c; -} - - -/** emit+resize: YAML to the given std::string/std::vector-like - * container, resizing it as needed to fit the emitted YAML. */ -template -substr emitrs_yaml(Tree const& t, CharOwningContainer * cont) -{ - if(t.empty()) - return {}; - return emitrs_yaml(t, t.root_id(), cont); -} -template -RYML_DEPRECATE_EMITRS substr emitrs(Tree const& t, CharOwningContainer * cont) -{ - return emitrs_yaml(t, cont); -} - -/** emit+resize: JSON to the given std::string/std::vector-like - * container, resizing it as needed to fit the emitted JSON. */ -template -substr emitrs_json(Tree const& t, CharOwningContainer * cont) -{ - if(t.empty()) - return {}; - return emitrs_json(t, t.root_id(), cont); -} - - -/** emit+resize: YAML to the given std::string/std::vector-like container, - * resizing it as needed to fit the emitted YAML. */ -template -CharOwningContainer emitrs_yaml(Tree const& t) -{ - CharOwningContainer c; - if(t.empty()) - return c; - emitrs_yaml(t, t.root_id(), &c); - return c; -} -template -RYML_DEPRECATE_EMITRS CharOwningContainer emitrs(Tree const& t) -{ - return emitrs_yaml(t); -} - -/** emit+resize: JSON to the given std::string/std::vector-like container, - * resizing it as needed to fit the emitted JSON. */ -template -CharOwningContainer emitrs_json(Tree const& t) -{ - CharOwningContainer c; - if(t.empty()) - return c; - emitrs_json(t, t.root_id(), &c); - return c; -} - - -/** emit+resize: YAML to the given std::string/std::vector-like container, - * resizing it as needed to fit the emitted YAML. */ -template -substr emitrs_yaml(ConstNodeRef const& n, CharOwningContainer * cont) -{ - _RYML_CB_CHECK(n.tree()->callbacks(), n.valid()); - return emitrs_yaml(*n.tree(), n.id(), cont); -} -template -RYML_DEPRECATE_EMITRS substr emitrs(ConstNodeRef const& n, CharOwningContainer * cont) -{ - return emitrs_yaml(n, cont); -} - -/** emit+resize: JSON to the given std::string/std::vector-like container, - * resizing it as needed to fit the emitted JSON. */ -template -substr emitrs_json(ConstNodeRef const& n, CharOwningContainer * cont) -{ - _RYML_CB_CHECK(n.tree()->callbacks(), n.valid()); - return emitrs_json(*n.tree(), n.id(), cont); -} - - -/** emit+resize: YAML to the given std::string/std::vector-like container, - * resizing it as needed to fit the emitted YAML. */ -template -CharOwningContainer emitrs_yaml(ConstNodeRef const& n) -{ - _RYML_CB_CHECK(n.tree()->callbacks(), n.valid()); - CharOwningContainer c; - emitrs_yaml(*n.tree(), n.id(), &c); - return c; -} -template -RYML_DEPRECATE_EMITRS CharOwningContainer emitrs(ConstNodeRef const& n) -{ - return emitrs_yaml(n); -} - -/** emit+resize: JSON to the given std::string/std::vector-like container, - * resizing it as needed to fit the emitted JSON. */ -template -CharOwningContainer emitrs_json(ConstNodeRef const& n) -{ - _RYML_CB_CHECK(n.tree()->callbacks(), n.valid()); - CharOwningContainer c; - emitrs_json(*n.tree(), n.id(), &c); - return c; -} - -} // namespace yml -} // namespace c4 - -#undef RYML_DEPRECATE_EMIT -#undef RYML_DEPRECATE_EMITRS - -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/yml/emit.def.hpp -//#include "c4/yml/emit.def.hpp" -#if !defined(C4_YML_EMIT_DEF_HPP_) && !defined(_C4_YML_EMIT_DEF_HPP_) -#error "amalgamate: file c4/yml/emit.def.hpp must have been included at this point" -#endif /* C4_YML_EMIT_DEF_HPP_ */ - - -#endif /* _C4_YML_EMIT_HPP_ */ - - -// (end https://github.com/biojppm/rapidyaml/src/c4/yml/emit.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/yml/emit.def.hpp -// https://github.com/biojppm/rapidyaml/src/c4/yml/emit.def.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_YML_EMIT_DEF_HPP_ -#define _C4_YML_EMIT_DEF_HPP_ - -#ifndef _C4_YML_EMIT_HPP_ -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/yml/emit.hpp -//#include "c4/yml/emit.hpp" -#if !defined(C4_YML_EMIT_HPP_) && !defined(_C4_YML_EMIT_HPP_) -#error "amalgamate: file c4/yml/emit.hpp must have been included at this point" -#endif /* C4_YML_EMIT_HPP_ */ - -#endif - -namespace c4 { -namespace yml { - -template -substr Emitter::emit_as(EmitType_e type, Tree const& t, size_t id, bool error_on_excess) -{ - if(t.empty()) - { - _RYML_CB_ASSERT(t.callbacks(), id == NONE); - return {}; - } - _RYML_CB_CHECK(t.callbacks(), id < t.size()); - m_tree = &t; - if(type == EMIT_YAML) - _emit_yaml(id); - else if(type == EMIT_JSON) - _do_visit_json(id); - else - _RYML_CB_ERR(m_tree->callbacks(), "unknown emit type"); - return this->Writer::_get(error_on_excess); -} - -template -substr Emitter::emit_as(EmitType_e type, Tree const& t, bool error_on_excess) -{ - if(t.empty()) - return {}; - return this->emit_as(type, t, t.root_id(), error_on_excess); -} - -template -substr Emitter::emit_as(EmitType_e type, ConstNodeRef const& n, bool error_on_excess) -{ - _RYML_CB_CHECK(n.tree()->callbacks(), n.valid()); - return this->emit_as(type, *n.tree(), n.id(), error_on_excess); -} - - -//----------------------------------------------------------------------------- - -template -void Emitter::_emit_yaml(size_t id) -{ - // save branches in the visitor by doing the initial stream/doc - // logic here, sparing the need to check stream/val/keyval inside - // the visitor functions - auto dispatch = [this](size_t node){ - NodeType ty = m_tree->type(node); - if(ty.marked_flow_sl()) - _do_visit_flow_sl(node, 0); - else if(ty.marked_flow_ml()) - _do_visit_flow_ml(node, 0); - else - { - _do_visit_block(node, 0); - } - }; - if(!m_tree->is_root(id)) - { - if(m_tree->is_container(id) && !m_tree->type(id).marked_flow()) - { - size_t ilevel = 0; - if(m_tree->has_key(id)) - { - this->Writer::_do_write(m_tree->key(id)); - this->Writer::_do_write(":\n"); - ++ilevel; - } - _do_visit_block_container(id, ilevel, ilevel); - return; - } - } - - auto *btd = m_tree->tag_directives().b; - auto *etd = m_tree->tag_directives().e; - auto write_tag_directives = [&btd, etd, this](size_t next_node){ - auto end = btd; - while(end < etd) - { - if(end->next_node_id > next_node) - break; - ++end; - } - for( ; btd != end; ++btd) - { - if(next_node != m_tree->first_child(m_tree->parent(next_node))) - this->Writer::_do_write("...\n"); - this->Writer::_do_write("%TAG "); - this->Writer::_do_write(btd->handle); - this->Writer::_do_write(' '); - this->Writer::_do_write(btd->prefix); - this->Writer::_do_write('\n'); - } - }; - if(m_tree->is_stream(id)) - { - if(m_tree->first_child(id) != NONE) - write_tag_directives(m_tree->first_child(id)); - for(size_t child = m_tree->first_child(id); child != NONE; child = m_tree->next_sibling(child)) - { - dispatch(child); - if(m_tree->next_sibling(child) != NONE) - write_tag_directives(m_tree->next_sibling(child)); - } - } - else if(m_tree->is_container(id)) - { - dispatch(id); - } - else if(m_tree->is_doc(id)) - { - _RYML_CB_ASSERT(m_tree->callbacks(), !m_tree->is_container(id)); // checked above - _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->is_val(id)); // so it must be a val - _write_doc(id); - } - else if(m_tree->is_keyval(id)) - { - _writek(id, 0); - this->Writer::_do_write(": "); - _writev(id, 0); - if(!m_tree->type(id).marked_flow()) - this->Writer::_do_write('\n'); - } - else if(m_tree->is_val(id)) - { - //this->Writer::_do_write("- "); - _writev(id, 0); - if(!m_tree->type(id).marked_flow()) - this->Writer::_do_write('\n'); - } - else if(m_tree->type(id) == NOTYPE) - { - ; - } - else - { - _RYML_CB_ERR(m_tree->callbacks(), "unknown type"); - } -} - -template -void Emitter::_write_doc(size_t id) -{ - RYML_ASSERT(m_tree->is_doc(id)); - if(!m_tree->is_root(id)) - { - RYML_ASSERT(m_tree->is_stream(m_tree->parent(id))); - this->Writer::_do_write("---"); - } - if(!m_tree->has_val(id)) // this is more frequent - { - if(m_tree->has_val_tag(id)) - { - if(!m_tree->is_root(id)) - this->Writer::_do_write(' '); - _write_tag(m_tree->val_tag(id)); - } - if(m_tree->has_val_anchor(id)) - { - if(!m_tree->is_root(id)) - this->Writer::_do_write(' '); - this->Writer::_do_write('&'); - this->Writer::_do_write(m_tree->val_anchor(id)); - } - } - else // docval - { - RYML_ASSERT(m_tree->has_val(id)); - RYML_ASSERT(!m_tree->has_key(id)); - if(!m_tree->is_root(id)) - this->Writer::_do_write(' '); - _writev(id, 0); - } - this->Writer::_do_write('\n'); -} - -template -void Emitter::_do_visit_flow_sl(size_t node, size_t ilevel) -{ - RYML_ASSERT(!m_tree->is_stream(node)); - RYML_ASSERT(m_tree->is_container(node) || m_tree->is_doc(node)); - RYML_ASSERT(m_tree->is_root(node) || (m_tree->parent_is_map(node) || m_tree->parent_is_seq(node))); - - if(m_tree->is_doc(node)) - { - _write_doc(node); - if(!m_tree->has_children(node)) - return; - } - else if(m_tree->is_container(node)) - { - RYML_ASSERT(m_tree->is_map(node) || m_tree->is_seq(node)); - - bool spc = false; // write a space - - if(m_tree->has_key(node)) - { - _writek(node, ilevel); - this->Writer::_do_write(':'); - spc = true; - } - - if(m_tree->has_val_tag(node)) - { - if(spc) - this->Writer::_do_write(' '); - _write_tag(m_tree->val_tag(node)); - spc = true; - } - - if(m_tree->has_val_anchor(node)) - { - if(spc) - this->Writer::_do_write(' '); - this->Writer::_do_write('&'); - this->Writer::_do_write(m_tree->val_anchor(node)); - spc = true; - } - - if(spc) - this->Writer::_do_write(' '); - - if(m_tree->is_map(node)) - { - this->Writer::_do_write('{'); - } - else - { - _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->is_seq(node)); - this->Writer::_do_write('['); - } - } // container - - for(size_t child = m_tree->first_child(node), count = 0; child != NONE; child = m_tree->next_sibling(child)) - { - if(count++) - this->Writer::_do_write(','); - if(m_tree->is_keyval(child)) - { - _writek(child, ilevel); - this->Writer::_do_write(": "); - _writev(child, ilevel); - } - else if(m_tree->is_val(child)) - { - _writev(child, ilevel); - } - else - { - // with single-line flow, we can never go back to block - _do_visit_flow_sl(child, ilevel + 1); - } - } - - if(m_tree->is_map(node)) - { - this->Writer::_do_write('}'); - } - else if(m_tree->is_seq(node)) - { - this->Writer::_do_write(']'); - } -} - -template -void Emitter::_do_visit_flow_ml(size_t id, size_t ilevel, size_t do_indent) -{ - C4_UNUSED(id); - C4_UNUSED(ilevel); - C4_UNUSED(do_indent); - RYML_CHECK(false/*not implemented*/); -} - -template -void Emitter::_do_visit_block_container(size_t node, size_t next_level, size_t do_indent) -{ - RepC ind = indent_to(do_indent * next_level); - - if(m_tree->is_seq(node)) - { - for(size_t child = m_tree->first_child(node); child != NONE; child = m_tree->next_sibling(child)) - { - _RYML_CB_ASSERT(m_tree->callbacks(), !m_tree->has_key(child)); - if(m_tree->is_val(child)) - { - this->Writer::_do_write(ind); - this->Writer::_do_write("- "); - _writev(child, next_level); - this->Writer::_do_write('\n'); - } - else - { - _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->is_container(child)); - NodeType ty = m_tree->type(child); - if(ty.marked_flow_sl()) - { - this->Writer::_do_write(ind); - this->Writer::_do_write("- "); - _do_visit_flow_sl(child, 0u); - this->Writer::_do_write('\n'); - } - else if(ty.marked_flow_ml()) - { - this->Writer::_do_write(ind); - this->Writer::_do_write("- "); - _do_visit_flow_ml(child, next_level, do_indent); - this->Writer::_do_write('\n'); - } - else - { - _do_visit_block(child, next_level, do_indent); - } - } - do_indent = true; - ind = indent_to(do_indent * next_level); - } - } - else // map - { - _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->is_map(node)); - for(size_t ich = m_tree->first_child(node); ich != NONE; ich = m_tree->next_sibling(ich)) - { - _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->has_key(ich)); - if(m_tree->is_keyval(ich)) - { - this->Writer::_do_write(ind); - _writek(ich, next_level); - this->Writer::_do_write(": "); - _writev(ich, next_level); - this->Writer::_do_write('\n'); - } - else - { - _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->is_container(ich)); - NodeType ty = m_tree->type(ich); - if(ty.marked_flow_sl()) - { - this->Writer::_do_write(ind); - _do_visit_flow_sl(ich, 0u); - this->Writer::_do_write('\n'); - } - else if(ty.marked_flow_ml()) - { - this->Writer::_do_write(ind); - _do_visit_flow_ml(ich, 0u); - this->Writer::_do_write('\n'); - } - else - { - _do_visit_block(ich, next_level, do_indent); - } - } - do_indent = true; - ind = indent_to(do_indent * next_level); - } - } -} - -template -void Emitter::_do_visit_block(size_t node, size_t ilevel, size_t do_indent) -{ - RYML_ASSERT(!m_tree->is_stream(node)); - RYML_ASSERT(m_tree->is_container(node) || m_tree->is_doc(node)); - RYML_ASSERT(m_tree->is_root(node) || (m_tree->parent_is_map(node) || m_tree->parent_is_seq(node))); - RepC ind = indent_to(do_indent * ilevel); - - if(m_tree->is_doc(node)) - { - _write_doc(node); - if(!m_tree->has_children(node)) - return; - } - else if(m_tree->is_container(node)) - { - RYML_ASSERT(m_tree->is_map(node) || m_tree->is_seq(node)); - - bool spc = false; // write a space - bool nl = false; // write a newline - - if(m_tree->has_key(node)) - { - this->Writer::_do_write(ind); - _writek(node, ilevel); - this->Writer::_do_write(':'); - spc = true; - } - else if(!m_tree->is_root(node)) - { - this->Writer::_do_write(ind); - this->Writer::_do_write('-'); - spc = true; - } - - if(m_tree->has_val_tag(node)) - { - if(spc) - this->Writer::_do_write(' '); - _write_tag(m_tree->val_tag(node)); - spc = true; - nl = true; - } - - if(m_tree->has_val_anchor(node)) - { - if(spc) - this->Writer::_do_write(' '); - this->Writer::_do_write('&'); - this->Writer::_do_write(m_tree->val_anchor(node)); - spc = true; - nl = true; - } - - if(m_tree->has_children(node)) - { - if(m_tree->has_key(node)) - nl = true; - else - if(!m_tree->is_root(node) && !nl) - spc = true; - } - else - { - if(m_tree->is_seq(node)) - this->Writer::_do_write(" []\n"); - else if(m_tree->is_map(node)) - this->Writer::_do_write(" {}\n"); - return; - } - - if(spc && !nl) - this->Writer::_do_write(' '); - - do_indent = 0; - if(nl) - { - this->Writer::_do_write('\n'); - do_indent = 1; - } - } // container - - size_t next_level = ilevel + 1; - if(m_tree->is_root(node) || m_tree->is_doc(node)) - next_level = ilevel; // do not indent at top level - - _do_visit_block_container(node, next_level, do_indent); -} - -template -void Emitter::_do_visit_json(size_t id) -{ - _RYML_CB_CHECK(m_tree->callbacks(), !m_tree->is_stream(id)); // JSON does not have streams - if(m_tree->is_keyval(id)) - { - _writek_json(id); - this->Writer::_do_write(": "); - _writev_json(id); - } - else if(m_tree->is_val(id)) - { - _writev_json(id); - } - else if(m_tree->is_container(id)) - { - if(m_tree->has_key(id)) - { - _writek_json(id); - this->Writer::_do_write(": "); - } - if(m_tree->is_seq(id)) - this->Writer::_do_write('['); - else if(m_tree->is_map(id)) - this->Writer::_do_write('{'); - } // container - - for(size_t ich = m_tree->first_child(id); ich != NONE; ich = m_tree->next_sibling(ich)) - { - if(ich != m_tree->first_child(id)) - this->Writer::_do_write(','); - _do_visit_json(ich); - } - - if(m_tree->is_seq(id)) - this->Writer::_do_write(']'); - else if(m_tree->is_map(id)) - this->Writer::_do_write('}'); -} - -template -void Emitter::_write(NodeScalar const& C4_RESTRICT sc, NodeType flags, size_t ilevel) -{ - if( ! sc.tag.empty()) - { - _write_tag(sc.tag); - this->Writer::_do_write(' '); - } - if(flags.has_anchor()) - { - RYML_ASSERT(flags.is_ref() != flags.has_anchor()); - RYML_ASSERT( ! sc.anchor.empty()); - this->Writer::_do_write('&'); - this->Writer::_do_write(sc.anchor); - this->Writer::_do_write(' '); - } - else if(flags.is_ref()) - { - if(sc.anchor != "<<") - this->Writer::_do_write('*'); - this->Writer::_do_write(sc.anchor); - return; - } - - // ensure the style flags only have one of KEY or VAL - _RYML_CB_ASSERT(m_tree->callbacks(), ((flags & (_WIP_KEY_STYLE|_WIP_VAL_STYLE)) == 0) || (((flags&_WIP_KEY_STYLE) == 0) != ((flags&_WIP_VAL_STYLE) == 0))); - - auto style_marks = flags & (_WIP_KEY_STYLE|_WIP_VAL_STYLE); - if(style_marks & (_WIP_KEY_LITERAL|_WIP_VAL_LITERAL)) - { - _write_scalar_literal(sc.scalar, ilevel, flags.has_key()); - } - else if(style_marks & (_WIP_KEY_FOLDED|_WIP_VAL_FOLDED)) - { - _write_scalar_folded(sc.scalar, ilevel, flags.has_key()); - } - else if(style_marks & (_WIP_KEY_SQUO|_WIP_VAL_SQUO)) - { - _write_scalar_squo(sc.scalar, ilevel); - } - else if(style_marks & (_WIP_KEY_DQUO|_WIP_VAL_DQUO)) - { - _write_scalar_dquo(sc.scalar, ilevel); - } - else if(style_marks & (_WIP_KEY_PLAIN|_WIP_VAL_PLAIN)) - { - _write_scalar_plain(sc.scalar, ilevel); - } - else if(!style_marks) - { - size_t first_non_nl = sc.scalar.first_not_of('\n'); - bool all_newlines = first_non_nl == npos; - bool has_leading_ws = (!all_newlines) && sc.scalar.sub(first_non_nl).begins_with_any(" \t"); - bool do_literal = ((!sc.scalar.empty() && all_newlines) || (has_leading_ws && !sc.scalar.trim(' ').empty())); - if(do_literal) - { - _write_scalar_literal(sc.scalar, ilevel, flags.has_key(), /*explicit_indentation*/has_leading_ws); - } - else - { - for(size_t i = 0; i < sc.scalar.len; ++i) - { - if(sc.scalar.str[i] == '\n') - { - _write_scalar_literal(sc.scalar, ilevel, flags.has_key(), /*explicit_indentation*/has_leading_ws); - goto wrote_special; - } - // todo: check for escaped characters requiring double quotes - } - _write_scalar(sc.scalar, flags.is_quoted()); - wrote_special: - ; - } - } - else - { - _RYML_CB_ERR(m_tree->callbacks(), "not implemented"); - } -} -template -void Emitter::_write_json(NodeScalar const& C4_RESTRICT sc, NodeType flags) -{ - if(C4_UNLIKELY( ! sc.tag.empty())) - _RYML_CB_ERR(m_tree->callbacks(), "JSON does not have tags"); - if(C4_UNLIKELY(flags.has_anchor())) - _RYML_CB_ERR(m_tree->callbacks(), "JSON does not have anchors"); - _write_scalar_json(sc.scalar, flags.has_key(), flags.is_quoted()); -} - -#define _rymlindent_nextline() for(size_t lv = 0; lv < ilevel+1; ++lv) { this->Writer::_do_write(' '); this->Writer::_do_write(' '); } - -template -void Emitter::_write_scalar_literal(csubstr s, size_t ilevel, bool explicit_key, bool explicit_indentation) -{ - if(explicit_key) - this->Writer::_do_write("? "); - csubstr trimmed = s.trimr("\n\r"); - size_t numnewlines_at_end = s.len - trimmed.len - s.sub(trimmed.len).count('\r'); - // - if(!explicit_indentation) - this->Writer::_do_write('|'); - else - this->Writer::_do_write("|2"); - // - if(numnewlines_at_end > 1 || (trimmed.len == 0 && s.len > 0)/*only newlines*/) - this->Writer::_do_write("+\n"); - else if(numnewlines_at_end == 1) - this->Writer::_do_write('\n'); - else - this->Writer::_do_write("-\n"); - // - if(trimmed.len) - { - size_t pos = 0; // tracks the last character that was already written - for(size_t i = 0; i < trimmed.len; ++i) - { - if(trimmed[i] != '\n') - continue; - // write everything up to this point - csubstr since_pos = trimmed.range(pos, i+1); // include the newline - _rymlindent_nextline() - this->Writer::_do_write(since_pos); - pos = i+1; // already written - } - if(pos < trimmed.len) - { - _rymlindent_nextline() - this->Writer::_do_write(trimmed.sub(pos)); - } - if(numnewlines_at_end) - { - this->Writer::_do_write('\n'); - --numnewlines_at_end; - } - } - for(size_t i = 0; i < numnewlines_at_end; ++i) - { - _rymlindent_nextline() - if(i+1 < numnewlines_at_end || explicit_key) - this->Writer::_do_write('\n'); - } - if(explicit_key && !numnewlines_at_end) - this->Writer::_do_write('\n'); -} - -template -void Emitter::_write_scalar_folded(csubstr s, size_t ilevel, bool explicit_key) -{ - if(explicit_key) - { - this->Writer::_do_write("? "); - } - RYML_ASSERT(s.find("\r") == csubstr::npos); - csubstr trimmed = s.trimr('\n'); - size_t numnewlines_at_end = s.len - trimmed.len; - if(numnewlines_at_end == 0) - { - this->Writer::_do_write(">-\n"); - } - else if(numnewlines_at_end == 1) - { - this->Writer::_do_write(">\n"); - } - else if(numnewlines_at_end > 1) - { - this->Writer::_do_write(">+\n"); - } - if(trimmed.len) - { - size_t pos = 0; // tracks the last character that was already written - for(size_t i = 0; i < trimmed.len; ++i) - { - if(trimmed[i] != '\n') - continue; - // write everything up to this point - csubstr since_pos = trimmed.range(pos, i+1); // include the newline - pos = i+1; // because of the newline - _rymlindent_nextline() - this->Writer::_do_write(since_pos); - this->Writer::_do_write('\n'); // write the newline twice - } - if(pos < trimmed.len) - { - _rymlindent_nextline() - this->Writer::_do_write(trimmed.sub(pos)); - } - if(numnewlines_at_end) - { - this->Writer::_do_write('\n'); - --numnewlines_at_end; - } - } - for(size_t i = 0; i < numnewlines_at_end; ++i) - { - _rymlindent_nextline() - if(i+1 < numnewlines_at_end || explicit_key) - this->Writer::_do_write('\n'); - } - if(explicit_key && !numnewlines_at_end) - this->Writer::_do_write('\n'); -} - -template -void Emitter::_write_scalar_squo(csubstr s, size_t ilevel) -{ - size_t pos = 0; // tracks the last character that was already written - this->Writer::_do_write('\''); - for(size_t i = 0; i < s.len; ++i) - { - if(s[i] == '\n') - { - csubstr sub = s.range(pos, i+1); - this->Writer::_do_write(sub); // write everything up to (including) this char - this->Writer::_do_write('\n'); // write the character again - if(i + 1 < s.len) - _rymlindent_nextline() // indent the next line - pos = i+1; - } - else if(s[i] == '\'') - { - csubstr sub = s.range(pos, i+1); - this->Writer::_do_write(sub); // write everything up to (including) this char - this->Writer::_do_write('\''); // write the character again - pos = i+1; - } - } - // write missing characters at the end of the string - if(pos < s.len) - this->Writer::_do_write(s.sub(pos)); - this->Writer::_do_write('\''); -} - -template -void Emitter::_write_scalar_dquo(csubstr s, size_t ilevel) -{ - size_t pos = 0; // tracks the last character that was already written - this->Writer::_do_write('"'); - for(size_t i = 0; i < s.len; ++i) - { - const char curr = s.str[i]; - if(curr == '"' || curr == '\\') - { - csubstr sub = s.range(pos, i); - this->Writer::_do_write(sub); // write everything up to (excluding) this char - this->Writer::_do_write('\\'); // write the escape - this->Writer::_do_write(curr); // write the char - pos = i+1; - } - else if(s[i] == '\n') - { - csubstr sub = s.range(pos, i+1); - this->Writer::_do_write(sub); // write everything up to (including) this newline - this->Writer::_do_write('\n'); // write the newline again - if(i + 1 < s.len) - _rymlindent_nextline() // indent the next line - pos = i+1; - if(i+1 < s.len) // escape leading whitespace after the newline - { - const char next = s.str[i+1]; - if(next == ' ' || next == '\t') - this->Writer::_do_write('\\'); - } - } - else if(curr == ' ' || curr == '\t') - { - // escape trailing whitespace before a newline - size_t next = s.first_not_of(" \t\r", i); - if(next != npos && s[next] == '\n') - { - csubstr sub = s.range(pos, i); - this->Writer::_do_write(sub); // write everything up to (excluding) this char - this->Writer::_do_write('\\'); // escape the whitespace - pos = i; - } - } - else if(C4_UNLIKELY(curr == '\r')) - { - csubstr sub = s.range(pos, i); - this->Writer::_do_write(sub); // write everything up to (excluding) this char - this->Writer::_do_write("\\r"); // write the escaped char - pos = i+1; - } - } - // write missing characters at the end of the string - if(pos < s.len) - { - csubstr sub = s.sub(pos); - this->Writer::_do_write(sub); - } - this->Writer::_do_write('"'); -} - -template -void Emitter::_write_scalar_plain(csubstr s, size_t ilevel) -{ - size_t pos = 0; // tracks the last character that was already written - for(size_t i = 0; i < s.len; ++i) - { - const char curr = s.str[i]; - if(curr == '\n') - { - csubstr sub = s.range(pos, i+1); - this->Writer::_do_write(sub); // write everything up to (including) this newline - this->Writer::_do_write('\n'); // write the newline again - if(i + 1 < s.len) - _rymlindent_nextline() // indent the next line - pos = i+1; - } - } - // write missing characters at the end of the string - if(pos < s.len) - { - csubstr sub = s.sub(pos); - this->Writer::_do_write(sub); - } -} - -#undef _rymlindent_nextline - -template -void Emitter::_write_scalar(csubstr s, bool was_quoted) -{ - // this block of code needed to be moved to before the needs_quotes - // assignment to work around a g++ optimizer bug where (s.str != nullptr) - // was evaluated as true even if s.str was actually a nullptr (!!!) - if(s.len == size_t(0)) - { - if(was_quoted || s.str != nullptr) - this->Writer::_do_write("''"); - return; - } - - const bool needs_quotes = ( - was_quoted - || - ( - ( ! s.is_number()) - && - ( - // has leading whitespace - // looks like reference or anchor - // would be treated as a directive - // see https://www.yaml.info/learn/quote.html#noplain - s.begins_with_any(" \n\t\r*&%@`") - || - s.begins_with("<<") - || - // has trailing whitespace - s.ends_with_any(" \n\t\r") - || - // has special chars - (s.first_of("#:-?,\n{}[]'\"") != npos) - ) - ) - ); - - if( ! needs_quotes) - { - this->Writer::_do_write(s); - } - else - { - const bool has_dquotes = s.first_of( '"') != npos; - const bool has_squotes = s.first_of('\'') != npos; - if(!has_squotes && has_dquotes) - { - this->Writer::_do_write('\''); - this->Writer::_do_write(s); - this->Writer::_do_write('\''); - } - else if(has_squotes && !has_dquotes) - { - RYML_ASSERT(s.count('\n') == 0); - this->Writer::_do_write('"'); - this->Writer::_do_write(s); - this->Writer::_do_write('"'); - } - else - { - _write_scalar_squo(s, /*FIXME FIXME FIXME*/0); - } - } -} -template -void Emitter::_write_scalar_json(csubstr s, bool as_key, bool use_quotes) -{ - if((!use_quotes) - // json keys require quotes - && (!as_key) - && ( - // do not quote special cases - (s == "true" || s == "false" || s == "null") - || ( - // do not quote numbers - (s.is_number() - && ( - // quote integral numbers if they have a leading 0 - // https://github.com/biojppm/rapidyaml/issues/291 - (!(s.len > 1 && s.begins_with('0'))) - // do not quote reals with leading 0 - // https://github.com/biojppm/rapidyaml/issues/313 - || (s.find('.') != csubstr::npos) )) - ) - ) - ) - { - this->Writer::_do_write(s); - } - else - { - size_t pos = 0; - this->Writer::_do_write('"'); - for(size_t i = 0; i < s.len; ++i) - { - switch(s.str[i]) - { - case '"': - this->Writer ::_do_write(s.range(pos, i)); - this->Writer ::_do_write("\\\""); - pos = i + 1; - break; - case '\n': - this->Writer ::_do_write(s.range(pos, i)); - this->Writer ::_do_write("\\n"); - pos = i + 1; - break; - case '\t': - this->Writer ::_do_write(s.range(pos, i)); - this->Writer ::_do_write("\\t"); - pos = i + 1; - break; - case '\\': - this->Writer ::_do_write(s.range(pos, i)); - this->Writer ::_do_write("\\\\"); - pos = i + 1; - break; - case '\r': - this->Writer ::_do_write(s.range(pos, i)); - this->Writer ::_do_write("\\r"); - pos = i + 1; - break; - case '\b': - this->Writer ::_do_write(s.range(pos, i)); - this->Writer ::_do_write("\\b"); - pos = i + 1; - break; - case '\f': - this->Writer ::_do_write(s.range(pos, i)); - this->Writer ::_do_write("\\f"); - pos = i + 1; - break; - } - } - if(pos < s.len) - { - csubstr sub = s.sub(pos); - this->Writer::_do_write(sub); - } - this->Writer::_do_write('"'); - } -} - -} // namespace yml -} // namespace c4 - -#endif /* _C4_YML_EMIT_DEF_HPP_ */ - - -// (end https://github.com/biojppm/rapidyaml/src/c4/yml/emit.def.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/yml/detail/stack.hpp -// https://github.com/biojppm/rapidyaml/src/c4/yml/detail/stack.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_YML_DETAIL_STACK_HPP_ -#define _C4_YML_DETAIL_STACK_HPP_ - -#ifndef _C4_YML_COMMON_HPP_ -//included above: -//#include "../common.hpp" -#endif - -#ifdef RYML_DBG -//included above: -//# include -#endif - -//included above: -//#include - -namespace c4 { -namespace yml { -namespace detail { - -/** A lightweight contiguous stack with SSO. This avoids a dependency on std. */ -template -class stack -{ - static_assert(std::is_trivially_copyable::value, "T must be trivially copyable"); - static_assert(std::is_trivially_destructible::value, "T must be trivially destructible"); - - enum : size_t { sso_size = N }; - -public: - - T m_buf[N]; - T * m_stack; - size_t m_size; - size_t m_capacity; - Callbacks m_callbacks; - -public: - - constexpr static bool is_contiguous() { return true; } - - stack(Callbacks const& cb) - : m_buf() - , m_stack(m_buf) - , m_size(0) - , m_capacity(N) - , m_callbacks(cb) {} - stack() : stack(get_callbacks()) {} - ~stack() - { - _free(); - } - - stack(stack const& that) noexcept : stack(that.m_callbacks) - { - resize(that.m_size); - _cp(&that); - } - - stack(stack &&that) noexcept : stack(that.m_callbacks) - { - _mv(&that); - } - - stack& operator= (stack const& that) noexcept - { - _cb(that.m_callbacks); - resize(that.m_size); - _cp(&that); - return *this; - } - - stack& operator= (stack &&that) noexcept - { - _cb(that.m_callbacks); - _mv(&that); - return *this; - } - -public: - - size_t size() const { return m_size; } - size_t empty() const { return m_size == 0; } - size_t capacity() const { return m_capacity; } - - void clear() - { - m_size = 0; - } - - void resize(size_t sz) - { - reserve(sz); - m_size = sz; - } - - void reserve(size_t sz); - - void push(T const& C4_RESTRICT n) - { - RYML_ASSERT((const char*)&n + sizeof(T) < (const char*)m_stack || &n > m_stack + m_capacity); - if(m_size == m_capacity) - { - size_t cap = m_capacity == 0 ? N : 2 * m_capacity; - reserve(cap); - } - m_stack[m_size] = n; - ++m_size; - } - - void push_top() - { - RYML_ASSERT(m_size > 0); - if(m_size == m_capacity) - { - size_t cap = m_capacity == 0 ? N : 2 * m_capacity; - reserve(cap); - } - m_stack[m_size] = m_stack[m_size - 1]; - ++m_size; - } - - T const& C4_RESTRICT pop() - { - RYML_ASSERT(m_size > 0); - --m_size; - return m_stack[m_size]; - } - - C4_ALWAYS_INLINE T const& C4_RESTRICT top() const { RYML_ASSERT(m_size > 0); return m_stack[m_size - 1]; } - C4_ALWAYS_INLINE T & C4_RESTRICT top() { RYML_ASSERT(m_size > 0); return m_stack[m_size - 1]; } - - C4_ALWAYS_INLINE T const& C4_RESTRICT bottom() const { RYML_ASSERT(m_size > 0); return m_stack[0]; } - C4_ALWAYS_INLINE T & C4_RESTRICT bottom() { RYML_ASSERT(m_size > 0); return m_stack[0]; } - - C4_ALWAYS_INLINE T const& C4_RESTRICT top(size_t i) const { RYML_ASSERT(i < m_size); return m_stack[m_size - 1 - i]; } - C4_ALWAYS_INLINE T & C4_RESTRICT top(size_t i) { RYML_ASSERT(i < m_size); return m_stack[m_size - 1 - i]; } - - C4_ALWAYS_INLINE T const& C4_RESTRICT bottom(size_t i) const { RYML_ASSERT(i < m_size); return m_stack[i]; } - C4_ALWAYS_INLINE T & C4_RESTRICT bottom(size_t i) { RYML_ASSERT(i < m_size); return m_stack[i]; } - - C4_ALWAYS_INLINE T const& C4_RESTRICT operator[](size_t i) const { RYML_ASSERT(i < m_size); return m_stack[i]; } - C4_ALWAYS_INLINE T & C4_RESTRICT operator[](size_t i) { RYML_ASSERT(i < m_size); return m_stack[i]; } - -public: - - using iterator = T *; - using const_iterator = T const *; - - iterator begin() { return m_stack; } - iterator end () { return m_stack + m_size; } - - const_iterator begin() const { return (const_iterator)m_stack; } - const_iterator end () const { return (const_iterator)m_stack + m_size; } - -public: - void _free(); - void _cp(stack const* C4_RESTRICT that); - void _mv(stack * that); - void _cb(Callbacks const& cb); -}; - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -template -void stack::reserve(size_t sz) -{ - if(sz <= m_size) - return; - if(sz <= N) - { - m_stack = m_buf; - m_capacity = N; - return; - } - T *buf = (T*) m_callbacks.m_allocate(sz * sizeof(T), m_stack, m_callbacks.m_user_data); - memcpy(buf, m_stack, m_size * sizeof(T)); - if(m_stack != m_buf) - { - m_callbacks.m_free(m_stack, m_capacity * sizeof(T), m_callbacks.m_user_data); - } - m_stack = buf; - m_capacity = sz; -} - - -//----------------------------------------------------------------------------- - -template -void stack::_free() -{ - RYML_ASSERT(m_stack != nullptr); // this structure cannot be memset() to zero - if(m_stack != m_buf) - { - m_callbacks.m_free(m_stack, m_capacity * sizeof(T), m_callbacks.m_user_data); - m_stack = m_buf; - m_size = N; - m_capacity = N; - } - else - { - RYML_ASSERT(m_capacity == N); - } -} - - -//----------------------------------------------------------------------------- - -template -void stack::_cp(stack const* C4_RESTRICT that) -{ - if(that->m_stack != that->m_buf) - { - RYML_ASSERT(that->m_capacity > N); - RYML_ASSERT(that->m_size <= that->m_capacity); - } - else - { - RYML_ASSERT(that->m_capacity <= N); - RYML_ASSERT(that->m_size <= that->m_capacity); - } - memcpy(m_stack, that->m_stack, that->m_size * sizeof(T)); - m_size = that->m_size; - m_capacity = that->m_size < N ? N : that->m_size; - m_callbacks = that->m_callbacks; -} - - -//----------------------------------------------------------------------------- - -template -void stack::_mv(stack * that) -{ - if(that->m_stack != that->m_buf) - { - RYML_ASSERT(that->m_capacity > N); - RYML_ASSERT(that->m_size <= that->m_capacity); - m_stack = that->m_stack; - } - else - { - RYML_ASSERT(that->m_capacity <= N); - RYML_ASSERT(that->m_size <= that->m_capacity); - memcpy(m_buf, that->m_buf, that->m_size * sizeof(T)); - m_stack = m_buf; - } - m_size = that->m_size; - m_capacity = that->m_capacity; - m_callbacks = that->m_callbacks; - // make sure no deallocation happens on destruction - RYML_ASSERT(that->m_stack != m_buf); - that->m_stack = that->m_buf; - that->m_capacity = N; - that->m_size = 0; -} - - -//----------------------------------------------------------------------------- - -template -void stack::_cb(Callbacks const& cb) -{ - if(cb != m_callbacks) - { - _free(); - m_callbacks = cb; - } -} - -} // namespace detail -} // namespace yml -} // namespace c4 - -#endif /* _C4_YML_DETAIL_STACK_HPP_ */ - - -// (end https://github.com/biojppm/rapidyaml/src/c4/yml/detail/stack.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/yml/parse.hpp -// https://github.com/biojppm/rapidyaml/src/c4/yml/parse.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_YML_PARSE_HPP_ -#define _C4_YML_PARSE_HPP_ - -#ifndef _C4_YML_TREE_HPP_ -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/yml/tree.hpp -//#include "c4/yml/tree.hpp" -#if !defined(C4_YML_TREE_HPP_) && !defined(_C4_YML_TREE_HPP_) -#error "amalgamate: file c4/yml/tree.hpp must have been included at this point" -#endif /* C4_YML_TREE_HPP_ */ - -#endif - -#ifndef _C4_YML_NODE_HPP_ -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/yml/node.hpp -//#include "c4/yml/node.hpp" -#if !defined(C4_YML_NODE_HPP_) && !defined(_C4_YML_NODE_HPP_) -#error "amalgamate: file c4/yml/node.hpp must have been included at this point" -#endif /* C4_YML_NODE_HPP_ */ - -#endif - -#ifndef _C4_YML_DETAIL_STACK_HPP_ -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/yml/detail/stack.hpp -//#include "c4/yml/detail/stack.hpp" -#if !defined(C4_YML_DETAIL_STACK_HPP_) && !defined(_C4_YML_DETAIL_STACK_HPP_) -#error "amalgamate: file c4/yml/detail/stack.hpp must have been included at this point" -#endif /* C4_YML_DETAIL_STACK_HPP_ */ - -#endif - -//included above: -//#include - -#if defined(_MSC_VER) -# pragma warning(push) -# pragma warning(disable: 4251/*needs to have dll-interface to be used by clients of struct*/) -#endif - -namespace c4 { -namespace yml { - -struct RYML_EXPORT ParserOptions -{ -private: - - typedef enum : uint32_t { - LOCATIONS = (1 << 0), - DEFAULTS = 0, - } Flags_e; - - uint32_t flags = DEFAULTS; -public: - ParserOptions() = default; - - /** @name source location tracking */ - /** @{ */ - - /** enable/disable source location tracking */ - ParserOptions& locations(bool enabled) - { - if(enabled) - flags |= LOCATIONS; - else - flags &= ~LOCATIONS; - return *this; - } - bool locations() const { return (flags & LOCATIONS) != 0u; } - - /** @} */ -}; - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -class RYML_EXPORT Parser -{ -public: - - /** @name construction and assignment */ - /** @{ */ - - Parser(Callbacks const& cb, ParserOptions opts={}); - Parser(ParserOptions opts={}) : Parser(get_callbacks(), opts) {} - ~Parser(); - - Parser(Parser &&); - Parser(Parser const&); - Parser& operator=(Parser &&); - Parser& operator=(Parser const&); - - /** @} */ - -public: - - /** @name modifiers */ - /** @{ */ - - /** Reserve a certain capacity for the parsing stack. - * This should be larger than the expected depth of the parsed - * YAML tree. - * - * The parsing stack is the only (potential) heap memory used by - * the parser. - * - * If the requested capacity is below the default - * stack size of 16, the memory is used directly in the parser - * object; otherwise it will be allocated from the heap. - * - * @note this reserves memory only for the parser itself; all the - * allocations for the parsed tree will go through the tree's - * allocator. - * - * @note the tree and the arena can (and should) also be reserved. */ - void reserve_stack(size_t capacity) - { - m_stack.reserve(capacity); - } - - /** Reserve a certain capacity for the array used to track node - * locations in the source buffer. */ - void reserve_locations(size_t num_source_lines) - { - _resize_locations(num_source_lines); - } - - /** Reserve a certain capacity for the character arena used to - * filter scalars. */ - void reserve_filter_arena(size_t num_characters) - { - _resize_filter_arena(num_characters); - } - - /** @} */ - -public: - - /** @name getters and modifiers */ - /** @{ */ - - /** Get the current callbacks in the parser. */ - Callbacks callbacks() const { return m_stack.m_callbacks; } - - /** Get the name of the latest file parsed by this object. */ - csubstr filename() const { return m_file; } - - /** Get the latest YAML buffer parsed by this object. */ - csubstr source() const { return m_buf; } - - size_t stack_capacity() const { return m_stack.capacity(); } - size_t locations_capacity() const { return m_newline_offsets_capacity; } - size_t filter_arena_capacity() const { return m_filter_arena.len; } - - ParserOptions const& options() const { return m_options; } - - /** @} */ - -public: - - /** @name parse_in_place */ - /** @{ */ - - /** Create a new tree and parse into its root. - * The tree is created with the callbacks currently in the parser. */ - Tree parse_in_place(csubstr filename, substr src) - { - Tree t(callbacks()); - t.reserve(_estimate_capacity(src)); - this->parse_in_place(filename, src, &t, t.root_id()); - return t; - } - - /** Parse into an existing tree, starting at its root node. - * The callbacks in the tree are kept, and used to allocate - * the tree members, if any allocation is required. */ - void parse_in_place(csubstr filename, substr src, Tree *t) - { - this->parse_in_place(filename, src, t, t->root_id()); - } - - /** Parse into an existing node. - * The callbacks in the tree are kept, and used to allocate - * the tree members, if any allocation is required. */ - void parse_in_place(csubstr filename, substr src, Tree *t, size_t node_id); - // ^^^^^^^^^^^^^ this is the workhorse overload; everything else is syntactic candy - - /** Parse into an existing node. - * The callbacks in the tree are kept, and used to allocate - * the tree members, if any allocation is required. */ - void parse_in_place(csubstr filename, substr src, NodeRef node) - { - this->parse_in_place(filename, src, node.tree(), node.id()); - } - - RYML_DEPRECATED("use parse_in_place() instead") Tree parse(csubstr filename, substr src) { return parse_in_place(filename, src); } - RYML_DEPRECATED("use parse_in_place() instead") void parse(csubstr filename, substr src, Tree *t) { parse_in_place(filename, src, t); } - RYML_DEPRECATED("use parse_in_place() instead") void parse(csubstr filename, substr src, Tree *t, size_t node_id) { parse_in_place(filename, src, t, node_id); } - RYML_DEPRECATED("use parse_in_place() instead") void parse(csubstr filename, substr src, NodeRef node) { parse_in_place(filename, src, node); } - - /** @} */ - -public: - - /** @name parse_in_arena: copy the YAML source buffer to the - * tree's arena, then parse the copy in situ - * - * @note overloads receiving a substr YAML buffer are intentionally - * left undefined, such that calling parse_in_arena() with a substr - * will cause a linker error. This is to prevent an accidental - * copy of the source buffer to the tree's arena, because substr - * is implicitly convertible to csubstr. If you really intend to parse - * a mutable buffer in the tree's arena, convert it first to immutable - * by assigning the substr to a csubstr prior to calling parse_in_arena(). - * This is not needed for parse_in_place() because csubstr is not - * implicitly convertible to substr. */ - /** @{ */ - - // READ THE NOTE ABOVE! - #define RYML_DONT_PARSE_SUBSTR_IN_ARENA "Do not pass a (mutable) substr to parse_in_arena(); if you have a substr, it should be parsed in place. Consider using parse_in_place() instead, or convert the buffer to csubstr prior to calling. This function is deliberately left undefined and will cause a linker error." - RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) Tree parse_in_arena(csubstr filename, substr csrc); - RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena(csubstr filename, substr csrc, Tree *t); - RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena(csubstr filename, substr csrc, Tree *t, size_t node_id); - RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena(csubstr filename, substr csrc, NodeRef node); - - /** Create a new tree and parse into its root. - * The immutable YAML source is first copied to the tree's arena, - * and parsed from there. - * The callbacks in the tree are kept, and used to allocate - * the tree members, if any allocation is required. */ - Tree parse_in_arena(csubstr filename, csubstr csrc) - { - Tree t(callbacks()); - substr src = t.copy_to_arena(csrc); - t.reserve(_estimate_capacity(csrc)); - this->parse_in_place(filename, src, &t, t.root_id()); - return t; - } - - /** Parse into an existing tree, starting at its root node. - * The immutable YAML source is first copied to the tree's arena, - * and parsed from there. - * The callbacks in the tree are kept, and used to allocate - * the tree members, if any allocation is required. */ - void parse_in_arena(csubstr filename, csubstr csrc, Tree *t) - { - substr src = t->copy_to_arena(csrc); - this->parse_in_place(filename, src, t, t->root_id()); - } - - /** Parse into a specific node in an existing tree. - * The immutable YAML source is first copied to the tree's arena, - * and parsed from there. - * The callbacks in the tree are kept, and used to allocate - * the tree members, if any allocation is required. */ - void parse_in_arena(csubstr filename, csubstr csrc, Tree *t, size_t node_id) - { - substr src = t->copy_to_arena(csrc); - this->parse_in_place(filename, src, t, node_id); - } - - /** Parse into a specific node in an existing tree. - * The immutable YAML source is first copied to the tree's arena, - * and parsed from there. - * The callbacks in the tree are kept, and used to allocate - * the tree members, if any allocation is required. */ - void parse_in_arena(csubstr filename, csubstr csrc, NodeRef node) - { - substr src = node.tree()->copy_to_arena(csrc); - this->parse_in_place(filename, src, node.tree(), node.id()); - } - - RYML_DEPRECATED("use parse_in_arena() instead") Tree parse(csubstr filename, csubstr csrc) { return parse_in_arena(filename, csrc); } - RYML_DEPRECATED("use parse_in_arena() instead") void parse(csubstr filename, csubstr csrc, Tree *t) { parse_in_arena(filename, csrc, t); } - RYML_DEPRECATED("use parse_in_arena() instead") void parse(csubstr filename, csubstr csrc, Tree *t, size_t node_id) { parse_in_arena(filename, csrc, t, node_id); } - RYML_DEPRECATED("use parse_in_arena() instead") void parse(csubstr filename, csubstr csrc, NodeRef node) { parse_in_arena(filename, csrc, node); } - - /** @} */ - -public: - - /** @name locations */ - /** @{ */ - - /** Get the location of a node of the last tree to be parsed by this parser. */ - Location location(Tree const& tree, size_t node_id) const; - /** Get the location of a node of the last tree to be parsed by this parser. */ - Location location(ConstNodeRef node) const; - /** Get the string starting at a particular location, to the end - * of the parsed source buffer. */ - csubstr location_contents(Location const& loc) const; - /** Given a pointer to a buffer position, get the location. @p val - * must be pointing to somewhere in the source buffer that was - * last parsed by this object. */ - Location val_location(const char *val) const; - - /** @} */ - -private: - - typedef enum { - BLOCK_LITERAL, //!< keep newlines (|) - BLOCK_FOLD //!< replace newline with single space (>) - } BlockStyle_e; - - typedef enum { - CHOMP_CLIP, //!< single newline at end (default) - CHOMP_STRIP, //!< no newline at end (-) - CHOMP_KEEP //!< all newlines from end (+) - } BlockChomp_e; - -private: - - using flag_t = int; - - static size_t _estimate_capacity(csubstr src) { size_t c = _count_nlines(src); c = c >= 16 ? c : 16; return c; } - - void _reset(); - - bool _finished_file() const; - bool _finished_line() const; - - csubstr _peek_next_line(size_t pos=npos) const; - bool _advance_to_peeked(); - void _scan_line(); - - csubstr _slurp_doc_scalar(); - - /** - * @param [out] quoted - * Will only be written to if this method returns true. - * Will be set to true if the scanned scalar was quoted, by '', "", > or |. - */ - bool _scan_scalar_seq_blck(csubstr *C4_RESTRICT scalar, bool *C4_RESTRICT quoted); - bool _scan_scalar_map_blck(csubstr *C4_RESTRICT scalar, bool *C4_RESTRICT quoted); - bool _scan_scalar_seq_flow(csubstr *C4_RESTRICT scalar, bool *C4_RESTRICT quoted); - bool _scan_scalar_map_flow(csubstr *C4_RESTRICT scalar, bool *C4_RESTRICT quoted); - bool _scan_scalar_unk(csubstr *C4_RESTRICT scalar, bool *C4_RESTRICT quoted); - - csubstr _scan_comment(); - csubstr _scan_squot_scalar(); - csubstr _scan_dquot_scalar(); - csubstr _scan_block(); - substr _scan_plain_scalar_blck(csubstr currscalar, csubstr peeked_line, size_t indentation); - substr _scan_plain_scalar_flow(csubstr currscalar, csubstr peeked_line); - substr _scan_complex_key(csubstr currscalar, csubstr peeked_line); - csubstr _scan_to_next_nonempty_line(size_t indentation); - csubstr _extend_scanned_scalar(csubstr currscalar); - - csubstr _filter_squot_scalar(const substr s); - csubstr _filter_dquot_scalar(substr s); - csubstr _filter_plain_scalar(substr s, size_t indentation); - csubstr _filter_block_scalar(substr s, BlockStyle_e style, BlockChomp_e chomp, size_t indentation); - template - bool _filter_nl(substr scalar, size_t *C4_RESTRICT pos, size_t *C4_RESTRICT filter_arena_pos, size_t indentation); - template - void _filter_ws(substr scalar, size_t *C4_RESTRICT pos, size_t *C4_RESTRICT filter_arena_pos); - bool _apply_chomp(substr buf, size_t *C4_RESTRICT pos, BlockChomp_e chomp); - - void _handle_finished_file(); - void _handle_line(); - - bool _handle_indentation(); - - bool _handle_unk(); - bool _handle_map_flow(); - bool _handle_map_blck(); - bool _handle_seq_flow(); - bool _handle_seq_blck(); - bool _handle_top(); - bool _handle_types(); - bool _handle_key_anchors_and_refs(); - bool _handle_val_anchors_and_refs(); - void _move_val_tag_to_key_tag(); - void _move_key_tag_to_val_tag(); - void _move_key_tag2_to_key_tag(); - void _move_val_anchor_to_key_anchor(); - void _move_key_anchor_to_val_anchor(); - - void _push_level(bool explicit_flow_chars = false); - void _pop_level(); - - void _start_unk(bool as_child=true); - - void _start_map(bool as_child=true); - void _start_map_unk(bool as_child); - void _stop_map(); - - void _start_seq(bool as_child=true); - void _stop_seq(); - - void _start_seqimap(); - void _stop_seqimap(); - - void _start_doc(bool as_child=true); - void _stop_doc(); - void _start_new_doc(csubstr rem); - void _end_stream(); - - NodeData* _append_val(csubstr val, flag_t quoted=false); - NodeData* _append_key_val(csubstr val, flag_t val_quoted=false); - bool _rval_dash_start_or_continue_seq(); - - void _store_scalar(csubstr s, flag_t is_quoted); - csubstr _consume_scalar(); - void _move_scalar_from_top(); - - inline NodeData* _append_val_null(const char *str) { _RYML_CB_ASSERT(m_stack.m_callbacks, str >= m_buf.begin() && str <= m_buf.end()); return _append_val({nullptr, size_t(0)}); } - inline NodeData* _append_key_val_null(const char *str) { _RYML_CB_ASSERT(m_stack.m_callbacks, str >= m_buf.begin() && str <= m_buf.end()); return _append_key_val({nullptr, size_t(0)}); } - inline void _store_scalar_null(const char *str) { _RYML_CB_ASSERT(m_stack.m_callbacks, str >= m_buf.begin() && str <= m_buf.end()); _store_scalar({nullptr, size_t(0)}, false); } - - void _set_indentation(size_t behind); - void _save_indentation(size_t behind=0); - bool _maybe_set_indentation_from_anchor_or_tag(); - - void _write_key_anchor(size_t node_id); - void _write_val_anchor(size_t node_id); - - void _handle_directive(csubstr directive); - - void _skipchars(char c); - template - void _skipchars(const char (&chars)[N]); - -private: - - static size_t _count_nlines(csubstr src); - -private: - - typedef enum : flag_t { - RTOP = 0x01 << 0, ///< reading at top level - RUNK = 0x01 << 1, ///< reading an unknown: must determine whether scalar, map or seq - RMAP = 0x01 << 2, ///< reading a map - RSEQ = 0x01 << 3, ///< reading a seq - FLOW = 0x01 << 4, ///< reading is inside explicit flow chars: [] or {} - QMRK = 0x01 << 5, ///< reading an explicit key (`? key`) - RKEY = 0x01 << 6, ///< reading a scalar as key - RVAL = 0x01 << 7, ///< reading a scalar as val - RNXT = 0x01 << 8, ///< read next val or keyval - SSCL = 0x01 << 9, ///< there's a stored scalar - QSCL = 0x01 << 10, ///< stored scalar was quoted - RSET = 0x01 << 11, ///< the (implicit) map being read is a !!set. @see https://yaml.org/type/set.html - NDOC = 0x01 << 12, ///< no document mode. a document has ended and another has not started yet. - //! reading an implicit map nested in an explicit seq. - //! eg, {key: [key2: value2, key3: value3]} - //! is parsed as {key: [{key2: value2}, {key3: value3}]} - RSEQIMAP = 0x01 << 13, - } State_e; - - struct LineContents - { - csubstr full; ///< the full line, including newlines on the right - csubstr stripped; ///< the stripped line, excluding newlines on the right - csubstr rem; ///< the stripped line remainder; initially starts at the first non-space character - size_t indentation; ///< the number of spaces on the beginning of the line - - LineContents() : full(), stripped(), rem(), indentation() {} - - void reset_with_next_line(csubstr buf, size_t pos); - - void reset(csubstr full_, csubstr stripped_) - { - full = full_; - stripped = stripped_; - rem = stripped_; - // find the first column where the character is not a space - indentation = full.first_not_of(' '); - } - - size_t current_col() const - { - return current_col(rem); - } - - size_t current_col(csubstr s) const - { - RYML_ASSERT(s.str >= full.str); - RYML_ASSERT(full.is_super(s)); - size_t col = static_cast(s.str - full.str); - return col; - } - }; - - struct State - { - flag_t flags; - size_t level; - size_t node_id; // don't hold a pointer to the node as it will be relocated during tree resizes - csubstr scalar; - size_t scalar_col; // the column where the scalar (or its quotes) begin - - Location pos; - LineContents line_contents; - size_t indref; - - State() : flags(), level(), node_id(), scalar(), scalar_col(), pos(), line_contents(), indref() {} - - void reset(const char *file, size_t node_id_) - { - flags = RUNK|RTOP; - level = 0; - pos.name = to_csubstr(file); - pos.offset = 0; - pos.line = 1; - pos.col = 1; - node_id = node_id_; - scalar_col = 0; - scalar.clear(); - indref = 0; - } - }; - - void _line_progressed(size_t ahead); - void _line_ended(); - void _line_ended_undo(); - - void _prepare_pop() - { - RYML_ASSERT(m_stack.size() > 1); - State const& curr = m_stack.top(); - State & next = m_stack.top(1); - next.pos = curr.pos; - next.line_contents = curr.line_contents; - next.scalar = curr.scalar; - } - - inline bool _at_line_begin() const - { - return m_state->line_contents.rem.begin() == m_state->line_contents.full.begin(); - } - inline bool _at_line_end() const - { - csubstr r = m_state->line_contents.rem; - return r.empty() || r.begins_with(' ', r.len); - } - inline bool _token_is_from_this_line(csubstr token) const - { - return token.is_sub(m_state->line_contents.full); - } - - inline NodeData * node(State const* s) const { return m_tree->get(s->node_id); } - inline NodeData * node(State const& s) const { return m_tree->get(s .node_id); } - inline NodeData * node(size_t node_id) const { return m_tree->get( node_id); } - - inline bool has_all(flag_t f) const { return (m_state->flags & f) == f; } - inline bool has_any(flag_t f) const { return (m_state->flags & f) != 0; } - inline bool has_none(flag_t f) const { return (m_state->flags & f) == 0; } - - static inline bool has_all(flag_t f, State const* s) { return (s->flags & f) == f; } - static inline bool has_any(flag_t f, State const* s) { return (s->flags & f) != 0; } - static inline bool has_none(flag_t f, State const* s) { return (s->flags & f) == 0; } - - inline void set_flags(flag_t f) { set_flags(f, m_state); } - inline void add_flags(flag_t on) { add_flags(on, m_state); } - inline void addrem_flags(flag_t on, flag_t off) { addrem_flags(on, off, m_state); } - inline void rem_flags(flag_t off) { rem_flags(off, m_state); } - - void set_flags(flag_t f, State * s); - void add_flags(flag_t on, State * s); - void addrem_flags(flag_t on, flag_t off, State * s); - void rem_flags(flag_t off, State * s); - - void _resize_filter_arena(size_t num_characters); - void _grow_filter_arena(size_t num_characters); - substr _finish_filter_arena(substr dst, size_t pos); - - void _prepare_locations(); - void _resize_locations(size_t sz); - bool _locations_dirty() const; - - bool _location_from_cont(Tree const& tree, size_t node, Location *C4_RESTRICT loc) const; - bool _location_from_node(Tree const& tree, size_t node, Location *C4_RESTRICT loc, size_t level) const; - -private: - - void _free(); - void _clr(); - void _cp(Parser const* that); - void _mv(Parser *that); - -#ifdef RYML_DBG - template void _dbg(csubstr fmt, Args const& C4_RESTRICT ...args) const; -#endif - template void _err(csubstr fmt, Args const& C4_RESTRICT ...args) const; - template void _fmt_msg(DumpFn &&dumpfn) const; - static csubstr _prfl(substr buf, flag_t v); - -private: - - ParserOptions m_options; - - csubstr m_file; - substr m_buf; - - size_t m_root_id; - Tree * m_tree; - - detail::stack m_stack; - State * m_state; - - size_t m_key_tag_indentation; - size_t m_key_tag2_indentation; - csubstr m_key_tag; - csubstr m_key_tag2; - size_t m_val_tag_indentation; - csubstr m_val_tag; - - bool m_key_anchor_was_before; - size_t m_key_anchor_indentation; - csubstr m_key_anchor; - size_t m_val_anchor_indentation; - csubstr m_val_anchor; - - substr m_filter_arena; - - size_t *m_newline_offsets; - size_t m_newline_offsets_size; - size_t m_newline_offsets_capacity; - csubstr m_newline_offsets_buf; -}; - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -/** @name parse_in_place - * - * @desc parse a mutable YAML source buffer. - * - * @note These freestanding functions use a temporary parser object, - * and are convenience functions to easily parse YAML without the need - * to instantiate a separate parser. Note that some properties - * (notably node locations in the original source code) are only - * available through the parser object after it has parsed the - * code. If you need access to any of these properties, use - * Parser::parse_in_place() */ -/** @{ */ - -inline Tree parse_in_place( substr yaml ) { Parser np; return np.parse_in_place({} , yaml); } //!< parse in-situ a modifiable YAML source buffer. -inline Tree parse_in_place(csubstr filename, substr yaml ) { Parser np; return np.parse_in_place(filename, yaml); } //!< parse in-situ a modifiable YAML source buffer, providing a filename for error messages. -inline void parse_in_place( substr yaml, Tree *t ) { Parser np; np.parse_in_place({} , yaml, t); } //!< reusing the YAML tree, parse in-situ a modifiable YAML source buffer -inline void parse_in_place(csubstr filename, substr yaml, Tree *t ) { Parser np; np.parse_in_place(filename, yaml, t); } //!< reusing the YAML tree, parse in-situ a modifiable YAML source buffer, providing a filename for error messages. -inline void parse_in_place( substr yaml, Tree *t, size_t node_id) { Parser np; np.parse_in_place({} , yaml, t, node_id); } //!< reusing the YAML tree, parse in-situ a modifiable YAML source buffer -inline void parse_in_place(csubstr filename, substr yaml, Tree *t, size_t node_id) { Parser np; np.parse_in_place(filename, yaml, t, node_id); } //!< reusing the YAML tree, parse in-situ a modifiable YAML source buffer, providing a filename for error messages. -inline void parse_in_place( substr yaml, NodeRef node ) { Parser np; np.parse_in_place({} , yaml, node); } //!< reusing the YAML tree, parse in-situ a modifiable YAML source buffer -inline void parse_in_place(csubstr filename, substr yaml, NodeRef node ) { Parser np; np.parse_in_place(filename, yaml, node); } //!< reusing the YAML tree, parse in-situ a modifiable YAML source buffer, providing a filename for error messages. - -RYML_DEPRECATED("use parse_in_place() instead") inline Tree parse( substr yaml ) { Parser np; return np.parse_in_place({} , yaml); } -RYML_DEPRECATED("use parse_in_place() instead") inline Tree parse(csubstr filename, substr yaml ) { Parser np; return np.parse_in_place(filename, yaml); } -RYML_DEPRECATED("use parse_in_place() instead") inline void parse( substr yaml, Tree *t ) { Parser np; np.parse_in_place({} , yaml, t); } -RYML_DEPRECATED("use parse_in_place() instead") inline void parse(csubstr filename, substr yaml, Tree *t ) { Parser np; np.parse_in_place(filename, yaml, t); } -RYML_DEPRECATED("use parse_in_place() instead") inline void parse( substr yaml, Tree *t, size_t node_id) { Parser np; np.parse_in_place({} , yaml, t, node_id); } -RYML_DEPRECATED("use parse_in_place() instead") inline void parse(csubstr filename, substr yaml, Tree *t, size_t node_id) { Parser np; np.parse_in_place(filename, yaml, t, node_id); } -RYML_DEPRECATED("use parse_in_place() instead") inline void parse( substr yaml, NodeRef node ) { Parser np; np.parse_in_place({} , yaml, node); } -RYML_DEPRECATED("use parse_in_place() instead") inline void parse(csubstr filename, substr yaml, NodeRef node ) { Parser np; np.parse_in_place(filename, yaml, node); } - -/** @} */ - - -//----------------------------------------------------------------------------- - -/** @name parse_in_arena - * @desc parse a read-only YAML source buffer, copying it first to the tree's arena. - * - * @note These freestanding functions use a temporary parser object, - * and are convenience functions to easily parse YAML without the need - * to instantiate a separate parser. Note that some properties - * (notably node locations in the original source code) are only - * available through the parser object after it has parsed the - * code. If you need access to any of these properties, use - * Parser::parse_in_arena(). - * - * @note overloads receiving a substr YAML buffer are intentionally - * left undefined, such that calling parse_in_arena() with a substr - * will cause a linker error. This is to prevent an accidental - * copy of the source buffer to the tree's arena, because substr - * is implicitly convertible to csubstr. If you really intend to parse - * a mutable buffer in the tree's arena, convert it first to immutable - * by assigning the substr to a csubstr prior to calling parse_in_arena(). - * This is not needed for parse_in_place() because csubstr is not - * implicitly convertible to substr. */ -/** @{ */ - -/* READ THE NOTE ABOVE! */ -RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) Tree parse_in_arena( substr yaml ); -RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) Tree parse_in_arena(csubstr filename, substr yaml ); -RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena( substr yaml, Tree *t ); -RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena(csubstr filename, substr yaml, Tree *t ); -RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena( substr yaml, Tree *t, size_t node_id); -RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena(csubstr filename, substr yaml, Tree *t, size_t node_id); -RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena( substr yaml, NodeRef node ); -RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena(csubstr filename, substr yaml, NodeRef node ); - -inline Tree parse_in_arena( csubstr yaml ) { Parser np; return np.parse_in_arena({} , yaml); } //!< parse a read-only YAML source buffer, copying it first to the tree's source arena. -inline Tree parse_in_arena(csubstr filename, csubstr yaml ) { Parser np; return np.parse_in_arena(filename, yaml); } //!< parse a read-only YAML source buffer, copying it first to the tree's source arena, providing a filename for error messages. -inline void parse_in_arena( csubstr yaml, Tree *t ) { Parser np; np.parse_in_arena({} , yaml, t); } //!< reusing the YAML tree, parse a read-only YAML source buffer, copying it first to the tree's source arena. -inline void parse_in_arena(csubstr filename, csubstr yaml, Tree *t ) { Parser np; np.parse_in_arena(filename, yaml, t); } //!< reusing the YAML tree, parse a read-only YAML source buffer, copying it first to the tree's source arena, providing a filename for error messages. -inline void parse_in_arena( csubstr yaml, Tree *t, size_t node_id) { Parser np; np.parse_in_arena({} , yaml, t, node_id); } //!< reusing the YAML tree, parse a read-only YAML source buffer, copying it first to the tree's source arena. -inline void parse_in_arena(csubstr filename, csubstr yaml, Tree *t, size_t node_id) { Parser np; np.parse_in_arena(filename, yaml, t, node_id); } //!< reusing the YAML tree, parse a read-only YAML source buffer, copying it first to the tree's source arena, providing a filename for error messages. -inline void parse_in_arena( csubstr yaml, NodeRef node ) { Parser np; np.parse_in_arena({} , yaml, node); } //!< reusing the YAML tree, parse a read-only YAML source buffer, copying it first to the tree's source arena. -inline void parse_in_arena(csubstr filename, csubstr yaml, NodeRef node ) { Parser np; np.parse_in_arena(filename, yaml, node); } //!< reusing the YAML tree, parse a read-only YAML source buffer, copying it first to the tree's source arena, providing a filename for error messages. - -RYML_DEPRECATED("use parse_in_arena() instead") inline Tree parse( csubstr yaml ) { Parser np; return np.parse_in_arena({} , yaml); } //!< parse a read-only YAML source buffer, copying it first to the tree's source arena. -RYML_DEPRECATED("use parse_in_arena() instead") inline Tree parse(csubstr filename, csubstr yaml ) { Parser np; return np.parse_in_arena(filename, yaml); } //!< parse a read-only YAML source buffer, copying it first to the tree's source arena, providing a filename for error messages. -RYML_DEPRECATED("use parse_in_arena() instead") inline void parse( csubstr yaml, Tree *t ) { Parser np; np.parse_in_arena({} , yaml, t); } //!< reusing the YAML tree, parse a read-only YAML source buffer, copying it first to the tree's source arena. -RYML_DEPRECATED("use parse_in_arena() instead") inline void parse(csubstr filename, csubstr yaml, Tree *t ) { Parser np; np.parse_in_arena(filename, yaml, t); } //!< reusing the YAML tree, parse a read-only YAML source buffer, copying it first to the tree's source arena, providing a filename for error messages. -RYML_DEPRECATED("use parse_in_arena() instead") inline void parse( csubstr yaml, Tree *t, size_t node_id) { Parser np; np.parse_in_arena({} , yaml, t, node_id); } //!< reusing the YAML tree, parse a read-only YAML source buffer, copying it first to the tree's source arena. -RYML_DEPRECATED("use parse_in_arena() instead") inline void parse(csubstr filename, csubstr yaml, Tree *t, size_t node_id) { Parser np; np.parse_in_arena(filename, yaml, t, node_id); } //!< reusing the YAML tree, parse a read-only YAML source buffer, copying it first to the tree's source arena, providing a filename for error messages. -RYML_DEPRECATED("use parse_in_arena() instead") inline void parse( csubstr yaml, NodeRef node ) { Parser np; np.parse_in_arena({} , yaml, node); } //!< reusing the YAML tree, parse a read-only YAML source buffer, copying it first to the tree's source arena. -RYML_DEPRECATED("use parse_in_arena() instead") inline void parse(csubstr filename, csubstr yaml, NodeRef node ) { Parser np; np.parse_in_arena(filename, yaml, node); } //!< reusing the YAML tree, parse a read-only YAML source buffer, copying it first to the tree's source arena, providing a filename for error messages. - -/** @} */ - -} // namespace yml -} // namespace c4 - -#if defined(_MSC_VER) -# pragma warning(pop) -#endif - -#endif /* _C4_YML_PARSE_HPP_ */ - - -// (end https://github.com/biojppm/rapidyaml/src/c4/yml/parse.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/yml/std/map.hpp -// https://github.com/biojppm/rapidyaml/src/c4/yml/std/map.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_YML_STD_MAP_HPP_ -#define _C4_YML_STD_MAP_HPP_ - -/** @file map.hpp write/read std::map to/from a YAML tree. */ - -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/yml/node.hpp -//#include "c4/yml/node.hpp" -#if !defined(C4_YML_NODE_HPP_) && !defined(_C4_YML_NODE_HPP_) -#error "amalgamate: file c4/yml/node.hpp must have been included at this point" -#endif /* C4_YML_NODE_HPP_ */ - -#include - -namespace c4 { -namespace yml { - -// std::map requires child nodes in the data -// tree hierarchy (a MAP node in ryml parlance). -// So it should be serialized via write()/read(). - -template -void write(c4::yml::NodeRef *n, std::map const& m) -{ - *n |= c4::yml::MAP; - for(auto const& C4_RESTRICT p : m) - { - auto ch = n->append_child(); - ch << c4::yml::key(p.first); - ch << p.second; - } -} - -template -bool read(c4::yml::ConstNodeRef const& n, std::map * m) -{ - K k{}; - V v{}; - for(auto const& C4_RESTRICT ch : n) - { - ch >> c4::yml::key(k); - ch >> v; - m->emplace(std::make_pair(std::move(k), std::move(v))); - } - return true; -} - -} // namespace yml -} // namespace c4 - -#endif // _C4_YML_STD_MAP_HPP_ - - -// (end https://github.com/biojppm/rapidyaml/src/c4/yml/std/map.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/yml/std/string.hpp -// https://github.com/biojppm/rapidyaml/src/c4/yml/std/string.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef C4_YML_STD_STRING_HPP_ -#define C4_YML_STD_STRING_HPP_ - -/** @file string.hpp substring conversions for/from std::string */ - -// everything we need is implemented here: -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/std/string.hpp -//#include -#if !defined(C4_STD_STRING_HPP_) && !defined(_C4_STD_STRING_HPP_) -#error "amalgamate: file c4/std/string.hpp must have been included at this point" -#endif /* C4_STD_STRING_HPP_ */ - - -#endif // C4_YML_STD_STRING_HPP_ - - -// (end https://github.com/biojppm/rapidyaml/src/c4/yml/std/string.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/yml/std/vector.hpp -// https://github.com/biojppm/rapidyaml/src/c4/yml/std/vector.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_YML_STD_VECTOR_HPP_ -#define _C4_YML_STD_VECTOR_HPP_ - -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/yml/node.hpp -//#include "c4/yml/node.hpp" -#if !defined(C4_YML_NODE_HPP_) && !defined(_C4_YML_NODE_HPP_) -#error "amalgamate: file c4/yml/node.hpp must have been included at this point" -#endif /* C4_YML_NODE_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/std/vector.hpp -//#include -#if !defined(C4_STD_VECTOR_HPP_) && !defined(_C4_STD_VECTOR_HPP_) -#error "amalgamate: file c4/std/vector.hpp must have been included at this point" -#endif /* C4_STD_VECTOR_HPP_ */ - -//included above: -//#include - -namespace c4 { -namespace yml { - -// vector is a sequence-like type, and it requires child nodes -// in the data tree hierarchy (a SEQ node in ryml parlance). -// So it should be serialized via write()/read(). - - -template -void write(c4::yml::NodeRef *n, std::vector const& vec) -{ - *n |= c4::yml::SEQ; - for(auto const& v : vec) - n->append_child() << v; -} - -template -bool read(c4::yml::ConstNodeRef const& n, std::vector *vec) -{ - vec->resize(n.num_children()); - size_t pos = 0; - for(auto const ch : n) - ch >> (*vec)[pos++]; - return true; -} - -/** specialization: std::vector uses std::vector::reference as - * the return value of its operator[]. */ -template -bool read(c4::yml::ConstNodeRef const& n, std::vector *vec) -{ - vec->resize(n.num_children()); - size_t pos = 0; - bool tmp; - for(auto const ch : n) - { - ch >> tmp; - (*vec)[pos++] = tmp; - } - return true; -} - -} // namespace yml -} // namespace c4 - -#endif // _C4_YML_STD_VECTOR_HPP_ - - -// (end https://github.com/biojppm/rapidyaml/src/c4/yml/std/vector.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/yml/std/std.hpp -// https://github.com/biojppm/rapidyaml/src/c4/yml/std/std.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_YML_STD_STD_HPP_ -#define _C4_YML_STD_STD_HPP_ - -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/yml/std/string.hpp -//#include "c4/yml/std/string.hpp" -#if !defined(C4_YML_STD_STRING_HPP_) && !defined(_C4_YML_STD_STRING_HPP_) -#error "amalgamate: file c4/yml/std/string.hpp must have been included at this point" -#endif /* C4_YML_STD_STRING_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/yml/std/vector.hpp -//#include "c4/yml/std/vector.hpp" -#if !defined(C4_YML_STD_VECTOR_HPP_) && !defined(_C4_YML_STD_VECTOR_HPP_) -#error "amalgamate: file c4/yml/std/vector.hpp must have been included at this point" -#endif /* C4_YML_STD_VECTOR_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/yml/std/map.hpp -//#include "c4/yml/std/map.hpp" -#if !defined(C4_YML_STD_MAP_HPP_) && !defined(_C4_YML_STD_MAP_HPP_) -#error "amalgamate: file c4/yml/std/map.hpp must have been included at this point" -#endif /* C4_YML_STD_MAP_HPP_ */ - - -#endif // _C4_YML_STD_STD_HPP_ - - -// (end https://github.com/biojppm/rapidyaml/src/c4/yml/std/std.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/yml/common.cpp -// https://github.com/biojppm/rapidyaml/src/c4/yml/common.cpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifdef RYML_SINGLE_HDR_DEFINE_NOW -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/yml/common.hpp -//#include "c4/yml/common.hpp" -#if !defined(C4_YML_COMMON_HPP_) && !defined(_C4_YML_COMMON_HPP_) -#error "amalgamate: file c4/yml/common.hpp must have been included at this point" -#endif /* C4_YML_COMMON_HPP_ */ - - -#ifndef RYML_NO_DEFAULT_CALLBACKS -//included above: -//# include -//included above: -//# include -#endif // RYML_NO_DEFAULT_CALLBACKS - -namespace c4 { -namespace yml { - -namespace { -Callbacks s_default_callbacks; -} // anon namespace - -#ifndef RYML_NO_DEFAULT_CALLBACKS -void report_error_impl(const char* msg, size_t length, Location loc, FILE *f) -{ - if(!f) - f = stderr; - if(loc) - { - if(!loc.name.empty()) - { - fwrite(loc.name.str, 1, loc.name.len, f); - fputc(':', f); - } - fprintf(f, "%zu:", loc.line); - if(loc.col) - fprintf(f, "%zu:", loc.col); - if(loc.offset) - fprintf(f, " (%zuB):", loc.offset); - } - fprintf(f, "%.*s\n", (int)length, msg); - fflush(f); -} - -void error_impl(const char* msg, size_t length, Location loc, void * /*user_data*/) -{ - report_error_impl(msg, length, loc, nullptr); - ::abort(); -} - -void* allocate_impl(size_t length, void * /*hint*/, void * /*user_data*/) -{ - void *mem = ::malloc(length); - if(mem == nullptr) - { - const char msg[] = "could not allocate memory"; - error_impl(msg, sizeof(msg)-1, {}, nullptr); - } - return mem; -} - -void free_impl(void *mem, size_t /*length*/, void * /*user_data*/) -{ - ::free(mem); -} -#endif // RYML_NO_DEFAULT_CALLBACKS - - - -Callbacks::Callbacks() - : - m_user_data(nullptr), - #ifndef RYML_NO_DEFAULT_CALLBACKS - m_allocate(allocate_impl), - m_free(free_impl), - m_error(error_impl) - #else - m_allocate(nullptr), - m_free(nullptr), - m_error(nullptr) - #endif -{ -} - -Callbacks::Callbacks(void *user_data, pfn_allocate alloc_, pfn_free free_, pfn_error error_) - : - m_user_data(user_data), - #ifndef RYML_NO_DEFAULT_CALLBACKS - m_allocate(alloc_ ? alloc_ : allocate_impl), - m_free(free_ ? free_ : free_impl), - m_error(error_ ? error_ : error_impl) - #else - m_allocate(alloc_), - m_free(free_), - m_error(error_) - #endif -{ - C4_CHECK(m_allocate); - C4_CHECK(m_free); - C4_CHECK(m_error); -} - - -void set_callbacks(Callbacks const& c) -{ - s_default_callbacks = c; -} - -Callbacks const& get_callbacks() -{ - return s_default_callbacks; -} - -void reset_callbacks() -{ - set_callbacks(Callbacks()); -} - -void error(const char *msg, size_t msg_len, Location loc) -{ - s_default_callbacks.m_error(msg, msg_len, loc, s_default_callbacks.m_user_data); -} - -} // namespace yml -} // namespace c4 - -#endif /* RYML_SINGLE_HDR_DEFINE_NOW */ - - -// (end https://github.com/biojppm/rapidyaml/src/c4/yml/common.cpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/yml/tree.cpp -// https://github.com/biojppm/rapidyaml/src/c4/yml/tree.cpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifdef RYML_SINGLE_HDR_DEFINE_NOW -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/yml/tree.hpp -//#include "c4/yml/tree.hpp" -#if !defined(C4_YML_TREE_HPP_) && !defined(_C4_YML_TREE_HPP_) -#error "amalgamate: file c4/yml/tree.hpp must have been included at this point" -#endif /* C4_YML_TREE_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/yml/detail/parser_dbg.hpp -//#include "c4/yml/detail/parser_dbg.hpp" -#if !defined(C4_YML_DETAIL_PARSER_DBG_HPP_) && !defined(_C4_YML_DETAIL_PARSER_DBG_HPP_) -#error "amalgamate: file c4/yml/detail/parser_dbg.hpp must have been included at this point" -#endif /* C4_YML_DETAIL_PARSER_DBG_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/yml/node.hpp -//#include "c4/yml/node.hpp" -#if !defined(C4_YML_NODE_HPP_) && !defined(_C4_YML_NODE_HPP_) -#error "amalgamate: file c4/yml/node.hpp must have been included at this point" -#endif /* C4_YML_NODE_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/yml/detail/stack.hpp -//#include "c4/yml/detail/stack.hpp" -#if !defined(C4_YML_DETAIL_STACK_HPP_) && !defined(_C4_YML_DETAIL_STACK_HPP_) -#error "amalgamate: file c4/yml/detail/stack.hpp must have been included at this point" -#endif /* C4_YML_DETAIL_STACK_HPP_ */ - - - -C4_SUPPRESS_WARNING_GCC_WITH_PUSH("-Wtype-limits") -C4_SUPPRESS_WARNING_MSVC_WITH_PUSH(4296/*expression is always 'boolean_value'*/) - -namespace c4 { -namespace yml { - - -csubstr normalize_tag(csubstr tag) -{ - YamlTag_e t = to_tag(tag); - if(t != TAG_NONE) - return from_tag(t); - if(tag.begins_with("!<")) - tag = tag.sub(1); - if(tag.begins_with(""}; - case TAG_OMAP: - return {""}; - case TAG_PAIRS: - return {""}; - case TAG_SET: - return {""}; - case TAG_SEQ: - return {""}; - case TAG_BINARY: - return {""}; - case TAG_BOOL: - return {""}; - case TAG_FLOAT: - return {""}; - case TAG_INT: - return {""}; - case TAG_MERGE: - return {""}; - case TAG_NULL: - return {""}; - case TAG_STR: - return {""}; - case TAG_TIMESTAMP: - return {""}; - case TAG_VALUE: - return {""}; - case TAG_YAML: - return {""}; - case TAG_NONE: - return {""}; - } - return {""}; -} - -csubstr from_tag(YamlTag_e tag) -{ - switch(tag) - { - case TAG_MAP: - return {"!!map"}; - case TAG_OMAP: - return {"!!omap"}; - case TAG_PAIRS: - return {"!!pairs"}; - case TAG_SET: - return {"!!set"}; - case TAG_SEQ: - return {"!!seq"}; - case TAG_BINARY: - return {"!!binary"}; - case TAG_BOOL: - return {"!!bool"}; - case TAG_FLOAT: - return {"!!float"}; - case TAG_INT: - return {"!!int"}; - case TAG_MERGE: - return {"!!merge"}; - case TAG_NULL: - return {"!!null"}; - case TAG_STR: - return {"!!str"}; - case TAG_TIMESTAMP: - return {"!!timestamp"}; - case TAG_VALUE: - return {"!!value"}; - case TAG_YAML: - return {"!!yaml"}; - case TAG_NONE: - return {""}; - } - return {""}; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -const char* NodeType::type_str(NodeType_e ty) -{ - switch(ty & _TYMASK) - { - case KEYVAL: - return "KEYVAL"; - case KEY: - return "KEY"; - case VAL: - return "VAL"; - case MAP: - return "MAP"; - case SEQ: - return "SEQ"; - case KEYMAP: - return "KEYMAP"; - case KEYSEQ: - return "KEYSEQ"; - case DOCSEQ: - return "DOCSEQ"; - case DOCMAP: - return "DOCMAP"; - case DOCVAL: - return "DOCVAL"; - case DOC: - return "DOC"; - case STREAM: - return "STREAM"; - case NOTYPE: - return "NOTYPE"; - default: - if((ty & KEYVAL) == KEYVAL) - return "KEYVAL***"; - if((ty & KEYMAP) == KEYMAP) - return "KEYMAP***"; - if((ty & KEYSEQ) == KEYSEQ) - return "KEYSEQ***"; - if((ty & DOCSEQ) == DOCSEQ) - return "DOCSEQ***"; - if((ty & DOCMAP) == DOCMAP) - return "DOCMAP***"; - if((ty & DOCVAL) == DOCVAL) - return "DOCVAL***"; - if(ty & KEY) - return "KEY***"; - if(ty & VAL) - return "VAL***"; - if(ty & MAP) - return "MAP***"; - if(ty & SEQ) - return "SEQ***"; - if(ty & DOC) - return "DOC***"; - return "(unk)"; - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -NodeRef Tree::rootref() -{ - return NodeRef(this, root_id()); -} -ConstNodeRef Tree::rootref() const -{ - return ConstNodeRef(this, root_id()); -} - -ConstNodeRef Tree::crootref() -{ - return ConstNodeRef(this, root_id()); -} -ConstNodeRef Tree::crootref() const -{ - return ConstNodeRef(this, root_id()); -} - -NodeRef Tree::ref(size_t id) -{ - _RYML_CB_ASSERT(m_callbacks, id != NONE && id >= 0 && id < m_size); - return NodeRef(this, id); -} -ConstNodeRef Tree::ref(size_t id) const -{ - _RYML_CB_ASSERT(m_callbacks, id != NONE && id >= 0 && id < m_size); - return ConstNodeRef(this, id); -} - -ConstNodeRef Tree::cref(size_t id) -{ - _RYML_CB_ASSERT(m_callbacks, id != NONE && id >= 0 && id < m_size); - return ConstNodeRef(this, id); -} -ConstNodeRef Tree::cref(size_t id) const -{ - _RYML_CB_ASSERT(m_callbacks, id != NONE && id >= 0 && id < m_size); - return ConstNodeRef(this, id); -} - -NodeRef Tree::operator[] (csubstr key) -{ - return rootref()[key]; -} -ConstNodeRef Tree::operator[] (csubstr key) const -{ - return rootref()[key]; -} - -NodeRef Tree::operator[] (size_t i) -{ - return rootref()[i]; -} -ConstNodeRef Tree::operator[] (size_t i) const -{ - return rootref()[i]; -} - -NodeRef Tree::docref(size_t i) -{ - return ref(doc(i)); -} -ConstNodeRef Tree::docref(size_t i) const -{ - return cref(doc(i)); -} - - -//----------------------------------------------------------------------------- -Tree::Tree(Callbacks const& cb) - : m_buf(nullptr) - , m_cap(0) - , m_size(0) - , m_free_head(NONE) - , m_free_tail(NONE) - , m_arena() - , m_arena_pos(0) - , m_callbacks(cb) -{ -} - -Tree::Tree(size_t node_capacity, size_t arena_capacity, Callbacks const& cb) - : Tree(cb) -{ - reserve(node_capacity); - reserve_arena(arena_capacity); -} - -Tree::~Tree() -{ - _free(); -} - - -Tree::Tree(Tree const& that) noexcept : Tree(that.m_callbacks) -{ - _copy(that); -} - -Tree& Tree::operator= (Tree const& that) noexcept -{ - _free(); - m_callbacks = that.m_callbacks; - _copy(that); - return *this; -} - -Tree::Tree(Tree && that) noexcept : Tree(that.m_callbacks) -{ - _move(that); -} - -Tree& Tree::operator= (Tree && that) noexcept -{ - _free(); - m_callbacks = that.m_callbacks; - _move(that); - return *this; -} - -void Tree::_free() -{ - if(m_buf) - { - _RYML_CB_ASSERT(m_callbacks, m_cap > 0); - _RYML_CB_FREE(m_callbacks, m_buf, NodeData, m_cap); - } - if(m_arena.str) - { - _RYML_CB_ASSERT(m_callbacks, m_arena.len > 0); - _RYML_CB_FREE(m_callbacks, m_arena.str, char, m_arena.len); - } - _clear(); -} - - -C4_SUPPRESS_WARNING_GCC_PUSH -#if defined(__GNUC__) && __GNUC__>= 8 - C4_SUPPRESS_WARNING_GCC_WITH_PUSH("-Wclass-memaccess") // error: ‘void* memset(void*, int, size_t)’ clearing an object of type ‘class c4::yml::Tree’ with no trivial copy-assignment; use assignment or value-initialization instead -#endif - -void Tree::_clear() -{ - m_buf = nullptr; - m_cap = 0; - m_size = 0; - m_free_head = 0; - m_free_tail = 0; - m_arena = {}; - m_arena_pos = 0; - for(size_t i = 0; i < RYML_MAX_TAG_DIRECTIVES; ++i) - m_tag_directives[i] = {}; -} - -void Tree::_copy(Tree const& that) -{ - _RYML_CB_ASSERT(m_callbacks, m_buf == nullptr); - _RYML_CB_ASSERT(m_callbacks, m_arena.str == nullptr); - _RYML_CB_ASSERT(m_callbacks, m_arena.len == 0); - m_buf = _RYML_CB_ALLOC_HINT(m_callbacks, NodeData, that.m_cap, that.m_buf); - memcpy(m_buf, that.m_buf, that.m_cap * sizeof(NodeData)); - m_cap = that.m_cap; - m_size = that.m_size; - m_free_head = that.m_free_head; - m_free_tail = that.m_free_tail; - m_arena_pos = that.m_arena_pos; - m_arena = that.m_arena; - if(that.m_arena.str) - { - _RYML_CB_ASSERT(m_callbacks, that.m_arena.len > 0); - substr arena; - arena.str = _RYML_CB_ALLOC_HINT(m_callbacks, char, that.m_arena.len, that.m_arena.str); - arena.len = that.m_arena.len; - _relocate(arena); // does a memcpy of the arena and updates nodes using the old arena - m_arena = arena; - } - for(size_t i = 0; i < RYML_MAX_TAG_DIRECTIVES; ++i) - m_tag_directives[i] = that.m_tag_directives[i]; -} - -void Tree::_move(Tree & that) -{ - _RYML_CB_ASSERT(m_callbacks, m_buf == nullptr); - _RYML_CB_ASSERT(m_callbacks, m_arena.str == nullptr); - _RYML_CB_ASSERT(m_callbacks, m_arena.len == 0); - m_buf = that.m_buf; - m_cap = that.m_cap; - m_size = that.m_size; - m_free_head = that.m_free_head; - m_free_tail = that.m_free_tail; - m_arena = that.m_arena; - m_arena_pos = that.m_arena_pos; - for(size_t i = 0; i < RYML_MAX_TAG_DIRECTIVES; ++i) - m_tag_directives[i] = that.m_tag_directives[i]; - that._clear(); -} - -void Tree::_relocate(substr next_arena) -{ - _RYML_CB_ASSERT(m_callbacks, next_arena.not_empty()); - _RYML_CB_ASSERT(m_callbacks, next_arena.len >= m_arena.len); - memcpy(next_arena.str, m_arena.str, m_arena_pos); - for(NodeData *C4_RESTRICT n = m_buf, *e = m_buf + m_cap; n != e; ++n) - { - if(in_arena(n->m_key.scalar)) - n->m_key.scalar = _relocated(n->m_key.scalar, next_arena); - if(in_arena(n->m_key.tag)) - n->m_key.tag = _relocated(n->m_key.tag, next_arena); - if(in_arena(n->m_key.anchor)) - n->m_key.anchor = _relocated(n->m_key.anchor, next_arena); - if(in_arena(n->m_val.scalar)) - n->m_val.scalar = _relocated(n->m_val.scalar, next_arena); - if(in_arena(n->m_val.tag)) - n->m_val.tag = _relocated(n->m_val.tag, next_arena); - if(in_arena(n->m_val.anchor)) - n->m_val.anchor = _relocated(n->m_val.anchor, next_arena); - } - for(TagDirective &C4_RESTRICT td : m_tag_directives) - { - if(in_arena(td.prefix)) - td.prefix = _relocated(td.prefix, next_arena); - if(in_arena(td.handle)) - td.handle = _relocated(td.handle, next_arena); - } -} - - -//----------------------------------------------------------------------------- -void Tree::reserve(size_t cap) -{ - if(cap > m_cap) - { - NodeData *buf = _RYML_CB_ALLOC_HINT(m_callbacks, NodeData, cap, m_buf); - if(m_buf) - { - memcpy(buf, m_buf, m_cap * sizeof(NodeData)); - _RYML_CB_FREE(m_callbacks, m_buf, NodeData, m_cap); - } - size_t first = m_cap, del = cap - m_cap; - m_cap = cap; - m_buf = buf; - _clear_range(first, del); - if(m_free_head != NONE) - { - _RYML_CB_ASSERT(m_callbacks, m_buf != nullptr); - _RYML_CB_ASSERT(m_callbacks, m_free_tail != NONE); - m_buf[m_free_tail].m_next_sibling = first; - m_buf[first].m_prev_sibling = m_free_tail; - m_free_tail = cap-1; - } - else - { - _RYML_CB_ASSERT(m_callbacks, m_free_tail == NONE); - m_free_head = first; - m_free_tail = cap-1; - } - _RYML_CB_ASSERT(m_callbacks, m_free_head == NONE || (m_free_head >= 0 && m_free_head < cap)); - _RYML_CB_ASSERT(m_callbacks, m_free_tail == NONE || (m_free_tail >= 0 && m_free_tail < cap)); - - if( ! m_size) - _claim_root(); - } -} - - -//----------------------------------------------------------------------------- -void Tree::clear() -{ - _clear_range(0, m_cap); - m_size = 0; - if(m_buf) - { - _RYML_CB_ASSERT(m_callbacks, m_cap >= 0); - m_free_head = 0; - m_free_tail = m_cap-1; - _claim_root(); - } - else - { - m_free_head = NONE; - m_free_tail = NONE; - } - for(size_t i = 0; i < RYML_MAX_TAG_DIRECTIVES; ++i) - m_tag_directives[i] = {}; -} - -void Tree::_claim_root() -{ - size_t r = _claim(); - _RYML_CB_ASSERT(m_callbacks, r == 0); - _set_hierarchy(r, NONE, NONE); -} - - -//----------------------------------------------------------------------------- -void Tree::_clear_range(size_t first, size_t num) -{ - if(num == 0) - return; // prevent overflow when subtracting - _RYML_CB_ASSERT(m_callbacks, first >= 0 && first + num <= m_cap); - memset(m_buf + first, 0, num * sizeof(NodeData)); // TODO we should not need this - for(size_t i = first, e = first + num; i < e; ++i) - { - _clear(i); - NodeData *n = m_buf + i; - n->m_prev_sibling = i - 1; - n->m_next_sibling = i + 1; - } - m_buf[first + num - 1].m_next_sibling = NONE; -} - -C4_SUPPRESS_WARNING_GCC_POP - - -//----------------------------------------------------------------------------- -void Tree::_release(size_t i) -{ - _RYML_CB_ASSERT(m_callbacks, i >= 0 && i < m_cap); - - _rem_hierarchy(i); - _free_list_add(i); - _clear(i); - - --m_size; -} - -//----------------------------------------------------------------------------- -// add to the front of the free list -void Tree::_free_list_add(size_t i) -{ - _RYML_CB_ASSERT(m_callbacks, i >= 0 && i < m_cap); - NodeData &C4_RESTRICT w = m_buf[i]; - - w.m_parent = NONE; - w.m_next_sibling = m_free_head; - w.m_prev_sibling = NONE; - if(m_free_head != NONE) - m_buf[m_free_head].m_prev_sibling = i; - m_free_head = i; - if(m_free_tail == NONE) - m_free_tail = m_free_head; -} - -void Tree::_free_list_rem(size_t i) -{ - if(m_free_head == i) - m_free_head = _p(i)->m_next_sibling; - _rem_hierarchy(i); -} - -//----------------------------------------------------------------------------- -size_t Tree::_claim() -{ - if(m_free_head == NONE || m_buf == nullptr) - { - size_t sz = 2 * m_cap; - sz = sz ? sz : 16; - reserve(sz); - _RYML_CB_ASSERT(m_callbacks, m_free_head != NONE); - } - - _RYML_CB_ASSERT(m_callbacks, m_size < m_cap); - _RYML_CB_ASSERT(m_callbacks, m_free_head >= 0 && m_free_head < m_cap); - - size_t ichild = m_free_head; - NodeData *child = m_buf + ichild; - - ++m_size; - m_free_head = child->m_next_sibling; - if(m_free_head == NONE) - { - m_free_tail = NONE; - _RYML_CB_ASSERT(m_callbacks, m_size == m_cap); - } - - _clear(ichild); - - return ichild; -} - -//----------------------------------------------------------------------------- - -C4_SUPPRESS_WARNING_GCC_PUSH -C4_SUPPRESS_WARNING_CLANG_PUSH -C4_SUPPRESS_WARNING_CLANG("-Wnull-dereference") -#if defined(__GNUC__) && (__GNUC__ >= 6) -C4_SUPPRESS_WARNING_GCC("-Wnull-dereference") -#endif - -void Tree::_set_hierarchy(size_t ichild, size_t iparent, size_t iprev_sibling) -{ - _RYML_CB_ASSERT(m_callbacks, iparent == NONE || (iparent >= 0 && iparent < m_cap)); - _RYML_CB_ASSERT(m_callbacks, iprev_sibling == NONE || (iprev_sibling >= 0 && iprev_sibling < m_cap)); - - NodeData *C4_RESTRICT child = get(ichild); - - child->m_parent = iparent; - child->m_prev_sibling = NONE; - child->m_next_sibling = NONE; - - if(iparent == NONE) - { - _RYML_CB_ASSERT(m_callbacks, ichild == 0); - _RYML_CB_ASSERT(m_callbacks, iprev_sibling == NONE); - } - - if(iparent == NONE) - return; - - size_t inext_sibling = iprev_sibling != NONE ? next_sibling(iprev_sibling) : first_child(iparent); - NodeData *C4_RESTRICT parent = get(iparent); - NodeData *C4_RESTRICT psib = get(iprev_sibling); - NodeData *C4_RESTRICT nsib = get(inext_sibling); - - if(psib) - { - _RYML_CB_ASSERT(m_callbacks, next_sibling(iprev_sibling) == id(nsib)); - child->m_prev_sibling = id(psib); - psib->m_next_sibling = id(child); - _RYML_CB_ASSERT(m_callbacks, psib->m_prev_sibling != psib->m_next_sibling || psib->m_prev_sibling == NONE); - } - - if(nsib) - { - _RYML_CB_ASSERT(m_callbacks, prev_sibling(inext_sibling) == id(psib)); - child->m_next_sibling = id(nsib); - nsib->m_prev_sibling = id(child); - _RYML_CB_ASSERT(m_callbacks, nsib->m_prev_sibling != nsib->m_next_sibling || nsib->m_prev_sibling == NONE); - } - - if(parent->m_first_child == NONE) - { - _RYML_CB_ASSERT(m_callbacks, parent->m_last_child == NONE); - parent->m_first_child = id(child); - parent->m_last_child = id(child); - } - else - { - if(child->m_next_sibling == parent->m_first_child) - parent->m_first_child = id(child); - - if(child->m_prev_sibling == parent->m_last_child) - parent->m_last_child = id(child); - } -} - -C4_SUPPRESS_WARNING_GCC_POP -C4_SUPPRESS_WARNING_CLANG_POP - - -//----------------------------------------------------------------------------- -void Tree::_rem_hierarchy(size_t i) -{ - _RYML_CB_ASSERT(m_callbacks, i >= 0 && i < m_cap); - - NodeData &C4_RESTRICT w = m_buf[i]; - - // remove from the parent - if(w.m_parent != NONE) - { - NodeData &C4_RESTRICT p = m_buf[w.m_parent]; - if(p.m_first_child == i) - { - p.m_first_child = w.m_next_sibling; - } - if(p.m_last_child == i) - { - p.m_last_child = w.m_prev_sibling; - } - } - - // remove from the used list - if(w.m_prev_sibling != NONE) - { - NodeData *C4_RESTRICT prev = get(w.m_prev_sibling); - prev->m_next_sibling = w.m_next_sibling; - } - if(w.m_next_sibling != NONE) - { - NodeData *C4_RESTRICT next = get(w.m_next_sibling); - next->m_prev_sibling = w.m_prev_sibling; - } -} - -//----------------------------------------------------------------------------- -void Tree::reorder() -{ - size_t r = root_id(); - _do_reorder(&r, 0); -} - -//----------------------------------------------------------------------------- -size_t Tree::_do_reorder(size_t *node, size_t count) -{ - // swap this node if it's not in place - if(*node != count) - { - _swap(*node, count); - *node = count; - } - ++count; // bump the count from this node - - // now descend in the hierarchy - for(size_t i = first_child(*node); i != NONE; i = next_sibling(i)) - { - // this child may have been relocated to a different index, - // so get an updated version - count = _do_reorder(&i, count); - } - return count; -} - -//----------------------------------------------------------------------------- -void Tree::_swap(size_t n_, size_t m_) -{ - _RYML_CB_ASSERT(m_callbacks, (parent(n_) != NONE) || type(n_) == NOTYPE); - _RYML_CB_ASSERT(m_callbacks, (parent(m_) != NONE) || type(m_) == NOTYPE); - NodeType tn = type(n_); - NodeType tm = type(m_); - if(tn != NOTYPE && tm != NOTYPE) - { - _swap_props(n_, m_); - _swap_hierarchy(n_, m_); - } - else if(tn == NOTYPE && tm != NOTYPE) - { - _copy_props(n_, m_); - _free_list_rem(n_); - _copy_hierarchy(n_, m_); - _clear(m_); - _free_list_add(m_); - } - else if(tn != NOTYPE && tm == NOTYPE) - { - _copy_props(m_, n_); - _free_list_rem(m_); - _copy_hierarchy(m_, n_); - _clear(n_); - _free_list_add(n_); - } - else - { - C4_NEVER_REACH(); - } -} - -//----------------------------------------------------------------------------- -void Tree::_swap_hierarchy(size_t ia, size_t ib) -{ - if(ia == ib) return; - - for(size_t i = first_child(ia); i != NONE; i = next_sibling(i)) - { - if(i == ib || i == ia) - continue; - _p(i)->m_parent = ib; - } - - for(size_t i = first_child(ib); i != NONE; i = next_sibling(i)) - { - if(i == ib || i == ia) - continue; - _p(i)->m_parent = ia; - } - - auto & C4_RESTRICT a = *_p(ia); - auto & C4_RESTRICT b = *_p(ib); - auto & C4_RESTRICT pa = *_p(a.m_parent); - auto & C4_RESTRICT pb = *_p(b.m_parent); - - if(&pa == &pb) - { - if((pa.m_first_child == ib && pa.m_last_child == ia) - || - (pa.m_first_child == ia && pa.m_last_child == ib)) - { - std::swap(pa.m_first_child, pa.m_last_child); - } - else - { - bool changed = false; - if(pa.m_first_child == ia) - { - pa.m_first_child = ib; - changed = true; - } - if(pa.m_last_child == ia) - { - pa.m_last_child = ib; - changed = true; - } - if(pb.m_first_child == ib && !changed) - { - pb.m_first_child = ia; - } - if(pb.m_last_child == ib && !changed) - { - pb.m_last_child = ia; - } - } - } - else - { - if(pa.m_first_child == ia) - pa.m_first_child = ib; - if(pa.m_last_child == ia) - pa.m_last_child = ib; - if(pb.m_first_child == ib) - pb.m_first_child = ia; - if(pb.m_last_child == ib) - pb.m_last_child = ia; - } - std::swap(a.m_first_child , b.m_first_child); - std::swap(a.m_last_child , b.m_last_child); - - if(a.m_prev_sibling != ib && b.m_prev_sibling != ia && - a.m_next_sibling != ib && b.m_next_sibling != ia) - { - if(a.m_prev_sibling != NONE && a.m_prev_sibling != ib) - _p(a.m_prev_sibling)->m_next_sibling = ib; - if(a.m_next_sibling != NONE && a.m_next_sibling != ib) - _p(a.m_next_sibling)->m_prev_sibling = ib; - if(b.m_prev_sibling != NONE && b.m_prev_sibling != ia) - _p(b.m_prev_sibling)->m_next_sibling = ia; - if(b.m_next_sibling != NONE && b.m_next_sibling != ia) - _p(b.m_next_sibling)->m_prev_sibling = ia; - std::swap(a.m_prev_sibling, b.m_prev_sibling); - std::swap(a.m_next_sibling, b.m_next_sibling); - } - else - { - if(a.m_next_sibling == ib) // n will go after m - { - _RYML_CB_ASSERT(m_callbacks, b.m_prev_sibling == ia); - if(a.m_prev_sibling != NONE) - { - _RYML_CB_ASSERT(m_callbacks, a.m_prev_sibling != ib); - _p(a.m_prev_sibling)->m_next_sibling = ib; - } - if(b.m_next_sibling != NONE) - { - _RYML_CB_ASSERT(m_callbacks, b.m_next_sibling != ia); - _p(b.m_next_sibling)->m_prev_sibling = ia; - } - size_t ns = b.m_next_sibling; - b.m_prev_sibling = a.m_prev_sibling; - b.m_next_sibling = ia; - a.m_prev_sibling = ib; - a.m_next_sibling = ns; - } - else if(a.m_prev_sibling == ib) // m will go after n - { - _RYML_CB_ASSERT(m_callbacks, b.m_next_sibling == ia); - if(b.m_prev_sibling != NONE) - { - _RYML_CB_ASSERT(m_callbacks, b.m_prev_sibling != ia); - _p(b.m_prev_sibling)->m_next_sibling = ia; - } - if(a.m_next_sibling != NONE) - { - _RYML_CB_ASSERT(m_callbacks, a.m_next_sibling != ib); - _p(a.m_next_sibling)->m_prev_sibling = ib; - } - size_t ns = b.m_prev_sibling; - a.m_prev_sibling = b.m_prev_sibling; - a.m_next_sibling = ib; - b.m_prev_sibling = ia; - b.m_next_sibling = ns; - } - else - { - C4_NEVER_REACH(); - } - } - _RYML_CB_ASSERT(m_callbacks, a.m_next_sibling != ia); - _RYML_CB_ASSERT(m_callbacks, a.m_prev_sibling != ia); - _RYML_CB_ASSERT(m_callbacks, b.m_next_sibling != ib); - _RYML_CB_ASSERT(m_callbacks, b.m_prev_sibling != ib); - - if(a.m_parent != ib && b.m_parent != ia) - { - std::swap(a.m_parent, b.m_parent); - } - else - { - if(a.m_parent == ib && b.m_parent != ia) - { - a.m_parent = b.m_parent; - b.m_parent = ia; - } - else if(a.m_parent != ib && b.m_parent == ia) - { - b.m_parent = a.m_parent; - a.m_parent = ib; - } - else - { - C4_NEVER_REACH(); - } - } -} - -//----------------------------------------------------------------------------- -void Tree::_copy_hierarchy(size_t dst_, size_t src_) -{ - auto const& C4_RESTRICT src = *_p(src_); - auto & C4_RESTRICT dst = *_p(dst_); - auto & C4_RESTRICT prt = *_p(src.m_parent); - for(size_t i = src.m_first_child; i != NONE; i = next_sibling(i)) - { - _p(i)->m_parent = dst_; - } - if(src.m_prev_sibling != NONE) - { - _p(src.m_prev_sibling)->m_next_sibling = dst_; - } - if(src.m_next_sibling != NONE) - { - _p(src.m_next_sibling)->m_prev_sibling = dst_; - } - if(prt.m_first_child == src_) - { - prt.m_first_child = dst_; - } - if(prt.m_last_child == src_) - { - prt.m_last_child = dst_; - } - dst.m_parent = src.m_parent; - dst.m_first_child = src.m_first_child; - dst.m_last_child = src.m_last_child; - dst.m_prev_sibling = src.m_prev_sibling; - dst.m_next_sibling = src.m_next_sibling; -} - -//----------------------------------------------------------------------------- -void Tree::_swap_props(size_t n_, size_t m_) -{ - NodeData &C4_RESTRICT n = *_p(n_); - NodeData &C4_RESTRICT m = *_p(m_); - std::swap(n.m_type, m.m_type); - std::swap(n.m_key, m.m_key); - std::swap(n.m_val, m.m_val); -} - -//----------------------------------------------------------------------------- -void Tree::move(size_t node, size_t after) -{ - _RYML_CB_ASSERT(m_callbacks, node != NONE); - _RYML_CB_ASSERT(m_callbacks, node != after); - _RYML_CB_ASSERT(m_callbacks, ! is_root(node)); - _RYML_CB_ASSERT(m_callbacks, (after == NONE) || (has_sibling(node, after) && has_sibling(after, node))); - - _rem_hierarchy(node); - _set_hierarchy(node, parent(node), after); -} - -//----------------------------------------------------------------------------- - -void Tree::move(size_t node, size_t new_parent, size_t after) -{ - _RYML_CB_ASSERT(m_callbacks, node != NONE); - _RYML_CB_ASSERT(m_callbacks, node != after); - _RYML_CB_ASSERT(m_callbacks, new_parent != NONE); - _RYML_CB_ASSERT(m_callbacks, new_parent != node); - _RYML_CB_ASSERT(m_callbacks, new_parent != after); - _RYML_CB_ASSERT(m_callbacks, ! is_root(node)); - - _rem_hierarchy(node); - _set_hierarchy(node, new_parent, after); -} - -size_t Tree::move(Tree *src, size_t node, size_t new_parent, size_t after) -{ - _RYML_CB_ASSERT(m_callbacks, src != nullptr); - _RYML_CB_ASSERT(m_callbacks, node != NONE); - _RYML_CB_ASSERT(m_callbacks, new_parent != NONE); - _RYML_CB_ASSERT(m_callbacks, new_parent != after); - - size_t dup = duplicate(src, node, new_parent, after); - src->remove(node); - return dup; -} - -void Tree::set_root_as_stream() -{ - size_t root = root_id(); - if(is_stream(root)) - return; - // don't use _add_flags() because it's checked and will fail - if(!has_children(root)) - { - if(is_val(root)) - { - _p(root)->m_type.add(SEQ); - size_t next_doc = append_child(root); - _copy_props_wo_key(next_doc, root); - _p(next_doc)->m_type.add(DOC); - _p(next_doc)->m_type.rem(SEQ); - } - _p(root)->m_type = STREAM; - return; - } - _RYML_CB_ASSERT(m_callbacks, !has_key(root)); - size_t next_doc = append_child(root); - _copy_props_wo_key(next_doc, root); - _add_flags(next_doc, DOC); - for(size_t prev = NONE, ch = first_child(root), next = next_sibling(ch); ch != NONE; ) - { - if(ch == next_doc) - break; - move(ch, next_doc, prev); - prev = ch; - ch = next; - next = next_sibling(next); - } - _p(root)->m_type = STREAM; -} - - -//----------------------------------------------------------------------------- -void Tree::remove_children(size_t node) -{ - _RYML_CB_ASSERT(m_callbacks, get(node) != nullptr); - size_t ich = get(node)->m_first_child; - while(ich != NONE) - { - remove_children(ich); - _RYML_CB_ASSERT(m_callbacks, get(ich) != nullptr); - size_t next = get(ich)->m_next_sibling; - _release(ich); - if(ich == get(node)->m_last_child) - break; - ich = next; - } -} - -bool Tree::change_type(size_t node, NodeType type) -{ - _RYML_CB_ASSERT(m_callbacks, type.is_val() || type.is_map() || type.is_seq()); - _RYML_CB_ASSERT(m_callbacks, type.is_val() + type.is_map() + type.is_seq() == 1); - _RYML_CB_ASSERT(m_callbacks, type.has_key() == has_key(node) || (has_key(node) && !type.has_key())); - NodeData *d = _p(node); - if(type.is_map() && is_map(node)) - return false; - else if(type.is_seq() && is_seq(node)) - return false; - else if(type.is_val() && is_val(node)) - return false; - d->m_type = (d->m_type & (~(MAP|SEQ|VAL))) | type; - remove_children(node); - return true; -} - - -//----------------------------------------------------------------------------- -size_t Tree::duplicate(size_t node, size_t parent, size_t after) -{ - return duplicate(this, node, parent, after); -} - -size_t Tree::duplicate(Tree const* src, size_t node, size_t parent, size_t after) -{ - _RYML_CB_ASSERT(m_callbacks, src != nullptr); - _RYML_CB_ASSERT(m_callbacks, node != NONE); - _RYML_CB_ASSERT(m_callbacks, parent != NONE); - _RYML_CB_ASSERT(m_callbacks, ! src->is_root(node)); - - size_t copy = _claim(); - - _copy_props(copy, src, node); - _set_hierarchy(copy, parent, after); - duplicate_children(src, node, copy, NONE); - - return copy; -} - -//----------------------------------------------------------------------------- -size_t Tree::duplicate_children(size_t node, size_t parent, size_t after) -{ - return duplicate_children(this, node, parent, after); -} - -size_t Tree::duplicate_children(Tree const* src, size_t node, size_t parent, size_t after) -{ - _RYML_CB_ASSERT(m_callbacks, src != nullptr); - _RYML_CB_ASSERT(m_callbacks, node != NONE); - _RYML_CB_ASSERT(m_callbacks, parent != NONE); - _RYML_CB_ASSERT(m_callbacks, after == NONE || has_child(parent, after)); - - size_t prev = after; - for(size_t i = src->first_child(node); i != NONE; i = src->next_sibling(i)) - { - prev = duplicate(src, i, parent, prev); - } - - return prev; -} - -//----------------------------------------------------------------------------- -void Tree::duplicate_contents(size_t node, size_t where) -{ - duplicate_contents(this, node, where); -} - -void Tree::duplicate_contents(Tree const *src, size_t node, size_t where) -{ - _RYML_CB_ASSERT(m_callbacks, src != nullptr); - _RYML_CB_ASSERT(m_callbacks, node != NONE); - _RYML_CB_ASSERT(m_callbacks, where != NONE); - _copy_props_wo_key(where, src, node); - duplicate_children(src, node, where, last_child(where)); -} - -//----------------------------------------------------------------------------- -size_t Tree::duplicate_children_no_rep(size_t node, size_t parent, size_t after) -{ - return duplicate_children_no_rep(this, node, parent, after); -} - -size_t Tree::duplicate_children_no_rep(Tree const *src, size_t node, size_t parent, size_t after) -{ - _RYML_CB_ASSERT(m_callbacks, node != NONE); - _RYML_CB_ASSERT(m_callbacks, parent != NONE); - _RYML_CB_ASSERT(m_callbacks, after == NONE || has_child(parent, after)); - - // don't loop using pointers as there may be a relocation - - // find the position where "after" is - size_t after_pos = NONE; - if(after != NONE) - { - for(size_t i = first_child(parent), icount = 0; i != NONE; ++icount, i = next_sibling(i)) - { - if(i == after) - { - after_pos = icount; - break; - } - } - _RYML_CB_ASSERT(m_callbacks, after_pos != NONE); - } - - // for each child to be duplicated... - size_t prev = after; - for(size_t i = src->first_child(node), icount = 0; i != NONE; ++icount, i = src->next_sibling(i)) - { - if(is_seq(parent)) - { - prev = duplicate(i, parent, prev); - } - else - { - _RYML_CB_ASSERT(m_callbacks, is_map(parent)); - // does the parent already have a node with key equal to that of the current duplicate? - size_t rep = NONE, rep_pos = NONE; - for(size_t j = first_child(parent), jcount = 0; j != NONE; ++jcount, j = next_sibling(j)) - { - if(key(j) == key(i)) - { - rep = j; - rep_pos = jcount; - break; - } - } - if(rep == NONE) // there is no repetition; just duplicate - { - prev = duplicate(src, i, parent, prev); - } - else // yes, there is a repetition - { - if(after_pos != NONE && rep_pos < after_pos) - { - // rep is located before the node which will be inserted, - // and will be overridden by the duplicate. So replace it. - remove(rep); - prev = duplicate(src, i, parent, prev); - } - else if(prev == NONE) - { - // first iteration with prev = after = NONE and repetition - prev = rep; - } - else if(rep != prev) - { - // rep is located after the node which will be inserted - // and overrides it. So move the rep into this node's place. - move(rep, prev); - prev = rep; - } - } // there's a repetition - } - } - - return prev; -} - - -//----------------------------------------------------------------------------- - -void Tree::merge_with(Tree const *src, size_t src_node, size_t dst_node) -{ - _RYML_CB_ASSERT(m_callbacks, src != nullptr); - if(src_node == NONE) - src_node = src->root_id(); - if(dst_node == NONE) - dst_node = root_id(); - _RYML_CB_ASSERT(m_callbacks, src->has_val(src_node) || src->is_seq(src_node) || src->is_map(src_node)); - - if(src->has_val(src_node)) - { - if( ! has_val(dst_node)) - { - if(has_children(dst_node)) - remove_children(dst_node); - } - if(src->is_keyval(src_node)) - _copy_props(dst_node, src, src_node); - else if(src->is_val(src_node)) - _copy_props_wo_key(dst_node, src, src_node); - else - C4_NEVER_REACH(); - } - else if(src->is_seq(src_node)) - { - if( ! is_seq(dst_node)) - { - if(has_children(dst_node)) - remove_children(dst_node); - _clear_type(dst_node); - if(src->has_key(src_node)) - to_seq(dst_node, src->key(src_node)); - else - to_seq(dst_node); - } - for(size_t sch = src->first_child(src_node); sch != NONE; sch = src->next_sibling(sch)) - { - size_t dch = append_child(dst_node); - _copy_props_wo_key(dch, src, sch); - merge_with(src, sch, dch); - } - } - else if(src->is_map(src_node)) - { - if( ! is_map(dst_node)) - { - if(has_children(dst_node)) - remove_children(dst_node); - _clear_type(dst_node); - if(src->has_key(src_node)) - to_map(dst_node, src->key(src_node)); - else - to_map(dst_node); - } - for(size_t sch = src->first_child(src_node); sch != NONE; sch = src->next_sibling(sch)) - { - size_t dch = find_child(dst_node, src->key(sch)); - if(dch == NONE) - { - dch = append_child(dst_node); - _copy_props(dch, src, sch); - } - merge_with(src, sch, dch); - } - } - else - { - C4_NEVER_REACH(); - } -} - - -//----------------------------------------------------------------------------- - -namespace detail { -/** @todo make this part of the public API, refactoring as appropriate - * to be able to use the same resolver to handle multiple trees (one - * at a time) */ -struct ReferenceResolver -{ - struct refdata - { - NodeType type; - size_t node; - size_t prev_anchor; - size_t target; - size_t parent_ref; - size_t parent_ref_sibling; - }; - - Tree *t; - /** from the specs: "an alias node refers to the most recent - * node in the serialization having the specified anchor". So - * we need to start looking upward from ref nodes. - * - * @see http://yaml.org/spec/1.2/spec.html#id2765878 */ - stack refs; - - ReferenceResolver(Tree *t_) : t(t_), refs(t_->callbacks()) - { - resolve(); - } - - void store_anchors_and_refs() - { - // minimize (re-)allocations by counting first - size_t num_anchors_and_refs = count_anchors_and_refs(t->root_id()); - if(!num_anchors_and_refs) - return; - refs.reserve(num_anchors_and_refs); - - // now descend through the hierarchy - _store_anchors_and_refs(t->root_id()); - - // finally connect the reference list - size_t prev_anchor = npos; - size_t count = 0; - for(auto &rd : refs) - { - rd.prev_anchor = prev_anchor; - if(rd.type.is_anchor()) - prev_anchor = count; - ++count; - } - } - - size_t count_anchors_and_refs(size_t n) - { - size_t c = 0; - c += t->has_key_anchor(n); - c += t->has_val_anchor(n); - c += t->is_key_ref(n); - c += t->is_val_ref(n); - for(size_t ch = t->first_child(n); ch != NONE; ch = t->next_sibling(ch)) - c += count_anchors_and_refs(ch); - return c; - } - - void _store_anchors_and_refs(size_t n) - { - if(t->is_key_ref(n) || t->is_val_ref(n) || (t->has_key(n) && t->key(n) == "<<")) - { - if(t->is_seq(n)) - { - // for merging multiple inheritance targets - // <<: [ *CENTER, *BIG ] - for(size_t ich = t->first_child(n); ich != NONE; ich = t->next_sibling(ich)) - { - RYML_ASSERT(t->num_children(ich) == 0); - refs.push({VALREF, ich, npos, npos, n, t->next_sibling(n)}); - } - return; - } - if(t->is_key_ref(n) && t->key(n) != "<<") // insert key refs BEFORE inserting val refs - { - RYML_CHECK((!t->has_key(n)) || t->key(n).ends_with(t->key_ref(n))); - refs.push({KEYREF, n, npos, npos, NONE, NONE}); - } - if(t->is_val_ref(n)) - { - RYML_CHECK((!t->has_val(n)) || t->val(n).ends_with(t->val_ref(n))); - refs.push({VALREF, n, npos, npos, NONE, NONE}); - } - } - if(t->has_key_anchor(n)) - { - RYML_CHECK(t->has_key(n)); - refs.push({KEYANCH, n, npos, npos, NONE, NONE}); - } - if(t->has_val_anchor(n)) - { - RYML_CHECK(t->has_val(n) || t->is_container(n)); - refs.push({VALANCH, n, npos, npos, NONE, NONE}); - } - for(size_t ch = t->first_child(n); ch != NONE; ch = t->next_sibling(ch)) - { - _store_anchors_and_refs(ch); - } - } - - size_t lookup_(refdata *C4_RESTRICT ra) - { - RYML_ASSERT(ra->type.is_key_ref() || ra->type.is_val_ref()); - RYML_ASSERT(ra->type.is_key_ref() != ra->type.is_val_ref()); - csubstr refname; - if(ra->type.is_val_ref()) - { - refname = t->val_ref(ra->node); - } - else - { - RYML_ASSERT(ra->type.is_key_ref()); - refname = t->key_ref(ra->node); - } - while(ra->prev_anchor != npos) - { - ra = &refs[ra->prev_anchor]; - if(t->has_anchor(ra->node, refname)) - return ra->node; - } - - #ifndef RYML_ERRMSG_SIZE - #define RYML_ERRMSG_SIZE 1024 - #endif - - char errmsg[RYML_ERRMSG_SIZE]; - snprintf(errmsg, RYML_ERRMSG_SIZE, "anchor does not exist: '%.*s'", - static_cast(refname.size()), refname.data()); - c4::yml::error(errmsg); - return NONE; - } - - void resolve() - { - store_anchors_and_refs(); - if(refs.empty()) - return; - - /* from the specs: "an alias node refers to the most recent - * node in the serialization having the specified anchor". So - * we need to start looking upward from ref nodes. - * - * @see http://yaml.org/spec/1.2/spec.html#id2765878 */ - for(size_t i = 0, e = refs.size(); i < e; ++i) - { - auto &C4_RESTRICT rd = refs.top(i); - if( ! rd.type.is_ref()) - continue; - rd.target = lookup_(&rd); - } - } - -}; // ReferenceResolver -} // namespace detail - -void Tree::resolve() -{ - if(m_size == 0) - return; - - detail::ReferenceResolver rr(this); - - // insert the resolved references - size_t prev_parent_ref = NONE; - size_t prev_parent_ref_after = NONE; - for(auto const& C4_RESTRICT rd : rr.refs) - { - if( ! rd.type.is_ref()) - continue; - if(rd.parent_ref != NONE) - { - _RYML_CB_ASSERT(m_callbacks, is_seq(rd.parent_ref)); - size_t after, p = parent(rd.parent_ref); - if(prev_parent_ref != rd.parent_ref) - { - after = rd.parent_ref;//prev_sibling(rd.parent_ref_sibling); - prev_parent_ref_after = after; - } - else - { - after = prev_parent_ref_after; - } - prev_parent_ref = rd.parent_ref; - prev_parent_ref_after = duplicate_children_no_rep(rd.target, p, after); - remove(rd.node); - } - else - { - if(has_key(rd.node) && is_key_ref(rd.node) && key(rd.node) == "<<") - { - _RYML_CB_ASSERT(m_callbacks, is_keyval(rd.node)); - size_t p = parent(rd.node); - size_t after = prev_sibling(rd.node); - duplicate_children_no_rep(rd.target, p, after); - remove(rd.node); - } - else if(rd.type.is_key_ref()) - { - _RYML_CB_ASSERT(m_callbacks, is_key_ref(rd.node)); - _RYML_CB_ASSERT(m_callbacks, has_key_anchor(rd.target) || has_val_anchor(rd.target)); - if(has_val_anchor(rd.target) && val_anchor(rd.target) == key_ref(rd.node)) - { - _RYML_CB_CHECK(m_callbacks, !is_container(rd.target)); - _RYML_CB_CHECK(m_callbacks, has_val(rd.target)); - _p(rd.node)->m_key.scalar = val(rd.target); - _add_flags(rd.node, KEY); - } - else - { - _RYML_CB_CHECK(m_callbacks, key_anchor(rd.target) == key_ref(rd.node)); - _p(rd.node)->m_key.scalar = key(rd.target); - _add_flags(rd.node, VAL); - } - } - else - { - _RYML_CB_ASSERT(m_callbacks, rd.type.is_val_ref()); - if(has_key_anchor(rd.target) && key_anchor(rd.target) == val_ref(rd.node)) - { - _RYML_CB_CHECK(m_callbacks, !is_container(rd.target)); - _RYML_CB_CHECK(m_callbacks, has_val(rd.target)); - _p(rd.node)->m_val.scalar = key(rd.target); - _add_flags(rd.node, VAL); - } - else - { - duplicate_contents(rd.target, rd.node); - } - } - } - } - - // clear anchors and refs - for(auto const& C4_RESTRICT ar : rr.refs) - { - rem_anchor_ref(ar.node); - if(ar.parent_ref != NONE) - if(type(ar.parent_ref) != NOTYPE) - remove(ar.parent_ref); - } - -} - -//----------------------------------------------------------------------------- - -size_t Tree::num_children(size_t node) const -{ - size_t count = 0; - for(size_t i = first_child(node); i != NONE; i = next_sibling(i)) - ++count; - return count; -} - -size_t Tree::child(size_t node, size_t pos) const -{ - _RYML_CB_ASSERT(m_callbacks, node != NONE); - size_t count = 0; - for(size_t i = first_child(node); i != NONE; i = next_sibling(i)) - { - if(count++ == pos) - return i; - } - return NONE; -} - -size_t Tree::child_pos(size_t node, size_t ch) const -{ - size_t count = 0; - for(size_t i = first_child(node); i != NONE; i = next_sibling(i)) - { - if(i == ch) - return count; - ++count; - } - return npos; -} - -#if defined(__clang__) -# pragma clang diagnostic push -# pragma GCC diagnostic ignored "-Wnull-dereference" -#elif defined(__GNUC__) -# pragma GCC diagnostic push -# if __GNUC__ >= 6 -# pragma GCC diagnostic ignored "-Wnull-dereference" -# endif -#endif - -size_t Tree::find_child(size_t node, csubstr const& name) const -{ - _RYML_CB_ASSERT(m_callbacks, node != NONE); - _RYML_CB_ASSERT(m_callbacks, is_map(node)); - if(get(node)->m_first_child == NONE) - { - _RYML_CB_ASSERT(m_callbacks, _p(node)->m_last_child == NONE); - return NONE; - } - else - { - _RYML_CB_ASSERT(m_callbacks, _p(node)->m_last_child != NONE); - } - for(size_t i = first_child(node); i != NONE; i = next_sibling(i)) - { - if(_p(i)->m_key.scalar == name) - { - return i; - } - } - return NONE; -} - -#if defined(__clang__) -# pragma clang diagnostic pop -#elif defined(__GNUC__) -# pragma GCC diagnostic pop -#endif - - -//----------------------------------------------------------------------------- - -void Tree::to_val(size_t node, csubstr val, type_bits more_flags) -{ - _RYML_CB_ASSERT(m_callbacks, ! has_children(node)); - _RYML_CB_ASSERT(m_callbacks, parent(node) == NONE || ! parent_is_map(node)); - _set_flags(node, VAL|more_flags); - _p(node)->m_key.clear(); - _p(node)->m_val = val; -} - -void Tree::to_keyval(size_t node, csubstr key, csubstr val, type_bits more_flags) -{ - _RYML_CB_ASSERT(m_callbacks, ! has_children(node)); - _RYML_CB_ASSERT(m_callbacks, parent(node) == NONE || parent_is_map(node)); - _set_flags(node, KEYVAL|more_flags); - _p(node)->m_key = key; - _p(node)->m_val = val; -} - -void Tree::to_map(size_t node, type_bits more_flags) -{ - _RYML_CB_ASSERT(m_callbacks, ! has_children(node)); - _RYML_CB_ASSERT(m_callbacks, parent(node) == NONE || ! parent_is_map(node)); // parent must not have children with keys - _set_flags(node, MAP|more_flags); - _p(node)->m_key.clear(); - _p(node)->m_val.clear(); -} - -void Tree::to_map(size_t node, csubstr key, type_bits more_flags) -{ - _RYML_CB_ASSERT(m_callbacks, ! has_children(node)); - _RYML_CB_ASSERT(m_callbacks, parent(node) == NONE || parent_is_map(node)); - _set_flags(node, KEY|MAP|more_flags); - _p(node)->m_key = key; - _p(node)->m_val.clear(); -} - -void Tree::to_seq(size_t node, type_bits more_flags) -{ - _RYML_CB_ASSERT(m_callbacks, ! has_children(node)); - _RYML_CB_ASSERT(m_callbacks, parent(node) == NONE || parent_is_seq(node)); - _set_flags(node, SEQ|more_flags); - _p(node)->m_key.clear(); - _p(node)->m_val.clear(); -} - -void Tree::to_seq(size_t node, csubstr key, type_bits more_flags) -{ - _RYML_CB_ASSERT(m_callbacks, ! has_children(node)); - _RYML_CB_ASSERT(m_callbacks, parent(node) == NONE || parent_is_map(node)); - _set_flags(node, KEY|SEQ|more_flags); - _p(node)->m_key = key; - _p(node)->m_val.clear(); -} - -void Tree::to_doc(size_t node, type_bits more_flags) -{ - _RYML_CB_ASSERT(m_callbacks, ! has_children(node)); - _set_flags(node, DOC|more_flags); - _p(node)->m_key.clear(); - _p(node)->m_val.clear(); -} - -void Tree::to_stream(size_t node, type_bits more_flags) -{ - _RYML_CB_ASSERT(m_callbacks, ! has_children(node)); - _set_flags(node, STREAM|more_flags); - _p(node)->m_key.clear(); - _p(node)->m_val.clear(); -} - - -//----------------------------------------------------------------------------- -size_t Tree::num_tag_directives() const -{ - // this assumes we have a very small number of tag directives - for(size_t i = 0; i < RYML_MAX_TAG_DIRECTIVES; ++i) - if(m_tag_directives[i].handle.empty()) - return i; - return RYML_MAX_TAG_DIRECTIVES; -} - -void Tree::clear_tag_directives() -{ - for(TagDirective &td : m_tag_directives) - td = {}; -} - -size_t Tree::add_tag_directive(TagDirective const& td) -{ - _RYML_CB_CHECK(m_callbacks, !td.handle.empty()); - _RYML_CB_CHECK(m_callbacks, !td.prefix.empty()); - _RYML_CB_ASSERT(m_callbacks, td.handle.begins_with('!')); - _RYML_CB_ASSERT(m_callbacks, td.handle.ends_with('!')); - // https://yaml.org/spec/1.2.2/#rule-ns-word-char - _RYML_CB_ASSERT(m_callbacks, td.handle == '!' || td.handle == "!!" || td.handle.trim('!').first_not_of("01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-") == npos); - size_t pos = num_tag_directives(); - _RYML_CB_CHECK(m_callbacks, pos < RYML_MAX_TAG_DIRECTIVES); - m_tag_directives[pos] = td; - return pos; -} - -size_t Tree::resolve_tag(substr output, csubstr tag, size_t node_id) const -{ - // lookup from the end. We want to find the first directive that - // matches the tag and has a target node id leq than the given - // node_id. - for(size_t i = RYML_MAX_TAG_DIRECTIVES-1; i != (size_t)-1; --i) - { - auto const& td = m_tag_directives[i]; - if(td.handle.empty()) - continue; - if(tag.begins_with(td.handle) && td.next_node_id <= node_id) - { - _RYML_CB_ASSERT(m_callbacks, tag.len >= td.handle.len); - csubstr rest = tag.sub(td.handle.len); - size_t len = 1u + td.prefix.len + rest.len + 1u; - size_t numpc = rest.count('%'); - if(numpc == 0) - { - if(len <= output.len) - { - output.str[0] = '<'; - memcpy(1u + output.str, td.prefix.str, td.prefix.len); - memcpy(1u + output.str + td.prefix.len, rest.str, rest.len); - output.str[1u + td.prefix.len + rest.len] = '>'; - } - } - else - { - // need to decode URI % sequences - size_t pos = rest.find('%'); - _RYML_CB_ASSERT(m_callbacks, pos != npos); - do { - size_t next = rest.first_not_of("0123456789abcdefABCDEF", pos+1); - if(next == npos) - next = rest.len; - _RYML_CB_CHECK(m_callbacks, pos+1 < next); - _RYML_CB_CHECK(m_callbacks, pos+1 + 2 <= next); - size_t delta = next - (pos+1); - len -= delta; - pos = rest.find('%', pos+1); - } while(pos != npos); - if(len <= output.len) - { - size_t prev = 0, wpos = 0; - auto appendstr = [&](csubstr s) { memcpy(output.str + wpos, s.str, s.len); wpos += s.len; }; - auto appendchar = [&](char c) { output.str[wpos++] = c; }; - appendchar('<'); - appendstr(td.prefix); - pos = rest.find('%'); - _RYML_CB_ASSERT(m_callbacks, pos != npos); - do { - size_t next = rest.first_not_of("0123456789abcdefABCDEF", pos+1); - if(next == npos) - next = rest.len; - _RYML_CB_CHECK(m_callbacks, pos+1 < next); - _RYML_CB_CHECK(m_callbacks, pos+1 + 2 <= next); - uint8_t val; - if(C4_UNLIKELY(!read_hex(rest.range(pos+1, next), &val) || val > 127)) - _RYML_CB_ERR(m_callbacks, "invalid URI character"); - appendstr(rest.range(prev, pos)); - appendchar((char)val); - prev = next; - pos = rest.find('%', pos+1); - } while(pos != npos); - _RYML_CB_ASSERT(m_callbacks, pos == npos); - _RYML_CB_ASSERT(m_callbacks, prev > 0); - _RYML_CB_ASSERT(m_callbacks, rest.len >= prev); - appendstr(rest.sub(prev)); - appendchar('>'); - _RYML_CB_ASSERT(m_callbacks, wpos == len); - } - } - return len; - } - } - return 0; // return 0 to signal that the tag is local and cannot be resolved -} - -namespace { -csubstr _transform_tag(Tree *t, csubstr tag, size_t node) -{ - size_t required_size = t->resolve_tag(substr{}, tag, node); - if(!required_size) - return tag; - const char *prev_arena = t->arena().str; - substr buf = t->alloc_arena(required_size); - _RYML_CB_ASSERT(t->m_callbacks, t->arena().str == prev_arena); - size_t actual_size = t->resolve_tag(buf, tag, node); - _RYML_CB_ASSERT(t->m_callbacks, actual_size <= required_size); - return buf.first(actual_size); -} -void _resolve_tags(Tree *t, size_t node) -{ - for(size_t child = t->first_child(node); child != NONE; child = t->next_sibling(child)) - { - if(t->has_key(child) && t->has_key_tag(child)) - t->set_key_tag(child, _transform_tag(t, t->key_tag(child), child)); - if(t->has_val(child) && t->has_val_tag(child)) - t->set_val_tag(child, _transform_tag(t, t->val_tag(child), child)); - _resolve_tags(t, child); - } -} -size_t _count_resolved_tags_size(Tree const* t, size_t node) -{ - size_t sz = 0; - for(size_t child = t->first_child(node); child != NONE; child = t->next_sibling(child)) - { - if(t->has_key(child) && t->has_key_tag(child)) - sz += t->resolve_tag(substr{}, t->key_tag(child), child); - if(t->has_val(child) && t->has_val_tag(child)) - sz += t->resolve_tag(substr{}, t->val_tag(child), child); - sz += _count_resolved_tags_size(t, child); - } - return sz; -} -} // namespace - -void Tree::resolve_tags() -{ - if(empty()) - return; - if(num_tag_directives() == 0) - return; - size_t needed_size = _count_resolved_tags_size(this, root_id()); - if(needed_size) - reserve_arena(arena_size() + needed_size); - _resolve_tags(this, root_id()); -} - - -//----------------------------------------------------------------------------- - -csubstr Tree::lookup_result::resolved() const -{ - csubstr p = path.first(path_pos); - if(p.ends_with('.')) - p = p.first(p.len-1); - return p; -} - -csubstr Tree::lookup_result::unresolved() const -{ - return path.sub(path_pos); -} - -void Tree::_advance(lookup_result *r, size_t more) const -{ - r->path_pos += more; - if(r->path.sub(r->path_pos).begins_with('.')) - ++r->path_pos; -} - -Tree::lookup_result Tree::lookup_path(csubstr path, size_t start) const -{ - if(start == NONE) - start = root_id(); - lookup_result r(path, start); - if(path.empty()) - return r; - _lookup_path(&r); - if(r.target == NONE && r.closest == start) - r.closest = NONE; - return r; -} - -size_t Tree::lookup_path_or_modify(csubstr default_value, csubstr path, size_t start) -{ - size_t target = _lookup_path_or_create(path, start); - if(parent_is_map(target)) - to_keyval(target, key(target), default_value); - else - to_val(target, default_value); - return target; -} - -size_t Tree::lookup_path_or_modify(Tree const *src, size_t src_node, csubstr path, size_t start) -{ - size_t target = _lookup_path_or_create(path, start); - merge_with(src, src_node, target); - return target; -} - -size_t Tree::_lookup_path_or_create(csubstr path, size_t start) -{ - if(start == NONE) - start = root_id(); - lookup_result r(path, start); - _lookup_path(&r); - if(r.target != NONE) - { - C4_ASSERT(r.unresolved().empty()); - return r.target; - } - _lookup_path_modify(&r); - return r.target; -} - -void Tree::_lookup_path(lookup_result *r) const -{ - C4_ASSERT( ! r->unresolved().empty()); - _lookup_path_token parent{"", type(r->closest)}; - size_t node; - do - { - node = _next_node(r, &parent); - if(node != NONE) - r->closest = node; - if(r->unresolved().empty()) - { - r->target = node; - return; - } - } while(node != NONE); -} - -void Tree::_lookup_path_modify(lookup_result *r) -{ - C4_ASSERT( ! r->unresolved().empty()); - _lookup_path_token parent{"", type(r->closest)}; - size_t node; - do - { - node = _next_node_modify(r, &parent); - if(node != NONE) - r->closest = node; - if(r->unresolved().empty()) - { - r->target = node; - return; - } - } while(node != NONE); -} - -size_t Tree::_next_node(lookup_result * r, _lookup_path_token *parent) const -{ - _lookup_path_token token = _next_token(r, *parent); - if( ! token) - return NONE; - - size_t node = NONE; - csubstr prev = token.value; - if(token.type == MAP || token.type == SEQ) - { - _RYML_CB_ASSERT(m_callbacks, !token.value.begins_with('[')); - //_RYML_CB_ASSERT(m_callbacks, is_container(r->closest) || r->closest == NONE); - _RYML_CB_ASSERT(m_callbacks, is_map(r->closest)); - node = find_child(r->closest, token.value); - } - else if(token.type == KEYVAL) - { - _RYML_CB_ASSERT(m_callbacks, r->unresolved().empty()); - if(is_map(r->closest)) - node = find_child(r->closest, token.value); - } - else if(token.type == KEY) - { - _RYML_CB_ASSERT(m_callbacks, token.value.begins_with('[') && token.value.ends_with(']')); - token.value = token.value.offs(1, 1).trim(' '); - size_t idx = 0; - _RYML_CB_CHECK(m_callbacks, from_chars(token.value, &idx)); - node = child(r->closest, idx); - } - else - { - C4_NEVER_REACH(); - } - - if(node != NONE) - { - *parent = token; - } - else - { - csubstr p = r->path.sub(r->path_pos > 0 ? r->path_pos - 1 : r->path_pos); - r->path_pos -= prev.len; - if(p.begins_with('.')) - r->path_pos -= 1u; - } - - return node; -} - -size_t Tree::_next_node_modify(lookup_result * r, _lookup_path_token *parent) -{ - _lookup_path_token token = _next_token(r, *parent); - if( ! token) - return NONE; - - size_t node = NONE; - if(token.type == MAP || token.type == SEQ) - { - _RYML_CB_ASSERT(m_callbacks, !token.value.begins_with('[')); - //_RYML_CB_ASSERT(m_callbacks, is_container(r->closest) || r->closest == NONE); - if( ! is_container(r->closest)) - { - if(has_key(r->closest)) - to_map(r->closest, key(r->closest)); - else - to_map(r->closest); - } - else - { - if(is_map(r->closest)) - node = find_child(r->closest, token.value); - else - { - size_t pos = NONE; - _RYML_CB_CHECK(m_callbacks, c4::atox(token.value, &pos)); - _RYML_CB_ASSERT(m_callbacks, pos != NONE); - node = child(r->closest, pos); - } - } - if(node == NONE) - { - _RYML_CB_ASSERT(m_callbacks, is_map(r->closest)); - node = append_child(r->closest); - NodeData *n = _p(node); - n->m_key.scalar = token.value; - n->m_type.add(KEY); - } - } - else if(token.type == KEYVAL) - { - _RYML_CB_ASSERT(m_callbacks, r->unresolved().empty()); - if(is_map(r->closest)) - { - node = find_child(r->closest, token.value); - if(node == NONE) - node = append_child(r->closest); - } - else - { - _RYML_CB_ASSERT(m_callbacks, !is_seq(r->closest)); - _add_flags(r->closest, MAP); - node = append_child(r->closest); - } - NodeData *n = _p(node); - n->m_key.scalar = token.value; - n->m_val.scalar = ""; - n->m_type.add(KEYVAL); - } - else if(token.type == KEY) - { - _RYML_CB_ASSERT(m_callbacks, token.value.begins_with('[') && token.value.ends_with(']')); - token.value = token.value.offs(1, 1).trim(' '); - size_t idx; - if( ! from_chars(token.value, &idx)) - return NONE; - if( ! is_container(r->closest)) - { - if(has_key(r->closest)) - { - csubstr k = key(r->closest); - _clear_type(r->closest); - to_seq(r->closest, k); - } - else - { - _clear_type(r->closest); - to_seq(r->closest); - } - } - _RYML_CB_ASSERT(m_callbacks, is_container(r->closest)); - node = child(r->closest, idx); - if(node == NONE) - { - _RYML_CB_ASSERT(m_callbacks, num_children(r->closest) <= idx); - for(size_t i = num_children(r->closest); i <= idx; ++i) - { - node = append_child(r->closest); - if(i < idx) - { - if(is_map(r->closest)) - to_keyval(node, /*"~"*/{}, /*"~"*/{}); - else if(is_seq(r->closest)) - to_val(node, /*"~"*/{}); - } - } - } - } - else - { - C4_NEVER_REACH(); - } - - _RYML_CB_ASSERT(m_callbacks, node != NONE); - *parent = token; - return node; -} - -/** types of tokens: - * - seeing "map." ---> "map"/MAP - * - finishing "scalar" ---> "scalar"/KEYVAL - * - seeing "seq[n]" ---> "seq"/SEQ (--> "[n]"/KEY) - * - seeing "[n]" ---> "[n]"/KEY - */ -Tree::_lookup_path_token Tree::_next_token(lookup_result *r, _lookup_path_token const& parent) const -{ - csubstr unres = r->unresolved(); - if(unres.empty()) - return {}; - - // is it an indexation like [0], [1], etc? - if(unres.begins_with('[')) - { - size_t pos = unres.find(']'); - if(pos == csubstr::npos) - return {}; - csubstr idx = unres.first(pos + 1); - _advance(r, pos + 1); - return {idx, KEY}; - } - - // no. so it must be a name - size_t pos = unres.first_of(".["); - if(pos == csubstr::npos) - { - _advance(r, unres.len); - NodeType t; - if(( ! parent) || parent.type.is_seq()) - return {unres, VAL}; - return {unres, KEYVAL}; - } - - // it's either a map or a seq - _RYML_CB_ASSERT(m_callbacks, unres[pos] == '.' || unres[pos] == '['); - if(unres[pos] == '.') - { - _RYML_CB_ASSERT(m_callbacks, pos != 0); - _advance(r, pos + 1); - return {unres.first(pos), MAP}; - } - - _RYML_CB_ASSERT(m_callbacks, unres[pos] == '['); - _advance(r, pos); - return {unres.first(pos), SEQ}; -} - - -} // namespace ryml -} // namespace c4 - - -C4_SUPPRESS_WARNING_GCC_POP -C4_SUPPRESS_WARNING_MSVC_POP - -#endif /* RYML_SINGLE_HDR_DEFINE_NOW */ - - -// (end https://github.com/biojppm/rapidyaml/src/c4/yml/tree.cpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/yml/parse.cpp -// https://github.com/biojppm/rapidyaml/src/c4/yml/parse.cpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifdef RYML_SINGLE_HDR_DEFINE_NOW -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/yml/parse.hpp -//#include "c4/yml/parse.hpp" -#if !defined(C4_YML_PARSE_HPP_) && !defined(_C4_YML_PARSE_HPP_) -#error "amalgamate: file c4/yml/parse.hpp must have been included at this point" -#endif /* C4_YML_PARSE_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/error.hpp -//#include "c4/error.hpp" -#if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) -#error "amalgamate: file c4/error.hpp must have been included at this point" -#endif /* C4_ERROR_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/utf.hpp -//#include "c4/utf.hpp" -#if !defined(C4_UTF_HPP_) && !defined(_C4_UTF_HPP_) -#error "amalgamate: file c4/utf.hpp must have been included at this point" -#endif /* C4_UTF_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/dump.hpp -//#include -#if !defined(C4_DUMP_HPP_) && !defined(_C4_DUMP_HPP_) -#error "amalgamate: file c4/dump.hpp must have been included at this point" -#endif /* C4_DUMP_HPP_ */ - - -//included above: -//#include -//included above: -//#include -//included above: -//#include - -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/yml/detail/parser_dbg.hpp -//#include "c4/yml/detail/parser_dbg.hpp" -#if !defined(C4_YML_DETAIL_PARSER_DBG_HPP_) && !defined(_C4_YML_DETAIL_PARSER_DBG_HPP_) -#error "amalgamate: file c4/yml/detail/parser_dbg.hpp must have been included at this point" -#endif /* C4_YML_DETAIL_PARSER_DBG_HPP_ */ - -#ifdef RYML_DBG -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/yml/detail/print.hpp -//#include "c4/yml/detail/print.hpp" -#if !defined(C4_YML_DETAIL_PRINT_HPP_) && !defined(_C4_YML_DETAIL_PRINT_HPP_) -#error "amalgamate: file c4/yml/detail/print.hpp must have been included at this point" -#endif /* C4_YML_DETAIL_PRINT_HPP_ */ - -#endif - -#ifndef RYML_ERRMSG_SIZE - #define RYML_ERRMSG_SIZE 1024 -#endif - -//#define RYML_WITH_TAB_TOKENS -#ifdef RYML_WITH_TAB_TOKENS -#define _RYML_WITH_TAB_TOKENS(...) __VA_ARGS__ -#define _RYML_WITH_OR_WITHOUT_TAB_TOKENS(with, without) with -#else -#define _RYML_WITH_TAB_TOKENS(...) -#define _RYML_WITH_OR_WITHOUT_TAB_TOKENS(with, without) without -#endif - - -#if defined(_MSC_VER) -# pragma warning(push) -# pragma warning(disable: 4296/*expression is always 'boolean_value'*/) -#elif defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wtype-limits" // to remove a warning on an assertion that a size_t >= 0. Later on, this size_t will turn into a template argument, and then it can become < 0. -# pragma clang diagnostic ignored "-Wformat-nonliteral" -#elif defined(__GNUC__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wtype-limits" // to remove a warning on an assertion that a size_t >= 0. Later on, this size_t will turn into a template argument, and then it can become < 0. -# pragma GCC diagnostic ignored "-Wformat-nonliteral" -# if __GNUC__ >= 7 -# pragma GCC diagnostic ignored "-Wduplicated-branches" -# endif -#endif - -namespace c4 { -namespace yml { - -namespace { - -template -void _parse_dump(DumpFn dumpfn, c4::csubstr fmt, Args&& ...args) -{ - char writebuf[256]; - auto results = c4::format_dump_resume(dumpfn, writebuf, fmt, std::forward(args)...); - // resume writing if the results failed to fit the buffer - if(C4_UNLIKELY(results.bufsize > sizeof(writebuf))) // bufsize will be that of the largest element serialized. Eg int(1), will require 1 byte. - { - results = format_dump_resume(dumpfn, results, writebuf, fmt, std::forward(args)...); - if(C4_UNLIKELY(results.bufsize > sizeof(writebuf))) - { - results = format_dump_resume(dumpfn, results, writebuf, fmt, std::forward(args)...); - } - } -} - -bool _is_scalar_next__runk(csubstr s) -{ - return !(s.begins_with(": ") || s.begins_with_any("#,{}[]%&") || s.begins_with("? ") || s == "-" || s.begins_with("- ") || s.begins_with(":\"") || s.begins_with(":'")); -} - -bool _is_scalar_next__rseq_rval(csubstr s) -{ - return !(s.begins_with_any("[{!&") || s.begins_with("? ") || s.begins_with("- ") || s == "-"); -} - -bool _is_scalar_next__rmap(csubstr s) -{ - return !(s.begins_with(": ") || s.begins_with_any("#,!&") || s.begins_with("? ") _RYML_WITH_TAB_TOKENS(|| s.begins_with(":\t"))); -} - -bool _is_scalar_next__rmap_val(csubstr s) -{ - return !(s.begins_with("- ") || s.begins_with_any("{[") || s == "-"); -} - -bool _is_doc_sep(csubstr s) -{ - constexpr const csubstr dashes = "---"; - constexpr const csubstr ellipsis = "..."; - constexpr const csubstr whitesp = " \t"; - if(s.begins_with(dashes)) - return s == dashes || s.sub(3).begins_with_any(whitesp); - else if(s.begins_with(ellipsis)) - return s == ellipsis || s.sub(3).begins_with_any(whitesp); - return false; -} - -/** @p i is set to the first non whitespace character after the line - * @return the number of empty lines after the initial position */ -size_t count_following_newlines(csubstr r, size_t *C4_RESTRICT i, size_t indentation) -{ - RYML_ASSERT(r[*i] == '\n'); - size_t numnl_following = 0; - ++(*i); - for( ; *i < r.len; ++(*i)) - { - if(r.str[*i] == '\n') - { - ++numnl_following; - if(indentation) // skip the indentation after the newline - { - size_t stop = *i + indentation; - for( ; *i < r.len; ++(*i)) - { - if(r.str[*i] != ' ' && r.str[*i] != '\r') - break; - RYML_ASSERT(*i < stop); - } - C4_UNUSED(stop); - } - } - else if(r.str[*i] == ' ' || r.str[*i] == '\t' || r.str[*i] == '\r') // skip leading whitespace - ; - else - break; - } - return numnl_following; -} - -} // anon namespace - - -//----------------------------------------------------------------------------- - -Parser::~Parser() -{ - _free(); - _clr(); -} - -Parser::Parser(Callbacks const& cb, ParserOptions opts) - : m_options(opts) - , m_file() - , m_buf() - , m_root_id(NONE) - , m_tree() - , m_stack(cb) - , m_state() - , m_key_tag_indentation(0) - , m_key_tag2_indentation(0) - , m_key_tag() - , m_key_tag2() - , m_val_tag_indentation(0) - , m_val_tag() - , m_key_anchor_was_before(false) - , m_key_anchor_indentation(0) - , m_key_anchor() - , m_val_anchor_indentation(0) - , m_val_anchor() - , m_filter_arena() - , m_newline_offsets() - , m_newline_offsets_size(0) - , m_newline_offsets_capacity(0) - , m_newline_offsets_buf() -{ - m_stack.push(State{}); - m_state = &m_stack.top(); -} - -Parser::Parser(Parser &&that) - : m_options(that.m_options) - , m_file(that.m_file) - , m_buf(that.m_buf) - , m_root_id(that.m_root_id) - , m_tree(that.m_tree) - , m_stack(std::move(that.m_stack)) - , m_state(&m_stack.top()) - , m_key_tag_indentation(that.m_key_tag_indentation) - , m_key_tag2_indentation(that.m_key_tag2_indentation) - , m_key_tag(that.m_key_tag) - , m_key_tag2(that.m_key_tag2) - , m_val_tag_indentation(that.m_val_tag_indentation) - , m_val_tag(that.m_val_tag) - , m_key_anchor_was_before(that.m_key_anchor_was_before) - , m_key_anchor_indentation(that.m_key_anchor_indentation) - , m_key_anchor(that.m_key_anchor) - , m_val_anchor_indentation(that.m_val_anchor_indentation) - , m_val_anchor(that.m_val_anchor) - , m_filter_arena(that.m_filter_arena) - , m_newline_offsets(that.m_newline_offsets) - , m_newline_offsets_size(that.m_newline_offsets_size) - , m_newline_offsets_capacity(that.m_newline_offsets_capacity) - , m_newline_offsets_buf(that.m_newline_offsets_buf) -{ - that._clr(); -} - -Parser::Parser(Parser const& that) - : m_options(that.m_options) - , m_file(that.m_file) - , m_buf(that.m_buf) - , m_root_id(that.m_root_id) - , m_tree(that.m_tree) - , m_stack(that.m_stack) - , m_state(&m_stack.top()) - , m_key_tag_indentation(that.m_key_tag_indentation) - , m_key_tag2_indentation(that.m_key_tag2_indentation) - , m_key_tag(that.m_key_tag) - , m_key_tag2(that.m_key_tag2) - , m_val_tag_indentation(that.m_val_tag_indentation) - , m_val_tag(that.m_val_tag) - , m_key_anchor_was_before(that.m_key_anchor_was_before) - , m_key_anchor_indentation(that.m_key_anchor_indentation) - , m_key_anchor(that.m_key_anchor) - , m_val_anchor_indentation(that.m_val_anchor_indentation) - , m_val_anchor(that.m_val_anchor) - , m_filter_arena() - , m_newline_offsets() - , m_newline_offsets_size() - , m_newline_offsets_capacity() - , m_newline_offsets_buf() -{ - if(that.m_newline_offsets_capacity) - { - _resize_locations(that.m_newline_offsets_capacity); - _RYML_CB_CHECK(m_stack.m_callbacks, m_newline_offsets_capacity == that.m_newline_offsets_capacity); - memcpy(m_newline_offsets, that.m_newline_offsets, that.m_newline_offsets_size * sizeof(size_t)); - m_newline_offsets_size = that.m_newline_offsets_size; - } - if(that.m_filter_arena.len) - { - _resize_filter_arena(that.m_filter_arena.len); - } -} - -Parser& Parser::operator=(Parser &&that) -{ - _free(); - m_options = (that.m_options); - m_file = (that.m_file); - m_buf = (that.m_buf); - m_root_id = (that.m_root_id); - m_tree = (that.m_tree); - m_stack = std::move(that.m_stack); - m_state = (&m_stack.top()); - m_key_tag_indentation = (that.m_key_tag_indentation); - m_key_tag2_indentation = (that.m_key_tag2_indentation); - m_key_tag = (that.m_key_tag); - m_key_tag2 = (that.m_key_tag2); - m_val_tag_indentation = (that.m_val_tag_indentation); - m_val_tag = (that.m_val_tag); - m_key_anchor_was_before = (that.m_key_anchor_was_before); - m_key_anchor_indentation = (that.m_key_anchor_indentation); - m_key_anchor = (that.m_key_anchor); - m_val_anchor_indentation = (that.m_val_anchor_indentation); - m_val_anchor = (that.m_val_anchor); - m_filter_arena = that.m_filter_arena; - m_newline_offsets = (that.m_newline_offsets); - m_newline_offsets_size = (that.m_newline_offsets_size); - m_newline_offsets_capacity = (that.m_newline_offsets_capacity); - m_newline_offsets_buf = (that.m_newline_offsets_buf); - that._clr(); - return *this; -} - -Parser& Parser::operator=(Parser const& that) -{ - _free(); - m_options = (that.m_options); - m_file = (that.m_file); - m_buf = (that.m_buf); - m_root_id = (that.m_root_id); - m_tree = (that.m_tree); - m_stack = that.m_stack; - m_state = &m_stack.top(); - m_key_tag_indentation = (that.m_key_tag_indentation); - m_key_tag2_indentation = (that.m_key_tag2_indentation); - m_key_tag = (that.m_key_tag); - m_key_tag2 = (that.m_key_tag2); - m_val_tag_indentation = (that.m_val_tag_indentation); - m_val_tag = (that.m_val_tag); - m_key_anchor_was_before = (that.m_key_anchor_was_before); - m_key_anchor_indentation = (that.m_key_anchor_indentation); - m_key_anchor = (that.m_key_anchor); - m_val_anchor_indentation = (that.m_val_anchor_indentation); - m_val_anchor = (that.m_val_anchor); - if(that.m_filter_arena.len > 0) - _resize_filter_arena(that.m_filter_arena.len); - if(that.m_newline_offsets_capacity > m_newline_offsets_capacity) - _resize_locations(that.m_newline_offsets_capacity); - _RYML_CB_CHECK(m_stack.m_callbacks, m_newline_offsets_capacity >= that.m_newline_offsets_capacity); - _RYML_CB_CHECK(m_stack.m_callbacks, m_newline_offsets_capacity >= that.m_newline_offsets_size); - memcpy(m_newline_offsets, that.m_newline_offsets, that.m_newline_offsets_size * sizeof(size_t)); - m_newline_offsets_size = that.m_newline_offsets_size; - m_newline_offsets_buf = that.m_newline_offsets_buf; - return *this; -} - -void Parser::_clr() -{ - m_options = {}; - m_file = {}; - m_buf = {}; - m_root_id = {}; - m_tree = {}; - m_stack.clear(); - m_state = {}; - m_key_tag_indentation = {}; - m_key_tag2_indentation = {}; - m_key_tag = {}; - m_key_tag2 = {}; - m_val_tag_indentation = {}; - m_val_tag = {}; - m_key_anchor_was_before = {}; - m_key_anchor_indentation = {}; - m_key_anchor = {}; - m_val_anchor_indentation = {}; - m_val_anchor = {}; - m_filter_arena = {}; - m_newline_offsets = {}; - m_newline_offsets_size = {}; - m_newline_offsets_capacity = {}; - m_newline_offsets_buf = {}; -} - -void Parser::_free() -{ - if(m_newline_offsets) - { - _RYML_CB_FREE(m_stack.m_callbacks, m_newline_offsets, size_t, m_newline_offsets_capacity); - m_newline_offsets = nullptr; - m_newline_offsets_size = 0u; - m_newline_offsets_capacity = 0u; - m_newline_offsets_buf = 0u; - } - if(m_filter_arena.len) - { - _RYML_CB_FREE(m_stack.m_callbacks, m_filter_arena.str, char, m_filter_arena.len); - m_filter_arena = {}; - } - m_stack._free(); -} - - -//----------------------------------------------------------------------------- -void Parser::_reset() -{ - _RYML_CB_ASSERT(m_stack.m_callbacks, m_stack.size() == 1); - m_stack.clear(); - m_stack.push({}); - m_state = &m_stack.top(); - m_state->reset(m_file.str, m_root_id); - - m_key_tag_indentation = 0; - m_key_tag2_indentation = 0; - m_key_tag.clear(); - m_key_tag2.clear(); - m_val_tag_indentation = 0; - m_val_tag.clear(); - m_key_anchor_was_before = false; - m_key_anchor_indentation = 0; - m_key_anchor.clear(); - m_val_anchor_indentation = 0; - m_val_anchor.clear(); - - if(m_options.locations()) - { - _prepare_locations(); - } -} - -//----------------------------------------------------------------------------- -template -void Parser::_fmt_msg(DumpFn &&dumpfn) const -{ - auto const& lc = m_state->line_contents; - csubstr contents = lc.stripped; - if(contents.len) - { - // print the yaml src line - size_t offs = 3u + to_chars(substr{}, m_state->pos.line) + to_chars(substr{}, m_state->pos.col); - if(m_file.len) - { - _parse_dump(dumpfn, "{}:", m_file); - offs += m_file.len + 1; - } - _parse_dump(dumpfn, "{}:{}: ", m_state->pos.line, m_state->pos.col); - csubstr maybe_full_content = (contents.len < 80u ? contents : contents.first(80u)); - csubstr maybe_ellipsis = (contents.len < 80u ? csubstr{} : csubstr("...")); - _parse_dump(dumpfn, "{}{} (size={})\n", maybe_full_content, maybe_ellipsis, contents.len); - // highlight the remaining portion of the previous line - size_t firstcol = (size_t)(lc.rem.begin() - lc.full.begin()); - size_t lastcol = firstcol + lc.rem.len; - for(size_t i = 0; i < offs + firstcol; ++i) - dumpfn(" "); - dumpfn("^"); - for(size_t i = 1, e = (lc.rem.len < 80u ? lc.rem.len : 80u); i < e; ++i) - dumpfn("~"); - _parse_dump(dumpfn, "{} (cols {}-{})\n", maybe_ellipsis, firstcol+1, lastcol+1); - } - else - { - dumpfn("\n"); - } - -#ifdef RYML_DBG - // next line: print the state flags - { - char flagbuf_[64]; - _parse_dump(dumpfn, "top state: {}\n", _prfl(flagbuf_, m_state->flags)); - } -#endif -} - - -//----------------------------------------------------------------------------- -template -void Parser::_err(csubstr fmt, Args const& C4_RESTRICT ...args) const -{ - char errmsg[RYML_ERRMSG_SIZE]; - detail::_SubstrWriter writer(errmsg); - auto dumpfn = [&writer](csubstr s){ writer.append(s); }; - _parse_dump(dumpfn, fmt, args...); - writer.append('\n'); - _fmt_msg(dumpfn); - size_t len = writer.pos < RYML_ERRMSG_SIZE ? writer.pos : RYML_ERRMSG_SIZE; - m_tree->m_callbacks.m_error(errmsg, len, m_state->pos, m_tree->m_callbacks.m_user_data); -} - -//----------------------------------------------------------------------------- -#ifdef RYML_DBG -template -void Parser::_dbg(csubstr fmt, Args const& C4_RESTRICT ...args) const -{ - auto dumpfn = [](csubstr s){ fwrite(s.str, 1, s.len, stdout); }; - _parse_dump(dumpfn, fmt, args...); - dumpfn("\n"); - _fmt_msg(dumpfn); -} -#endif - -//----------------------------------------------------------------------------- -bool Parser::_finished_file() const -{ - bool ret = m_state->pos.offset >= m_buf.len; - if(ret) - { - _c4dbgp("finished file!!!"); - } - return ret; -} - -//----------------------------------------------------------------------------- -bool Parser::_finished_line() const -{ - return m_state->line_contents.rem.empty(); -} - -//----------------------------------------------------------------------------- -void Parser::parse_in_place(csubstr file, substr buf, Tree *t, size_t node_id) -{ - m_file = file; - m_buf = buf; - m_root_id = node_id; - m_tree = t; - _reset(); - while( ! _finished_file()) - { - _scan_line(); - while( ! _finished_line()) - _handle_line(); - if(_finished_file()) - break; // it may have finished because of multiline blocks - _line_ended(); - } - _handle_finished_file(); -} - -//----------------------------------------------------------------------------- -void Parser::_handle_finished_file() -{ - _end_stream(); -} - -//----------------------------------------------------------------------------- -void Parser::_handle_line() -{ - _c4dbgq("\n-----------"); - _c4dbgt("handling line={}, offset={}B", m_state->pos.line, m_state->pos.offset); - _RYML_CB_ASSERT(m_stack.m_callbacks, ! m_state->line_contents.rem.empty()); - if(has_any(RSEQ)) - { - if(has_any(FLOW)) - { - if(_handle_seq_flow()) - return; - } - else - { - if(_handle_seq_blck()) - return; - } - } - else if(has_any(RMAP)) - { - if(has_any(FLOW)) - { - if(_handle_map_flow()) - return; - } - else - { - if(_handle_map_blck()) - return; - } - } - else if(has_any(RUNK)) - { - if(_handle_unk()) - return; - } - - if(_handle_top()) - return; -} - - -//----------------------------------------------------------------------------- -bool Parser::_handle_unk() -{ - _c4dbgp("handle_unk"); - - csubstr rem = m_state->line_contents.rem; - const bool start_as_child = (node(m_state) == nullptr); - - if(C4_UNLIKELY(has_any(NDOC))) - { - if(rem == "---" || rem.begins_with("--- ")) - { - _start_new_doc(rem); - return true; - } - auto trimmed = rem.triml(' '); - if(trimmed == "---" || trimmed.begins_with("--- ")) - { - _RYML_CB_ASSERT(m_stack.m_callbacks, rem.len >= trimmed.len); - _line_progressed(rem.len - trimmed.len); - _start_new_doc(trimmed); - _save_indentation(); - return true; - } - else if(trimmed.begins_with("...")) - { - _end_stream(); - } - else if(trimmed.first_of("#%") == csubstr::npos) // neither a doc nor a tag - { - _c4dbgpf("starting implicit doc to accomodate unexpected tokens: '{}'", rem); - size_t indref = m_state->indref; - _push_level(); - _start_doc(); - _set_indentation(indref); - } - _RYML_CB_ASSERT(m_stack.m_callbacks, !trimmed.empty()); - } - - _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RNXT|RSEQ|RMAP)); - if(m_state->indref > 0) - { - csubstr ws = rem.left_of(rem.first_not_of(' ')); - if(m_state->indref <= ws.len) - { - _c4dbgpf("skipping base indentation of {}", m_state->indref); - _line_progressed(m_state->indref); - rem = rem.sub(m_state->indref); - } - } - - if(rem.begins_with("- ") _RYML_WITH_TAB_TOKENS( || rem.begins_with("-\t"))) - { - _c4dbgpf("it's a seq (as_child={})", start_as_child); - _move_key_anchor_to_val_anchor(); - _move_key_tag_to_val_tag(); - _push_level(); - _start_seq(start_as_child); - _save_indentation(); - _line_progressed(2); - return true; - } - else if(rem == '-') - { - _c4dbgpf("it's a seq (as_child={})", start_as_child); - _move_key_anchor_to_val_anchor(); - _move_key_tag_to_val_tag(); - _push_level(); - _start_seq(start_as_child); - _save_indentation(); - _line_progressed(1); - return true; - } - else if(rem.begins_with('[')) - { - _c4dbgpf("it's a seq, flow (as_child={})", start_as_child); - _move_key_anchor_to_val_anchor(); - _move_key_tag_to_val_tag(); - _push_level(/*explicit flow*/true); - _start_seq(start_as_child); - add_flags(FLOW); - _line_progressed(1); - return true; - } - else if(rem.begins_with('{')) - { - _c4dbgpf("it's a map, flow (as_child={})", start_as_child); - _move_key_anchor_to_val_anchor(); - _move_key_tag_to_val_tag(); - _push_level(/*explicit flow*/true); - _start_map(start_as_child); - addrem_flags(FLOW|RKEY, RVAL); - _line_progressed(1); - return true; - } - else if(rem.begins_with("? ")) - { - _c4dbgpf("it's a map (as_child={}) + this key is complex", start_as_child); - _move_key_anchor_to_val_anchor(); - _move_key_tag_to_val_tag(); - _push_level(); - _start_map(start_as_child); - addrem_flags(RKEY|QMRK, RVAL); - _save_indentation(); - _line_progressed(2); - return true; - } - else if(rem.begins_with(": ") && !has_all(SSCL)) - { - _c4dbgp("it's a map with an empty key"); - _move_key_anchor_to_val_anchor(); - _move_key_tag_to_val_tag(); - _push_level(); - _start_map(start_as_child); - _store_scalar_null(rem.str); - addrem_flags(RVAL, RKEY); - _save_indentation(); - _line_progressed(2); - return true; - } - else if(rem == ':' && !has_all(SSCL)) - { - _c4dbgp("it's a map with an empty key"); - _move_key_anchor_to_val_anchor(); - _move_key_tag_to_val_tag(); - _push_level(); - _start_map(start_as_child); - _store_scalar_null(rem.str); - addrem_flags(RVAL, RKEY); - _save_indentation(); - _line_progressed(1); - return true; - } - else if(_handle_types()) - { - return true; - } - else if(!rem.begins_with('*') && _handle_key_anchors_and_refs()) - { - return true; - } - else if(has_all(SSCL)) - { - _c4dbgpf("there's a stored scalar: '{}'", m_state->scalar); - - csubstr saved_scalar; - bool is_quoted; - if(_scan_scalar_unk(&saved_scalar, &is_quoted)) - { - rem = m_state->line_contents.rem; - _c4dbgpf("... and there's also a scalar next! '{}'", saved_scalar); - if(rem.begins_with_any(" \t")) - { - size_t n = rem.first_not_of(" \t"); - _c4dbgpf("skipping {} spaces/tabs", n); - rem = rem.sub(n); - _line_progressed(n); - } - } - - _c4dbgpf("rem='{}'", rem); - - if(rem.begins_with(", ")) - { - _c4dbgpf("got a ',' -- it's a seq (as_child={})", start_as_child); - _start_seq(start_as_child); - add_flags(FLOW); - _append_val(_consume_scalar()); - _line_progressed(2); - } - else if(rem.begins_with(',')) - { - _c4dbgpf("got a ',' -- it's a seq (as_child={})", start_as_child); - _start_seq(start_as_child); - add_flags(FLOW); - _append_val(_consume_scalar()); - _line_progressed(1); - } - else if(rem.begins_with(": ") _RYML_WITH_TAB_TOKENS( || rem.begins_with(":\t"))) - { - _c4dbgpf("got a ': ' -- it's a map (as_child={})", start_as_child); - _start_map_unk(start_as_child); // wait for the val scalar to append the key-val pair - _line_progressed(2); - } - else if(rem == ":" || rem.begins_with(":\"") || rem.begins_with(":'")) - { - if(rem == ":") { _c4dbgpf("got a ':' -- it's a map (as_child={})", start_as_child); } - else { _c4dbgpf("got a '{}' -- it's a map (as_child={})", rem.first(2), start_as_child); } - _start_map_unk(start_as_child); // wait for the val scalar to append the key-val pair - _line_progressed(1); // advance only 1 - } - else if(rem.begins_with('}')) - { - if(!has_all(RMAP|FLOW)) - { - _c4err("invalid token: not reading a map"); - } - if(!has_all(SSCL)) - { - _c4err("no scalar stored"); - } - _append_key_val(saved_scalar); - _stop_map(); - _line_progressed(1); - } - else if(rem.begins_with("...")) - { - _c4dbgp("got stream end '...'"); - _end_stream(); - _line_progressed(3); - } - else if(rem.begins_with('#')) - { - _c4dbgpf("it's a comment: '{}'", rem); - _scan_comment(); - return true; - } - else if(_handle_key_anchors_and_refs()) - { - return true; - } - else if(rem.begins_with(" ") || rem.begins_with("\t")) - { - size_t n = rem.first_not_of(" \t"); - if(n == npos) - n = rem.len; - _c4dbgpf("has {} spaces/tabs, skip...", n); - _line_progressed(n); - return true; - } - else if(rem.empty()) - { - // nothing to do - } - else if(rem == "---" || rem.begins_with("--- ")) - { - _c4dbgp("caught ---: starting doc"); - _start_new_doc(rem); - return true; - } - else if(rem.begins_with('%')) - { - _c4dbgp("caught a directive: ignoring..."); - _line_progressed(rem.len); - return true; - } - else - { - _c4err("parse error"); - } - - if( ! saved_scalar.empty()) - { - _store_scalar(saved_scalar, is_quoted); - } - - return true; - } - else - { - _RYML_CB_ASSERT(m_stack.m_callbacks, ! has_any(SSCL)); - csubstr scalar; - size_t indentation = m_state->line_contents.indentation; // save - bool is_quoted; - if(_scan_scalar_unk(&scalar, &is_quoted)) - { - _c4dbgpf("got a {} scalar", is_quoted ? "quoted" : ""); - rem = m_state->line_contents.rem; - { - size_t first = rem.first_not_of(" \t"); - if(first && first != npos) - { - _c4dbgpf("skip {} whitespace characters", first); - _line_progressed(first); - rem = rem.sub(first); - } - } - _store_scalar(scalar, is_quoted); - if(rem.begins_with(": ") _RYML_WITH_TAB_TOKENS( || rem.begins_with(":\t"))) - { - _c4dbgpf("got a ': ' next -- it's a map (as_child={})", start_as_child); - _push_level(); - _start_map(start_as_child); // wait for the val scalar to append the key-val pair - _set_indentation(indentation); - _line_progressed(2); // call this AFTER saving the indentation - } - else if(rem == ":") - { - _c4dbgpf("got a ':' next -- it's a map (as_child={})", start_as_child); - _push_level(); - _start_map(start_as_child); // wait for the val scalar to append the key-val pair - _set_indentation(indentation); - _line_progressed(1); // call this AFTER saving the indentation - } - else - { - // we still don't know whether it's a seq or a map - // so just store the scalar - } - return true; - } - else if(rem.begins_with_any(" \t")) - { - csubstr ws = rem.left_of(rem.first_not_of(" \t")); - rem = rem.right_of(ws); - if(has_all(RTOP) && rem.begins_with("---")) - { - _c4dbgp("there's a doc starting, and it's indented"); - _set_indentation(ws.len); - } - _c4dbgpf("skipping {} spaces/tabs", ws.len); - _line_progressed(ws.len); - return true; - } - } - - return false; -} - - -//----------------------------------------------------------------------------- -C4_ALWAYS_INLINE void Parser::_skipchars(char c) -{ - _RYML_CB_ASSERT(m_stack.m_callbacks, m_state->line_contents.rem.begins_with(c)); - size_t pos = m_state->line_contents.rem.first_not_of(c); - if(pos == npos) - pos = m_state->line_contents.rem.len; // maybe the line is just whitespace - _c4dbgpf("skip {} '{}'", pos, c); - _line_progressed(pos); -} - -template -C4_ALWAYS_INLINE void Parser::_skipchars(const char (&chars)[N]) -{ - _RYML_CB_ASSERT(m_stack.m_callbacks, m_state->line_contents.rem.begins_with_any(chars)); - size_t pos = m_state->line_contents.rem.first_not_of(chars); - if(pos == npos) - pos = m_state->line_contents.rem.len; // maybe the line is just whitespace - _c4dbgpf("skip {} characters", pos); - _line_progressed(pos); -} - - -//----------------------------------------------------------------------------- -bool Parser::_handle_seq_flow() -{ - _c4dbgpf("handle_seq_flow: node_id={} level={}", m_state->node_id, m_state->level); - csubstr rem = m_state->line_contents.rem; - - _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RKEY)); - _RYML_CB_ASSERT(m_stack.m_callbacks, has_all(RSEQ|FLOW)); - - if(rem.begins_with(' ')) - { - // with explicit flow, indentation does not matter - _c4dbgp("starts with spaces"); - _skipchars(' '); - return true; - } - _RYML_WITH_TAB_TOKENS(else if(rem.begins_with('\t')) - { - _c4dbgp("starts with tabs"); - _skipchars('\t'); - return true; - }) - else if(rem.begins_with('#')) - { - _c4dbgp("it's a comment"); - rem = _scan_comment(); // also progresses the line - return true; - } - else if(rem.begins_with(']')) - { - _c4dbgp("end the sequence"); - _pop_level(); - _line_progressed(1); - if(has_all(RSEQIMAP)) - { - _stop_seqimap(); - _pop_level(); - } - return true; - } - - if(has_any(RVAL)) - { - _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RNXT)); - bool is_quoted; - if(_scan_scalar_seq_flow(&rem, &is_quoted)) - { - _c4dbgp("it's a scalar"); - addrem_flags(RNXT, RVAL); - _append_val(rem, is_quoted); - return true; - } - else if(rem.begins_with('[')) - { - _c4dbgp("val is a child seq"); - addrem_flags(RNXT, RVAL); // before _push_level! - _push_level(/*explicit flow*/true); - _start_seq(); - add_flags(FLOW); - _line_progressed(1); - return true; - } - else if(rem.begins_with('{')) - { - _c4dbgp("val is a child map"); - addrem_flags(RNXT, RVAL); // before _push_level! - _push_level(/*explicit flow*/true); - _start_map(); - addrem_flags(FLOW|RKEY, RVAL); - _line_progressed(1); - return true; - } - else if(rem == ':') - { - _c4dbgpf("found ':' -- there's an implicit map in the seq node[{}]", m_state->node_id); - _start_seqimap(); - _line_progressed(1); - return true; - } - else if(rem.begins_with(": ") _RYML_WITH_TAB_TOKENS( || rem.begins_with(":\t"))) - { - _c4dbgpf("found ': ' -- there's an implicit map in the seq node[{}]", m_state->node_id); - _start_seqimap(); - _line_progressed(2); - return true; - } - else if(rem.begins_with("? ")) - { - _c4dbgpf("found '? ' -- there's an implicit map in the seq node[{}]", m_state->node_id); - _start_seqimap(); - _line_progressed(2); - _RYML_CB_ASSERT(m_stack.m_callbacks, has_any(SSCL) && m_state->scalar == ""); - addrem_flags(QMRK|RKEY, RVAL|SSCL); - return true; - } - else if(_handle_types()) - { - return true; - } - else if(_handle_val_anchors_and_refs()) - { - return true; - } - else if(rem.begins_with(", ")) - { - _c4dbgp("found ',' -- the value was null"); - _append_val_null(rem.str - 1); - _line_progressed(2); - return true; - } - else if(rem.begins_with(',')) - { - _c4dbgp("found ',' -- the value was null"); - _append_val_null(rem.str - 1); - _line_progressed(1); - return true; - } - else if(rem.begins_with('\t')) - { - _skipchars('\t'); - return true; - } - else - { - _c4err("parse error"); - } - } - else if(has_any(RNXT)) - { - _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RVAL)); - if(rem.begins_with(", ")) - { - _RYML_CB_ASSERT(m_stack.m_callbacks, has_all(FLOW)); - _c4dbgp("seq: expect next val"); - addrem_flags(RVAL, RNXT); - _line_progressed(2); - return true; - } - else if(rem.begins_with(',')) - { - _RYML_CB_ASSERT(m_stack.m_callbacks, has_all(FLOW)); - _c4dbgp("seq: expect next val"); - addrem_flags(RVAL, RNXT); - _line_progressed(1); - return true; - } - else if(rem == ':') - { - _c4dbgpf("found ':' -- there's an implicit map in the seq node[{}]", m_state->node_id); - _start_seqimap(); - _line_progressed(1); - return true; - } - else if(rem.begins_with(": ") _RYML_WITH_TAB_TOKENS( || rem.begins_with(":\t"))) - { - _c4dbgpf("found ': ' -- there's an implicit map in the seq node[{}]", m_state->node_id); - _start_seqimap(); - _line_progressed(2); - return true; - } - else - { - _c4err("was expecting a comma"); - } - } - else - { - _c4err("internal error"); - } - - return true; -} - -//----------------------------------------------------------------------------- -bool Parser::_handle_seq_blck() -{ - _c4dbgpf("handle_seq_impl: node_id={} level={}", m_state->node_id, m_state->level); - csubstr rem = m_state->line_contents.rem; - - _RYML_CB_ASSERT(m_stack.m_callbacks, has_all(RSEQ)); - _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RKEY)); - _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(FLOW)); - - if(rem.begins_with('#')) - { - _c4dbgp("it's a comment"); - rem = _scan_comment(); - return true; - } - if(has_any(RNXT)) - { - _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RVAL)); - - if(_handle_indentation()) - return true; - - if(rem.begins_with("- ") _RYML_WITH_TAB_TOKENS( || rem.begins_with("-\t"))) - { - _c4dbgp("expect another val"); - addrem_flags(RVAL, RNXT); - _line_progressed(2); - return true; - } - else if(rem == '-') - { - _c4dbgp("expect another val"); - addrem_flags(RVAL, RNXT); - _line_progressed(1); - return true; - } - else if(rem.begins_with_any(" \t")) - { - _RYML_CB_ASSERT(m_stack.m_callbacks, ! _at_line_begin()); - _skipchars(" \t"); - return true; - } - else if(rem.begins_with("...")) - { - _c4dbgp("got stream end '...'"); - _end_stream(); - _line_progressed(3); - return true; - } - else if(rem.begins_with("---")) - { - _c4dbgp("got document start '---'"); - _start_new_doc(rem); - return true; - } - else - { - _c4err("parse error"); - } - } - else if(has_any(RVAL)) - { - // there can be empty values - if(_handle_indentation()) - return true; - - csubstr s; - bool is_quoted; - if(_scan_scalar_seq_blck(&s, &is_quoted)) // this also progresses the line - { - _c4dbgpf("it's a{} scalar", is_quoted ? " quoted" : ""); - - rem = m_state->line_contents.rem; - if(_RYML_WITH_OR_WITHOUT_TAB_TOKENS(rem.begins_with_any(" \t"), rem.begins_with(' '))) - { - _c4dbgp("skipping whitespace..."); - size_t skip = rem.first_not_of(_RYML_WITH_OR_WITHOUT_TAB_TOKENS(" \t", ' ')); - if(skip == csubstr::npos) - skip = rem.len; // maybe the line is just whitespace - _line_progressed(skip); - rem = rem.sub(skip); - } - - _c4dbgpf("rem=[{}]~~~{}~~~", rem.len, rem); - if(!rem.begins_with('#') && (rem.ends_with(':') || rem.begins_with(": ") _RYML_WITH_TAB_TOKENS( || rem.begins_with(":\t")))) - { - _c4dbgp("actually, the scalar is the first key of a map, and it opens a new scope"); - if(m_key_anchor.empty()) - _move_val_anchor_to_key_anchor(); - if(m_key_tag.empty()) - _move_val_tag_to_key_tag(); - addrem_flags(RNXT, RVAL); // before _push_level! This prepares the current level for popping by setting it to RNXT - _push_level(); - _start_map(); - _store_scalar(s, is_quoted); - if( ! _maybe_set_indentation_from_anchor_or_tag()) - { - _c4dbgpf("set indentation from scalar: {}", m_state->scalar_col); - _set_indentation(m_state->scalar_col); // this is the column where the scalar starts - } - _move_key_tag2_to_key_tag(); - addrem_flags(RVAL, RKEY); - _line_progressed(1); - } - else - { - _c4dbgp("appending val to current seq"); - _append_val(s, is_quoted); - addrem_flags(RNXT, RVAL); - } - return true; - } - else if(rem.begins_with("- ") _RYML_WITH_TAB_TOKENS( || rem.begins_with("-\t"))) - { - if(_rval_dash_start_or_continue_seq()) - _line_progressed(2); - return true; - } - else if(rem == '-') - { - if(_rval_dash_start_or_continue_seq()) - _line_progressed(1); - return true; - } - else if(rem.begins_with('[')) - { - _c4dbgp("val is a child seq, flow"); - addrem_flags(RNXT, RVAL); // before _push_level! - _push_level(/*explicit flow*/true); - _start_seq(); - add_flags(FLOW); - _line_progressed(1); - return true; - } - else if(rem.begins_with('{')) - { - _c4dbgp("val is a child map, flow"); - addrem_flags(RNXT, RVAL); // before _push_level! - _push_level(/*explicit flow*/true); - _start_map(); - addrem_flags(FLOW|RKEY, RVAL); - _line_progressed(1); - return true; - } - else if(rem.begins_with("? ")) - { - _c4dbgp("val is a child map + this key is complex"); - addrem_flags(RNXT, RVAL); // before _push_level! - _push_level(); - _start_map(); - addrem_flags(QMRK|RKEY, RVAL); - _save_indentation(); - _line_progressed(2); - return true; - } - else if(rem.begins_with(' ')) - { - csubstr spc = rem.left_of(rem.first_not_of(' ')); - if(_at_line_begin()) - { - _c4dbgpf("skipping value indentation: {} spaces", spc.len); - _line_progressed(spc.len); - return true; - } - else - { - _c4dbgpf("skipping {} spaces", spc.len); - _line_progressed(spc.len); - return true; - } - } - else if(_handle_types()) - { - return true; - } - else if(_handle_val_anchors_and_refs()) - { - return true; - } - /* pathological case: - * - &key : val - * - &key : - * - : val - */ - else if((!has_all(SSCL)) && - (rem.begins_with(": ") || rem.left_of(rem.find("#")).trimr("\t") == ":")) - { - if(!m_val_anchor.empty() || !m_val_tag.empty()) - { - _c4dbgp("val is a child map + this key is empty, with anchors or tags"); - addrem_flags(RNXT, RVAL); // before _push_level! - _move_val_tag_to_key_tag(); - _move_val_anchor_to_key_anchor(); - _push_level(); - _start_map(); - _store_scalar_null(rem.str); - addrem_flags(RVAL, RKEY); - RYML_CHECK(_maybe_set_indentation_from_anchor_or_tag()); // one of them must exist - _line_progressed(rem.begins_with(": ") ? 2u : 1u); - return true; - } - else - { - _c4dbgp("val is a child map + this key is empty, no anchors or tags"); - addrem_flags(RNXT, RVAL); // before _push_level! - size_t ind = m_state->indref; - _push_level(); - _start_map(); - _store_scalar_null(rem.str); - addrem_flags(RVAL, RKEY); - _c4dbgpf("set indentation from map anchor: {}", ind + 2); - _set_indentation(ind + 2); // this is the column where the map starts - _line_progressed(rem.begins_with(": ") ? 2u : 1u); - return true; - } - } - else - { - _c4err("parse error"); - } - } - - return false; -} - -//----------------------------------------------------------------------------- - -bool Parser::_rval_dash_start_or_continue_seq() -{ - size_t ind = m_state->line_contents.current_col(); - _RYML_CB_ASSERT(m_stack.m_callbacks, ind >= m_state->indref); - size_t delta_ind = ind - m_state->indref; - if( ! delta_ind) - { - _c4dbgp("prev val was empty"); - addrem_flags(RNXT, RVAL); - _append_val_null(&m_state->line_contents.full[ind]); - return false; - } - _c4dbgp("val is a nested seq, indented"); - addrem_flags(RNXT, RVAL); // before _push_level! - _push_level(); - _start_seq(); - _save_indentation(); - return true; -} - -//----------------------------------------------------------------------------- -bool Parser::_handle_map_flow() -{ - // explicit flow, ie, inside {}, separated by commas - _c4dbgpf("handle_map_flow: node_id={} level={}", m_state->node_id, m_state->level); - csubstr rem = m_state->line_contents.rem; - - _RYML_CB_ASSERT(m_stack.m_callbacks, has_all(RMAP|FLOW)); - - if(rem.begins_with(' ')) - { - // with explicit flow, indentation does not matter - _c4dbgp("starts with spaces"); - _skipchars(' '); - return true; - } - _RYML_WITH_TAB_TOKENS(else if(rem.begins_with('\t')) - { - // with explicit flow, indentation does not matter - _c4dbgp("starts with tabs"); - _skipchars('\t'); - return true; - }) - else if(rem.begins_with('#')) - { - _c4dbgp("it's a comment"); - rem = _scan_comment(); // also progresses the line - return true; - } - else if(rem.begins_with('}')) - { - _c4dbgp("end the map"); - if(has_all(SSCL)) - { - _c4dbgp("the last val was null"); - _append_key_val_null(rem.str - 1); - rem_flags(RVAL); - } - _pop_level(); - _line_progressed(1); - if(has_all(RSEQIMAP)) - { - _c4dbgp("stopping implicitly nested 1x map"); - _stop_seqimap(); - _pop_level(); - } - return true; - } - - if(has_any(RNXT)) - { - _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RKEY)); - _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RVAL)); - _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RSEQIMAP)); - - if(rem.begins_with(", ")) - { - _c4dbgp("seq: expect next keyval"); - addrem_flags(RKEY, RNXT); - _line_progressed(2); - return true; - } - else if(rem.begins_with(',')) - { - _c4dbgp("seq: expect next keyval"); - addrem_flags(RKEY, RNXT); - _line_progressed(1); - return true; - } - else - { - _c4err("parse error"); - } - } - else if(has_any(RKEY)) - { - _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RNXT)); - _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RVAL)); - - bool is_quoted; - if(has_none(SSCL) && _scan_scalar_map_flow(&rem, &is_quoted)) - { - _c4dbgp("it's a scalar"); - _store_scalar(rem, is_quoted); - rem = m_state->line_contents.rem; - csubstr trimmed = rem.triml(" \t"); - if(trimmed.len && (trimmed.begins_with(": ") || trimmed.begins_with_any(":,}") _RYML_WITH_TAB_TOKENS( || rem.begins_with(":\t")))) - { - _RYML_CB_ASSERT(m_stack.m_callbacks, trimmed.str >= rem.str); - size_t num = static_cast(trimmed.str - rem.str); - _c4dbgpf("trimming {} whitespace after the scalar: '{}' --> '{}'", num, rem, rem.sub(num)); - rem = rem.sub(num); - _line_progressed(num); - } - } - - if(rem.begins_with(": ") _RYML_WITH_TAB_TOKENS( || rem.begins_with(":\t"))) - { - _c4dbgp("wait for val"); - addrem_flags(RVAL, RKEY|QMRK); - _line_progressed(2); - if(!has_all(SSCL)) - { - _c4dbgp("no key was found, defaulting to empty key ''"); - _store_scalar_null(rem.str); - } - return true; - } - else if(rem == ':') - { - _c4dbgp("wait for val"); - addrem_flags(RVAL, RKEY|QMRK); - _line_progressed(1); - if(!has_all(SSCL)) - { - _c4dbgp("no key was found, defaulting to empty key ''"); - _store_scalar_null(rem.str); - } - return true; - } - else if(rem.begins_with('?')) - { - _c4dbgp("complex key"); - add_flags(QMRK); - _line_progressed(1); - return true; - } - else if(rem.begins_with(',')) - { - _c4dbgp("prev scalar was a key with null value"); - _append_key_val_null(rem.str - 1); - _line_progressed(1); - return true; - } - else if(rem.begins_with('}')) - { - _c4dbgp("map terminates after a key..."); - _RYML_CB_ASSERT(m_stack.m_callbacks, has_all(SSCL)); - _c4dbgp("the last val was null"); - _append_key_val_null(rem.str - 1); - rem_flags(RVAL); - if(has_all(RSEQIMAP)) - { - _c4dbgp("stopping implicitly nested 1x map"); - _stop_seqimap(); - _pop_level(); - } - _pop_level(); - _line_progressed(1); - return true; - } - else if(_handle_types()) - { - return true; - } - else if(_handle_key_anchors_and_refs()) - { - return true; - } - else if(rem == "") - { - return true; - } - else - { - size_t pos = rem.first_not_of(" \t"); - if(pos == csubstr::npos) - pos = 0; - rem = rem.sub(pos); - if(rem.begins_with(':')) - { - _c4dbgp("wait for val"); - addrem_flags(RVAL, RKEY|QMRK); - _line_progressed(pos + 1); - if(!has_all(SSCL)) - { - _c4dbgp("no key was found, defaulting to empty key ''"); - _store_scalar_null(rem.str); - } - return true; - } - else if(rem.begins_with('#')) - { - _c4dbgp("it's a comment"); - _line_progressed(pos); - rem = _scan_comment(); // also progresses the line - return true; - } - else - { - _c4err("parse error"); - } - } - } - else if(has_any(RVAL)) - { - _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RNXT)); - _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RKEY)); - _RYML_CB_ASSERT(m_stack.m_callbacks, has_all(SSCL)); - bool is_quoted; - if(_scan_scalar_map_flow(&rem, &is_quoted)) - { - _c4dbgp("it's a scalar"); - addrem_flags(RNXT, RVAL|RKEY); - _append_key_val(rem, is_quoted); - if(has_all(RSEQIMAP)) - { - _c4dbgp("stopping implicitly nested 1x map"); - _stop_seqimap(); - _pop_level(); - } - return true; - } - else if(rem.begins_with('[')) - { - _c4dbgp("val is a child seq"); - addrem_flags(RNXT, RVAL|RKEY); // before _push_level! - _push_level(/*explicit flow*/true); - _move_scalar_from_top(); - _start_seq(); - add_flags(FLOW); - _line_progressed(1); - return true; - } - else if(rem.begins_with('{')) - { - _c4dbgp("val is a child map"); - addrem_flags(RNXT, RVAL|RKEY); // before _push_level! - _push_level(/*explicit flow*/true); - _move_scalar_from_top(); - _start_map(); - addrem_flags(FLOW|RKEY, RNXT|RVAL); - _line_progressed(1); - return true; - } - else if(_handle_types()) - { - return true; - } - else if(_handle_val_anchors_and_refs()) - { - return true; - } - else if(rem.begins_with(',')) - { - _c4dbgp("appending empty val"); - _append_key_val_null(rem.str - 1); - addrem_flags(RKEY, RVAL); - _line_progressed(1); - if(has_any(RSEQIMAP)) - { - _c4dbgp("stopping implicitly nested 1x map"); - _stop_seqimap(); - _pop_level(); - } - return true; - } - else if(has_any(RSEQIMAP) && rem.begins_with(']')) - { - _c4dbgp("stopping implicitly nested 1x map"); - if(has_any(SSCL)) - { - _append_key_val_null(rem.str - 1); - } - _stop_seqimap(); - _pop_level(); - return true; - } - else - { - _c4err("parse error"); - } - } - else - { - _c4err("internal error"); - } - - return false; -} - -//----------------------------------------------------------------------------- -bool Parser::_handle_map_blck() -{ - _c4dbgpf("handle_map_blck: node_id={} level={}", m_state->node_id, m_state->level); - csubstr rem = m_state->line_contents.rem; - - _RYML_CB_ASSERT(m_stack.m_callbacks, has_all(RMAP)); - _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(FLOW)); - - if(rem.begins_with('#')) - { - _c4dbgp("it's a comment"); - rem = _scan_comment(); - return true; - } - - if(has_any(RNXT)) - { - _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RKEY)); - _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RVAL)); - // actually, we don't need RNXT in indent-based maps. - addrem_flags(RKEY, RNXT); - } - - if(_handle_indentation()) - { - _c4dbgp("indentation token"); - return true; - } - - if(has_any(RKEY)) - { - _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RNXT)); - _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RVAL)); - - _c4dbgp("RMAP|RKEY read scalar?"); - bool is_quoted; - if(_scan_scalar_map_blck(&rem, &is_quoted)) // this also progresses the line - { - _c4dbgpf("it's a{} scalar", is_quoted ? " quoted" : ""); - if(has_all(QMRK|SSCL)) - { - _c4dbgpf("current key is QMRK; SSCL is set. so take store scalar='{}' as key and add an empty val", m_state->scalar); - _append_key_val_null(rem.str - 1); - } - _store_scalar(rem, is_quoted); - if(has_all(QMRK|RSET)) - { - _c4dbgp("it's a complex key, so use null value '~'"); - _append_key_val_null(rem.str); - } - rem = m_state->line_contents.rem; - - if(rem.begins_with(':')) - { - _c4dbgp("wait for val"); - addrem_flags(RVAL, RKEY|QMRK); - _line_progressed(1); - rem = m_state->line_contents.rem; - if(rem.begins_with_any(" \t")) - { - _RYML_CB_ASSERT(m_stack.m_callbacks, ! _at_line_begin()); - rem = rem.left_of(rem.first_not_of(" \t")); - _c4dbgpf("skip {} spaces/tabs", rem.len); - _line_progressed(rem.len); - } - } - return true; - } - else if(rem.begins_with_any(" \t")) - { - size_t pos = rem.first_not_of(" \t"); - if(pos == npos) - pos = rem.len; - _c4dbgpf("skip {} spaces/tabs", pos); - _line_progressed(pos); - return true; - } - else if(rem == '?' || rem.begins_with("? ")) - { - _c4dbgp("it's a complex key"); - _line_progressed(rem.begins_with("? ") ? 2u : 1u); - if(has_any(SSCL)) - _append_key_val_null(rem.str - 1); - add_flags(QMRK); - return true; - } - else if(has_all(QMRK) && rem.begins_with(':')) - { - _c4dbgp("complex key finished"); - if(!has_any(SSCL)) - _store_scalar_null(rem.str); - addrem_flags(RVAL, RKEY|QMRK); - _line_progressed(1); - rem = m_state->line_contents.rem; - if(rem.begins_with(' ')) - { - _RYML_CB_ASSERT(m_stack.m_callbacks, ! _at_line_begin()); - _skipchars(' '); - } - return true; - } - else if(rem == ':' || rem.begins_with(": ") _RYML_WITH_TAB_TOKENS( || rem.begins_with(":\t"))) - { - _c4dbgp("key finished"); - if(!has_all(SSCL)) - { - _c4dbgp("key was empty..."); - _store_scalar_null(rem.str); - rem_flags(QMRK); - } - addrem_flags(RVAL, RKEY); - _line_progressed(rem == ':' ? 1 : 2); - return true; - } - else if(rem.begins_with("...")) - { - _c4dbgp("end current document"); - _end_stream(); - _line_progressed(3); - return true; - } - else if(rem.begins_with("---")) - { - _c4dbgp("start new document '---'"); - _start_new_doc(rem); - return true; - } - else if(_handle_types()) - { - return true; - } - else if(_handle_key_anchors_and_refs()) - { - return true; - } - else - { - _c4err("parse error"); - } - } - else if(has_any(RVAL)) - { - _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RNXT)); - _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RKEY)); - - _c4dbgp("RMAP|RVAL read scalar?"); - csubstr s; - bool is_quoted; - if(_scan_scalar_map_blck(&s, &is_quoted)) // this also progresses the line - { - _c4dbgpf("it's a{} scalar", is_quoted ? " quoted" : ""); - - rem = m_state->line_contents.rem; - - if(rem.begins_with(": ")) - { - _c4dbgp("actually, the scalar is the first key of a map"); - addrem_flags(RKEY, RVAL); // before _push_level! This prepares the current level for popping by setting it to RNXT - _push_level(); - _move_scalar_from_top(); - _move_val_anchor_to_key_anchor(); - _start_map(); - _save_indentation(m_state->scalar_col); - addrem_flags(RVAL, RKEY); - _line_progressed(2); - } - else if(rem.begins_with(':')) - { - _c4dbgp("actually, the scalar is the first key of a map, and it opens a new scope"); - addrem_flags(RKEY, RVAL); // before _push_level! This prepares the current level for popping by setting it to RNXT - _push_level(); - _move_scalar_from_top(); - _move_val_anchor_to_key_anchor(); - _start_map(); - _save_indentation(/*behind*/s.len); - addrem_flags(RVAL, RKEY); - _line_progressed(1); - } - else - { - _c4dbgp("appending keyval to current map"); - _append_key_val(s, is_quoted); - addrem_flags(RKEY, RVAL); - } - return true; - } - else if(rem.begins_with("- ") _RYML_WITH_TAB_TOKENS( || rem.begins_with("-\t"))) - { - _c4dbgp("val is a nested seq, indented"); - addrem_flags(RKEY, RVAL); // before _push_level! - _push_level(); - _move_scalar_from_top(); - _start_seq(); - _save_indentation(); - _line_progressed(2); - return true; - } - else if(rem == '-') - { - _c4dbgp("maybe a seq. start unknown, indented"); - _start_unk(); - _save_indentation(); - _line_progressed(1); - return true; - } - else if(rem.begins_with('[')) - { - _c4dbgp("val is a child seq, flow"); - addrem_flags(RKEY, RVAL); // before _push_level! - _push_level(/*explicit flow*/true); - _move_scalar_from_top(); - _start_seq(); - add_flags(FLOW); - _line_progressed(1); - return true; - } - else if(rem.begins_with('{')) - { - _c4dbgp("val is a child map, flow"); - addrem_flags(RKEY, RVAL); // before _push_level! - _push_level(/*explicit flow*/true); - _move_scalar_from_top(); - _start_map(); - addrem_flags(FLOW|RKEY, RVAL); - _line_progressed(1); - return true; - } - else if(rem.begins_with(' ')) - { - csubstr spc = rem.left_of(rem.first_not_of(' ')); - if(_at_line_begin()) - { - _c4dbgpf("skipping value indentation: {} spaces", spc.len); - _line_progressed(spc.len); - return true; - } - else - { - _c4dbgpf("skipping {} spaces", spc.len); - _line_progressed(spc.len); - return true; - } - } - else if(_handle_types()) - { - return true; - } - else if(_handle_val_anchors_and_refs()) - { - return true; - } - else if(rem.begins_with("--- ") || rem == "---" || rem.begins_with("---\t")) - { - _start_new_doc(rem); - return true; - } - else if(rem.begins_with("...")) - { - _c4dbgp("end current document"); - _end_stream(); - _line_progressed(3); - return true; - } - else - { - _c4err("parse error"); - } - } - else - { - _c4err("internal error"); - } - - return false; -} - - -//----------------------------------------------------------------------------- -bool Parser::_handle_top() -{ - _c4dbgp("handle_top"); - csubstr rem = m_state->line_contents.rem; - - if(rem.begins_with('#')) - { - _c4dbgp("a comment line"); - _scan_comment(); - return true; - } - - csubstr trimmed = rem.triml(' '); - - if(trimmed.begins_with('%')) - { - _handle_directive(trimmed); - _line_progressed(rem.len); - return true; - } - else if(trimmed.begins_with("--- ") || trimmed == "---" || trimmed.begins_with("---\t")) - { - _start_new_doc(rem); - if(trimmed.len < rem.len) - { - _line_progressed(rem.len - trimmed.len); - _save_indentation(); - } - return true; - } - else if(trimmed.begins_with("...")) - { - _c4dbgp("end current document"); - _end_stream(); - if(trimmed.len < rem.len) - { - _line_progressed(rem.len - trimmed.len); - } - _line_progressed(3); - return true; - } - else - { - _c4err("parse error"); - } - - return false; -} - - -//----------------------------------------------------------------------------- - -bool Parser::_handle_key_anchors_and_refs() -{ - _RYML_CB_ASSERT(m_stack.m_callbacks, !has_any(RVAL)); - const csubstr rem = m_state->line_contents.rem; - if(rem.begins_with('&')) - { - _c4dbgp("found a key anchor!!!"); - if(has_all(QMRK|SSCL)) - { - _RYML_CB_ASSERT(m_stack.m_callbacks, has_any(RKEY)); - _c4dbgp("there is a stored key, so this anchor is for the next element"); - _append_key_val_null(rem.str - 1); - rem_flags(QMRK); - return true; - } - csubstr anchor = rem.left_of(rem.first_of(' ')); - _line_progressed(anchor.len); - anchor = anchor.sub(1); // skip the first character - _move_key_anchor_to_val_anchor(); - _c4dbgpf("key anchor value: '{}'", anchor); - m_key_anchor = anchor; - m_key_anchor_indentation = m_state->line_contents.current_col(rem); - return true; - } - else if(C4_UNLIKELY(rem.begins_with('*'))) - { - _c4err("not implemented - this should have been catched elsewhere"); - C4_NEVER_REACH(); - return false; - } - return false; -} - -bool Parser::_handle_val_anchors_and_refs() -{ - _RYML_CB_ASSERT(m_stack.m_callbacks, !has_any(RKEY)); - const csubstr rem = m_state->line_contents.rem; - if(rem.begins_with('&')) - { - csubstr anchor = rem.left_of(rem.first_of(' ')); - _line_progressed(anchor.len); - anchor = anchor.sub(1); // skip the first character - _c4dbgpf("val: found an anchor: '{}', indentation={}!!!", anchor, m_state->line_contents.current_col(rem)); - if(m_val_anchor.empty()) - { - _c4dbgpf("save val anchor: '{}'", anchor); - m_val_anchor = anchor; - m_val_anchor_indentation = m_state->line_contents.current_col(rem); - } - else - { - _c4dbgpf("there is a pending val anchor '{}'", m_val_anchor); - if(m_tree->is_seq(m_state->node_id)) - { - if(m_tree->has_children(m_state->node_id)) - { - _c4dbgpf("current node={} is a seq, has {} children", m_state->node_id, m_tree->num_children(m_state->node_id)); - _c4dbgpf("... so take the new one as a key anchor '{}'", anchor); - m_key_anchor = anchor; - m_key_anchor_indentation = m_state->line_contents.current_col(rem); - } - else - { - _c4dbgpf("current node={} is a seq, has no children", m_state->node_id); - if(m_tree->has_val_anchor(m_state->node_id)) - { - _c4dbgpf("... node={} already has val anchor: '{}'", m_state->node_id, m_tree->val_anchor(m_state->node_id)); - _c4dbgpf("... so take the new one as a key anchor '{}'", anchor); - m_key_anchor = anchor; - m_key_anchor_indentation = m_state->line_contents.current_col(rem); - } - else - { - _c4dbgpf("... so set pending val anchor: '{}' on current node {}", m_val_anchor, m_state->node_id); - m_tree->set_val_anchor(m_state->node_id, m_val_anchor); - m_val_anchor = anchor; - m_val_anchor_indentation = m_state->line_contents.current_col(rem); - } - } - } - } - return true; - } - else if(C4_UNLIKELY(rem.begins_with('*'))) - { - _c4err("not implemented - this should have been catched elsewhere"); - C4_NEVER_REACH(); - return false; - } - return false; -} - -void Parser::_move_key_anchor_to_val_anchor() -{ - if(m_key_anchor.empty()) - return; - _c4dbgpf("move current key anchor to val slot: key='{}' -> val='{}'", m_key_anchor, m_val_anchor); - if(!m_val_anchor.empty()) - _c4err("triple-pending anchor"); - m_val_anchor = m_key_anchor; - m_val_anchor_indentation = m_key_anchor_indentation; - m_key_anchor = {}; - m_key_anchor_indentation = {}; -} - -void Parser::_move_val_anchor_to_key_anchor() -{ - if(m_val_anchor.empty()) - return; - if(!_token_is_from_this_line(m_val_anchor)) - return; - _c4dbgpf("move current val anchor to key slot: key='{}' <- val='{}'", m_key_anchor, m_val_anchor); - if(!m_key_anchor.empty()) - _c4err("triple-pending anchor"); - m_key_anchor = m_val_anchor; - m_key_anchor_indentation = m_val_anchor_indentation; - m_val_anchor = {}; - m_val_anchor_indentation = {}; -} - -void Parser::_move_key_tag_to_val_tag() -{ - if(m_key_tag.empty()) - return; - _c4dbgpf("move key tag to val tag: key='{}' -> val='{}'", m_key_tag, m_val_tag); - m_val_tag = m_key_tag; - m_val_tag_indentation = m_key_tag_indentation; - m_key_tag.clear(); - m_key_tag_indentation = 0; -} - -void Parser::_move_val_tag_to_key_tag() -{ - if(m_val_tag.empty()) - return; - if(!_token_is_from_this_line(m_val_tag)) - return; - _c4dbgpf("move val tag to key tag: key='{}' <- val='{}'", m_key_tag, m_val_tag); - m_key_tag = m_val_tag; - m_key_tag_indentation = m_val_tag_indentation; - m_val_tag.clear(); - m_val_tag_indentation = 0; -} - -void Parser::_move_key_tag2_to_key_tag() -{ - if(m_key_tag2.empty()) - return; - _c4dbgpf("move key tag2 to key tag: key='{}' <- key2='{}'", m_key_tag, m_key_tag2); - m_key_tag = m_key_tag2; - m_key_tag_indentation = m_key_tag2_indentation; - m_key_tag2.clear(); - m_key_tag2_indentation = 0; -} - - -//----------------------------------------------------------------------------- - -bool Parser::_handle_types() -{ - csubstr rem = m_state->line_contents.rem.triml(' '); - csubstr t; - - if(rem.begins_with("!!")) - { - _c4dbgp("begins with '!!'"); - t = rem.left_of(rem.first_of(" ,")); - _RYML_CB_ASSERT(m_stack.m_callbacks, t.len >= 2); - //t = t.sub(2); - if(t == "!!set") - add_flags(RSET); - } - else if(rem.begins_with("!<")) - { - _c4dbgp("begins with '!<'"); - t = rem.left_of(rem.first_of('>'), true); - _RYML_CB_ASSERT(m_stack.m_callbacks, t.len >= 2); - //t = t.sub(2, t.len-1); - } - else if(rem.begins_with("!h!")) - { - _c4dbgp("begins with '!h!'"); - t = rem.left_of(rem.first_of(' ')); - _RYML_CB_ASSERT(m_stack.m_callbacks, t.len >= 3); - //t = t.sub(3); - } - else if(rem.begins_with('!')) - { - _c4dbgp("begins with '!'"); - t = rem.left_of(rem.first_of(' ')); - _RYML_CB_ASSERT(m_stack.m_callbacks, t.len >= 1); - //t = t.sub(1); - } - - if(t.empty()) - return false; - - if(has_all(QMRK|SSCL)) - { - _RYML_CB_ASSERT(m_stack.m_callbacks, has_any(RKEY)); - _c4dbgp("there is a stored key, so this tag is for the next element"); - _append_key_val_null(rem.str - 1); - rem_flags(QMRK); - } - - #ifdef RYML_NO_COVERAGE__TO_BE_DELETED - const char *tag_beginning = rem.str; - #endif - size_t tag_indentation = m_state->line_contents.current_col(t); - _c4dbgpf("there was a tag: '{}', indentation={}", t, tag_indentation); - _RYML_CB_ASSERT(m_stack.m_callbacks, t.end() > m_state->line_contents.rem.begin()); - _line_progressed(static_cast(t.end() - m_state->line_contents.rem.begin())); - { - size_t pos = m_state->line_contents.rem.first_not_of(" \t"); - if(pos != csubstr::npos) - _line_progressed(pos); - } - - if(has_all(RMAP|RKEY)) - { - _c4dbgpf("saving map key tag '{}'", t); - _RYML_CB_ASSERT(m_stack.m_callbacks, m_key_tag.empty()); - m_key_tag = t; - m_key_tag_indentation = tag_indentation; - } - else if(has_all(RMAP|RVAL)) - { - /* foo: !!str - * !!str : bar */ - rem = m_state->line_contents.rem; - rem = rem.left_of(rem.find("#")); - rem = rem.trimr(" \t"); - _c4dbgpf("rem='{}'", rem); - #ifdef RYML_NO_COVERAGE__TO_BE_DELETED - if(rem == ':' || rem.begins_with(": ")) - { - _c4dbgp("the last val was null, and this is a tag from a null key"); - _append_key_val_null(tag_beginning - 1); - _store_scalar_null(rem.str - 1); - // do not change the flag to key, it is ~ - _RYML_CB_ASSERT(m_stack.m_callbacks, rem.begin() > m_state->line_contents.rem.begin()); - size_t token_len = rem == ':' ? 1 : 2; - _line_progressed(static_cast(token_len + rem.begin() - m_state->line_contents.rem.begin())); - } - #endif - _c4dbgpf("saving map val tag '{}'", t); - _RYML_CB_ASSERT(m_stack.m_callbacks, m_val_tag.empty()); - m_val_tag = t; - m_val_tag_indentation = tag_indentation; - } - else if(has_all(RSEQ|RVAL) || has_all(RTOP|RUNK|NDOC)) - { - if(m_val_tag.empty()) - { - _c4dbgpf("saving seq/doc val tag '{}'", t); - m_val_tag = t; - m_val_tag_indentation = tag_indentation; - } - else - { - _c4dbgpf("saving seq/doc key tag '{}'", t); - m_key_tag = t; - m_key_tag_indentation = tag_indentation; - } - } - else if(has_all(RTOP|RUNK) || has_any(RUNK)) - { - rem = m_state->line_contents.rem; - rem = rem.left_of(rem.find("#")); - rem = rem.trimr(" \t"); - if(rem.empty()) - { - _c4dbgpf("saving val tag '{}'", t); - _RYML_CB_ASSERT(m_stack.m_callbacks, m_val_tag.empty()); - m_val_tag = t; - m_val_tag_indentation = tag_indentation; - } - else - { - _c4dbgpf("saving key tag '{}'", t); - if(m_key_tag.empty()) - { - m_key_tag = t; - m_key_tag_indentation = tag_indentation; - } - else - { - /* handle this case: - * !!str foo: !!map - * !!int 1: !!float 20.0 - * !!int 3: !!float 40.0 - * - * (m_key_tag would be !!str and m_key_tag2 would be !!int) - */ - m_key_tag2 = t; - m_key_tag2_indentation = tag_indentation; - } - } - } - else - { - _c4err("internal error"); - } - - if(m_val_tag.not_empty()) - { - YamlTag_e tag = to_tag(t); - if(tag == TAG_STR) - { - _c4dbgpf("tag '{}' is a str-type tag", t); - if(has_all(RTOP|RUNK|NDOC)) - { - _c4dbgpf("docval. slurping the string. pos={}", m_state->pos.offset); - csubstr scalar = _slurp_doc_scalar(); - _c4dbgpf("docval. after slurp: {}, at node {}: '{}'", m_state->pos.offset, m_state->node_id, scalar); - m_tree->to_val(m_state->node_id, scalar, DOC); - _c4dbgpf("docval. val tag {} -> {}", m_val_tag, normalize_tag(m_val_tag)); - m_tree->set_val_tag(m_state->node_id, normalize_tag(m_val_tag)); - m_val_tag.clear(); - if(!m_val_anchor.empty()) - { - _c4dbgpf("setting val anchor[{}]='{}'", m_state->node_id, m_val_anchor); - m_tree->set_val_anchor(m_state->node_id, m_val_anchor); - m_val_anchor.clear(); - } - _end_stream(); - } - } - } - return true; -} - -//----------------------------------------------------------------------------- -csubstr Parser::_slurp_doc_scalar() -{ - csubstr s = m_state->line_contents.rem; - size_t pos = m_state->pos.offset; - _RYML_CB_ASSERT(m_stack.m_callbacks, m_state->line_contents.full.find("---") != csubstr::npos); - _c4dbgpf("slurp 0 '{}'. REM='{}'", s, m_buf.sub(m_state->pos.offset)); - if(s.len == 0) - { - _line_ended(); - _scan_line(); - s = m_state->line_contents.rem; - pos = m_state->pos.offset; - } - - size_t skipws = s.first_not_of(" \t"); - _c4dbgpf("slurp 1 '{}'. REM='{}'", s, m_buf.sub(m_state->pos.offset)); - if(skipws != npos) - { - _line_progressed(skipws); - s = m_state->line_contents.rem; - pos = m_state->pos.offset; - _c4dbgpf("slurp 2 '{}'. REM='{}'", s, m_buf.sub(m_state->pos.offset)); - } - - _RYML_CB_ASSERT(m_stack.m_callbacks, m_val_anchor.empty()); - _handle_val_anchors_and_refs(); - if(!m_val_anchor.empty()) - { - s = m_state->line_contents.rem; - skipws = s.first_not_of(" \t"); - if(skipws != npos) - { - _line_progressed(skipws); - } - s = m_state->line_contents.rem; - pos = m_state->pos.offset; - _c4dbgpf("slurp 3 '{}'. REM='{}'", s, m_buf.sub(m_state->pos.offset)); - } - - if(s.begins_with('\'')) - { - m_state->scalar_col = m_state->line_contents.current_col(s); - return _scan_squot_scalar(); - } - else if(s.begins_with('"')) - { - m_state->scalar_col = m_state->line_contents.current_col(s); - return _scan_dquot_scalar(); - } - else if(s.begins_with('|') || s.begins_with('>')) - { - return _scan_block(); - } - - _c4dbgpf("slurp 4 '{}'. REM='{}'", s, m_buf.sub(m_state->pos.offset)); - - m_state->scalar_col = m_state->line_contents.current_col(s); - _RYML_CB_ASSERT(m_stack.m_callbacks, s.end() >= m_buf.begin() + pos); - _line_progressed(static_cast(s.end() - (m_buf.begin() + pos))); - - _c4dbgpf("slurp 5 '{}'. REM='{}'", s, m_buf.sub(m_state->pos.offset)); - - if(_at_line_end()) - { - _c4dbgpf("at line end. curr='{}'", s); - s = _extend_scanned_scalar(s); - } - - _c4dbgpf("scalar was '{}'", s); - - return s; -} - - -//----------------------------------------------------------------------------- - -bool Parser::_scan_scalar_seq_blck(csubstr *C4_RESTRICT scalar, bool *C4_RESTRICT quoted) -{ - _RYML_CB_ASSERT(m_stack.m_callbacks, has_any(RSEQ)); - _RYML_CB_ASSERT(m_stack.m_callbacks, has_any(RVAL)); - _RYML_CB_ASSERT(m_stack.m_callbacks, ! has_any(RKEY)); - _RYML_CB_ASSERT(m_stack.m_callbacks, ! has_any(FLOW)); - - csubstr s = m_state->line_contents.rem; - if(s.len == 0) - return false; - s = s.trim(" \t"); - if(s.len == 0) - return false; - - if(s.begins_with('\'')) - { - _c4dbgp("got a ': scanning single-quoted scalar"); - m_state->scalar_col = m_state->line_contents.current_col(s); - *scalar = _scan_squot_scalar(); - *quoted = true; - return true; - } - else if(s.begins_with('"')) - { - _c4dbgp("got a \": scanning double-quoted scalar"); - m_state->scalar_col = m_state->line_contents.current_col(s); - *scalar = _scan_dquot_scalar(); - *quoted = true; - return true; - } - else if(s.begins_with('|') || s.begins_with('>')) - { - *scalar = _scan_block(); - *quoted = true; - return true; - } - else if(has_any(RTOP) && _is_doc_sep(s)) - { - return false; - } - - _c4dbgp("RSEQ|RVAL"); - if( ! _is_scalar_next__rseq_rval(s)) - return false; - _RYML_WITH_TAB_TOKENS(else if(s.begins_with("-\t")) - return false; - ) - - if(s.ends_with(':')) - { - --s.len; - } - else - { - auto first = s.first_of_any(": " _RYML_WITH_TAB_TOKENS( , ":\t"), " #"); - if(first) - s.len = first.pos; - } - s = s.trimr(_RYML_WITH_OR_WITHOUT_TAB_TOKENS(" \t", ' ')); - - if(s.empty()) - return false; - - m_state->scalar_col = m_state->line_contents.current_col(s); - _RYML_CB_ASSERT(m_stack.m_callbacks, s.str >= m_state->line_contents.rem.str); - _line_progressed(static_cast(s.str - m_state->line_contents.rem.str) + s.len); - - if(_at_line_end() && s != '~') - { - _c4dbgpf("at line end. curr='{}'", s); - s = _extend_scanned_scalar(s); - } - - _c4dbgpf("scalar was '{}'", s); - - *scalar = s; - *quoted = false; - return true; -} - -bool Parser::_scan_scalar_map_blck(csubstr *C4_RESTRICT scalar, bool *C4_RESTRICT quoted) -{ - _c4dbgp("_scan_scalar_map_blck"); - _RYML_CB_ASSERT(m_stack.m_callbacks, has_any(RMAP)); - _RYML_CB_ASSERT(m_stack.m_callbacks, ! has_any(FLOW)); - _RYML_CB_ASSERT(m_stack.m_callbacks, has_any(RKEY|RVAL)); - - csubstr s = m_state->line_contents.rem; - #ifdef RYML_NO_COVERAGE__TO_BE_DELETED__OR_REFACTORED - if(s.len == 0) - return false; - #endif - s = s.trim(" \t"); - if(s.len == 0) - return false; - - if(s.begins_with('\'')) - { - _c4dbgp("got a ': scanning single-quoted scalar"); - m_state->scalar_col = m_state->line_contents.current_col(s); - *scalar = _scan_squot_scalar(); - *quoted = true; - return true; - } - else if(s.begins_with('"')) - { - _c4dbgp("got a \": scanning double-quoted scalar"); - m_state->scalar_col = m_state->line_contents.current_col(s); - *scalar = _scan_dquot_scalar(); - *quoted = true; - return true; - } - else if(s.begins_with('|') || s.begins_with('>')) - { - *scalar = _scan_block(); - *quoted = true; - return true; - } - else if(has_any(RTOP) && _is_doc_sep(s)) - { - return false; - } - - if( ! _is_scalar_next__rmap(s)) - return false; - - size_t colon_token = s.find(": "); - if(colon_token == npos) - { - _RYML_WITH_OR_WITHOUT_TAB_TOKENS( - // with tab tokens - colon_token = s.find(":\t"); - if(colon_token == npos) - { - _RYML_CB_ASSERT(m_stack.m_callbacks, s.len > 0); - colon_token = s.find(':'); - if(colon_token != s.len-1) - colon_token = npos; - } - , - // without tab tokens - colon_token = s.find(':'); - _RYML_CB_ASSERT(m_stack.m_callbacks, s.len > 0); - if(colon_token != s.len-1) - colon_token = npos; - ) - } - - if(has_all(RKEY)) - { - _RYML_CB_ASSERT(m_stack.m_callbacks, !s.begins_with(' ')); - if(has_any(QMRK)) - { - _c4dbgp("RMAP|RKEY|CPLX"); - _RYML_CB_ASSERT(m_stack.m_callbacks, has_any(RMAP)); - if(s.begins_with("? ") || s == '?') - return false; - s = s.left_of(colon_token); - s = s.left_of(s.first_of("#")); - s = s.trimr(" \t"); - if(s.begins_with("---")) - return false; - else if(s.begins_with("...")) - return false; - } - else - { - _c4dbgp("RMAP|RKEY"); - _RYML_CB_CHECK(m_stack.m_callbacks, !s.begins_with('{')); - if(s.begins_with("? ") || s == '?') - return false; - s = s.left_of(colon_token); - s = s.trimr(_RYML_WITH_OR_WITHOUT_TAB_TOKENS(" \t", ' ')); - if(s.begins_with("---")) - { - return false; - } - else if(s.begins_with("...")) - { - return false; - } - } - } - else if(has_all(RVAL)) - { - _c4dbgp("RMAP|RVAL"); - _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(QMRK)); - if( ! _is_scalar_next__rmap_val(s)) - return false; - _RYML_WITH_TAB_TOKENS( - else if(s.begins_with("-\t")) - return false; - ) - _c4dbgp("RMAP|RVAL: scalar"); - s = s.left_of(s.find(" #")); // is there a comment? - s = s.left_of(s.find("\t#")); // is there a comment? - s = s.trim(_RYML_WITH_OR_WITHOUT_TAB_TOKENS(" \t", ' ')); - if(s.begins_with("---")) - return false; - #ifdef RYML_NO_COVERAGE__TO_BE_DELETED__OR_REFACTORED - else if(s.begins_with("...")) - return false; - #endif - } - - if(s.empty()) - return false; - - m_state->scalar_col = m_state->line_contents.current_col(s); - _RYML_CB_ASSERT(m_stack.m_callbacks, s.str >= m_state->line_contents.rem.str); - _line_progressed(static_cast(s.str - m_state->line_contents.rem.str) + s.len); - - if(_at_line_end() && s != '~') - { - _c4dbgpf("at line end. curr='{}'", s); - s = _extend_scanned_scalar(s); - } - - _c4dbgpf("scalar was '{}'", s); - - *scalar = s; - *quoted = false; - return true; -} - -bool Parser::_scan_scalar_seq_flow(csubstr *C4_RESTRICT scalar, bool *C4_RESTRICT quoted) -{ - _RYML_CB_ASSERT(m_stack.m_callbacks, has_any(RSEQ)); - _RYML_CB_ASSERT(m_stack.m_callbacks, has_any(FLOW)); - _RYML_CB_ASSERT(m_stack.m_callbacks, has_any(RVAL)); - _RYML_CB_ASSERT(m_stack.m_callbacks, ! has_any(RKEY)); - - csubstr s = m_state->line_contents.rem; - if(s.len == 0) - return false; - s = s.trim(" \t"); - if(s.len == 0) - return false; - - if(s.begins_with('\'')) - { - _c4dbgp("got a ': scanning single-quoted scalar"); - m_state->scalar_col = m_state->line_contents.current_col(s); - *scalar = _scan_squot_scalar(); - *quoted = true; - return true; - } - else if(s.begins_with('"')) - { - _c4dbgp("got a \": scanning double-quoted scalar"); - m_state->scalar_col = m_state->line_contents.current_col(s); - *scalar = _scan_dquot_scalar(); - *quoted = true; - return true; - } - - if(has_all(RVAL)) - { - _c4dbgp("RSEQ|RVAL"); - if( ! _is_scalar_next__rseq_rval(s)) - return false; - _RYML_WITH_TAB_TOKENS(else if(s.begins_with("-\t")) - return false; - ) - _c4dbgp("RSEQ|RVAL|FLOW"); - s = s.left_of(s.first_of(",]")); - if(s.ends_with(':')) - { - --s.len; - } - else - { - auto first = s.first_of_any(": " _RYML_WITH_TAB_TOKENS( , ":\t"), " #"); - if(first) - s.len = first.pos; - } - s = s.trimr(_RYML_WITH_OR_WITHOUT_TAB_TOKENS(" \t", ' ')); - } - - if(s.empty()) - return false; - - m_state->scalar_col = m_state->line_contents.current_col(s); - _RYML_CB_ASSERT(m_stack.m_callbacks, s.str >= m_state->line_contents.rem.str); - _line_progressed(static_cast(s.str - m_state->line_contents.rem.str) + s.len); - - if(_at_line_end() && s != '~') - { - _c4dbgpf("at line end. curr='{}'", s); - s = _extend_scanned_scalar(s); - } - - _c4dbgpf("scalar was '{}'", s); - - *scalar = s; - *quoted = false; - return true; -} - -bool Parser::_scan_scalar_map_flow(csubstr *C4_RESTRICT scalar, bool *C4_RESTRICT quoted) -{ - _RYML_CB_ASSERT(m_stack.m_callbacks, has_any(RMAP)); - _RYML_CB_ASSERT(m_stack.m_callbacks, has_any(FLOW)); - _RYML_CB_ASSERT(m_stack.m_callbacks, has_any(RKEY|RVAL)); - - csubstr s = m_state->line_contents.rem; - if(s.len == 0) - return false; - s = s.trim(" \t"); - if(s.len == 0) - return false; - - if(s.begins_with('\'')) - { - _c4dbgp("got a ': scanning single-quoted scalar"); - m_state->scalar_col = m_state->line_contents.current_col(s); - *scalar = _scan_squot_scalar(); - *quoted = true; - return true; - } - else if(s.begins_with('"')) - { - _c4dbgp("got a \": scanning double-quoted scalar"); - m_state->scalar_col = m_state->line_contents.current_col(s); - *scalar = _scan_dquot_scalar(); - *quoted = true; - return true; - } - - if( ! _is_scalar_next__rmap(s)) - return false; - - if(has_all(RKEY)) - { - _RYML_CB_ASSERT(m_stack.m_callbacks, !s.begins_with(' ')); - size_t colon_token = s.find(": "); - if(colon_token == npos) - { - _RYML_WITH_OR_WITHOUT_TAB_TOKENS( - // with tab tokens - colon_token = s.find(":\t"); - if(colon_token == npos) - { - _RYML_CB_ASSERT(m_stack.m_callbacks, s.len > 0); - colon_token = s.find(':'); - if(colon_token != s.len-1) - colon_token = npos; - } - , - // without tab tokens - colon_token = s.find(':'); - _RYML_CB_ASSERT(m_stack.m_callbacks, s.len > 0); - if(colon_token != s.len-1) - colon_token = npos; - ) - } - if(s.begins_with("? ") || s == '?') - return false; - if(has_any(QMRK)) - { - _c4dbgp("RMAP|RKEY|CPLX"); - _RYML_CB_ASSERT(m_stack.m_callbacks, has_any(RMAP)); - s = s.left_of(colon_token); - s = s.left_of(s.first_of("#")); - s = s.left_of(s.first_of(':')); - s = s.trimr(" \t"); - if(s.begins_with("---")) - return false; - else if(s.begins_with("...")) - return false; - } - else - { - _RYML_CB_CHECK(m_stack.m_callbacks, !s.begins_with('{')); - _c4dbgp("RMAP|RKEY"); - s = s.left_of(colon_token); - s = s.trimr(_RYML_WITH_OR_WITHOUT_TAB_TOKENS(" \t", ' ')); - _c4dbgpf("RMAP|RKEY|FLOW: '{}'", s); - s = s.left_of(s.first_of(",}")); - if(s.ends_with(':')) - --s.len; - } - } - else if(has_all(RVAL)) - { - _c4dbgp("RMAP|RVAL"); - _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(QMRK)); - if( ! _is_scalar_next__rmap_val(s)) - return false; - _RYML_WITH_TAB_TOKENS(else if(s.begins_with("-\t")) - return false; - ) - _c4dbgp("RMAP|RVAL|FLOW"); - if(has_none(RSEQIMAP)) - s = s.left_of(s.first_of(",}")); - else - s = s.left_of(s.first_of(",]")); - s = s.left_of(s.find(" #")); // is there a comment? - s = s.left_of(s.find("\t#")); // is there a comment? - s = s.trim(_RYML_WITH_OR_WITHOUT_TAB_TOKENS(" \t", ' ')); - } - - if(s.empty()) - return false; - - m_state->scalar_col = m_state->line_contents.current_col(s); - _RYML_CB_ASSERT(m_stack.m_callbacks, s.str >= m_state->line_contents.rem.str); - _line_progressed(static_cast(s.str - m_state->line_contents.rem.str) + s.len); - - if(_at_line_end() && s != '~') - { - _c4dbgpf("at line end. curr='{}'", s); - s = _extend_scanned_scalar(s); - } - - _c4dbgpf("scalar was '{}'", s); - - *scalar = s; - *quoted = false; - return true; -} - -bool Parser::_scan_scalar_unk(csubstr *C4_RESTRICT scalar, bool *C4_RESTRICT quoted) -{ - _RYML_CB_ASSERT(m_stack.m_callbacks, has_any(RUNK)); - - csubstr s = m_state->line_contents.rem; - if(s.len == 0) - return false; - s = s.trim(" \t"); - if(s.len == 0) - return false; - - if(s.begins_with('\'')) - { - _c4dbgp("got a ': scanning single-quoted scalar"); - m_state->scalar_col = m_state->line_contents.current_col(s); - *scalar = _scan_squot_scalar(); - *quoted = true; - return true; - } - else if(s.begins_with('"')) - { - _c4dbgp("got a \": scanning double-quoted scalar"); - m_state->scalar_col = m_state->line_contents.current_col(s); - *scalar = _scan_dquot_scalar(); - *quoted = true; - return true; - } - else if(s.begins_with('|') || s.begins_with('>')) - { - *scalar = _scan_block(); - *quoted = true; - return true; - } - else if(has_any(RTOP) && _is_doc_sep(s)) - { - return false; - } - - _c4dbgpf("RUNK '[{}]~~~{}~~~", s.len, s); - if( ! _is_scalar_next__runk(s)) - { - _c4dbgp("RUNK: no scalar next"); - return false; - } - size_t pos = s.find(" #"); - if(pos != npos) - s = s.left_of(pos); - pos = s.find(": "); - if(pos != npos) - s = s.left_of(pos); - else if(s.ends_with(':')) - s = s.left_of(s.len-1); - _RYML_WITH_TAB_TOKENS( - else if((pos = s.find(":\t")) != npos) // TABS - s = s.left_of(pos); - ) - else - s = s.left_of(s.first_of(',')); - s = s.trim(" \t"); - _c4dbgpf("RUNK: scalar='{}'", s); - - if(s.empty()) - return false; - - m_state->scalar_col = m_state->line_contents.current_col(s); - _RYML_CB_ASSERT(m_stack.m_callbacks, s.str >= m_state->line_contents.rem.str); - _line_progressed(static_cast(s.str - m_state->line_contents.rem.str) + s.len); - - if(_at_line_end() && s != '~') - { - _c4dbgpf("at line end. curr='{}'", s); - s = _extend_scanned_scalar(s); - } - - _c4dbgpf("scalar was '{}'", s); - - *scalar = s; - *quoted = false; - return true; -} - - -//----------------------------------------------------------------------------- - -csubstr Parser::_extend_scanned_scalar(csubstr s) -{ - if(has_all(RMAP|RKEY|QMRK)) - { - size_t scalar_indentation = has_any(FLOW) ? 0 : m_state->scalar_col; - _c4dbgpf("extend_scalar: explicit key! indref={} scalar_indentation={} scalar_col={}", m_state->indref, scalar_indentation, m_state->scalar_col); - csubstr n = _scan_to_next_nonempty_line(scalar_indentation); - if(!n.empty()) - { - substr full = _scan_complex_key(s, n).trimr(" \t\r\n"); - if(full != s) - s = _filter_plain_scalar(full, scalar_indentation); - } - } - // deal with plain (unquoted) scalars that continue to the next line - else if(!s.begins_with_any("*")) // cannot be a plain scalar if it starts with * (that's an anchor reference) - { - _c4dbgpf("extend_scalar: line ended, scalar='{}'", s); - if(has_none(FLOW)) - { - size_t scalar_indentation = m_state->indref + 1; - if(has_all(RUNK) && scalar_indentation == 1) - scalar_indentation = 0; - csubstr n = _scan_to_next_nonempty_line(scalar_indentation); - if(!n.empty()) - { - _c4dbgpf("rscalar[IMPL]: state_indref={} state_indentation={} scalar_indentation={}", m_state->indref, m_state->line_contents.indentation, scalar_indentation); - _RYML_CB_ASSERT(m_stack.m_callbacks, m_state->line_contents.full.is_super(n)); - substr full = _scan_plain_scalar_blck(s, n, scalar_indentation); - if(full.len >= s.len) - s = _filter_plain_scalar(full, scalar_indentation); - } - } - else - { - _RYML_CB_ASSERT(m_stack.m_callbacks, has_all(FLOW)); - csubstr n = _scan_to_next_nonempty_line(/*indentation*/0); - if(!n.empty()) - { - _c4dbgp("rscalar[FLOW]"); - substr full = _scan_plain_scalar_flow(s, n); - s = _filter_plain_scalar(full, /*indentation*/0); - } - } - } - - return s; -} - - -//----------------------------------------------------------------------------- - -substr Parser::_scan_plain_scalar_flow(csubstr currscalar, csubstr peeked_line) -{ - static constexpr const csubstr chars = "[]{}?#,"; - size_t pos = peeked_line.first_of(chars); - bool first = true; - while(pos != 0) - { - if(has_all(RMAP|RKEY) || has_any(RUNK)) - { - csubstr tpkl = peeked_line.triml(' ').trimr("\r\n"); - if(tpkl.begins_with(": ") || tpkl == ':') - { - _c4dbgpf("rscalar[FLOW]: map value starts on the peeked line: '{}'", peeked_line); - peeked_line = peeked_line.first(0); - break; - } - else - { - auto colon_pos = peeked_line.first_of_any(": ", ":"); - if(colon_pos && colon_pos.pos < pos) - { - peeked_line = peeked_line.first(colon_pos.pos); - _c4dbgpf("rscalar[FLOW]: found colon at {}. peeked='{}'", colon_pos.pos, peeked_line); - _RYML_CB_ASSERT(m_stack.m_callbacks, peeked_line.end() >= m_state->line_contents.rem.begin()); - _line_progressed(static_cast(peeked_line.end() - m_state->line_contents.rem.begin())); - break; - } - } - } - if(pos != npos) - { - _c4dbgpf("rscalar[FLOW]: found special character '{}' at {}, stopping: '{}'", peeked_line[pos], pos, peeked_line.left_of(pos).trimr("\r\n")); - peeked_line = peeked_line.left_of(pos); - _RYML_CB_ASSERT(m_stack.m_callbacks, peeked_line.end() >= m_state->line_contents.rem.begin()); - _line_progressed(static_cast(peeked_line.end() - m_state->line_contents.rem.begin())); - break; - } - _c4dbgpf("rscalar[FLOW]: append another line, full: '{}'", peeked_line.trimr("\r\n")); - if(!first) - { - RYML_CHECK(_advance_to_peeked()); - } - peeked_line = _scan_to_next_nonempty_line(/*indentation*/0); - if(peeked_line.empty()) - { - _c4err("expected token or continuation"); - } - pos = peeked_line.first_of(chars); - first = false; - } - substr full(m_buf.str + (currscalar.str - m_buf.str), m_buf.begin() + m_state->pos.offset); - full = full.trimr("\n\r "); - return full; -} - - -//----------------------------------------------------------------------------- - -substr Parser::_scan_plain_scalar_blck(csubstr currscalar, csubstr peeked_line, size_t indentation) -{ - _RYML_CB_ASSERT(m_stack.m_callbacks, m_buf.is_super(currscalar)); - // NOTE. there's a problem with _scan_to_next_nonempty_line(), as it counts newlines twice - // size_t offs = m_state->pos.offset; // so we workaround by directly counting from the end of the given scalar - _RYML_CB_ASSERT(m_stack.m_callbacks, currscalar.end() >= m_buf.begin()); - size_t offs = static_cast(currscalar.end() - m_buf.begin()); - _RYML_CB_ASSERT(m_stack.m_callbacks, peeked_line.begins_with(' ', indentation)); - while(true) - { - _c4dbgpf("rscalar[IMPL]: continuing... ref_indentation={}", indentation); - if(peeked_line.begins_with("...") || peeked_line.begins_with("---")) - { - _c4dbgpf("rscalar[IMPL]: document termination next -- bail now '{}'", peeked_line.trimr("\r\n")); - break; - } - else if(( ! peeked_line.begins_with(' ', indentation))) // is the line deindented? - { - if(!peeked_line.trim(" \r\n\t").empty()) // is the line not blank? - { - _c4dbgpf("rscalar[IMPL]: deindented line, not blank -- bail now '{}'", peeked_line.trimr("\r\n")); - break; - } - _c4dbgpf("rscalar[IMPL]: line is blank and has less indentation: ref={} line={}: '{}'", indentation, peeked_line.first_not_of(' ') == csubstr::npos ? 0 : peeked_line.first_not_of(' '), peeked_line.trimr("\r\n")); - _c4dbgpf("rscalar[IMPL]: ... searching for a line starting at indentation {}", indentation); - csubstr next_peeked = _scan_to_next_nonempty_line(indentation); - if(next_peeked.empty()) - { - _c4dbgp("rscalar[IMPL]: ... finished."); - break; - } - _c4dbgp("rscalar[IMPL]: ... continuing."); - peeked_line = next_peeked; - } - - _c4dbgpf("rscalar[IMPL]: line contents: '{}'", peeked_line.right_of(indentation, true).trimr("\r\n")); - size_t token_pos; - if(peeked_line.find(": ") != npos) - { - _line_progressed(peeked_line.find(": ")); - _c4err("': ' is not a valid token in plain flow (unquoted) scalars"); - } - else if(peeked_line.ends_with(':')) - { - _line_progressed(peeked_line.find(':')); - _c4err("lines cannot end with ':' in plain flow (unquoted) scalars"); - } - else if((token_pos = peeked_line.find(" #")) != npos) - { - _line_progressed(token_pos); - break; - //_c4err("' #' is not a valid token in plain flow (unquoted) scalars"); - } - - _c4dbgpf("rscalar[IMPL]: append another line: (len={})'{}'", peeked_line.len, peeked_line.trimr("\r\n")); - if(!_advance_to_peeked()) - { - _c4dbgp("rscalar[IMPL]: file finishes after the scalar"); - break; - } - peeked_line = m_state->line_contents.rem; - } - _RYML_CB_ASSERT(m_stack.m_callbacks, m_state->pos.offset >= offs); - substr full(m_buf.str + (currscalar.str - m_buf.str), - currscalar.len + (m_state->pos.offset - offs)); - full = full.trimr("\r\n "); - return full; -} - -substr Parser::_scan_complex_key(csubstr currscalar, csubstr peeked_line) -{ - _RYML_CB_ASSERT(m_stack.m_callbacks, m_buf.is_super(currscalar)); - // NOTE. there's a problem with _scan_to_next_nonempty_line(), as it counts newlines twice - // size_t offs = m_state->pos.offset; // so we workaround by directly counting from the end of the given scalar - _RYML_CB_ASSERT(m_stack.m_callbacks, currscalar.end() >= m_buf.begin()); - size_t offs = static_cast(currscalar.end() - m_buf.begin()); - while(true) - { - _c4dbgp("rcplxkey: continuing..."); - if(peeked_line.begins_with("...") || peeked_line.begins_with("---")) - { - _c4dbgpf("rcplxkey: document termination next -- bail now '{}'", peeked_line.trimr("\r\n")); - break; - } - else - { - size_t pos = peeked_line.first_of("?:[]{}"); - if(pos == csubstr::npos) - { - pos = peeked_line.find("- "); - } - if(pos != csubstr::npos) - { - _c4dbgpf("rcplxkey: found special characters at pos={}: '{}'", pos, peeked_line.trimr("\r\n")); - _line_progressed(pos); - break; - } - } - - _c4dbgpf("rcplxkey: no special chars found '{}'", peeked_line.trimr("\r\n")); - csubstr next_peeked = _scan_to_next_nonempty_line(0); - if(next_peeked.empty()) - { - _c4dbgp("rcplxkey: empty ... finished."); - break; - } - _c4dbgp("rcplxkey: ... continuing."); - peeked_line = next_peeked; - - _c4dbgpf("rcplxkey: line contents: '{}'", peeked_line.trimr("\r\n")); - size_t colpos; - if((colpos = peeked_line.find(": ")) != npos) - { - _c4dbgp("rcplxkey: found ': ', stopping."); - _line_progressed(colpos); - break; - } - #ifdef RYML_NO_COVERAGE__TO_BE_DELETED - else if((colpos = peeked_line.ends_with(':'))) - { - _c4dbgp("rcplxkey: ends with ':', stopping."); - _line_progressed(colpos); - break; - } - #endif - _c4dbgpf("rcplxkey: append another line: (len={})'{}'", peeked_line.len, peeked_line.trimr("\r\n")); - if(!_advance_to_peeked()) - { - _c4dbgp("rcplxkey: file finishes after the scalar"); - break; - } - peeked_line = m_state->line_contents.rem; - } - _RYML_CB_ASSERT(m_stack.m_callbacks, m_state->pos.offset >= offs); - substr full(m_buf.str + (currscalar.str - m_buf.str), - currscalar.len + (m_state->pos.offset - offs)); - return full; -} - -//! scans to the next non-blank line starting with the given indentation -csubstr Parser::_scan_to_next_nonempty_line(size_t indentation) -{ - csubstr next_peeked; - while(true) - { - _c4dbgpf("rscalar: ... curr offset: {} indentation={}", m_state->pos.offset, indentation); - next_peeked = _peek_next_line(m_state->pos.offset); - csubstr next_peeked_triml = next_peeked.triml(' '); - _c4dbgpf("rscalar: ... next peeked line='{}'", next_peeked.trimr("\r\n")); - if(next_peeked_triml.begins_with('#')) - { - _c4dbgp("rscalar: ... first non-space character is #"); - return {}; - } - else if(next_peeked.begins_with(' ', indentation)) - { - _c4dbgpf("rscalar: ... begins at same indentation {}, assuming continuation", indentation); - _advance_to_peeked(); - return next_peeked; - } - else // check for de-indentation - { - csubstr trimmed = next_peeked_triml.trimr("\t\r\n"); - _c4dbgpf("rscalar: ... deindented! trimmed='{}'", trimmed); - if(!trimmed.empty()) - { - _c4dbgp("rscalar: ... and not empty. bailing out."); - return {}; - } - } - if(!_advance_to_peeked()) - { - _c4dbgp("rscalar: file finished"); - return {}; - } - } - return {}; -} - -// returns false when the file finished -bool Parser::_advance_to_peeked() -{ - _line_progressed(m_state->line_contents.rem.len); - _line_ended(); // advances to the peeked-at line, consuming all remaining (probably newline) characters on the current line - _RYML_CB_ASSERT(m_stack.m_callbacks, m_state->line_contents.rem.first_of("\r\n") == csubstr::npos); - _c4dbgpf("advance to peeked: scan more... pos={} len={}", m_state->pos.offset, m_buf.len); - _scan_line(); // puts the peeked-at line in the buffer - if(_finished_file()) - { - _c4dbgp("rscalar: finished file!"); - return false; - } - return true; -} - -//----------------------------------------------------------------------------- - -C4_ALWAYS_INLINE size_t _extend_from_combined_newline(char nl, char following) -{ - return (nl == '\n' && following == '\r') || (nl == '\r' && following == '\n'); -} - -//! look for the next newline chars, and jump to the right of those -csubstr from_next_line(csubstr rem) -{ - size_t nlpos = rem.first_of("\r\n"); - if(nlpos == csubstr::npos) - return {}; - const char nl = rem[nlpos]; - rem = rem.right_of(nlpos); - if(rem.empty()) - return {}; - if(_extend_from_combined_newline(nl, rem.front())) - rem = rem.sub(1); - return rem; -} - -csubstr Parser::_peek_next_line(size_t pos) const -{ - csubstr rem{}; // declare here because of the goto - size_t nlpos{}; // declare here because of the goto - pos = pos == npos ? m_state->pos.offset : pos; - if(pos >= m_buf.len) - goto next_is_empty; - - // look for the next newline chars, and jump to the right of those - rem = from_next_line(m_buf.sub(pos)); - if(rem.empty()) - goto next_is_empty; - - // now get everything up to and including the following newline chars - nlpos = rem.first_of("\r\n"); - if((nlpos != csubstr::npos) && (nlpos + 1 < rem.len)) - nlpos += _extend_from_combined_newline(rem[nlpos], rem[nlpos+1]); - rem = rem.left_of(nlpos, /*include_pos*/true); - - _c4dbgpf("peek next line @ {}: (len={})'{}'", pos, rem.len, rem.trimr("\r\n")); - return rem; - -next_is_empty: - _c4dbgpf("peek next line @ {}: (len=0)''", pos); - return {}; -} - - -//----------------------------------------------------------------------------- -void Parser::LineContents::reset_with_next_line(csubstr buf, size_t offset) -{ - RYML_ASSERT(offset <= buf.len); - char const* C4_RESTRICT b = &buf[offset]; - char const* C4_RESTRICT e = b; - // get the current line stripped of newline chars - while(e < buf.end() && (*e != '\n' && *e != '\r')) - ++e; - RYML_ASSERT(e >= b); - const csubstr stripped_ = buf.sub(offset, static_cast(e - b)); - // advance pos to include the first line ending - if(e != buf.end() && *e == '\r') - ++e; - if(e != buf.end() && *e == '\n') - ++e; - RYML_ASSERT(e >= b); - const csubstr full_ = buf.sub(offset, static_cast(e - b)); - reset(full_, stripped_); -} - -void Parser::_scan_line() -{ - if(m_state->pos.offset >= m_buf.len) - { - m_state->line_contents.reset(m_buf.last(0), m_buf.last(0)); - return; - } - m_state->line_contents.reset_with_next_line(m_buf, m_state->pos.offset); -} - - -//----------------------------------------------------------------------------- -void Parser::_line_progressed(size_t ahead) -{ - _c4dbgpf("line[{}] ({} cols) progressed by {}: col {}-->{} offset {}-->{}", m_state->pos.line, m_state->line_contents.full.len, ahead, m_state->pos.col, m_state->pos.col+ahead, m_state->pos.offset, m_state->pos.offset+ahead); - m_state->pos.offset += ahead; - m_state->pos.col += ahead; - _RYML_CB_ASSERT(m_stack.m_callbacks, m_state->pos.col <= m_state->line_contents.stripped.len+1); - m_state->line_contents.rem = m_state->line_contents.rem.sub(ahead); -} - -void Parser::_line_ended() -{ - _c4dbgpf("line[{}] ({} cols) ended! offset {}-->{}", m_state->pos.line, m_state->line_contents.full.len, m_state->pos.offset, m_state->pos.offset+m_state->line_contents.full.len - m_state->line_contents.stripped.len); - _RYML_CB_ASSERT(m_stack.m_callbacks, m_state->pos.col == m_state->line_contents.stripped.len+1); - m_state->pos.offset += m_state->line_contents.full.len - m_state->line_contents.stripped.len; - ++m_state->pos.line; - m_state->pos.col = 1; -} - -void Parser::_line_ended_undo() -{ - _RYML_CB_ASSERT(m_stack.m_callbacks, m_state->pos.col == 1u); - _RYML_CB_ASSERT(m_stack.m_callbacks, m_state->pos.line > 0u); - _RYML_CB_ASSERT(m_stack.m_callbacks, m_state->pos.offset >= m_state->line_contents.full.len - m_state->line_contents.stripped.len); - size_t delta = m_state->line_contents.full.len - m_state->line_contents.stripped.len; - _c4dbgpf("line[{}] undo ended! line {}-->{}, offset {}-->{}", m_state->pos.line, m_state->pos.line, m_state->pos.line - 1, m_state->pos.offset, m_state->pos.offset - delta); - m_state->pos.offset -= delta; - --m_state->pos.line; - m_state->pos.col = m_state->line_contents.stripped.len + 1u; - // don't forget to undo also the changes to the remainder of the line - _RYML_CB_ASSERT(m_stack.m_callbacks, m_state->pos.offset >= m_buf.len || m_buf[m_state->pos.offset] == '\n' || m_buf[m_state->pos.offset] == '\r'); - m_state->line_contents.rem = m_buf.sub(m_state->pos.offset, 0); -} - - -//----------------------------------------------------------------------------- -void Parser::_set_indentation(size_t indentation) -{ - m_state->indref = indentation; - _c4dbgpf("state[{}]: saving indentation: {}", m_state-m_stack.begin(), m_state->indref); -} - -void Parser::_save_indentation(size_t behind) -{ - _RYML_CB_ASSERT(m_stack.m_callbacks, m_state->line_contents.rem.begin() >= m_state->line_contents.full.begin()); - m_state->indref = static_cast(m_state->line_contents.rem.begin() - m_state->line_contents.full.begin()); - _RYML_CB_ASSERT(m_stack.m_callbacks, behind <= m_state->indref); - m_state->indref -= behind; - _c4dbgpf("state[{}]: saving indentation: {}", m_state-m_stack.begin(), m_state->indref); -} - -bool Parser::_maybe_set_indentation_from_anchor_or_tag() -{ - if(m_key_anchor.not_empty()) - { - _c4dbgpf("set indentation from key anchor: {}", m_key_anchor_indentation); - _set_indentation(m_key_anchor_indentation); // this is the column where the anchor starts - return true; - } - else if(m_key_tag.not_empty()) - { - _c4dbgpf("set indentation from key tag: {}", m_key_tag_indentation); - _set_indentation(m_key_tag_indentation); // this is the column where the tag starts - return true; - } - return false; -} - - -//----------------------------------------------------------------------------- -void Parser::_write_key_anchor(size_t node_id) -{ - _RYML_CB_ASSERT(m_stack.m_callbacks, m_tree->has_key(node_id)); - if( ! m_key_anchor.empty()) - { - _c4dbgpf("node={}: set key anchor to '{}'", node_id, m_key_anchor); - m_tree->set_key_anchor(node_id, m_key_anchor); - m_key_anchor.clear(); - m_key_anchor_was_before = false; - m_key_anchor_indentation = 0; - } - else if( ! m_tree->is_key_quoted(node_id)) - { - csubstr r = m_tree->key(node_id); - if(r.begins_with('*')) - { - _c4dbgpf("node={}: set key reference: '{}'", node_id, r); - m_tree->set_key_ref(node_id, r.sub(1)); - } - else if(r == "<<") - { - m_tree->set_key_ref(node_id, r); - _c4dbgpf("node={}: it's an inheriting reference", node_id); - if(m_tree->is_seq(node_id)) - { - _c4dbgpf("node={}: inheriting from seq of {}", node_id, m_tree->num_children(node_id)); - for(size_t i = m_tree->first_child(node_id); i != NONE; i = m_tree->next_sibling(i)) - { - if( ! (m_tree->val(i).begins_with('*'))) - _c4err("malformed reference: '{}'", m_tree->val(i)); - } - } - else if( ! m_tree->val(node_id).begins_with('*')) - { - _c4err("malformed reference: '{}'", m_tree->val(node_id)); - } - //m_tree->set_key_ref(node_id, r); - } - } -} - -//----------------------------------------------------------------------------- -void Parser::_write_val_anchor(size_t node_id) -{ - if( ! m_val_anchor.empty()) - { - _c4dbgpf("node={}: set val anchor to '{}'", node_id, m_val_anchor); - m_tree->set_val_anchor(node_id, m_val_anchor); - m_val_anchor.clear(); - } - csubstr r = m_tree->has_val(node_id) ? m_tree->val(node_id) : ""; - if(!m_tree->is_val_quoted(node_id) && r.begins_with('*')) - { - _c4dbgpf("node={}: set val reference: '{}'", node_id, r); - RYML_CHECK(!m_tree->has_val_anchor(node_id)); - m_tree->set_val_ref(node_id, r.sub(1)); - } -} - -//----------------------------------------------------------------------------- -void Parser::_push_level(bool explicit_flow_chars) -{ - _c4dbgpf("pushing level! currnode={} currlevel={} stacksize={} stackcap={}", m_state->node_id, m_state->level, m_stack.size(), m_stack.capacity()); - _RYML_CB_ASSERT(m_stack.m_callbacks, m_state == &m_stack.top()); - if(node(m_state) == nullptr) - { - _c4dbgp("pushing level! actually no, current node is null"); - //_RYML_CB_ASSERT(m_stack.m_callbacks, ! explicit_flow_chars); - return; - } - flag_t st = RUNK; - if(explicit_flow_chars || has_all(FLOW)) - { - st |= FLOW; - } - m_stack.push_top(); - m_state = &m_stack.top(); - set_flags(st); - m_state->node_id = (size_t)NONE; - m_state->indref = (size_t)NONE; - ++m_state->level; - _c4dbgpf("pushing level: now, currlevel={}", m_state->level); -} - -void Parser::_pop_level() -{ - _c4dbgpf("popping level! currnode={} currlevel={}", m_state->node_id, m_state->level); - if(has_any(RMAP) || m_tree->is_map(m_state->node_id)) - { - _stop_map(); - } - if(has_any(RSEQ) || m_tree->is_seq(m_state->node_id)) - { - _stop_seq(); - } - if(m_tree->is_doc(m_state->node_id)) - { - _stop_doc(); - } - _RYML_CB_ASSERT(m_stack.m_callbacks, m_stack.size() > 1); - _prepare_pop(); - m_stack.pop(); - m_state = &m_stack.top(); - /*if(has_any(RMAP)) - { - _toggle_key_val(); - }*/ - if(m_state->line_contents.indentation == 0) - { - //_RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RTOP)); - add_flags(RTOP); - } - _c4dbgpf("popping level: now, currnode={} currlevel={}", m_state->node_id, m_state->level); -} - -//----------------------------------------------------------------------------- -void Parser::_start_unk(bool /*as_child*/) -{ - _c4dbgp("start_unk"); - _push_level(); - _move_scalar_from_top(); -} - -//----------------------------------------------------------------------------- -void Parser::_start_doc(bool as_child) -{ - _c4dbgpf("start_doc (as child={})", as_child); - _RYML_CB_ASSERT(m_stack.m_callbacks, node(m_stack.bottom()) == node(m_root_id)); - size_t parent_id = m_stack.size() < 2 ? m_root_id : m_stack.top(1).node_id; - _RYML_CB_ASSERT(m_stack.m_callbacks, parent_id != NONE); - _RYML_CB_ASSERT(m_stack.m_callbacks, m_tree->is_root(parent_id)); - _RYML_CB_ASSERT(m_stack.m_callbacks, node(m_state) == nullptr || node(m_state) == node(m_root_id)); - if(as_child) - { - _c4dbgpf("start_doc: parent={}", parent_id); - if( ! m_tree->is_stream(parent_id)) - { - _c4dbgp("start_doc: rearranging with root as STREAM"); - m_tree->set_root_as_stream(); - } - m_state->node_id = m_tree->append_child(parent_id); - m_tree->to_doc(m_state->node_id); - } - #ifdef RYML_NO_COVERAGE__TO_BE_DELETED - else - { - _RYML_CB_ASSERT(m_stack.m_callbacks, m_tree->is_seq(parent_id) || m_tree->empty(parent_id)); - m_state->node_id = parent_id; - if( ! m_tree->is_doc(parent_id)) - { - m_tree->to_doc(parent_id, DOC); - } - } - #endif - _c4dbgpf("start_doc: id={}", m_state->node_id); - add_flags(RUNK|RTOP|NDOC); - _handle_types(); - rem_flags(NDOC); -} - -void Parser::_stop_doc() -{ - size_t doc_node = m_state->node_id; - _c4dbgpf("stop_doc[{}]", doc_node); - _RYML_CB_ASSERT(m_stack.m_callbacks, m_tree->is_doc(doc_node)); - if(!m_tree->is_seq(doc_node) && !m_tree->is_map(doc_node) && !m_tree->is_val(doc_node)) - { - _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(SSCL)); - _c4dbgpf("stop_doc[{}]: there was nothing; adding null val", doc_node); - m_tree->to_val(doc_node, {}, DOC); - } -} - -void Parser::_end_stream() -{ - _c4dbgpf("end_stream, level={} node_id={}", m_state->level, m_state->node_id); - _RYML_CB_ASSERT(m_stack.m_callbacks, ! m_stack.empty()); - NodeData *added = nullptr; - if(has_any(SSCL)) - { - if(m_tree->is_seq(m_state->node_id)) - { - _c4dbgp("append val..."); - added = _append_val(_consume_scalar()); - } - else if(m_tree->is_map(m_state->node_id)) - { - _c4dbgp("append null key val..."); - added = _append_key_val_null(m_state->line_contents.rem.str); - #ifdef RYML_NO_COVERAGE__TO_BE_DELETED - if(has_any(RSEQIMAP)) - { - _stop_seqimap(); - _pop_level(); - } - #endif - } - else if(m_tree->is_doc(m_state->node_id) || m_tree->type(m_state->node_id) == NOTYPE) - { - NodeType_e quoted = has_any(QSCL) ? VALQUO : NOTYPE; // do this before consuming the scalar - csubstr scalar = _consume_scalar(); - _c4dbgpf("node[{}]: to docval '{}'{}", m_state->node_id, scalar, quoted == VALQUO ? ", quoted" : ""); - m_tree->to_val(m_state->node_id, scalar, DOC|quoted); - added = m_tree->get(m_state->node_id); - } - else - { - _c4err("internal error"); - } - } - else if(has_all(RSEQ|RVAL) && has_none(FLOW)) - { - _c4dbgp("add last..."); - added = _append_val_null(m_state->line_contents.rem.str); - } - else if(!m_val_tag.empty() && (m_tree->is_doc(m_state->node_id) || m_tree->type(m_state->node_id) == NOTYPE)) - { - csubstr scalar = m_state->line_contents.rem.first(0); - _c4dbgpf("node[{}]: add null scalar as docval", m_state->node_id); - m_tree->to_val(m_state->node_id, scalar, DOC); - added = m_tree->get(m_state->node_id); - } - - if(added) - { - size_t added_id = m_tree->id(added); - if(m_tree->is_seq(m_state->node_id) || m_tree->is_doc(m_state->node_id)) - { - if(!m_key_anchor.empty()) - { - _c4dbgpf("node[{}]: move key to val anchor: '{}'", added_id, m_key_anchor); - m_val_anchor = m_key_anchor; - m_key_anchor = {}; - } - if(!m_key_tag.empty()) - { - _c4dbgpf("node[{}]: move key to val tag: '{}'", added_id, m_key_tag); - m_val_tag = m_key_tag; - m_key_tag = {}; - } - } - #ifdef RYML_NO_COVERAGE__TO_BE_DELETED - if(!m_key_anchor.empty()) - { - _c4dbgpf("node[{}]: set key anchor='{}'", added_id, m_key_anchor); - m_tree->set_key_anchor(added_id, m_key_anchor); - m_key_anchor = {}; - } - #endif - if(!m_val_anchor.empty()) - { - _c4dbgpf("node[{}]: set val anchor='{}'", added_id, m_val_anchor); - m_tree->set_val_anchor(added_id, m_val_anchor); - m_val_anchor = {}; - } - #ifdef RYML_NO_COVERAGE__TO_BE_DELETED - if(!m_key_tag.empty()) - { - _c4dbgpf("node[{}]: set key tag='{}' -> '{}'", added_id, m_key_tag, normalize_tag(m_key_tag)); - m_tree->set_key_tag(added_id, normalize_tag(m_key_tag)); - m_key_tag = {}; - } - #endif - if(!m_val_tag.empty()) - { - _c4dbgpf("node[{}]: set val tag='{}' -> '{}'", added_id, m_val_tag, normalize_tag(m_val_tag)); - m_tree->set_val_tag(added_id, normalize_tag(m_val_tag)); - m_val_tag = {}; - } - } - - while(m_stack.size() > 1) - { - _c4dbgpf("popping level: {} (stack sz={})", m_state->level, m_stack.size()); - _RYML_CB_ASSERT(m_stack.m_callbacks, ! has_any(SSCL, &m_stack.top())); - if(has_all(RSEQ|FLOW)) - _err("closing ] not found"); - _pop_level(); - } - add_flags(NDOC); -} - -void Parser::_start_new_doc(csubstr rem) -{ - _c4dbgp("_start_new_doc"); - _RYML_CB_ASSERT(m_stack.m_callbacks, rem.begins_with("---")); - C4_UNUSED(rem); - - _end_stream(); - - size_t indref = m_state->indref; - _c4dbgpf("start a document, indentation={}", indref); - _line_progressed(3); - _push_level(); - _start_doc(); - _set_indentation(indref); -} - - -//----------------------------------------------------------------------------- -void Parser::_start_map(bool as_child) -{ - _c4dbgpf("start_map (as child={})", as_child); - addrem_flags(RMAP|RVAL, RKEY|RUNK); - _RYML_CB_ASSERT(m_stack.m_callbacks, node(m_stack.bottom()) == node(m_root_id)); - size_t parent_id = m_stack.size() < 2 ? m_root_id : m_stack.top(1).node_id; - _RYML_CB_ASSERT(m_stack.m_callbacks, parent_id != NONE); - _RYML_CB_ASSERT(m_stack.m_callbacks, node(m_state) == nullptr || node(m_state) == node(m_root_id)); - if(as_child) - { - m_state->node_id = m_tree->append_child(parent_id); - if(has_all(SSCL)) - { - type_bits key_quoted = NOTYPE; - if(m_state->flags & QSCL) // before consuming the scalar - key_quoted |= KEYQUO; - csubstr key = _consume_scalar(); - m_tree->to_map(m_state->node_id, key, key_quoted); - _c4dbgpf("start_map: id={} key='{}'", m_state->node_id, m_tree->key(m_state->node_id)); - _write_key_anchor(m_state->node_id); - if( ! m_key_tag.empty()) - { - _c4dbgpf("node[{}]: set key tag='{}' -> '{}'", m_state->node_id, m_key_tag, normalize_tag(m_key_tag)); - m_tree->set_key_tag(m_state->node_id, normalize_tag(m_key_tag)); - m_key_tag.clear(); - } - } - else - { - m_tree->to_map(m_state->node_id); - _c4dbgpf("start_map: id={}", m_state->node_id); - } - m_tree->_p(m_state->node_id)->m_val.scalar.str = m_state->line_contents.rem.str; - _write_val_anchor(m_state->node_id); - } - else - { - _RYML_CB_ASSERT(m_stack.m_callbacks, parent_id != NONE); - m_state->node_id = parent_id; - _c4dbgpf("start_map: id={}", m_state->node_id); - type_bits as_doc = 0; - if(m_tree->is_doc(m_state->node_id)) - as_doc |= DOC; - if(!m_tree->is_map(parent_id)) - { - RYML_CHECK(!m_tree->has_children(parent_id)); - m_tree->to_map(parent_id, as_doc); - } - else - { - m_tree->_add_flags(parent_id, as_doc); - } - _move_scalar_from_top(); - if(m_key_anchor.not_empty()) - m_key_anchor_was_before = true; - _write_val_anchor(parent_id); - if(m_stack.size() >= 2) - { - State const& parent_state = m_stack.top(1); - if(parent_state.flags & RSET) - add_flags(RSET); - } - m_tree->_p(parent_id)->m_val.scalar.str = m_state->line_contents.rem.str; - } - if( ! m_val_tag.empty()) - { - _c4dbgpf("node[{}]: set val tag='{}' -> '{}'", m_state->node_id, m_val_tag, normalize_tag(m_val_tag)); - m_tree->set_val_tag(m_state->node_id, normalize_tag(m_val_tag)); - m_val_tag.clear(); - } -} - -void Parser::_start_map_unk(bool as_child) -{ - if(!m_key_anchor_was_before) - { - _c4dbgpf("stash key anchor before starting map... '{}'", m_key_anchor); - csubstr ka = m_key_anchor; - m_key_anchor = {}; - _start_map(as_child); - m_key_anchor = ka; - } - else - { - _start_map(as_child); - m_key_anchor_was_before = false; - } - if(m_key_tag2.not_empty()) - { - m_key_tag = m_key_tag2; - m_key_tag_indentation = m_key_tag2_indentation; - m_key_tag2.clear(); - m_key_tag2_indentation = 0; - } -} - -void Parser::_stop_map() -{ - _c4dbgpf("stop_map[{}]", m_state->node_id); - _RYML_CB_ASSERT(m_stack.m_callbacks, m_tree->is_map(m_state->node_id)); - if(has_all(QMRK|RKEY) && !has_all(SSCL)) - { - _c4dbgpf("stop_map[{}]: RKEY", m_state->node_id); - _store_scalar_null(m_state->line_contents.rem.str); - _append_key_val_null(m_state->line_contents.rem.str); - } -} - - -//----------------------------------------------------------------------------- -void Parser::_start_seq(bool as_child) -{ - _c4dbgpf("start_seq (as child={})", as_child); - if(has_all(RTOP|RUNK)) - { - _c4dbgpf("start_seq: moving key tag to val tag: '{}'", m_key_tag); - m_val_tag = m_key_tag; - m_key_tag.clear(); - } - addrem_flags(RSEQ|RVAL, RUNK); - _RYML_CB_ASSERT(m_stack.m_callbacks, node(m_stack.bottom()) == node(m_root_id)); - size_t parent_id = m_stack.size() < 2 ? m_root_id : m_stack.top(1).node_id; - _RYML_CB_ASSERT(m_stack.m_callbacks, parent_id != NONE); - _RYML_CB_ASSERT(m_stack.m_callbacks, node(m_state) == nullptr || node(m_state) == node(m_root_id)); - if(as_child) - { - m_state->node_id = m_tree->append_child(parent_id); - if(has_all(SSCL)) - { - _RYML_CB_ASSERT(m_stack.m_callbacks, m_tree->is_map(parent_id)); - type_bits key_quoted = 0; - if(m_state->flags & QSCL) // before consuming the scalar - key_quoted |= KEYQUO; - csubstr key = _consume_scalar(); - m_tree->to_seq(m_state->node_id, key, key_quoted); - _c4dbgpf("start_seq: id={} name='{}'", m_state->node_id, m_tree->key(m_state->node_id)); - _write_key_anchor(m_state->node_id); - if( ! m_key_tag.empty()) - { - _c4dbgpf("start_seq[{}]: set key tag='{}' -> '{}'", m_state->node_id, m_key_tag, normalize_tag(m_key_tag)); - m_tree->set_key_tag(m_state->node_id, normalize_tag(m_key_tag)); - m_key_tag.clear(); - } - } - else - { - type_bits as_doc = 0; - _RYML_CB_ASSERT(m_stack.m_callbacks, !m_tree->is_doc(m_state->node_id)); - m_tree->to_seq(m_state->node_id, as_doc); - _c4dbgpf("start_seq: id={}{}", m_state->node_id, as_doc ? " as doc" : ""); - } - _write_val_anchor(m_state->node_id); - m_tree->_p(m_state->node_id)->m_val.scalar.str = m_state->line_contents.rem.str; - } - else - { - m_state->node_id = parent_id; - type_bits as_doc = 0; - if(m_tree->is_doc(m_state->node_id)) - as_doc |= DOC; - if(!m_tree->is_seq(parent_id)) - { - RYML_CHECK(!m_tree->has_children(parent_id)); - m_tree->to_seq(parent_id, as_doc); - } - else - { - m_tree->_add_flags(parent_id, as_doc); - } - _move_scalar_from_top(); - _c4dbgpf("start_seq: id={}{}", m_state->node_id, as_doc ? " as_doc" : ""); - _write_val_anchor(parent_id); - m_tree->_p(parent_id)->m_val.scalar.str = m_state->line_contents.rem.str; - } - if( ! m_val_tag.empty()) - { - _c4dbgpf("start_seq[{}]: set val tag='{}' -> '{}'", m_state->node_id, m_val_tag, normalize_tag(m_val_tag)); - m_tree->set_val_tag(m_state->node_id, normalize_tag(m_val_tag)); - m_val_tag.clear(); - } -} - -void Parser::_stop_seq() -{ - _c4dbgp("stop_seq"); - _RYML_CB_ASSERT(m_stack.m_callbacks, m_tree->is_seq(m_state->node_id)); -} - - -//----------------------------------------------------------------------------- -void Parser::_start_seqimap() -{ - _c4dbgpf("start_seqimap at node={}. has_children={}", m_state->node_id, m_tree->has_children(m_state->node_id)); - _RYML_CB_ASSERT(m_stack.m_callbacks, has_all(RSEQ|FLOW)); - // create a map, and turn the last scalar of this sequence - // into the key of the map's first child. This scalar was - // understood to be a value in the sequence, but it is - // actually a key of a map, implicitly opened here. - // Eg [val, key: val] - // - // Yep, YAML is crazy. - if(m_tree->has_children(m_state->node_id) && m_tree->has_val(m_tree->last_child(m_state->node_id))) - { - size_t prev = m_tree->last_child(m_state->node_id); - NodeType ty = m_tree->_p(prev)->m_type; // don't use type() because it masks out the quotes - NodeScalar tmp = m_tree->valsc(prev); - _c4dbgpf("has children and last child={} has val. saving the scalars, val='{}' quoted={}", prev, tmp.scalar, ty.is_val_quoted()); - m_tree->remove(prev); - _push_level(); - _start_map(); - _store_scalar(tmp.scalar, ty.is_val_quoted()); - m_key_anchor = tmp.anchor; - m_key_tag = tmp.tag; - } - else - { - _c4dbgpf("node {} has no children yet, using empty key", m_state->node_id); - _push_level(); - _start_map(); - _store_scalar_null(m_state->line_contents.rem.str); - } - add_flags(RSEQIMAP|FLOW); -} - -void Parser::_stop_seqimap() -{ - _c4dbgp("stop_seqimap"); - _RYML_CB_ASSERT(m_stack.m_callbacks, has_all(RSEQIMAP)); -} - - -//----------------------------------------------------------------------------- -NodeData* Parser::_append_val(csubstr val, flag_t quoted) -{ - _RYML_CB_ASSERT(m_stack.m_callbacks, ! has_all(SSCL)); - _RYML_CB_ASSERT(m_stack.m_callbacks, node(m_state) != nullptr); - _RYML_CB_ASSERT(m_stack.m_callbacks, m_tree->is_seq(m_state->node_id)); - type_bits additional_flags = quoted ? VALQUO : NOTYPE; - _c4dbgpf("append val: '{}' to parent id={} (level={}){}", val, m_state->node_id, m_state->level, quoted ? " VALQUO!" : ""); - size_t nid = m_tree->append_child(m_state->node_id); - m_tree->to_val(nid, val, additional_flags); - - _c4dbgpf("append val: id={} val='{}'", nid, m_tree->get(nid)->m_val.scalar); - if( ! m_val_tag.empty()) - { - _c4dbgpf("append val[{}]: set val tag='{}' -> '{}'", nid, m_val_tag, normalize_tag(m_val_tag)); - m_tree->set_val_tag(nid, normalize_tag(m_val_tag)); - m_val_tag.clear(); - } - _write_val_anchor(nid); - return m_tree->get(nid); -} - -NodeData* Parser::_append_key_val(csubstr val, flag_t val_quoted) -{ - _RYML_CB_ASSERT(m_stack.m_callbacks, m_tree->is_map(m_state->node_id)); - type_bits additional_flags = 0; - if(m_state->flags & QSCL) - additional_flags |= KEYQUO; - if(val_quoted) - additional_flags |= VALQUO; - - csubstr key = _consume_scalar(); - _c4dbgpf("append keyval: '{}' '{}' to parent id={} (level={}){}{}", key, val, m_state->node_id, m_state->level, (additional_flags & KEYQUO) ? " KEYQUO!" : "", (additional_flags & VALQUO) ? " VALQUO!" : ""); - size_t nid = m_tree->append_child(m_state->node_id); - m_tree->to_keyval(nid, key, val, additional_flags); - _c4dbgpf("append keyval: id={} key='{}' val='{}'", nid, m_tree->key(nid), m_tree->val(nid)); - if( ! m_key_tag.empty()) - { - _c4dbgpf("append keyval[{}]: set key tag='{}' -> '{}'", nid, m_key_tag, normalize_tag(m_key_tag)); - m_tree->set_key_tag(nid, normalize_tag(m_key_tag)); - m_key_tag.clear(); - } - if( ! m_val_tag.empty()) - { - _c4dbgpf("append keyval[{}]: set val tag='{}' -> '{}'", nid, m_val_tag, normalize_tag(m_val_tag)); - m_tree->set_val_tag(nid, normalize_tag(m_val_tag)); - m_val_tag.clear(); - } - _write_key_anchor(nid); - _write_val_anchor(nid); - rem_flags(QMRK); - return m_tree->get(nid); -} - - -//----------------------------------------------------------------------------- -void Parser::_store_scalar(csubstr s, flag_t is_quoted) -{ - _c4dbgpf("state[{}]: storing scalar '{}' (flag: {}) (old scalar='{}')", - m_state-m_stack.begin(), s, m_state->flags & SSCL, m_state->scalar); - RYML_CHECK(has_none(SSCL)); - add_flags(SSCL | (is_quoted * QSCL)); - m_state->scalar = s; -} - -csubstr Parser::_consume_scalar() -{ - _c4dbgpf("state[{}]: consuming scalar '{}' (flag: {}))", m_state-m_stack.begin(), m_state->scalar, m_state->flags & SSCL); - RYML_CHECK(m_state->flags & SSCL); - csubstr s = m_state->scalar; - rem_flags(SSCL | QSCL); - m_state->scalar.clear(); - return s; -} - -void Parser::_move_scalar_from_top() -{ - if(m_stack.size() < 2) return; - State &prev = m_stack.top(1); - _RYML_CB_ASSERT(m_stack.m_callbacks, m_state == &m_stack.top()); - _RYML_CB_ASSERT(m_stack.m_callbacks, m_state != &prev); - if(prev.flags & SSCL) - { - _c4dbgpf("moving scalar '{}' from state[{}] to state[{}] (overwriting '{}')", prev.scalar, &prev-m_stack.begin(), m_state-m_stack.begin(), m_state->scalar); - add_flags(prev.flags & (SSCL | QSCL)); - m_state->scalar = prev.scalar; - rem_flags(SSCL | QSCL, &prev); - prev.scalar.clear(); - } -} - -//----------------------------------------------------------------------------- -/** @todo this function is a monster and needs love. Likely, it needs - * to be split like _scan_scalar_*() */ -bool Parser::_handle_indentation() -{ - _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(FLOW)); - if( ! _at_line_begin()) - return false; - - size_t ind = m_state->line_contents.indentation; - csubstr rem = m_state->line_contents.rem; - /** @todo instead of trimming, we should use the indentation index from above */ - csubstr remt = rem.triml(' '); - - if(remt.empty() || remt.begins_with('#')) // this is a blank or comment line - { - _line_progressed(rem.size()); - return true; - } - - _c4dbgpf("indentation? ind={} indref={}", ind, m_state->indref); - if(ind == m_state->indref) - { - _c4dbgpf("same indentation: {}", ind); - if(!rem.sub(ind).begins_with('-')) - { - _c4dbgp("does not begin with -"); - if(has_any(RMAP)) - { - if(has_all(SSCL|RVAL)) - { - _c4dbgp("add with null val"); - _append_key_val_null(rem.str + ind - 1); - addrem_flags(RKEY, RVAL); - } - } - else if(has_any(RSEQ)) - { - if(m_stack.size() > 2) // do not pop to root level - { - if(has_any(RNXT)) - { - _c4dbgp("end the indentless seq"); - _pop_level(); - return true; - } - else if(has_any(RVAL)) - { - _c4dbgp("add with null val"); - _append_val_null(rem.str); - _c4dbgp("end the indentless seq"); - _pop_level(); - return true; - } - } - } - } - _line_progressed(ind); - return ind > 0; - } - else if(ind < m_state->indref) - { - _c4dbgpf("smaller indentation ({} < {})!!!", ind, m_state->indref); - if(has_all(RVAL)) - { - _c4dbgp("there was an empty val -- appending"); - if(has_all(RMAP)) - { - _RYML_CB_ASSERT(m_stack.m_callbacks, has_all(SSCL)); - _append_key_val_null(rem.sub(ind).str - 1); - } - else if(has_all(RSEQ)) - { - _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(SSCL)); - _append_val_null(rem.sub(ind).str - 1); - } - } - // search the stack frame to jump to based on its indentation - State const* popto = nullptr; - _RYML_CB_ASSERT(m_stack.m_callbacks, m_stack.is_contiguous()); // this search relies on the stack being contiguous - for(State const* s = m_state-1; s >= m_stack.begin(); --s) - { - _c4dbgpf("searching for state with indentation {}. curr={} (level={},node={})", ind, s->indref, s->level, s->node_id); - if(s->indref == ind) - { - _c4dbgpf("gotit!!! level={} node={}", s->level, s->node_id); - popto = s; - // while it may be tempting to think we're done at this - // point, we must still determine whether we're jumping to a - // parent with the same indentation. Consider this case with - // an indentless sequence: - // - // product: - // - sku: BL394D - // quantity: 4 - // description: Basketball - // price: 450.00 - // - sku: BL4438H - // quantity: 1 - // description: Super Hoop - // price: 2392.00 # jumping one level here would be wrong. - // tax: 1234.5 # we must jump two levels - if(popto > m_stack.begin()) - { - auto parent = popto - 1; - if(parent->indref == popto->indref) - { - _c4dbgpf("the parent (level={},node={}) has the same indentation ({}). is this in an indentless sequence?", parent->level, parent->node_id, popto->indref); - _c4dbgpf("isseq(popto)={} ismap(parent)={}", m_tree->is_seq(popto->node_id), m_tree->is_map(parent->node_id)); - if(m_tree->is_seq(popto->node_id) && m_tree->is_map(parent->node_id)) - { - if( ! remt.begins_with('-')) - { - _c4dbgp("this is an indentless sequence"); - popto = parent; - } - else - { - _c4dbgp("not an indentless sequence"); - } - } - } - } - break; - } - } - if(!popto || popto >= m_state || popto->level >= m_state->level) - { - _c4err("parse error: incorrect indentation?"); - } - _c4dbgpf("popping {} levels: from level {} to level {}", m_state->level-popto->level, m_state->level, popto->level); - while(m_state != popto) - { - _c4dbgpf("popping level {} (indentation={})", m_state->level, m_state->indref); - _pop_level(); - } - _RYML_CB_ASSERT(m_stack.m_callbacks, ind == m_state->indref); - _line_progressed(ind); - return true; - } - else - { - _c4dbgpf("larger indentation ({} > {})!!!", ind, m_state->indref); - _RYML_CB_ASSERT(m_stack.m_callbacks, ind > m_state->indref); - if(has_all(RMAP|RVAL)) - { - if(_is_scalar_next__rmap_val(remt) && remt.first_of(":?") == npos) - { - _c4dbgpf("actually it seems a value: '{}'", remt); - } - else - { - addrem_flags(RKEY, RVAL); - _start_unk(); - //_move_scalar_from_top(); - _line_progressed(ind); - _save_indentation(); - return true; - } - } - else if(has_all(RSEQ|RVAL)) - { - // nothing to do here - } - else - { - _c4err("parse error - indentation should not increase at this point"); - } - } - - return false; -} - -//----------------------------------------------------------------------------- -csubstr Parser::_scan_comment() -{ - csubstr s = m_state->line_contents.rem; - _RYML_CB_ASSERT(m_stack.m_callbacks, s.begins_with('#')); - _line_progressed(s.len); - // skip the # character - s = s.sub(1); - // skip leading whitespace - s = s.right_of(s.first_not_of(' '), /*include_pos*/true); - _c4dbgpf("comment was '{}'", s); - return s; -} - -//----------------------------------------------------------------------------- -csubstr Parser::_scan_squot_scalar() -{ - // quoted scalars can spread over multiple lines! - // nice explanation here: http://yaml-multiline.info/ - - // a span to the end of the file - size_t b = m_state->pos.offset; - substr s = m_buf.sub(b); - if(s.begins_with(' ')) - { - s = s.triml(' '); - _RYML_CB_ASSERT(m_stack.m_callbacks, m_buf.sub(b).is_super(s)); - _RYML_CB_ASSERT(m_stack.m_callbacks, s.begin() >= m_buf.sub(b).begin()); - _line_progressed((size_t)(s.begin() - m_buf.sub(b).begin())); - } - b = m_state->pos.offset; // take this into account - _RYML_CB_ASSERT(m_stack.m_callbacks, s.begins_with('\'')); - - // skip the opening quote - _line_progressed(1); - s = s.sub(1); - - bool needs_filter = false; - - size_t numlines = 1; // we already have one line - size_t pos = npos; // find the pos of the matching quote - while( ! _finished_file()) - { - const csubstr line = m_state->line_contents.rem; - bool line_is_blank = true; - _c4dbgpf("scanning single quoted scalar @ line[{}]: ~~~{}~~~", m_state->pos.line, line); - for(size_t i = 0; i < line.len; ++i) - { - const char curr = line.str[i]; - if(curr == '\'') // single quotes are escaped with two single quotes - { - const char next = i+1 < line.len ? line.str[i+1] : '~'; - if(next != '\'') // so just look for the first quote - { // without another after it - pos = i; - break; - } - else - { - needs_filter = true; // needs filter to remove escaped quotes - ++i; // skip the escaped quote - } - } - else if(curr != ' ') - { - line_is_blank = false; - } - } - - // leading whitespace also needs filtering - needs_filter = needs_filter - || (numlines > 1) - || line_is_blank - || (_at_line_begin() && line.begins_with(' ')); - - if(pos == npos) - { - _line_progressed(line.len); - ++numlines; - } - else - { - _RYML_CB_ASSERT(m_stack.m_callbacks, pos >= 0 && pos < m_buf.len); - _RYML_CB_ASSERT(m_stack.m_callbacks, m_buf[m_state->pos.offset + pos] == '\''); - _line_progressed(pos + 1); // progress beyond the quote - pos = m_state->pos.offset - b - 1; // but we stop before it - break; - } - - _line_ended(); - _scan_line(); - } - - if(pos == npos) - { - _c4err("reached end of file while looking for closing quote"); - } - else - { - _RYML_CB_ASSERT(m_stack.m_callbacks, pos > 0); - _RYML_CB_ASSERT(m_stack.m_callbacks, s.end() >= m_buf.begin() && s.end() <= m_buf.end()); - _RYML_CB_ASSERT(m_stack.m_callbacks, s.end() == m_buf.end() || *s.end() == '\''); - s = s.sub(0, pos-1); - } - - if(needs_filter) - { - csubstr ret = _filter_squot_scalar(s); - _RYML_CB_ASSERT(m_stack.m_callbacks, ret.len <= s.len || s.empty() || s.trim(' ').empty()); - _c4dbgpf("final scalar: \"{}\"", ret); - return ret; - } - - _c4dbgpf("final scalar: \"{}\"", s); - - return s; -} - -//----------------------------------------------------------------------------- -csubstr Parser::_scan_dquot_scalar() -{ - // quoted scalars can spread over multiple lines! - // nice explanation here: http://yaml-multiline.info/ - - // a span to the end of the file - size_t b = m_state->pos.offset; - substr s = m_buf.sub(b); - if(s.begins_with(' ')) - { - s = s.triml(' '); - _RYML_CB_ASSERT(m_stack.m_callbacks, m_buf.sub(b).is_super(s)); - _RYML_CB_ASSERT(m_stack.m_callbacks, s.begin() >= m_buf.sub(b).begin()); - _line_progressed((size_t)(s.begin() - m_buf.sub(b).begin())); - } - b = m_state->pos.offset; // take this into account - _RYML_CB_ASSERT(m_stack.m_callbacks, s.begins_with('"')); - - // skip the opening quote - _line_progressed(1); - s = s.sub(1); - - bool needs_filter = false; - - size_t numlines = 1; // we already have one line - size_t pos = npos; // find the pos of the matching quote - while( ! _finished_file()) - { - const csubstr line = m_state->line_contents.rem; - bool line_is_blank = true; - _c4dbgpf("scanning double quoted scalar @ line[{}]: line='{}'", m_state->pos.line, line); - for(size_t i = 0; i < line.len; ++i) - { - const char curr = line.str[i]; - if(curr != ' ') - line_is_blank = false; - // every \ is an escape - if(curr == '\\') - { - const char next = i+1 < line.len ? line.str[i+1] : '~'; - needs_filter = true; - if(next == '"' || next == '\\') - ++i; - } - else if(curr == '"') - { - pos = i; - break; - } - } - - // leading whitespace also needs filtering - needs_filter = needs_filter - || (numlines > 1) - || line_is_blank - || (_at_line_begin() && line.begins_with(' ')); - - if(pos == npos) - { - _line_progressed(line.len); - ++numlines; - } - else - { - _RYML_CB_ASSERT(m_stack.m_callbacks, pos >= 0 && pos < m_buf.len); - _RYML_CB_ASSERT(m_stack.m_callbacks, m_buf[m_state->pos.offset + pos] == '"'); - _line_progressed(pos + 1); // progress beyond the quote - pos = m_state->pos.offset - b - 1; // but we stop before it - break; - } - - _line_ended(); - _scan_line(); - } - - if(pos == npos) - { - _c4err("reached end of file looking for closing quote"); - } - else - { - _RYML_CB_ASSERT(m_stack.m_callbacks, pos > 0); - _RYML_CB_ASSERT(m_stack.m_callbacks, s.end() == m_buf.end() || *s.end() == '"'); - _RYML_CB_ASSERT(m_stack.m_callbacks, s.end() >= m_buf.begin() && s.end() <= m_buf.end()); - s = s.sub(0, pos-1); - } - - if(needs_filter) - { - csubstr ret = _filter_dquot_scalar(s); - _c4dbgpf("final scalar: [{}]\"{}\"", ret.len, ret); - _RYML_CB_ASSERT(m_stack.m_callbacks, ret.len <= s.len || s.empty() || s.trim(' ').empty()); - return ret; - } - - _c4dbgpf("final scalar: \"{}\"", s); - - return s; -} - -//----------------------------------------------------------------------------- -csubstr Parser::_scan_block() -{ - // nice explanation here: http://yaml-multiline.info/ - csubstr s = m_state->line_contents.rem; - csubstr trimmed = s.triml(' '); - if(trimmed.str > s.str) - { - _c4dbgp("skipping whitespace"); - _RYML_CB_ASSERT(m_stack.m_callbacks, trimmed.str >= s.str); - _line_progressed(static_cast(trimmed.str - s.str)); - s = trimmed; - } - _RYML_CB_ASSERT(m_stack.m_callbacks, s.begins_with('|') || s.begins_with('>')); - - _c4dbgpf("scanning block: specs=\"{}\"", s); - - // parse the spec - BlockStyle_e newline = s.begins_with('>') ? BLOCK_FOLD : BLOCK_LITERAL; - BlockChomp_e chomp = CHOMP_CLIP; // default to clip unless + or - are used - size_t indentation = npos; // have to find out if no spec is given - csubstr digits; - if(s.len > 1) - { - _RYML_CB_ASSERT(m_stack.m_callbacks, s.begins_with_any("|>")); - csubstr t = s.sub(1); - _c4dbgpf("scanning block: spec is multichar: '{}'", t); - _RYML_CB_ASSERT(m_stack.m_callbacks, t.len >= 1); - size_t pos = t.first_of("-+"); - _c4dbgpf("scanning block: spec chomp char at {}", pos); - if(pos != npos) - { - if(t[pos] == '-') - chomp = CHOMP_STRIP; - else if(t[pos] == '+') - chomp = CHOMP_KEEP; - if(pos == 0) - t = t.sub(1); - else - t = t.first(pos); - } - // from here to the end, only digits are considered - digits = t.left_of(t.first_not_of("0123456789")); - if( ! digits.empty()) - { - if( ! c4::atou(digits, &indentation)) - _c4err("parse error: could not read decimal"); - _c4dbgpf("scanning block: indentation specified: {}. add {} from curr state -> {}", indentation, m_state->indref, indentation+m_state->indref); - indentation += m_state->indref; - } - } - - // finish the current line - _line_progressed(s.len); - _line_ended(); - _scan_line(); - - _c4dbgpf("scanning block: style={} chomp={} indentation={}", newline==BLOCK_FOLD ? "fold" : "literal", chomp==CHOMP_CLIP ? "clip" : (chomp==CHOMP_STRIP ? "strip" : "keep"), indentation); - - // start with a zero-length block, already pointing at the right place - substr raw_block(m_buf.data() + m_state->pos.offset, size_t(0));// m_state->line_contents.full.sub(0, 0); - _RYML_CB_ASSERT(m_stack.m_callbacks, raw_block.begin() == m_state->line_contents.full.begin()); - - // read every full line into a raw block, - // from which newlines are to be stripped as needed. - // - // If no explicit indentation was given, pick it from the first - // non-empty line. See - // https://yaml.org/spec/1.2.2/#8111-block-indentation-indicator - size_t num_lines = 0, first = m_state->pos.line, provisional_indentation = npos; - LineContents lc; - while(( ! _finished_file())) - { - // peek next line, but do not advance immediately - lc.reset_with_next_line(m_buf, m_state->pos.offset); - _c4dbgpf("scanning block: peeking at '{}'", lc.stripped); - // evaluate termination conditions - if(indentation != npos) - { - // stop when the line is deindented and not empty - if(lc.indentation < indentation && ( ! lc.rem.trim(" \t\r\n").empty())) - { - _c4dbgpf("scanning block: indentation decreased ref={} thisline={}", indentation, lc.indentation); - break; - } - else if(indentation == 0) - { - if((lc.rem == "..." || lc.rem.begins_with("... ")) - || - (lc.rem == "---" || lc.rem.begins_with("--- "))) - { - _c4dbgp("scanning block: stop. indentation=0 and stream ended"); - break; - } - } - } - else - { - _c4dbgpf("scanning block: indentation ref not set. firstnonws={}", lc.stripped.first_not_of(' ')); - if(lc.stripped.first_not_of(' ') != npos) // non-empty line - { - _c4dbgpf("scanning block: line not empty. indref={} indprov={} indentation={}", m_state->indref, provisional_indentation, lc.indentation); - if(provisional_indentation == npos) - { - if(lc.indentation < m_state->indref) - { - _c4dbgpf("scanning block: block terminated indentation={} < indref={}", lc.indentation, m_state->indref); - if(raw_block.len == 0) - { - _c4dbgp("scanning block: was empty, undo next line"); - _line_ended_undo(); - } - break; - } - else if(lc.indentation == m_state->indref) - { - if(has_any(RSEQ|RMAP)) - { - _c4dbgpf("scanning block: block terminated. reading container and indentation={}==indref={}", lc.indentation, m_state->indref); - break; - } - } - _c4dbgpf("scanning block: set indentation ref from this line: ref={}", lc.indentation); - indentation = lc.indentation; - } - else - { - if(lc.indentation >= provisional_indentation) - { - _c4dbgpf("scanning block: set indentation ref from provisional indentation: provisional_ref={}, thisline={}", provisional_indentation, lc.indentation); - //indentation = provisional_indentation ? provisional_indentation : lc.indentation; - indentation = lc.indentation; - } - else - { - break; - //_c4err("parse error: first non-empty block line should have at least the original indentation"); - } - } - } - else // empty line - { - _c4dbgpf("scanning block: line empty or {} spaces. line_indentation={} prov_indentation={}", lc.stripped.len, lc.indentation, provisional_indentation); - if(provisional_indentation != npos) - { - if(lc.stripped.len >= provisional_indentation) - { - _c4dbgpf("scanning block: increase provisional_ref {} -> {}", provisional_indentation, lc.stripped.len); - provisional_indentation = lc.stripped.len; - } - #ifdef RYML_NO_COVERAGE__TO_BE_DELETED - else if(lc.indentation >= provisional_indentation && lc.indentation != npos) - { - _c4dbgpf("scanning block: increase provisional_ref {} -> {}", provisional_indentation, lc.indentation); - provisional_indentation = lc.indentation; - } - #endif - } - else - { - provisional_indentation = lc.indentation ? lc.indentation : has_any(RSEQ|RVAL); - _c4dbgpf("scanning block: initialize provisional_ref={}", provisional_indentation); - if(provisional_indentation == npos) - { - provisional_indentation = lc.stripped.len ? lc.stripped.len : has_any(RSEQ|RVAL); - _c4dbgpf("scanning block: initialize provisional_ref={}", provisional_indentation); - } - } - } - } - // advance now that we know the folded scalar continues - m_state->line_contents = lc; - _c4dbgpf("scanning block: append '{}'", m_state->line_contents.rem); - raw_block.len += m_state->line_contents.full.len; - _line_progressed(m_state->line_contents.rem.len); - _line_ended(); - ++num_lines; - } - _RYML_CB_ASSERT(m_stack.m_callbacks, m_state->pos.line == (first + num_lines) || (raw_block.len == 0)); - C4_UNUSED(num_lines); - C4_UNUSED(first); - - if(indentation == npos) - { - _c4dbgpf("scanning block: set indentation from provisional: {}", provisional_indentation); - indentation = provisional_indentation; - } - - if(num_lines) - _line_ended_undo(); - - _c4dbgpf("scanning block: raw=~~~{}~~~", raw_block); - - // ok! now we strip the newlines and spaces according to the specs - s = _filter_block_scalar(raw_block, newline, chomp, indentation); - - _c4dbgpf("scanning block: final=~~~{}~~~", s); - - return s; -} - - -//----------------------------------------------------------------------------- - -template -bool Parser::_filter_nl(substr r, size_t *C4_RESTRICT i, size_t *C4_RESTRICT pos, size_t indentation) -{ - // a debugging scaffold: - #if 0 - #define _c4dbgfnl(fmt, ...) _c4dbgpf("filter_nl[{}]: " fmt, *i, __VA_ARGS__) - #else - #define _c4dbgfnl(...) - #endif - - const char curr = r[*i]; - bool replaced = false; - - _RYML_CB_ASSERT(m_stack.m_callbacks, indentation != npos); - _RYML_CB_ASSERT(m_stack.m_callbacks, curr == '\n'); - - _c4dbgfnl("found newline. sofar=[{}]~~~{}~~~", *pos, m_filter_arena.first(*pos)); - size_t ii = *i; - size_t numnl_following = count_following_newlines(r, &ii, indentation); - if(numnl_following) - { - _c4dbgfnl("{} consecutive (empty) lines {} in the middle. totalws={}", 1+numnl_following, ii < r.len ? "in the middle" : "at the end", ii - *i); - for(size_t j = 0; j < numnl_following; ++j) - m_filter_arena.str[(*pos)++] = '\n'; - } - else - { - if(r.first_not_of(" \t", *i+1) != npos) - { - m_filter_arena.str[(*pos)++] = ' '; - _c4dbgfnl("single newline. convert to space. ii={}/{}. sofar=[{}]~~~{}~~~", ii, r.len, *pos, m_filter_arena.first(*pos)); - replaced = true; - } - else - { - if C4_IF_CONSTEXPR (keep_trailing_whitespace) - { - m_filter_arena.str[(*pos)++] = ' '; - _c4dbgfnl("single newline. convert to space. ii={}/{}. sofar=[{}]~~~{}~~~", ii, r.len, *pos, m_filter_arena.first(*pos)); - replaced = true; - } - else - { - _c4dbgfnl("last newline, everything else is whitespace. ii={}/{}", ii, r.len); - *i = r.len; - } - } - if C4_IF_CONSTEXPR (backslash_is_escape) - { - if(ii < r.len && r.str[ii] == '\\') - { - const char next = ii+1 < r.len ? r.str[ii+1] : '\0'; - if(next == ' ' || next == '\t') - { - _c4dbgfnl("extend skip to backslash{}", ""); - ++ii; - } - } - } - } - *i = ii - 1; // correct for the loop increment - - #undef _c4dbgfnl - - return replaced; -} - - -//----------------------------------------------------------------------------- - -template -void Parser::_filter_ws(substr r, size_t *C4_RESTRICT i, size_t *C4_RESTRICT pos) -{ - // a debugging scaffold: - #if 0 - #define _c4dbgfws(fmt, ...) _c4dbgpf("filt_nl[{}]: " fmt, *i, __VA_ARGS__) - #else - #define _c4dbgfws(...) - #endif - - const char curr = r[*i]; - _c4dbgfws("found whitespace '{}'", _c4prc(curr)); - _RYML_CB_ASSERT(m_stack.m_callbacks, curr == ' ' || curr == '\t'); - - size_t first = *i > 0 ? r.first_not_of(" \t", *i) : r.first_not_of(' ', *i); - if(first != npos) - { - if(r[first] == '\n' || r[first] == '\r') // skip trailing whitespace - { - _c4dbgfws("whitespace is trailing on line. firstnonws='{}'@{}", _c4prc(r[first]), first); - *i = first - 1; // correct for the loop increment - } - else // a legit whitespace - { - m_filter_arena.str[(*pos)++] = curr; - _c4dbgfws("legit whitespace. sofar=[{}]~~~{}~~~", *pos, m_filter_arena.first(*pos)); - } - } - else - { - _c4dbgfws("... everything else is trailing whitespace{}", ""); - if C4_IF_CONSTEXPR (keep_trailing_whitespace) - for(size_t j = *i; j < r.len; ++j) - m_filter_arena.str[(*pos)++] = r[j]; - *i = r.len; - } - - #undef _c4dbgfws -} - - -//----------------------------------------------------------------------------- -csubstr Parser::_filter_plain_scalar(substr s, size_t indentation) -{ - // a debugging scaffold: - #if 0 - #define _c4dbgfps(...) _c4dbgpf("filt_plain_scalar" __VA_ARGS__) - #else - #define _c4dbgfps(...) - #endif - - _c4dbgfps("before=~~~{}~~~", s); - - substr r = s.triml(" \t"); - _grow_filter_arena(r.len); - size_t pos = 0; // the filtered size - bool filtered_chars = false; - for(size_t i = 0; i < r.len; ++i) - { - const char curr = r.str[i]; - _c4dbgfps("[{}]: '{}'", i, _c4prc(curr)); - if(curr == ' ' || curr == '\t') - { - _filter_ws(r, &i, &pos); - } - else if(curr == '\n') - { - filtered_chars = _filter_nl(r, &i, &pos, indentation); - } - else if(curr == '\r') // skip \r --- https://stackoverflow.com/questions/1885900 - { - ; - } - else - { - m_filter_arena.str[pos++] = r[i]; - } - } - - _RYML_CB_ASSERT(m_stack.m_callbacks, pos <= m_filter_arena.len); - if(pos < r.len || filtered_chars) - { - r = _finish_filter_arena(r, pos); - } - - _RYML_CB_ASSERT(m_stack.m_callbacks, s.len >= r.len); - _c4dbgfps("#filteredchars={} after=~~~{}~~~", s.len - r.len, r); - - #undef _c4dbgfps - return r; -} - - -//----------------------------------------------------------------------------- -csubstr Parser::_filter_squot_scalar(substr s) -{ - // a debugging scaffold: - #if 0 - #define _c4dbgfsq(...) _c4dbgpf("filt_squo_scalar") - #else - #define _c4dbgfsq(...) - #endif - - // from the YAML spec for double-quoted scalars: - // https://yaml.org/spec/1.2-old/spec.html#style/flow/single-quoted - - _c4dbgfsq(": before=~~~{}~~~", s); - - _grow_filter_arena(s.len); - substr r = s; - size_t pos = 0; // the filtered size - bool filtered_chars = false; - for(size_t i = 0; i < r.len; ++i) - { - const char curr = r[i]; - _c4dbgfsq("[{}]: '{}'", i, _c4prc(curr)); - if(curr == ' ' || curr == '\t') - { - _filter_ws(r, &i, &pos); - } - else if(curr == '\n') - { - filtered_chars = _filter_nl(r, &i, &pos, /*indentation*/0); - } - else if(curr == '\r') // skip \r --- https://stackoverflow.com/questions/1885900 - { - ; - } - else if(curr == '\'') - { - char next = i+1 < r.len ? r[i+1] : '\0'; - if(next == '\'') - { - _c4dbgfsq("[{}]: two consecutive quotes", i); - filtered_chars = true; - m_filter_arena.str[pos++] = '\''; - ++i; - } - } - else - { - m_filter_arena.str[pos++] = curr; - } - } - - _RYML_CB_ASSERT(m_stack.m_callbacks, pos <= m_filter_arena.len); - if(pos < r.len || filtered_chars) - { - r = _finish_filter_arena(r, pos); - } - - _RYML_CB_ASSERT(m_stack.m_callbacks, s.len >= r.len); - _c4dbgpf(": #filteredchars={} after=~~~{}~~~", s.len - r.len, r); - - #undef _c4dbgfsq - return r; -} - - -//----------------------------------------------------------------------------- -csubstr Parser::_filter_dquot_scalar(substr s) -{ - // a debugging scaffold: - #if 0 - #define _c4dbgfdq(...) _c4dbgpf("filt_dquo_scalar" __VA_ARGS__) - #else - #define _c4dbgfdq(...) - #endif - - _c4dbgfdq(": before=~~~{}~~~", s); - - // from the YAML spec for double-quoted scalars: - // https://yaml.org/spec/1.2-old/spec.html#style/flow/double-quoted - // - // All leading and trailing white space characters are excluded - // from the content. Each continuation line must therefore contain - // at least one non-space character. Empty lines, if any, are - // consumed as part of the line folding. - - _grow_filter_arena(s.len + 2u * s.count('\\')); - substr r = s; - size_t pos = 0; // the filtered size - bool filtered_chars = false; - for(size_t i = 0; i < r.len; ++i) - { - const char curr = r[i]; - _c4dbgfdq("[{}]: '{}'", i, _c4prc(curr)); - if(curr == ' ' || curr == '\t') - { - _filter_ws(r, &i, &pos); - } - else if(curr == '\n') - { - filtered_chars = _filter_nl(r, &i, &pos, /*indentation*/0); - } - else if(curr == '\r') // skip \r --- https://stackoverflow.com/questions/1885900 - { - ; - } - else if(curr == '\\') - { - char next = i+1 < r.len ? r[i+1] : '\0'; - _c4dbgfdq("[{}]: backslash, next='{}'", i, _c4prc(next)); - filtered_chars = true; - if(next == '\r') - { - if(i+2 < r.len && r[i+2] == '\n') - { - ++i; // newline escaped with \ -- skip both (add only one as i is loop-incremented) - next = '\n'; - _c4dbgfdq("[{}]: was \\r\\n, now next='\\n'", i); - } - } - // remember the loop will also increment i - if(next == '\n') - { - size_t ii = i + 2; - for( ; ii < r.len; ++ii) - { - if(r.str[ii] == ' ' || r.str[ii] == '\t') // skip leading whitespace - ; - else - break; - } - i += ii - i - 1; - } - else if(next == '"' || next == '/' || next == ' ' || next == '\t') // escapes for json compatibility - { - m_filter_arena.str[pos++] = next; - ++i; - } - else if(next == '\r') - { - //++i; - } - else if(next == 'n') - { - m_filter_arena.str[pos++] = '\n'; - ++i; - } - else if(next == 'r') - { - m_filter_arena.str[pos++] = '\r'; - ++i; // skip - } - else if(next == 't') - { - m_filter_arena.str[pos++] = '\t'; - ++i; - } - else if(next == '\\') - { - m_filter_arena.str[pos++] = '\\'; - ++i; - } - else if(next == 'x') // UTF8 - { - if(i + 1u + 2u >= r.len) - _c4err("\\x requires 2 hex digits"); - uint8_t byteval = {}; - if(!read_hex(r.sub(i + 2u, 2u), &byteval)) - _c4err("failed to read \\x codepoint"); - m_filter_arena.str[pos++] = *(char*)&byteval; - i += 1u + 2u; - } - else if(next == 'u') // UTF16 - { - if(i + 1u + 4u >= r.len) - _c4err("\\u requires 4 hex digits"); - char readbuf[8]; - csubstr codepoint = r.sub(i + 2u, 4u); - uint32_t codepoint_val = {}; - if(!read_hex(codepoint, &codepoint_val)) - _c4err("failed to parse \\u codepoint"); - size_t numbytes = decode_code_point((uint8_t*)readbuf, sizeof(readbuf), codepoint_val); - C4_ASSERT(numbytes <= 4); - memcpy(m_filter_arena.str + pos, readbuf, numbytes); - pos += numbytes; - i += 1u + 4u; - } - else if(next == 'U') // UTF32 - { - if(i + 1u + 8u >= r.len) - _c4err("\\U requires 8 hex digits"); - char readbuf[8]; - csubstr codepoint = r.sub(i + 2u, 8u); - uint32_t codepoint_val = {}; - if(!read_hex(codepoint, &codepoint_val)) - _c4err("failed to parse \\U codepoint"); - size_t numbytes = decode_code_point((uint8_t*)readbuf, sizeof(readbuf), codepoint_val); - C4_ASSERT(numbytes <= 4); - memcpy(m_filter_arena.str + pos, readbuf, numbytes); - pos += numbytes; - i += 1u + 8u; - } - // https://yaml.org/spec/1.2.2/#rule-c-ns-esc-char - else if(next == '0') - { - m_filter_arena.str[pos++] = '\0'; - ++i; - } - else if(next == 'b') // backspace - { - m_filter_arena.str[pos++] = '\b'; - ++i; - } - else if(next == 'f') // form feed - { - m_filter_arena.str[pos++] = '\f'; - ++i; - } - else if(next == 'a') // bell character - { - m_filter_arena.str[pos++] = '\a'; - ++i; - } - else if(next == 'v') // vertical tab - { - m_filter_arena.str[pos++] = '\v'; - ++i; - } - else if(next == 'e') // escape character - { - m_filter_arena.str[pos++] = '\x1b'; - ++i; - } - else if(next == '_') // unicode non breaking space \u00a0 - { - // https://www.compart.com/en/unicode/U+00a0 - m_filter_arena.str[pos++] = _RYML_CHCONST(-0x3e, 0xc2); - m_filter_arena.str[pos++] = _RYML_CHCONST(-0x60, 0xa0); - ++i; - } - else if(next == 'N') // unicode next line \u0085 - { - // https://www.compart.com/en/unicode/U+0085 - m_filter_arena.str[pos++] = _RYML_CHCONST(-0x3e, 0xc2); - m_filter_arena.str[pos++] = _RYML_CHCONST(-0x7b, 0x85); - ++i; - } - else if(next == 'L') // unicode line separator \u2028 - { - // https://www.utf8-chartable.de/unicode-utf8-table.pl?start=8192&number=1024&names=-&utf8=0x&unicodeinhtml=hex - m_filter_arena.str[pos++] = _RYML_CHCONST(-0x1e, 0xe2); - m_filter_arena.str[pos++] = _RYML_CHCONST(-0x80, 0x80); - m_filter_arena.str[pos++] = _RYML_CHCONST(-0x58, 0xa8); - ++i; - } - else if(next == 'P') // unicode paragraph separator \u2029 - { - // https://www.utf8-chartable.de/unicode-utf8-table.pl?start=8192&number=1024&names=-&utf8=0x&unicodeinhtml=hex - m_filter_arena.str[pos++] = _RYML_CHCONST(-0x1e, 0xe2); - m_filter_arena.str[pos++] = _RYML_CHCONST(-0x80, 0x80); - m_filter_arena.str[pos++] = _RYML_CHCONST(-0x57, 0xa9); - ++i; - } - _c4dbgfdq("[{}]: backslash...sofar=[{}]~~~{}~~~", i, pos, m_filter_arena.first(pos)); - } - else - { - m_filter_arena.str[pos++] = curr; - } - } - - _RYML_CB_ASSERT(m_stack.m_callbacks, pos <= m_filter_arena.len); - if(pos < r.len || filtered_chars) - { - r = _finish_filter_arena(r, pos); - } - - _RYML_CB_ASSERT(m_stack.m_callbacks, s.len >= r.len); - _c4dbgpf(": #filteredchars={} after=~~~{}~~~", s.len - r.len, r); - - #undef _c4dbgfdq - - return r; -} - - -//----------------------------------------------------------------------------- -bool Parser::_apply_chomp(substr buf, size_t *C4_RESTRICT pos, BlockChomp_e chomp) -{ - substr trimmed = buf.first(*pos).trimr('\n'); - bool added_newline = false; - switch(chomp) - { - case CHOMP_KEEP: - if(trimmed.len == *pos) - { - _c4dbgpf("chomp=KEEP: add missing newline @{}", *pos); - //m_filter_arena.str[(*pos)++] = '\n'; - added_newline = true; - } - break; - case CHOMP_CLIP: - if(trimmed.len == *pos) - { - _c4dbgpf("chomp=CLIP: add missing newline @{}", *pos); - m_filter_arena.str[(*pos)++] = '\n'; - added_newline = true; - } - else - { - _c4dbgpf("chomp=CLIP: include single trailing newline @{}", trimmed.len+1); - *pos = trimmed.len + 1; - } - break; - case CHOMP_STRIP: - _c4dbgpf("chomp=STRIP: strip {}-{}-{} newlines", *pos, trimmed.len, *pos-trimmed.len); - *pos = trimmed.len; - break; - default: - _c4err("unknown chomp style"); - } - return added_newline; -} - - -//----------------------------------------------------------------------------- -csubstr Parser::_filter_block_scalar(substr s, BlockStyle_e style, BlockChomp_e chomp, size_t indentation) -{ - // a debugging scaffold: - #if 0 - #define _c4dbgfbl(fmt, ...) _c4dbgpf("filt_block" fmt, __VA_ARGS__) - #else - #define _c4dbgfbl(...) - #endif - - _c4dbgfbl(": indentation={} before=[{}]~~~{}~~~", indentation, s.len, s); - - if(chomp != CHOMP_KEEP && s.trim(" \n\r").len == 0u) - { - _c4dbgp("filt_block: empty scalar"); - return s.first(0); - } - - substr r = s; - - switch(style) - { - case BLOCK_LITERAL: - { - _c4dbgp("filt_block: style=literal"); - // trim leading whitespace up to indentation - { - size_t numws = r.first_not_of(' '); - if(numws != npos) - { - if(numws > indentation) - r = r.sub(indentation); - else - r = r.sub(numws); - _c4dbgfbl(": after triml=[{}]~~~{}~~~", r.len, r); - } - else - { - if(chomp != CHOMP_KEEP || r.len == 0) - { - _c4dbgfbl(": all spaces {}, return empty", r.len); - return r.first(0); - } - else - { - r[0] = '\n'; - return r.first(1); - } - } - } - _grow_filter_arena(s.len + 2u); // use s.len! because we may need to add a newline at the end, so the leading indentation will allow space for that newline - size_t pos = 0; // the filtered size - for(size_t i = 0; i < r.len; ++i) - { - const char curr = r.str[i]; - _c4dbgfbl("[{}]='{}' pos={}", i, _c4prc(curr), pos); - if(curr == '\r') - continue; - m_filter_arena.str[pos++] = curr; - if(curr == '\n') - { - _c4dbgfbl("[{}]: found newline", i); - // skip indentation on the next line - csubstr rem = r.sub(i+1); - size_t first = rem.first_not_of(' '); - if(first != npos) - { - _RYML_CB_ASSERT(m_stack.m_callbacks, first < rem.len); - _RYML_CB_ASSERT(m_stack.m_callbacks, i+1+first < r.len); - _c4dbgfbl("[{}]: {} spaces follow before next nonws character @ [{}]='{}'", i, first, i+1+first, rem.str[first]); - if(first < indentation) - { - _c4dbgfbl("[{}]: skip {}<{} spaces from indentation", i, first, indentation); - i += first; - } - else - { - _c4dbgfbl("[{}]: skip {} spaces from indentation", i, indentation); - i += indentation; - } - } - else - { - _RYML_CB_ASSERT(m_stack.m_callbacks, i+1 <= r.len); - first = rem.len; - _c4dbgfbl("[{}]: {} spaces to the end", i, first); - if(first) - { - if(first < indentation) - { - _c4dbgfbl("[{}]: skip everything", i); - --pos; - break; - } - else - { - _c4dbgfbl("[{}]: skip {} spaces from indentation", i, indentation); - i += indentation; - } - } - else if(i+1 == r.len) - { - if(chomp == CHOMP_STRIP) - --pos; - break; - } - } - } - } - _RYML_CB_ASSERT(m_stack.m_callbacks, s.len >= pos); - _c4dbgfbl(": #filteredchars={} after=~~~{}~~~", s.len - r.len, r); - bool changed = _apply_chomp(m_filter_arena, &pos, chomp); - _RYML_CB_ASSERT(m_stack.m_callbacks, pos <= m_filter_arena.len); - _RYML_CB_ASSERT(m_stack.m_callbacks, pos <= s.len); - if(pos < r.len || changed) - { - r = _finish_filter_arena(s, pos); // write into s - } - break; - } - case BLOCK_FOLD: - { - _c4dbgp("filt_block: style=fold"); - _grow_filter_arena(r.len + 2); - size_t pos = 0; // the filtered size - bool filtered_chars = false; - bool started = false; - bool is_indented = false; - size_t i = r.first_not_of(' '); - _c4dbgfbl(": first non space at {}", i); - if(i > indentation) - { - is_indented = true; - i = indentation; - } - _c4dbgfbl(": start folding at {}, is_indented={}", i, (int)is_indented); - auto on_change_indentation = [&](size_t numnl_following, size_t last_newl, size_t first_non_whitespace){ - _c4dbgfbl("[{}]: add 1+{} newlines", i, numnl_following); - for(size_t j = 0; j < 1 + numnl_following; ++j) - m_filter_arena.str[pos++] = '\n'; - for(i = last_newl + 1 + indentation; i < first_non_whitespace; ++i) - { - if(r.str[i] == '\r') - continue; - _c4dbgfbl("[{}]: add '{}'", i, _c4prc(r.str[i])); - m_filter_arena.str[pos++] = r.str[i]; - } - --i; - }; - for( ; i < r.len; ++i) - { - const char curr = r.str[i]; - _c4dbgfbl("[{}]='{}'", i, _c4prc(curr)); - if(curr == '\n') - { - filtered_chars = true; - // skip indentation on the next line, and advance over the next non-indented blank lines as well - size_t first_non_whitespace; - size_t numnl_following = (size_t)-1; - while(r[i] == '\n') - { - ++numnl_following; - csubstr rem = r.sub(i+1); - size_t first = rem.first_not_of(' '); - _c4dbgfbl("[{}]: found newline. first={} rem.len={}", i, first, rem.len); - if(first != npos) - { - first_non_whitespace = first + i+1; - while(first_non_whitespace < r.len && r[first_non_whitespace] == '\r') - ++first_non_whitespace; - _RYML_CB_ASSERT(m_stack.m_callbacks, first < rem.len); - _RYML_CB_ASSERT(m_stack.m_callbacks, i+1+first < r.len); - _c4dbgfbl("[{}]: {} spaces follow before next nonws character @ [{}]='{}'", i, first, i+1+first, _c4prc(rem.str[first])); - if(first < indentation) - { - _c4dbgfbl("[{}]: skip {}<{} spaces from indentation", i, first, indentation); - i += first; - } - else - { - _c4dbgfbl("[{}]: skip {} spaces from indentation", i, indentation); - i += indentation; - if(first > indentation) - { - _c4dbgfbl("[{}]: {} further indented than {}, stop newlining", i, first, indentation); - goto finished_counting_newlines; - } - } - // prepare the next while loop iteration - // by setting i at the next newline after - // an empty line - if(r[first_non_whitespace] == '\n') - i = first_non_whitespace; - else - goto finished_counting_newlines; - } - else - { - _RYML_CB_ASSERT(m_stack.m_callbacks, i+1 <= r.len); - first = rem.len; - first_non_whitespace = first + i+1; - if(first) - { - _c4dbgfbl("[{}]: {} spaces to the end", i, first); - if(first < indentation) - { - _c4dbgfbl("[{}]: skip everything", i); - i += first; - } - else - { - _c4dbgfbl("[{}]: skip {} spaces from indentation", i, indentation); - i += indentation; - if(first > indentation) - { - _c4dbgfbl("[{}]: {} spaces missing. not done yet", i, indentation - first); - goto finished_counting_newlines; - } - } - } - else // if(i+1 == r.len) - { - _c4dbgfbl("[{}]: it's the final newline", i); - _RYML_CB_ASSERT(m_stack.m_callbacks, i+1 == r.len); - _RYML_CB_ASSERT(m_stack.m_callbacks, rem.len == 0); - } - goto end_of_scalar; - } - } - end_of_scalar: - // Write all the trailing newlines. Since we're - // at the end no folding is needed, so write every - // newline (add 1). - _c4dbgfbl("[{}]: add {} trailing newlines", i, 1+numnl_following); - for(size_t j = 0; j < 1 + numnl_following; ++j) - m_filter_arena.str[pos++] = '\n'; - break; - finished_counting_newlines: - _c4dbgfbl("[{}]: #newlines={} firstnonws={}", i, numnl_following, first_non_whitespace); - while(first_non_whitespace < r.len && r[first_non_whitespace] == '\t') - ++first_non_whitespace; - _c4dbgfbl("[{}]: #newlines={} firstnonws={}", i, numnl_following, first_non_whitespace); - _RYML_CB_ASSERT(m_stack.m_callbacks, first_non_whitespace <= r.len); - size_t last_newl = r.last_of('\n', first_non_whitespace); - size_t this_indentation = first_non_whitespace - last_newl - 1; - _c4dbgfbl("[{}]: #newlines={} firstnonws={} lastnewl={} this_indentation={} vs indentation={}", i, numnl_following, first_non_whitespace, last_newl, this_indentation, indentation); - _RYML_CB_ASSERT(m_stack.m_callbacks, first_non_whitespace >= last_newl + 1); - _RYML_CB_ASSERT(m_stack.m_callbacks, this_indentation >= indentation); - if(!started) - { - _c4dbgfbl("[{}]: #newlines={}. write all leading newlines", i, numnl_following); - for(size_t j = 0; j < 1 + numnl_following; ++j) - m_filter_arena.str[pos++] = '\n'; - if(this_indentation > indentation) - { - is_indented = true; - _c4dbgfbl("[{}]: advance ->{}", i, last_newl + indentation); - i = last_newl + indentation; - } - else - { - i = first_non_whitespace - 1; - _c4dbgfbl("[{}]: advance ->{}", i, first_non_whitespace); - } - } - else if(this_indentation == indentation) - { - _c4dbgfbl("[{}]: same indentation", i); - if(!is_indented) - { - if(numnl_following == 0) - { - _c4dbgfbl("[{}]: fold!", i); - m_filter_arena.str[pos++] = ' '; - } - else - { - _c4dbgfbl("[{}]: add {} newlines", i, 1 + numnl_following); - for(size_t j = 0; j < numnl_following; ++j) - m_filter_arena.str[pos++] = '\n'; - } - i = first_non_whitespace - 1; - _c4dbgfbl("[{}]: advance {}->{}", i, i, first_non_whitespace); - } - else - { - _c4dbgfbl("[{}]: back to ref indentation", i); - is_indented = false; - on_change_indentation(numnl_following, last_newl, first_non_whitespace); - _c4dbgfbl("[{}]: advance {}->{}", i, i, first_non_whitespace); - } - } - else - { - _c4dbgfbl("[{}]: increased indentation.", i); - is_indented = true; - _RYML_CB_ASSERT(m_stack.m_callbacks, this_indentation > indentation); - on_change_indentation(numnl_following, last_newl, first_non_whitespace); - _c4dbgfbl("[{}]: advance {}->{}", i, i, first_non_whitespace); - } - } - else if(curr != '\r') - { - if(curr != '\t') - started = true; - m_filter_arena.str[pos++] = curr; - } - } - _RYML_CB_ASSERT(m_stack.m_callbacks, pos <= m_filter_arena.len); - _c4dbgfbl(": #filteredchars={} after=[{}]~~~{}~~~", (int)s.len - (int)pos, pos, m_filter_arena.first(pos)); - bool changed = _apply_chomp(m_filter_arena, &pos, chomp); - if(pos < r.len || filtered_chars || changed) - { - r = _finish_filter_arena(s, pos); // write into s - } - } - break; - default: - _c4err("unknown block style"); - } - - _c4dbgfbl(": final=[{}]~~~{}~~~", r.len, r); - - #undef _c4dbgfbl - - return r; -} - -//----------------------------------------------------------------------------- -size_t Parser::_count_nlines(csubstr src) -{ - return 1 + src.count('\n'); -} - -//----------------------------------------------------------------------------- -void Parser::_handle_directive(csubstr directive_) -{ - csubstr directive = directive_; - if(directive.begins_with("%TAG")) - { - TagDirective td; - _c4dbgpf("%TAG directive: {}", directive_); - directive = directive.sub(4); - if(!directive.begins_with(' ')) - _c4err("malformed tag directive: {}", directive_); - directive = directive.triml(' '); - size_t pos = directive.find(' '); - if(pos == npos) - _c4err("malformed tag directive: {}", directive_); - td.handle = directive.first(pos); - directive = directive.sub(td.handle.len).triml(' '); - pos = directive.find(' '); - if(pos != npos) - directive = directive.first(pos); - td.prefix = directive; - td.next_node_id = m_tree->size(); - if(m_tree->size() > 0) - { - size_t prev = m_tree->size() - 1; - if(m_tree->is_root(prev) && m_tree->type(prev) != NOTYPE && !m_tree->is_stream(prev)) - ++td.next_node_id; - } - _c4dbgpf("%TAG: handle={} prefix={} next_node={}", td.handle, td.prefix, td.next_node_id); - m_tree->add_tag_directive(td); - } - else if(directive.begins_with("%YAML")) - { - _c4dbgpf("%YAML directive! ignoring...: {}", directive); - } -} - -//----------------------------------------------------------------------------- -void Parser::set_flags(flag_t f, State * s) -{ -#ifdef RYML_DBG - char buf1_[64], buf2_[64]; - csubstr buf1 = _prfl(buf1_, f); - csubstr buf2 = _prfl(buf2_, s->flags); - _c4dbgpf("state[{}]: setting flags to {}: before={}", s-m_stack.begin(), buf1, buf2); -#endif - s->flags = f; -} - -void Parser::add_flags(flag_t on, State * s) -{ -#ifdef RYML_DBG - char buf1_[64], buf2_[64], buf3_[64]; - csubstr buf1 = _prfl(buf1_, on); - csubstr buf2 = _prfl(buf2_, s->flags); - csubstr buf3 = _prfl(buf3_, s->flags|on); - _c4dbgpf("state[{}]: adding flags {}: before={} after={}", s-m_stack.begin(), buf1, buf2, buf3); -#endif - s->flags |= on; -} - -void Parser::addrem_flags(flag_t on, flag_t off, State * s) -{ -#ifdef RYML_DBG - char buf1_[64], buf2_[64], buf3_[64], buf4_[64]; - csubstr buf1 = _prfl(buf1_, on); - csubstr buf2 = _prfl(buf2_, off); - csubstr buf3 = _prfl(buf3_, s->flags); - csubstr buf4 = _prfl(buf4_, ((s->flags|on)&(~off))); - _c4dbgpf("state[{}]: adding flags {} / removing flags {}: before={} after={}", s-m_stack.begin(), buf1, buf2, buf3, buf4); -#endif - s->flags |= on; - s->flags &= ~off; -} - -void Parser::rem_flags(flag_t off, State * s) -{ -#ifdef RYML_DBG - char buf1_[64], buf2_[64], buf3_[64]; - csubstr buf1 = _prfl(buf1_, off); - csubstr buf2 = _prfl(buf2_, s->flags); - csubstr buf3 = _prfl(buf3_, s->flags&(~off)); - _c4dbgpf("state[{}]: removing flags {}: before={} after={}", s-m_stack.begin(), buf1, buf2, buf3); -#endif - s->flags &= ~off; -} - -//----------------------------------------------------------------------------- - -csubstr Parser::_prfl(substr buf, flag_t flags) -{ - size_t pos = 0; - bool gotone = false; - - #define _prflag(fl) \ - if((flags & fl) == (fl)) \ - { \ - if(gotone) \ - { \ - if(pos + 1 < buf.len) \ - buf[pos] = '|'; \ - ++pos; \ - } \ - csubstr fltxt = #fl; \ - if(pos + fltxt.len <= buf.len) \ - memcpy(buf.str + pos, fltxt.str, fltxt.len); \ - pos += fltxt.len; \ - gotone = true; \ - } - - _prflag(RTOP); - _prflag(RUNK); - _prflag(RMAP); - _prflag(RSEQ); - _prflag(FLOW); - _prflag(QMRK); - _prflag(RKEY); - _prflag(RVAL); - _prflag(RNXT); - _prflag(SSCL); - _prflag(QSCL); - _prflag(RSET); - _prflag(NDOC); - _prflag(RSEQIMAP); - - #undef _prflag - - RYML_ASSERT(pos <= buf.len); - - return buf.first(pos); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -void Parser::_grow_filter_arena(size_t num_characters_needed) -{ - _c4dbgpf("grow: arena={} numchars={}", m_filter_arena.len, num_characters_needed); - if(num_characters_needed <= m_filter_arena.len) - return; - size_t sz = m_filter_arena.len << 1; - _c4dbgpf("grow: sz={}", sz); - sz = num_characters_needed > sz ? num_characters_needed : sz; - _c4dbgpf("grow: sz={}", sz); - sz = sz < 128u ? 128u : sz; - _c4dbgpf("grow: sz={}", sz); - _RYML_CB_ASSERT(m_stack.m_callbacks, sz >= num_characters_needed); - _resize_filter_arena(sz); -} - -void Parser::_resize_filter_arena(size_t num_characters) -{ - if(num_characters > m_filter_arena.len) - { - _c4dbgpf("resize: sz={}", num_characters); - char *prev = m_filter_arena.str; - if(m_filter_arena.str) - { - _RYML_CB_ASSERT(m_stack.m_callbacks, m_filter_arena.len > 0); - _RYML_CB_FREE(m_stack.m_callbacks, m_filter_arena.str, char, m_filter_arena.len); - } - m_filter_arena.str = _RYML_CB_ALLOC_HINT(m_stack.m_callbacks, char, num_characters, prev); - m_filter_arena.len = num_characters; - } -} - -substr Parser::_finish_filter_arena(substr dst, size_t pos) -{ - _RYML_CB_ASSERT(m_stack.m_callbacks, pos <= m_filter_arena.len); - _RYML_CB_ASSERT(m_stack.m_callbacks, pos <= dst.len); - memcpy(dst.str, m_filter_arena.str, pos); - return dst.first(pos); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -csubstr Parser::location_contents(Location const& loc) const -{ - _RYML_CB_ASSERT(m_stack.m_callbacks, loc.offset < m_buf.len); - return m_buf.sub(loc.offset); -} - -Location Parser::location(ConstNodeRef node) const -{ - _RYML_CB_ASSERT(m_stack.m_callbacks, node.valid()); - return location(*node.tree(), node.id()); -} - -Location Parser::location(Tree const& tree, size_t node) const -{ - // try hard to avoid getting the location from a null string. - Location loc; - if(_location_from_node(tree, node, &loc, 0)) - return loc; - return val_location(m_buf.str); -} - -bool Parser::_location_from_node(Tree const& tree, size_t node, Location *C4_RESTRICT loc, size_t level) const -{ - if(tree.has_key(node)) - { - csubstr k = tree.key(node); - if(C4_LIKELY(k.str != nullptr)) - { - _RYML_CB_ASSERT(m_stack.m_callbacks, k.is_sub(m_buf)); - _RYML_CB_ASSERT(m_stack.m_callbacks, m_buf.is_super(k)); - *loc = val_location(k.str); - return true; - } - } - - if(tree.has_val(node)) - { - csubstr v = tree.val(node); - if(C4_LIKELY(v.str != nullptr)) - { - _RYML_CB_ASSERT(m_stack.m_callbacks, v.is_sub(m_buf)); - _RYML_CB_ASSERT(m_stack.m_callbacks, m_buf.is_super(v)); - *loc = val_location(v.str); - return true; - } - } - - if(tree.is_container(node)) - { - if(_location_from_cont(tree, node, loc)) - return true; - } - - if(tree.type(node) != NOTYPE && level == 0) - { - // try the prev sibling - { - const size_t prev = tree.prev_sibling(node); - if(prev != NONE) - { - if(_location_from_node(tree, prev, loc, level+1)) - return true; - } - } - // try the next sibling - { - const size_t next = tree.next_sibling(node); - if(next != NONE) - { - if(_location_from_node(tree, next, loc, level+1)) - return true; - } - } - // try the parent - { - const size_t parent = tree.parent(node); - if(parent != NONE) - { - if(_location_from_node(tree, parent, loc, level+1)) - return true; - } - } - } - - return false; -} - -bool Parser::_location_from_cont(Tree const& tree, size_t node, Location *C4_RESTRICT loc) const -{ - _RYML_CB_ASSERT(m_stack.m_callbacks, tree.is_container(node)); - if(!tree.is_stream(node)) - { - const char *node_start = tree._p(node)->m_val.scalar.str; // this was stored in the container - if(tree.has_children(node)) - { - size_t child = tree.first_child(node); - if(tree.has_key(child)) - { - // when a map starts, the container was set after the key - csubstr k = tree.key(child); - if(k.str && node_start > k.str) - node_start = k.str; - } - } - *loc = val_location(node_start); - return true; - } - else // it's a stream - { - *loc = val_location(m_buf.str); // just return the front of the buffer - } - return true; -} - - -Location Parser::val_location(const char *val) const -{ - if(C4_UNLIKELY(val == nullptr)) - return {m_file, 0, 0, 0}; - - _RYML_CB_CHECK(m_stack.m_callbacks, m_options.locations()); - // NOTE: if any of these checks fails, the parser needs to be - // instantiated with locations enabled. - _RYML_CB_ASSERT(m_stack.m_callbacks, m_buf.str == m_newline_offsets_buf.str); - _RYML_CB_ASSERT(m_stack.m_callbacks, m_buf.len == m_newline_offsets_buf.len); - _RYML_CB_ASSERT(m_stack.m_callbacks, m_options.locations()); - _RYML_CB_ASSERT(m_stack.m_callbacks, !_locations_dirty()); - _RYML_CB_ASSERT(m_stack.m_callbacks, m_newline_offsets != nullptr); - _RYML_CB_ASSERT(m_stack.m_callbacks, m_newline_offsets_size > 0); - // NOTE: the pointer needs to belong to the buffer that was used to parse. - csubstr src = m_buf; - _RYML_CB_CHECK(m_stack.m_callbacks, val != nullptr || src.str == nullptr); - _RYML_CB_CHECK(m_stack.m_callbacks, (val >= src.begin() && val <= src.end()) || (src.str == nullptr && val == nullptr)); - // ok. search the first stored newline after the given ptr - using lineptr_type = size_t const* C4_RESTRICT; - lineptr_type lineptr = nullptr; - size_t offset = (size_t)(val - src.begin()); - if(m_newline_offsets_size < 30) // TODO magic number - { - // just do a linear search if the size is small. - for(lineptr_type curr = m_newline_offsets, last = m_newline_offsets + m_newline_offsets_size; curr < last; ++curr) - { - if(*curr > offset) - { - lineptr = curr; - break; - } - } - } - else - { - // do a bisection search if the size is not small. - // - // We could use std::lower_bound but this is simple enough and - // spares the include of . - size_t count = m_newline_offsets_size; - size_t step; - lineptr_type it; - lineptr = m_newline_offsets; - while(count) - { - step = count >> 1; - it = lineptr + step; - if(*it < offset) - { - lineptr = ++it; - count -= step + 1; - } - else - { - count = step; - } - } - } - _RYML_CB_ASSERT(m_stack.m_callbacks, lineptr >= m_newline_offsets); - _RYML_CB_ASSERT(m_stack.m_callbacks, lineptr <= m_newline_offsets + m_newline_offsets_size); - _RYML_CB_ASSERT(m_stack.m_callbacks, *lineptr > offset); - Location loc; - loc.name = m_file; - loc.offset = offset; - loc.line = (size_t)(lineptr - m_newline_offsets); - if(lineptr > m_newline_offsets) - loc.col = (offset - *(lineptr-1) - 1u); - else - loc.col = offset; - return loc; -} - -void Parser::_prepare_locations() -{ - m_newline_offsets_buf = m_buf; - size_t numnewlines = 1u + m_buf.count('\n'); - _resize_locations(numnewlines); - m_newline_offsets_size = 0; - for(size_t i = 0; i < m_buf.len; i++) - if(m_buf[i] == '\n') - m_newline_offsets[m_newline_offsets_size++] = i; - m_newline_offsets[m_newline_offsets_size++] = m_buf.len; - _RYML_CB_ASSERT(m_stack.m_callbacks, m_newline_offsets_size == numnewlines); -} - -void Parser::_resize_locations(size_t numnewlines) -{ - if(numnewlines > m_newline_offsets_capacity) - { - if(m_newline_offsets) - _RYML_CB_FREE(m_stack.m_callbacks, m_newline_offsets, size_t, m_newline_offsets_capacity); - m_newline_offsets = _RYML_CB_ALLOC_HINT(m_stack.m_callbacks, size_t, numnewlines, m_newline_offsets); - m_newline_offsets_capacity = numnewlines; - } -} - -bool Parser::_locations_dirty() const -{ - return !m_newline_offsets_size; -} - -} // namespace yml -} // namespace c4 - - -#if defined(_MSC_VER) -# pragma warning(pop) -#elif defined(__clang__) -# pragma clang diagnostic pop -#elif defined(__GNUC__) -# pragma GCC diagnostic pop -#endif - -#endif /* RYML_SINGLE_HDR_DEFINE_NOW */ - - -// (end https://github.com/biojppm/rapidyaml/src/c4/yml/parse.cpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/yml/node.cpp -// https://github.com/biojppm/rapidyaml/src/c4/yml/node.cpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifdef RYML_SINGLE_HDR_DEFINE_NOW -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/yml/node.hpp -//#include "c4/yml/node.hpp" -#if !defined(C4_YML_NODE_HPP_) && !defined(_C4_YML_NODE_HPP_) -#error "amalgamate: file c4/yml/node.hpp must have been included at this point" -#endif /* C4_YML_NODE_HPP_ */ - - -namespace c4 { -namespace yml { - - - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -size_t NodeRef::set_key_serialized(c4::fmt::const_base64_wrapper w) -{ - _apply_seed(); - csubstr encoded = this->to_arena(w); - this->set_key(encoded); - return encoded.len; -} - -size_t NodeRef::set_val_serialized(c4::fmt::const_base64_wrapper w) -{ - _apply_seed(); - csubstr encoded = this->to_arena(w); - this->set_val(encoded); - return encoded.len; -} - -} // namespace yml -} // namespace c4 - -#endif /* RYML_SINGLE_HDR_DEFINE_NOW */ - - -// (end https://github.com/biojppm/rapidyaml/src/c4/yml/node.cpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/yml/preprocess.hpp -// https://github.com/biojppm/rapidyaml/src/c4/yml/preprocess.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_YML_PREPROCESS_HPP_ -#define _C4_YML_PREPROCESS_HPP_ - -/** @file preprocess.hpp Functions for preprocessing YAML prior to parsing. */ - -/** @defgroup Preprocessors Preprocessor functions - * - * These are the existing preprocessors: - * - * @code{.cpp} - * size_t preprocess_json(csubstr json, substr buf) - * size_t preprocess_rxmap(csubstr json, substr buf) - * @endcode - */ - -#ifndef _C4_YML_COMMON_HPP_ -//included above: -//#include "./common.hpp" -#endif -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/substr.hpp -//#include -#if !defined(C4_SUBSTR_HPP_) && !defined(_C4_SUBSTR_HPP_) -#error "amalgamate: file c4/substr.hpp must have been included at this point" -#endif /* C4_SUBSTR_HPP_ */ - - - -namespace c4 { -namespace yml { - -namespace detail { -using Preprocessor = size_t(csubstr, substr); -template -substr preprocess_into_container(csubstr input, CharContainer *out) -{ - // try to write once. the preprocessor will stop writing at the end of - // the container, but will process all the input to determine the - // required container size. - size_t sz = PP(input, to_substr(*out)); - // if the container size is not enough, resize, and run again in the - // resized container - if(sz > out->size()) - { - out->resize(sz); - sz = PP(input, to_substr(*out)); - } - return to_substr(*out).first(sz); -} -} // namespace detail - - -//----------------------------------------------------------------------------- - -/** @name preprocess_rxmap - * Convert flow-type relaxed maps (with implicit bools) into strict YAML - * flow map. - * - * @code{.yaml} - * {a, b, c, d: [e, f], g: {a, b}} - * # is converted into this: - * {a: 1, b: 1, c: 1, d: [e, f], g: {a, b}} - * @endcode - - * @note this is NOT recursive - conversion happens only in the top-level map - * @param rxmap A relaxed map - * @param buf output buffer - * @param out output container - */ - -//@{ - -/** Write into a given output buffer. This function is safe to call with - * empty or small buffers; it won't write beyond the end of the buffer. - * - * @return the number of characters required for output - */ -RYML_EXPORT size_t preprocess_rxmap(csubstr rxmap, substr buf); - - -/** Write into an existing container. It is resized to contained the output. - * @return a substr of the container - * @overload preprocess_rxmap */ -template -substr preprocess_rxmap(csubstr rxmap, CharContainer *out) -{ - return detail::preprocess_into_container(rxmap, out); -} - - -/** Create a container with the result. - * @overload preprocess_rxmap */ -template -CharContainer preprocess_rxmap(csubstr rxmap) -{ - CharContainer out; - preprocess_rxmap(rxmap, &out); - return out; -} - -//@} - -} // namespace yml -} // namespace c4 - -#endif /* _C4_YML_PREPROCESS_HPP_ */ - - -// (end https://github.com/biojppm/rapidyaml/src/c4/yml/preprocess.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/yml/preprocess.cpp -// https://github.com/biojppm/rapidyaml/src/c4/yml/preprocess.cpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifdef RYML_SINGLE_HDR_DEFINE_NOW -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/yml/preprocess.hpp -//#include "c4/yml/preprocess.hpp" -#if !defined(C4_YML_PREPROCESS_HPP_) && !defined(_C4_YML_PREPROCESS_HPP_) -#error "amalgamate: file c4/yml/preprocess.hpp must have been included at this point" -#endif /* C4_YML_PREPROCESS_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/yml/detail/parser_dbg.hpp -//#include "c4/yml/detail/parser_dbg.hpp" -#if !defined(C4_YML_DETAIL_PARSER_DBG_HPP_) && !defined(_C4_YML_DETAIL_PARSER_DBG_HPP_) -#error "amalgamate: file c4/yml/detail/parser_dbg.hpp must have been included at this point" -#endif /* C4_YML_DETAIL_PARSER_DBG_HPP_ */ - - -/** @file preprocess.hpp Functions for preprocessing YAML prior to parsing. */ - -namespace c4 { -namespace yml { - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -namespace { -C4_ALWAYS_INLINE bool _is_idchar(char c) -{ - return (c >= 'a' && c <= 'z') - || (c >= 'A' && c <= 'Z') - || (c >= '0' && c <= '9') - || (c == '_' || c == '-' || c == '~' || c == '$'); -} - -typedef enum { kReadPending = 0, kKeyPending = 1, kValPending = 2 } _ppstate; -C4_ALWAYS_INLINE _ppstate _next(_ppstate s) -{ - int n = (int)s + 1; - return (_ppstate)(n <= (int)kValPending ? n : 0); -} -} // empty namespace - - -//----------------------------------------------------------------------------- - -size_t preprocess_rxmap(csubstr s, substr buf) -{ - detail::_SubstrWriter writer(buf); - _ppstate state = kReadPending; - size_t last = 0; - - if(s.begins_with('{')) - { - RYML_CHECK(s.ends_with('}')); - s = s.offs(1, 1); - } - - writer.append('{'); - - for(size_t i = 0; i < s.len; ++i) - { - const char curr = s[i]; - const char next = i+1 < s.len ? s[i+1] : '\0'; - - if(curr == '\'' || curr == '"') - { - csubstr ss = s.sub(i).pair_range_esc(curr, '\\'); - i += static_cast(ss.end() - (s.str + i)); - state = _next(state); - } - else if(state == kReadPending && _is_idchar(curr)) - { - state = _next(state); - } - - switch(state) - { - case kKeyPending: - { - if(curr == ':' && next == ' ') - { - state = _next(state); - } - else if(curr == ',' && next == ' ') - { - writer.append(s.range(last, i)); - writer.append(": 1, "); - last = i + 2; - } - break; - } - case kValPending: - { - if(curr == '[' || curr == '{' || curr == '(') - { - csubstr ss = s.sub(i).pair_range_nested(curr, '\\'); - i += static_cast(ss.end() - (s.str + i)); - state = _next(state); - } - else if(curr == ',' && next == ' ') - { - state = _next(state); - } - break; - } - default: - // nothing to do - break; - } - } - - writer.append(s.sub(last)); - if(state == kKeyPending) - writer.append(": 1"); - writer.append('}'); - - return writer.pos; -} - - -} // namespace yml -} // namespace c4 - -#endif /* RYML_SINGLE_HDR_DEFINE_NOW */ - - -// (end https://github.com/biojppm/rapidyaml/src/c4/yml/preprocess.cpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/yml/detail/checks.hpp -// https://github.com/biojppm/rapidyaml/src/c4/yml/detail/checks.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef C4_YML_DETAIL_CHECKS_HPP_ -#define C4_YML_DETAIL_CHECKS_HPP_ - -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/yml/tree.hpp -//#include "c4/yml/tree.hpp" -#if !defined(C4_YML_TREE_HPP_) && !defined(_C4_YML_TREE_HPP_) -#error "amalgamate: file c4/yml/tree.hpp must have been included at this point" -#endif /* C4_YML_TREE_HPP_ */ - - -#ifdef __clang__ -# pragma clang diagnostic push -#elif defined(__GNUC__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wtype-limits" // error: comparison of unsigned expression >= 0 is always true -#elif defined(_MSC_VER) -# pragma warning(push) -# pragma warning(disable: 4296/*expression is always 'boolean_value'*/) -#endif - -namespace c4 { -namespace yml { - - -void check_invariants(Tree const& t, size_t node=NONE); -void check_free_list(Tree const& t); -void check_arena(Tree const& t); - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -inline void check_invariants(Tree const& t, size_t node) -{ - if(node == NONE) - { - if(t.size() == 0) return; - node = t.root_id(); - } - - auto const& n = *t._p(node); -#ifdef RYML_DBG - if(n.m_first_child != NONE || n.m_last_child != NONE) - { - printf("check(%zu): fc=%zu lc=%zu\n", node, n.m_first_child, n.m_last_child); - } - else - { - printf("check(%zu)\n", node); - } -#endif - - C4_CHECK(n.m_parent != node); - if(n.m_parent == NONE) - { - C4_CHECK(t.is_root(node)); - } - else //if(n.m_parent != NONE) - { - C4_CHECK(t.has_child(n.m_parent, node)); - - auto const& p = *t._p(n.m_parent); - if(n.m_prev_sibling == NONE) - { - C4_CHECK(p.m_first_child == node); - C4_CHECK(t.first_sibling(node) == node); - } - else - { - C4_CHECK(p.m_first_child != node); - C4_CHECK(t.first_sibling(node) != node); - } - - if(n.m_next_sibling == NONE) - { - C4_CHECK(p.m_last_child == node); - C4_CHECK(t.last_sibling(node) == node); - } - else - { - C4_CHECK(p.m_last_child != node); - C4_CHECK(t.last_sibling(node) != node); - } - } - - C4_CHECK(n.m_first_child != node); - C4_CHECK(n.m_last_child != node); - if(n.m_first_child != NONE || n.m_last_child != NONE) - { - C4_CHECK(n.m_first_child != NONE); - C4_CHECK(n.m_last_child != NONE); - } - - C4_CHECK(n.m_prev_sibling != node); - C4_CHECK(n.m_next_sibling != node); - if(n.m_prev_sibling != NONE) - { - C4_CHECK(t._p(n.m_prev_sibling)->m_next_sibling == node); - C4_CHECK(t._p(n.m_prev_sibling)->m_prev_sibling != node); - } - if(n.m_next_sibling != NONE) - { - C4_CHECK(t._p(n.m_next_sibling)->m_prev_sibling == node); - C4_CHECK(t._p(n.m_next_sibling)->m_next_sibling != node); - } - - size_t count = 0; - for(size_t i = n.m_first_child; i != NONE; i = t.next_sibling(i)) - { -#ifdef RYML_DBG - printf("check(%zu): descend to child[%zu]=%zu\n", node, count, i); -#endif - auto const& ch = *t._p(i); - C4_CHECK(ch.m_parent == node); - C4_CHECK(ch.m_next_sibling != i); - ++count; - } - C4_CHECK(count == t.num_children(node)); - - if(n.m_prev_sibling == NONE && n.m_next_sibling == NONE) - { - if(n.m_parent != NONE) - { - C4_CHECK(t.num_children(n.m_parent) == 1); - C4_CHECK(t.num_siblings(node) == 1); - } - } - - if(node == t.root_id()) - { - C4_CHECK(t.size() == t.m_size); - C4_CHECK(t.capacity() == t.m_cap); - C4_CHECK(t.m_cap == t.m_size + t.slack()); - check_free_list(t); - check_arena(t); - } - - for(size_t i = t.first_child(node); i != NONE; i = t.next_sibling(i)) - { - check_invariants(t, i); - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -inline void check_free_list(Tree const& t) -{ - if(t.m_free_head == NONE) - { - C4_CHECK(t.m_free_tail == t.m_free_head); - return; - } - - C4_CHECK(t.m_free_head >= 0 && t.m_free_head < t.m_cap); - C4_CHECK(t.m_free_tail >= 0 && t.m_free_tail < t.m_cap); - - auto const& head = *t._p(t.m_free_head); - //auto const& tail = *t._p(t.m_free_tail); - - //C4_CHECK(head.m_prev_sibling == NONE); - //C4_CHECK(tail.m_next_sibling == NONE); - - size_t count = 0; - for(size_t i = t.m_free_head, prev = NONE; i != NONE; i = t._p(i)->m_next_sibling) - { - auto const& elm = *t._p(i); - if(&elm != &head) - { - C4_CHECK(elm.m_prev_sibling == prev); - } - prev = i; - ++count; - } - C4_CHECK(count == t.slack()); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -inline void check_arena(Tree const& t) -{ - C4_CHECK(t.m_arena.len == 0 || (t.m_arena_pos >= 0 && t.m_arena_pos <= t.m_arena.len)); - C4_CHECK(t.arena_size() == t.m_arena_pos); - C4_CHECK(t.arena_slack() + t.m_arena_pos == t.m_arena.len); -} - - -} /* namespace yml */ -} /* namespace c4 */ - -#ifdef __clang__ -# pragma clang diagnostic pop -#elif defined(__GNUC__) -# pragma GCC diagnostic pop -#elif defined(_MSC_VER) -# pragma warning(pop) -#endif - -#endif /* C4_YML_DETAIL_CHECKS_HPP_ */ - - -// (end https://github.com/biojppm/rapidyaml/src/c4/yml/detail/checks.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/yml/detail/print.hpp -// https://github.com/biojppm/rapidyaml/src/c4/yml/detail/print.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef C4_YML_DETAIL_PRINT_HPP_ -#define C4_YML_DETAIL_PRINT_HPP_ - -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/yml/tree.hpp -//#include "c4/yml/tree.hpp" -#if !defined(C4_YML_TREE_HPP_) && !defined(_C4_YML_TREE_HPP_) -#error "amalgamate: file c4/yml/tree.hpp must have been included at this point" -#endif /* C4_YML_TREE_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/yml/node.hpp -//#include "c4/yml/node.hpp" -#if !defined(C4_YML_NODE_HPP_) && !defined(_C4_YML_NODE_HPP_) -#error "amalgamate: file c4/yml/node.hpp must have been included at this point" -#endif /* C4_YML_NODE_HPP_ */ - - - -namespace c4 { -namespace yml { - - -inline size_t print_node(Tree const& p, size_t node, int level, size_t count, bool print_children) -{ - printf("[%zd]%*s[%zd] %p", count, (2*level), "", node, (void*)p.get(node)); - if(p.is_root(node)) - { - printf(" [ROOT]"); - } - printf(" %s:", p.type_str(node)); - if(p.has_key(node)) - { - if(p.has_key_anchor(node)) - { - csubstr ka = p.key_anchor(node); - printf(" &%.*s", (int)ka.len, ka.str); - } - if(p.has_key_tag(node)) - { - csubstr kt = p.key_tag(node); - csubstr k = p.key(node); - printf(" %.*s '%.*s'", (int)kt.len, kt.str, (int)k.len, k.str); - } - else - { - csubstr k = p.key(node); - printf(" '%.*s'", (int)k.len, k.str); - } - } - else - { - RYML_ASSERT( ! p.has_key_tag(node)); - } - if(p.has_val(node)) - { - if(p.has_val_tag(node)) - { - csubstr vt = p.val_tag(node); - csubstr v = p.val(node); - printf(" %.*s '%.*s'", (int)vt.len, vt.str, (int)v.len, v.str); - } - else - { - csubstr v = p.val(node); - printf(" '%.*s'", (int)v.len, v.str); - } - } - else - { - if(p.has_val_tag(node)) - { - csubstr vt = p.val_tag(node); - printf(" %.*s", (int)vt.len, vt.str); - } - } - if(p.has_val_anchor(node)) - { - auto &a = p.val_anchor(node); - printf(" valanchor='&%.*s'", (int)a.len, a.str); - } - printf(" (%zd sibs)", p.num_siblings(node)); - - ++count; - - if(p.is_container(node)) - { - printf(" %zd children:\n", p.num_children(node)); - if(print_children) - { - for(size_t i = p.first_child(node); i != NONE; i = p.next_sibling(i)) - { - count = print_node(p, i, level+1, count, print_children); - } - } - } - else - { - printf("\n"); - } - - return count; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -inline void print_node(ConstNodeRef const& p, int level=0) -{ - print_node(*p.tree(), p.id(), level, 0, true); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -inline size_t print_tree(Tree const& p, size_t node=NONE) -{ - printf("--------------------------------------\n"); - size_t ret = 0; - if(!p.empty()) - { - if(node == NONE) - node = p.root_id(); - ret = print_node(p, node, 0, 0, true); - } - printf("#nodes=%zd vs #printed=%zd\n", p.size(), ret); - printf("--------------------------------------\n"); - return ret; -} - - -} /* namespace yml */ -} /* namespace c4 */ - - -#endif /* C4_YML_DETAIL_PRINT_HPP_ */ - - -// (end https://github.com/biojppm/rapidyaml/src/c4/yml/detail/print.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/c4/yml/yml.hpp -// https://github.com/biojppm/rapidyaml/src/c4/yml/yml.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _C4_YML_YML_HPP_ -#define _C4_YML_YML_HPP_ - -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/yml/tree.hpp -//#include "c4/yml/tree.hpp" -#if !defined(C4_YML_TREE_HPP_) && !defined(_C4_YML_TREE_HPP_) -#error "amalgamate: file c4/yml/tree.hpp must have been included at this point" -#endif /* C4_YML_TREE_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/yml/node.hpp -//#include "c4/yml/node.hpp" -#if !defined(C4_YML_NODE_HPP_) && !defined(_C4_YML_NODE_HPP_) -#error "amalgamate: file c4/yml/node.hpp must have been included at this point" -#endif /* C4_YML_NODE_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/yml/emit.hpp -//#include "c4/yml/emit.hpp" -#if !defined(C4_YML_EMIT_HPP_) && !defined(_C4_YML_EMIT_HPP_) -#error "amalgamate: file c4/yml/emit.hpp must have been included at this point" -#endif /* C4_YML_EMIT_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/yml/parse.hpp -//#include "c4/yml/parse.hpp" -#if !defined(C4_YML_PARSE_HPP_) && !defined(_C4_YML_PARSE_HPP_) -#error "amalgamate: file c4/yml/parse.hpp must have been included at this point" -#endif /* C4_YML_PARSE_HPP_ */ - -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/yml/preprocess.hpp -//#include "c4/yml/preprocess.hpp" -#if !defined(C4_YML_PREPROCESS_HPP_) && !defined(_C4_YML_PREPROCESS_HPP_) -#error "amalgamate: file c4/yml/preprocess.hpp must have been included at this point" -#endif /* C4_YML_PREPROCESS_HPP_ */ - - -#endif // _C4_YML_YML_HPP_ - - -// (end https://github.com/biojppm/rapidyaml/src/c4/yml/yml.hpp) - - - -//******************************************************************************** -//-------------------------------------------------------------------------------- -// src/ryml.hpp -// https://github.com/biojppm/rapidyaml/src/ryml.hpp -//-------------------------------------------------------------------------------- -//******************************************************************************** - -#ifndef _RYML_HPP_ -#define _RYML_HPP_ - -// amalgamate: removed include of -// https://github.com/biojppm/rapidyaml/src/c4/yml/yml.hpp -//#include "c4/yml/yml.hpp" -#if !defined(C4_YML_YML_HPP_) && !defined(_C4_YML_YML_HPP_) -#error "amalgamate: file c4/yml/yml.hpp must have been included at this point" -#endif /* C4_YML_YML_HPP_ */ - - -namespace ryml { -using namespace c4::yml; -using namespace c4; -} - -#endif /* _RYML_HPP_ */ - - -// (end https://github.com/biojppm/rapidyaml/src/ryml.hpp) - -#endif /* _RYML_SINGLE_HEADER_AMALGAMATED_HPP_ */ diff --git a/CMakeLists.txt b/CMakeLists.txt index 81da7f3..ed532e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,10 +6,13 @@ project("ANTLER Project Tools " VERSION 1.0.0) include( ./common.cmake REQUIRED ) -add_subdirectory( project ) -add_subdirectory( aproj ) +add_subdirectory( external ) +add_subdirectory( include ) +add_subdirectory( src ) +add_subdirectory( tools ) +option(PEDANTIC_BUILD "Pedantic builds" On) option(BUILD_TESTS "Build and run the tests." On) if(BUILD_TESTS) enable_testing() diff --git a/aproj/CMakeLists.txt b/aproj/CMakeLists.txt deleted file mode 100644 index eb29c09..0000000 --- a/aproj/CMakeLists.txt +++ /dev/null @@ -1,48 +0,0 @@ -# @copyright See `LICENSE` in the root directory of this project. - -include(FindPkgConfig) - -# Boost -find_package( Boost REQUIRED filesystem CONFIG) -pkg_check_modules(CURL libcurl REQUIRED) - -# -# These files are generally built in a common way, so here are some functions to simplify the process. -# - -function(add_aproj target_name files) - - set(extra_libs "") - if(target_name STREQUAL "aproj") - set(extra_libs "Boost::filesystem" "dl") - endif() - - add_executable(${target_name} ${files}) - set_property(TARGET ${target_name} PROPERTY CXX_STANDARD 17) - target_link_libraries(${target_name} PUBLIC antler-project ${extra_libs} ${CURL_LIBRARIES}) - target_include_directories(${target_name} PRIVATE . ) - target_include_directories(${target_name} SYSTEM PRIVATE ../3p_include ) - target_include_directories(${target_name} PRIVATE ${CURL_INCLUDE_DIRS}) - install(FILES $ COMPONENT Runtime DESTINATION .) -endfunction() - - -file(GLOB src_cpp ./*.cpp) - -#foreach(file_name IN ITEMS ${src_cpp}) -# if(${CMAKE_VERSION} VERSION_LESS "3.20") -# get_filename_component(fn ${file_name} NAME) -# else() -# cmake_path(GET file_name FILENAME fn) -# endif() -# -# string(REPLACE ".cpp" "" name ${fn}) -# add_aproj("${name}" "${fn}") -#endforeach() - -add_executable(antler-proj aproj.cpp) -set_property(TARGET antler-proj PROPERTY CXX_STANDARD 17) -target_link_libraries(antler-proj PUBLIC antler-project ${extra_libs} ${CURL_LIBRARIES}) -target_include_directories(antler-proj PRIVATE . ) -target_include_directories(antler-proj SYSTEM PRIVATE ../3p_include ) -target_include_directories(antler-proj PRIVATE ${CURL_INCLUDE_DIRS}) \ No newline at end of file diff --git a/common.cmake b/common.cmake index 193f0af..fe19dc9 100644 --- a/common.cmake +++ b/common.cmake @@ -28,26 +28,28 @@ if(CMAKE_SIZEOF_VOID_P EQUAL 8) set_property(GLOBAL PROPERTY FIND_LIBRARY_USE_LIB64_PATHS ON) endif() -set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror" ) -set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden" ) -set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility-inlines-hidden" ) -set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wfloat-equal") -set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wshadow") +if(PEDANTIC) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror" ) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden" ) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility-inlines-hidden" ) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wfloat-equal") + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wshadow") -if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") - set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-return-type-c-linkage") -endif() + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-return-type-c-linkage") + endif() -if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - # Colorize output - set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color=always" ) + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + # Colorize output + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color=always" ) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wctor-dtor-privacy -Wnon-virtual-dtor" ) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsuggest-override" ) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wredundant-decls") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wlogical-op") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wold-style-cast") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wduplicated-branches" ) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wctor-dtor-privacy -Wnon-virtual-dtor" ) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsuggest-override" ) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wredundant-decls") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wlogical-op") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wold-style-cast") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wduplicated-branches" ) + endif() endif() SET_PROPERTY(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS $<$:_DEBUG> ) diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt new file mode 100644 index 0000000..a030868 --- /dev/null +++ b/external/CMakeLists.txt @@ -0,0 +1,6 @@ +add_subdirectory(rapidyaml) + +set(MAGIC_ENUM_OPT_BUILD_EXAMPLES OFF CACHE BOOL "Skip magic_enum examples") +set(MAGIC_ENUM_OPT_BUILD_TESTS OFF CACHE BOOL "Skip magic_enum tests") +set(MAGIC_ENUM_OPT_INSTALL OFF CACHE BOOL "Skip magic_enum install") +add_subdirectory(magic_enum) \ No newline at end of file diff --git a/external/json b/external/json new file mode 160000 index 0000000..4d4d5fc --- /dev/null +++ b/external/json @@ -0,0 +1 @@ +Subproject commit 4d4d5fc14e25f4eb40dd091edb66656383d26fd6 diff --git a/external/magic_enum b/external/magic_enum new file mode 160000 index 0000000..6542403 --- /dev/null +++ b/external/magic_enum @@ -0,0 +1 @@ +Subproject commit 6542403248d46136031a6ec5e85bc103bfb1a70c diff --git a/external/rapidyaml b/external/rapidyaml new file mode 160000 index 0000000..7c0036e --- /dev/null +++ b/external/rapidyaml @@ -0,0 +1 @@ +Subproject commit 7c0036e7c41318ec75368f9a2b2148c31b3121db diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt new file mode 100644 index 0000000..6669144 --- /dev/null +++ b/include/CMakeLists.txt @@ -0,0 +1,2 @@ +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/antler/system/version.hpp.in + ${CMAKE_CURRENT_BINARY_DIR}/antler/system/version.hpp) \ No newline at end of file diff --git a/project/antler/project/dependency.hpp b/include/antler/project/dependency.hpp similarity index 99% rename from project/antler/project/dependency.hpp rename to include/antler/project/dependency.hpp index 9c80c4d..4c34180 100644 --- a/project/antler/project/dependency.hpp +++ b/include/antler/project/dependency.hpp @@ -10,7 +10,7 @@ #include // std::pair #include -#include +#include "version.hpp" namespace antler::project { diff --git a/project/antler/project/location.hpp b/include/antler/project/location.hpp similarity index 95% rename from project/antler/project/location.hpp rename to include/antler/project/location.hpp index e7fd852..1be3929 100644 --- a/project/antler/project/location.hpp +++ b/include/antler/project/location.hpp @@ -18,7 +18,7 @@ namespace antler::project::location { [[nodiscard]] bool is_github_archive(std::string_view l); /// @param l Location to evaluate. /// @return true if l looks like a local file. -[[nodiscard]] bool is_local_file(std::string_view l); +[[nodiscard]] bool is_local(std::string_view l); /// @note requires `gh` is installed and user is authenticated or the repo is public. /// @param l Location to evaluate. /// @return true if l is a repo, calls `gh` to test. diff --git a/project/antler/project/object.hpp b/include/antler/project/object.hpp similarity index 98% rename from project/antler/project/object.hpp rename to include/antler/project/object.hpp index 4ec6b20..2855eeb 100644 --- a/project/antler/project/object.hpp +++ b/include/antler/project/object.hpp @@ -4,8 +4,7 @@ #include #include -#include -#include +#include "dependency.hpp" #include #include diff --git a/project/antler/project/project.hpp b/include/antler/project/project.hpp similarity index 89% rename from project/antler/project/project.hpp rename to include/antler/project/project.hpp index fcc4b74..04de703 100644 --- a/project/antler/project/project.hpp +++ b/include/antler/project/project.hpp @@ -2,25 +2,17 @@ /// @copyright See `LICENSE` in the root directory of this project. -#include -#include +#include "object.hpp" +#include "version.hpp" #include #include #include - namespace antler::project { /// This class represents the contents of a `project.yaml` file. Functions exist to encode and decode from a `project.yaml` file. class project { -public: - /// How to populate enum - this enum indicates how a project directory should be populated. - enum class pop { - force_replace, ///< Overwrite any existing files, even the ones the user marked do not change. - honor_deltas, ///< Honor do not change markers. - // merge_deltas, - }; public: // parse a project from a project.yml @@ -121,10 +113,10 @@ class project { bool sync(std::ostream& error_stream = std::cerr) noexcept; /// Populate the directory by generating files. - /// @param action_type The type of population action to perform. + /// @param replace The should replace during population. /// @param error_stream The stream to print failure reports to. /// @return true for success; false for failure. - [[nodiscard]] bool populate(pop action_type, std::ostream& error_stream = std::cerr) noexcept; + [[nodiscard]] bool populate(bool replace = true, std::ostream& error_stream = std::cerr) noexcept; /// Factory function. @@ -150,11 +142,6 @@ class project { /// @return true if the project file was found and is a regular file; otherwise, false. [[nodiscard]] static bool update_path(std::filesystem::path& path) noexcept; - /// Print the pop enum. - /// @param os The output stream to print to. - /// @param e The pop enum to print. - static void print(std::ostream& os, pop e) noexcept; - private: std::filesystem::path m_path; ///< path to the project.yaml file. std::string m_name; ///< The project name. @@ -167,5 +154,4 @@ class project { } // namespace antler::project -inline std::ostream& operator<<(std::ostream& os, const antler::project::project& o) { o.print(os); return os; } -inline std::ostream& operator<<(std::ostream& os, const antler::project::project::pop& e) { antler::project::project::print(os,e); return os; } +inline std::ostream& operator<<(std::ostream& os, const antler::project::project& o) { o.print(os); return os; } \ No newline at end of file diff --git a/project/antler/project/semver.hpp b/include/antler/project/semver.hpp similarity index 98% rename from project/antler/project/semver.hpp rename to include/antler/project/semver.hpp index b31cbd9..3899ea4 100644 --- a/project/antler/project/semver.hpp +++ b/include/antler/project/semver.hpp @@ -7,7 +7,7 @@ #include #include -#include +#include "version_compare.hpp" namespace antler::project { diff --git a/project/antler/project/version.hpp b/include/antler/project/version.hpp similarity index 92% rename from project/antler/project/version.hpp rename to include/antler/project/version.hpp index f4d898f..8699273 100644 --- a/project/antler/project/version.hpp +++ b/include/antler/project/version.hpp @@ -2,7 +2,7 @@ /// @copyright See `LICENSE` in the root directory of this project. -#include +#include "semver.hpp" #include #include @@ -63,11 +63,12 @@ class version { /// @return The version in semver_t format. If is_semver() would return false, this value is invalid. explicit operator semver() const noexcept; -private: /// compare the string value of this to rhs. Attempt to use semver rules. /// @param rhs The version to compare to. /// @return the result of the comparison: eq, lt, gt. - [[nodiscard]] static int64_t raw_compare(std::string_view l_in, std::string_view r_in) noexcept; + [[nodiscard]] int64_t raw_compare(std::string_view o) const noexcept; + +private: /// Load this version from a string. Attempts to parse and store as semver in the process. Either way, s is stored as m_raw. /// @param s The string to store. @@ -84,5 +85,4 @@ class version { } // namespace antler::project -inline std::ostream& operator<<(std::ostream& os, const antler::project::version& o) { os << o.raw(); return os; } -//std::istream& operator>>(std::istream& is, antler::project::object::version& e); +inline std::ostream& operator<<(std::ostream& os, const antler::project::version& o) { os << o.raw(); return os; } \ No newline at end of file diff --git a/project/antler/project/version_compare.hpp b/include/antler/project/version_compare.hpp similarity index 99% rename from project/antler/project/version_compare.hpp rename to include/antler/project/version_compare.hpp index 02bd595..b21f17a 100644 --- a/project/antler/project/version_compare.hpp +++ b/include/antler/project/version_compare.hpp @@ -12,5 +12,4 @@ namespace antler::project { /// @return Follows standard comparison. int64_t raw_compare(std::string_view lhs, std::string_view rhs) noexcept; - } // namespace antler::project diff --git a/project/antler/project/version_constraint.hpp b/include/antler/project/version_constraint.hpp similarity index 98% rename from project/antler/project/version_constraint.hpp rename to include/antler/project/version_constraint.hpp index b438d11..a4b1790 100644 --- a/project/antler/project/version_constraint.hpp +++ b/include/antler/project/version_constraint.hpp @@ -2,7 +2,7 @@ /// @copyright See `LICENSE` in the root directory of this project. -#include +#include "version.hpp" #include #include diff --git a/project/antler/string/from.hpp b/include/antler/string/from.hpp similarity index 100% rename from project/antler/string/from.hpp rename to include/antler/string/from.hpp diff --git a/project/antler/system/exec.hpp b/include/antler/system/exec.hpp similarity index 100% rename from project/antler/system/exec.hpp rename to include/antler/system/exec.hpp diff --git a/include/antler/system/preprocessor.hpp b/include/antler/system/preprocessor.hpp new file mode 100644 index 0000000..f743396 --- /dev/null +++ b/include/antler/system/preprocessor.hpp @@ -0,0 +1,9 @@ +#pragma once + +/* clang-format off */ + +#define ANTLER_PROJ_CREATE_ENUM(N, A, E) N, +#define ANTLER_PROJ_CREATE_STRINGS(N, A, E) #N, +#define ANTLER_PROJ_CREATE_MAP(N, A, E) {#N, E::N}, + +/* clang-format on */ \ No newline at end of file diff --git a/project/antler/system/version.hpp.in b/include/antler/system/version.hpp.in similarity index 100% rename from project/antler/system/version.hpp.in rename to include/antler/system/version.hpp.in diff --git a/project/CMakeLists.txt b/project/CMakeLists.txt deleted file mode 100644 index 75bdfba..0000000 --- a/project/CMakeLists.txt +++ /dev/null @@ -1,43 +0,0 @@ -# @copyright See `LICENSE` in the root directory of this project. - -set( target_name antler-project ) - -include(FindPkgConfig) - -# Boost -find_package( Boost REQUIRED ) -pkg_check_modules( CURL libcurl REQUIRED ) - -# I know. But it's appropriate to glob for now. -file(GLOB_RECURSE src_cpp ./src/*.cpp) -file(GLOB_RECURSE src_h ./src/*.h ./src/*.hpp) -file(GLOB inc_h ./antler/project/*.h ./antler/project/*.hpp ./antler/project/*.ipp) - -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/antler/system/version.hpp.in - ${CMAKE_CURRENT_BINARY_DIR}/antler/system/version.hpp) - -add_library(${target_name} ${src_cpp} ${src_h} ${inc_h}) -set_property(TARGET ${target_name} PROPERTY CXX_STANDARD 17) -target_compile_definitions(${target_name} PRIVATE - BUILDING_AP_PROJ -) -target_include_directories(${target_name} PRIVATE - src -) -target_include_directories(${target_name} SYSTEM PRIVATE - ../3p_include -) -target_include_directories(${target_name} PUBLIC - . - ${CMAKE_CURRENT_BINARY_DIR} - ${CURL_INCLUDE_DIRS} -) -target_link_libraries(${target_name} PUBLIC - ${CURL_LIBRARIES} -) - -# Install commands -install( - FILES ${inc_h} - DESTINATION antler/project COMPONENT Development -) diff --git a/project/antler/project/language.hpp b/project/antler/project/language.hpp deleted file mode 100644 index 0ebe3c7..0000000 --- a/project/antler/project/language.hpp +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once - -/// @copyright See `LICENSE` in the root directory of this project. - - -#include -#include -#include -#include - - -namespace antler::project { - -/// enum class to decode and store the language type. -enum class language { - none, - c, - cpp, - java, -}; - -/// Convert a string to a language enum value. -/// @param s The string to evaluate. -/// @return The language equivalent from the string. Will be none on failure to decode. -[[nodiscard]] language to_language(std::string_view s); - -/// Convert a language enum into a string. -/// @param e The enum to print and return. -/// @return The string repersentation of e. -[[nodiscard]] std::string to_string(language e); - -/// Convert a language enum into a string. -/// @param e The enum to print and return. -/// @return The string repersentation of e. -[[nodiscard]] std::string_view to_string_view(language e); - -/// This is a convinience for the yaml encoder/decoder. -/// array -constexpr auto language_literals = magic_enum::enum_names(); - - -} // namespace antler::project - - -std::ostream& operator<<(std::ostream& os, const antler::project::language& e); -std::istream& operator>>(std::istream& is, antler::project::language& e); - -namespace std { -[[nodiscard]] inline std::string to_string(antler::project::language e) { return antler::project::to_string(e); }; -} // namespace std diff --git a/project/src/key.cpp b/project/src/key.cpp deleted file mode 100644 index 44427c0..0000000 --- a/project/src/key.cpp +++ /dev/null @@ -1,37 +0,0 @@ -/// @copyright See `LICENSE` in the root directory of this project. - -#include -#include - -namespace key { - - -std::string to_string(word e) { - return std::string(magic_enum::enum_name(e)); -} - - -std::string_view to_string_view(word e) { - return magic_enum::enum_name(e); -} - - -// template -// word to_word(STRING_TYPE s) { -word to_word(std::string_view s) { - // Try string as is. - auto opt = magic_enum::enum_cast(s); - if (opt.has_value()) - return opt.value(); - - return key::word::none; -} - - -} // namespace key - - -std::ostream& operator<<(std::ostream& os, const key::word& e) { - os << magic_enum::enum_name(e); - return os; -} diff --git a/project/src/key.hpp b/project/src/key.hpp deleted file mode 100644 index f83be9c..0000000 --- a/project/src/key.hpp +++ /dev/null @@ -1,67 +0,0 @@ -#pragma once - -/// @copyright See `LICENSE` in the root directory of this project. - -#include -#include -#include -#include - -#include - -#include - - -namespace key { - -// These are the valid keywords for the `project.yaml` file. -enum class word { - none, - - apps, - command, - depends, - from, - hash, // Deps: indicates the SHA256 hash for an archive. Valid for github release and archive locations. - lang, - libraries, - name, - options, - patch, - project, - release, // Deps: this indicates a github version to download. - tag, // Deps: indicates the github tag to clone. - tests, - version, // Deps: version is a synonym for release. -}; - -// template -// word to_word(STRING_TYPE s); - -/// Convert a string into a word. `none` is returned for a failure to parse. -/// @param s The string to parse. -/// @return s as a key word, none if s could not be parsed. -[[nodiscard]] word to_word(std::string_view s); - -/// Convert e to a string. -/// @param e The word to convert to a string. -/// @return String representation of e. -[[nodiscard]] std::string to_string(word e); - -/// Convert e to a string. -/// @param e The word to convert to a string. -/// @return String representation of e. -[[nodiscard]] std::string_view to_string_view(word e); - -/// This is a convinience for the yaml encoder/decoder. -/// array -constexpr auto literals = magic_enum::enum_names(); - -} // namespace key - - -std::ostream& operator<<(std::ostream& os, const key::word& e); - -namespace std { -[[nodiscard]] inline std::string to_string(key::word e) { return key::to_string(e); }; -} // namespace std diff --git a/project/src/language.cpp b/project/src/language.cpp deleted file mode 100644 index 31846d8..0000000 --- a/project/src/language.cpp +++ /dev/null @@ -1,63 +0,0 @@ -/// @copyright See `LICENSE` in the root directory of this project. - -#include // std::transform -#include - - -namespace antler::project { - - -language to_language(std::string_view s) { - - // Try string as is. - auto lang = magic_enum::enum_cast(s); - if (lang.has_value()) - return lang.value(); - - // Then try again after downcasing. - std::string dc{s}; - std::transform(dc.cbegin(), dc.cend(), dc.begin(), [](unsigned char c) { return std::tolower(c); }); - - lang = magic_enum::enum_cast(s); - if (lang.has_value()) - return lang.value(); - - // Unable to find a matching string thus far, we now try some aliases before returning none. - if (dc == "c++") - return antler::project::language::cpp; - - // All options are exhausted, return none. - return antler::project::language::none; -} - - -std::string to_string(language e) { - return std::string(magic_enum::enum_name(e)); -} - - -std::string_view to_string_view(language e) { - return magic_enum::enum_name(e); -} - - -} // namespace antler::project - - -std::ostream& operator<<(std::ostream& os, const antler::project::language& e) { - os << to_string(e); - return os; -} - - -std::istream& operator>>(std::istream& is, antler::project::language& e) { - - std::string temp; - if (is >> temp) - e = antler::project::to_language(temp); - else { - // This might be an exceptional state and so maybe we should throw an exception? - e = antler::project::language::none; - } - return is; -} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..55e7305 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,23 @@ +find_package( Boost REQUIRED COMPONENTS system) +set(Boost_USE_STATIC_LIBS ON) + +add_library(antler-project + cmake.cpp + dependency.cpp + exec.cpp + location.cpp + object.cpp + project-is_valid.cpp + project-parse.cpp + project-populate.cpp + project-print.cpp + project.cpp + semver.cpp + version_compare.cpp + version_constraint.cpp + version.cpp +) + +target_include_directories(antler-project PUBLIC ../include ../external/json/ ${CMAKE_CURRENT_BINARY_DIR}/../include) +target_link_libraries(antler-project PUBLIC magic_enum ryml Boost::system ) +set_property(TARGET antler-project PROPERTY CXX_STANDARD 17) \ No newline at end of file diff --git a/project/src/cmake.cpp b/src/cmake.cpp similarity index 98% rename from project/src/cmake.cpp rename to src/cmake.cpp index 2696363..409cff2 100644 --- a/project/src/cmake.cpp +++ b/src/cmake.cpp @@ -1,8 +1,9 @@ /// @copyright See `LICENSE` in the root directory of this project. -#include #include +#include "cmake.hpp" + namespace antler::cmake { std::string add_subdirectory(const std::filesystem::path& path) noexcept { diff --git a/project/src/cmake.hpp b/src/cmake.hpp similarity index 100% rename from project/src/cmake.hpp rename to src/cmake.hpp diff --git a/project/src/dependency.cpp b/src/dependency.cpp similarity index 87% rename from project/src/dependency.cpp rename to src/dependency.cpp index 7f8ecec..e831e5a 100644 --- a/project/src/dependency.cpp +++ b/src/dependency.cpp @@ -142,12 +142,17 @@ void dependency::tag(std::string_view s) noexcept { } bool dependency::is_valid() const noexcept { - if (validate_location(m_loc)) { - //if (validate_location) - return true; - } else { - return false; + if (!m_tag_or_commit.empty()) { + if (!m_rel.empty()) { + std::cerr << "release AND tag/commit flags are not valid at the same time for location."; + return false; + } + if (!m_hash.empty()) { + std::cerr << "hash AND tag/commit flags are not valid at the same time for location."; + return false; + } } + return location::is_reachable(m_loc); } bool dependency::validate_location(std::string_view s) { @@ -173,19 +178,7 @@ bool dependency::validate_location(std::string_view loc, std::string_view tag, s } } - if (location::is_archive(loc)) { - if (hash.empty()) - os << "Warning: archive locations should have a SHA256 hash."; - } else if (location::is_github_repo(loc) || location::is_github_org_repo_shorthand(loc)) { - if (rel.empty() && tag.empty()) - os << "Warning: github locations should have either a tag/commit or release field."; - } else { - os << "Unknown location type."; - return false; - } - - return true; + return location::is_reachable(loc); } - } // namespace antler::project diff --git a/project/src/exec.cpp b/src/exec.cpp similarity index 100% rename from project/src/exec.cpp rename to src/exec.cpp diff --git a/project/src/location.cpp b/src/location.cpp similarity index 93% rename from project/src/location.cpp rename to src/location.cpp index 288c11f..cc25581 100644 --- a/project/src/location.cpp +++ b/src/location.cpp @@ -64,6 +64,7 @@ struct curl { curl_easy_setopt(curl_obj, CURLOPT_WRITEDATA, &buff); auto res = curl_easy_perform(curl_obj); + std::cerr << "Failure: " << curl_easy_strerror(res) << std::endl; if (res) { std::cerr << "Failure: " << curl_easy_strerror(res) << std::endl; throw std::runtime_error("internal curl failure"); @@ -97,7 +98,6 @@ struct github { url += std::string(org) + std::string("/"); url += repo; auto s = sender.authenticated_request(bearer_token, url.c_str()); - std::cout << s << std::endl; return s; } @@ -127,7 +127,7 @@ struct github { static inline bool is_shorthand(std::string_view s) { auto sub = s.substr(0, s.find_last_of("/")); - return sub.find_last_of("/") == std::string_view::npos; + return sub.size() != s.size() && sub.find_last_of("/") == std::string_view::npos; } curl sender; @@ -150,19 +150,19 @@ bool is_github_archive(std::string_view s) { return is_github(s) && is_archive(s bool is_url(std::string_view l) { return curl::is_url(l); } -bool is_local_file(std::string_view s) { return std::filesystem::exists(s); } +bool is_local(std::string_view l) { return std::filesystem::exists(l); } bool is_github_org_repo_shorthand(std::string_view s) { return github::is_shorthand(s); } bool is_github_repo(std::string_view s) { return is_github(s) && !is_archive(s); } bool is_reachable(std::string_view l) { - if (is_archive(l) || is_url(l) || is_github_archive(l)) { + if (is_local(l)) { + return true; + } else if (is_archive(l) || is_url(l) || is_github_archive(l)) { return curl{}.is_reachable(l); } else if (is_github_repo(l) || is_github_org_repo_shorthand(l)) { return github{}.is_reachable(l); - } else if (is_local_file(l)) { - return true; } else { return false; } diff --git a/project/src/object.cpp b/src/object.cpp similarity index 100% rename from project/src/object.cpp rename to src/object.cpp diff --git a/project/src/project-is_valid.cpp b/src/project-is_valid.cpp similarity index 100% rename from project/src/project-is_valid.cpp rename to src/project-is_valid.cpp diff --git a/project/src/project-parse.cpp b/src/project-parse.cpp similarity index 69% rename from project/src/project-parse.cpp rename to src/project-parse.cpp index c64e85b..b29b57f 100644 --- a/project/src/project-parse.cpp +++ b/src/project-parse.cpp @@ -1,7 +1,8 @@ /// @copyright See `LICENSE` in the root directory of this project. #include -#include +#include "token.hpp" +//#include "key.hpp" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wold-style-cast" @@ -24,6 +25,8 @@ namespace antler::project { namespace { // anonymous +inline static std::string_view sv_from_csubstr(const c4::csubstr& s) { return {s.data(), s.size()}; } + /// Load a text file into a string. /// /// @noteThis should be optimized and stored somewhere. Something like this is a useful library function - when optimized... @@ -33,17 +36,18 @@ namespace { // anonymous /// @return An optional string that is populated with the file contents *if* the load was successful; otherwise, it's invalid for any error. [[nodiscard]] std::optional load(const std::filesystem::path& path, std::ostream& os) { - std::error_code sec; // Sanity check and determine the file size. - if (!std::filesystem::exists(path, sec)) { + if (!std::filesystem::exists(path)) { os << "Path doesn't exist: " << path << "\n"; return {}; } - if (!std::filesystem::is_regular_file(path, sec)) { + if (!std::filesystem::is_regular_file(path)) { os << "Path must be regular file: " << path << "\n"; return {}; } + + std::error_code sec; std::uintmax_t sz = std::filesystem::file_size(path, sec); if (sec || sz == static_cast(-1)) { os << "Can't determine file size for: " << path << " with error " << sec << "\n"; @@ -90,80 +94,80 @@ template } // Get the key as one of our enums for a switch. - key::word word = key::to_word(i.key()); - switch (word) { + token tok = token_from_str(sv_from_csubstr(i.key())); + switch (tok) { - case key::word::name: { + case token::name: { // Sanity check before setting value. if (!i.has_val()) { - os << word << " tag in dependency list with no value.\n"; + os << tok << " tag in dependency list with no value.\n"; return {}; } if (!rv.name().empty()) { - os << "Duplicate " << word << " values in dependency list: " << i.val() << ", " << rv.name() << "\n"; + os << "Duplicate " << tok << " values in dependency list: " << i.val() << ", " << rv.name() << "\n"; return {}; } - rv.name(i.val()); + rv.name(sv_from_csubstr(i.val())); } break; - case key::word::tag: { + case token::tag: { // Sanity check before setting value. if (!i.has_val()) { - os << word << " tag in dependency list with no value.\n"; + os << tok << " tag in dependency list with no value.\n"; return {}; } if (!rv.tag().empty()) { - os << "Duplicate " << word << " values in dependency list: " << i.val() << ", " << rv.tag() << "\n"; + os << "Duplicate " << tok << " values in dependency list: " << i.val() << ", " << rv.tag() << "\n"; return {}; } - rv.tag(i.val()); + rv.tag(sv_from_csubstr(i.val())); } break; - case key::word::release: - case key::word::version: { // Allow version to mean release. + case token::release: + case token::version: { // Allow version to mean release. // Sanity check before setting value. if (!i.has_val()) { - os << word << " tag in dependency list with no value.\n"; + os << tok << " tag in dependency list with no value.\n"; return {}; } if (!rv.release().empty()) { - os << "Duplicate " << word << " values in dependency list: " << i.val() << ", " << rv.release() << "\n"; + os << "Duplicate " << tok << " values in dependency list: " << i.val() << ", " << rv.release() << "\n"; return {}; } - rv.release(i.val()); + rv.release(sv_from_csubstr(i.val())); } break; - case key::word::hash: { + case token::hash: { // Sanity check before setting value. if (!i.has_val()) { - os << word << " tag in dependency list with no value.\n"; + os << tok << " tag in dependency list with no value.\n"; return {}; } if (!rv.hash().empty()) { - os << "Duplicate " << word << " values in dependency list: " << i.val() << ", " << rv.hash() << "\n"; + os << "Duplicate " << tok << " values in dependency list: " << i.val() << ", " << rv.hash() << "\n"; return {}; } - rv.hash(i.val()); + rv.hash(sv_from_csubstr(i.val())); } break; - case key::word::from: { + case token::from: { // Sanity check before setting value. if (!i.has_val()) { - os << word << " tag in dependency list with no value.\n"; + os << tok << " tag in dependency list with no value.\n"; return {}; } if (!rv.location().empty()) { - os << "Duplicate " << word << " values in dependency list: " << i.val() << ", " << rv.location() << "\n"; + os << "Duplicate " << tok << " values in dependency list: " << i.val() << ", " << rv.location() << "\n"; return {}; } - if (!dependency::validate_location(i.val())) { + if (!dependency::validate_location(sv_from_csubstr(i.val()))) { os << "Invalid location: " << i.val() << "\n"; return {}; } - rv.location(i.val()); + rv.location(sv_from_csubstr(i.val())); } break; - case key::word::patch: { + case token::patch: { // Get the patch file paths. for (auto fn : i) { // Sanity check. @@ -171,25 +175,24 @@ template os << "no val\n"; continue; } - std::string_view temp = fn.val(); - rv.patch_add(temp); + rv.patch_add(sv_from_csubstr(fn.val())); } } break; - case key::word::project: - case key::word::libraries: - case key::word::apps: - case key::word::tests: - case key::word::lang: - case key::word::options: - case key::word::depends: - case key::word::command: { - os << "Unexpected tag in dependency list: " << word << "\n"; + case token::project: + case token::libraries: + case token::apps: + case token::tests: + case token::lang: + case token::options: + case token::depends: + case token::command: { + os << "Unexpected tag in dependency list: " << tok << "\n"; return {}; } - case key::word::none: { + case token::error: { os << "Unknown tag in dependency list: " << i.key() << "\n"; return {}; } @@ -217,10 +220,10 @@ template } // Get the key as one of our enums for a switch. - key::word word = key::to_word(i.key()); - switch (word) { + token tok = token_from_str(sv_from_csubstr(i.key())); + switch (tok) { - case key::word::name: { + case token::name: { // Sanity check before setting value. if (!i.has_val()) { os << "Name tag in " << type << " list with no value.\n"; @@ -230,13 +233,13 @@ template os << "Duplicate name values in " << type << " list: " << i.val() << "\n"; return {}; } - rv.name(i.val()); + rv.name(sv_from_csubstr(i.val())); } break; - case key::word::lang: { + case token::lang: { // Sanity check before setting value. if (!i.has_val()) { - os << word << " tag in " << type << " list with no value.\n"; + os << tok << " tag in " << type << " list with no value.\n"; return {}; } auto lang = i.val(); @@ -254,17 +257,17 @@ template return {}; } - rv.language(lang); + rv.language(sv_from_csubstr(lang)); } break; - case key::word::options: { + case token::options: { // Sanity check before setting value. if (!i.has_val()) { - os << word << " tag in " << type << " list with no value.\n"; + os << tok << " tag in " << type << " list with no value.\n"; return {}; } if (!rv.options().empty()) { - os << "Duplicate " << word << " values in " << type << " list: " << rv.options() << ", " << i.val() << "\n"; + os << "Duplicate " << tok << " values in " << type << " list: " << rv.options() << ", " << i.val() << "\n"; return {}; } if (type == object::type_t::test) { @@ -272,18 +275,18 @@ template return {}; } - rv.options(i.val()); + rv.options(sv_from_csubstr(i.val())); } break; - case key::word::command: { + case token::command: { // Sanity check before setting value. if (!i.has_val()) { - os << word << " tag in " << type << " list with no value.\n"; + os << tok << " tag in " << type << " list with no value.\n"; return {}; } if (!rv.command().empty()) { - os << "Duplicate " << word << " values in " << type << " list: " << rv.command() << ", " << i.val() << "\n"; + os << "Duplicate " << tok << " values in " << type << " list: " << rv.command() << ", " << i.val() << "\n"; return {}; } if (type != object::type_t::test) { @@ -291,14 +294,14 @@ template return {}; } - rv.command(i.val()); + rv.command(sv_from_csubstr(i.val())); } break; - case key::word::depends: { + case token::depends: { // sanity check if (i.has_val() && !i.val().empty()) { - os << "Unexpected value in " << word << " list: " << i.val() << "\n"; + os << "Unexpected value in " << tok << " list: " << i.val() << "\n"; return {}; } // Depends should be a map. For each element, parse out the dependency and store it. @@ -307,7 +310,7 @@ template if (!optional_dep) return {}; if (rv.dependency_exists(optional_dep.value().name())) { - os << "Multiple dependencies with the same name in " << word << " list: " << optional_dep.value().name() << "\n"; + os << "Multiple dependencies with the same name in " << tok << " list: " << optional_dep.value().name() << "\n"; return {}; } rv.upsert_dependency(std::move(optional_dep.value())); @@ -315,21 +318,21 @@ template } break; - case key::word::apps: - case key::word::from: - case key::word::hash: - case key::word::libraries: - case key::word::patch: - case key::word::project: - case key::word::release: - case key::word::tag: - case key::word::tests: - case key::word::version: { - os << "Unexpected tag in " << type << " list: " << word << "\n"; + case token::apps: + case token::from: + case token::hash: + case token::libraries: + case token::patch: + case token::project: + case token::release: + case token::tag: + case token::tests: + case token::version: { + os << "Unexpected tag in " << type << " list: " << tok << "\n"; return {}; } - case key::word::none: { + case token::error: { os << "Unknown tag in " << type << " list: " << i.key() << "\n"; return {}; } @@ -367,10 +370,10 @@ std::optional project::parse(const std::filesystem::path& path, std::os } // Get the key as one of our enums for a switch. - key::word word = key::to_word(i.key()); - switch (word) { + token tok = token_from_str(sv_from_csubstr(i.key())); + switch (tok) { - case key::word::project: { + case token::project: { // Sanity check before setting value. if (!i.has_val()) { os << "Project tag at root level with no value.\n"; @@ -380,28 +383,28 @@ std::optional project::parse(const std::filesystem::path& path, std::os os << "Multiple project tags at root level: " << rv.name() << ", " << i.val() << "\n"; return {}; } - rv.name(i.val()); + rv.name(sv_from_csubstr(i.val())); } break; - case key::word::version: { + case token::version: { // Sanity check before setting value. if (!i.has_val()) { os << "Version tag at root level with no value.\n"; return {}; } if (!rv.version().empty()) { - os << "Multiple version tags at root level: " << rv.version() << ", " << i.val() << "\n"; + os << "Multiple version tags at root level: " << rv.version().raw() << ", " << i.val() << "\n"; return {}; } - rv.version(antler::project::version(i.val())); + rv.version(antler::project::version(sv_from_csubstr(i.val()))); } break; - case key::word::apps: - case key::word::libraries: - case key::word::tests: { + case token::apps: + case token::libraries: + case token::tests: { // sanity check if (i.has_val() && !i.val().empty()) { - os << "Unexpected value in " << word << " list: " << i.val() << "\n"; + os << "Unexpected value in " << tok << " list: " << i.val() << "\n"; return {}; } @@ -412,8 +415,8 @@ std::optional project::parse(const std::filesystem::path& path, std::os // The list type. const object::type_t ot = - (word == key::word::apps ? object::app : - (word == key::word::libraries ? object::lib : object::test) ); + (tok == token::apps ? object::app : + (tok == token::libraries ? object::lib : object::test) ); // A reference to the list we want to populate. object::list_t& list = (ot == object::app ? rv.m_apps : @@ -426,7 +429,7 @@ std::optional project::parse(const std::filesystem::path& path, std::os if (!optional_obj) return {}; if (rv.object_exists(optional_obj.value().name(), ot)) { - os << "Multiple object with the same name in " << word << " list: " << optional_obj.value().name() << "\n"; + os << "Multiple object with the same name in " << tok << " list: " << optional_obj.value().name() << "\n"; return {}; } list.emplace_back(optional_obj.value()); @@ -434,21 +437,21 @@ std::optional project::parse(const std::filesystem::path& path, std::os } break; - case key::word::command: - case key::word::depends: - case key::word::from: - case key::word::hash: - case key::word::lang: - case key::word::name: - case key::word::options: - case key::word::patch: - case key::word::release: - case key::word::tag: { - os << "Unexpected tag at root level: " << word << "\n"; + case token::command: + case token::depends: + case token::from: + case token::hash: + case token::lang: + case token::name: + case token::options: + case token::patch: + case token::release: + case token::tag: { + os << "Unexpected tag at root level: " << tok << "\n"; return {}; } - case key::word::none: { + case token::error: { os << "Unknown tag at root level: " << i.key() << "\n"; return {}; } @@ -463,5 +466,4 @@ std::optional project::parse(const std::filesystem::path& path, std::os return rv; } - -} // namespace antler::project +} // namespace antler::project \ No newline at end of file diff --git a/project/src/project-populate.cpp b/src/project-populate.cpp similarity index 94% rename from project/src/project-populate.cpp rename to src/project-populate.cpp index a911a3b..48aab5a 100644 --- a/project/src/project-populate.cpp +++ b/src/project-populate.cpp @@ -1,7 +1,7 @@ /// @copyright See `LICENSE` in the root directory of this project. #include -#include +#include "cmake.hpp" #include #include @@ -46,9 +46,7 @@ const std::string magic_all{ std::string(magic1) + "\n" + std::string(magic2) + } // anonymous namespace -bool project::populate(pop action_type, std::ostream& error_stream) noexcept { - - bool force_replace = (action_type == pop::force_replace); +bool project::populate(bool replace, std::ostream& error_stream) noexcept { // Sanity check: ensure path is valid. if (m_path.empty()) { @@ -67,7 +65,7 @@ bool project::populate(pop action_type, std::ostream& error_stream) noexcept { { auto path = project_path / cmake_lists; bool create = true; - if (!force_replace && std::filesystem::exists(path, sec)) { + if (!replace && std::filesystem::exists(path, sec)) { // Look to see if the header contains the magic, if it does we will not create the file. create = has_magic(path); } diff --git a/project/src/project-print.cpp b/src/project-print.cpp similarity index 60% rename from project/src/project-print.cpp rename to src/project-print.cpp index fba921e..67d92d0 100644 --- a/project/src/project-print.cpp +++ b/src/project-print.cpp @@ -2,7 +2,6 @@ #include #include -#include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wold-style-cast" @@ -15,58 +14,15 @@ #pragma GCC diagnostic pop #include -#include - -namespace { // anonymous - -/// Templates to work around ryml::csubstr data type. Note that csubstr is very similar to std::string_view. - -template -[[nodiscard]] inline const c4::csubstr to_csubstr(T t) { - // The default function should probably not exist. It will ONLY work in liited situations. In other cases, it silently allows - // ryml to write garbage. - - t.force_this_template_function_to_generate_an_error(); // "disable" calls to this function. - std::stringstream ss; - ss << t; - return c4::to_csubstr(ss.str()); -} - -template<> -[[nodiscard]] inline const c4::csubstr to_csubstr(std::string s) { - return c4::to_csubstr(s); -} - -template<> -[[nodiscard]] inline const c4::csubstr to_csubstr(std::string_view s) { - return c4::to_csubstr(s); -} - -template<> -[[nodiscard]] inline const c4::csubstr to_csubstr(key::word e) { - return to_csubstr(key::literals[static_cast(e)]); -} - -template -[[nodiscard]] inline const c4::csubstr to_csubstr_insert(T t) { - std::stringstream ss; - ss << t; - return c4::to_csubstr(ss.str()); -} - -template<> -[[nodiscard]] inline const c4::csubstr to_csubstr_insert(antler::project::version v) { - // This is a hack for now. - return c4::to_csubstr(v.raw()); -} - - -} // anonymous namespace - +#include "token.hpp" namespace antler::project { +inline static c4::csubstr to_csubstr(std::string_view sv) noexcept { return {sv.data(), sv.size()}; } +inline static c4::csubstr to_csubstr(token tok) noexcept { return to_csubstr(token_to_str(tok)); } +inline static c4::csubstr to_csubstr(version v) noexcept { return to_csubstr(v.raw()); } + void project::print(std::ostream& os) const noexcept { // Warning: nodes do not like non-literal values!!! @@ -81,10 +37,10 @@ void project::print(std::ostream& os) const noexcept { // Store the project name and version if (!m_name.empty()) - root[to_csubstr(key::word::project)] << to_csubstr(m_name); + root[to_csubstr(token::project)] << to_csubstr(m_name); if (!m_ver.empty()) - root[to_csubstr(key::word::version)] << to_csubstr_insert(m_ver); + root[to_csubstr(token::version)] << to_csubstr(m_ver); // Maintain once. // @@ -92,7 +48,7 @@ void project::print(std::ostream& os) const noexcept { // containing a reference/pointer to the project's lists and a list of the corresponding type. We iterate through each one. const std::vector obj_lists{ &m_libs, &m_apps, &m_tests }; - const std::vector list_type{ key::word::libraries, key::word::apps, key::word::tests }; + const std::vector list_type{ token::libraries, token::apps, token::tests }; for (size_t i = 0; i < obj_lists.size(); ++i) { const auto& obj_list = *obj_lists[i]; // convenience. @@ -115,18 +71,18 @@ void project::print(std::ostream& os) const noexcept { // name if (!obj.name().empty()) - map_node[to_csubstr(key::word::name)] << to_csubstr(obj.name()); + map_node[to_csubstr(token::name)] << to_csubstr(obj.name()); // lang if (!obj.language().empty()) - map_node[to_csubstr(key::word::lang)] << to_csubstr(obj.language()); + map_node[to_csubstr(token::lang)] << to_csubstr(obj.language()); // options if (!obj.options().empty()) - map_node[to_csubstr(key::word::options)] << to_csubstr(obj.options()); + map_node[to_csubstr(token::options)] << to_csubstr(obj.options()); // depends - dependencies are also a list. if (!obj.dependencies().empty()) { // Make the sequence node with the correct key. - auto dep_node = (map_node[to_csubstr(key::word::depends)] << ""); + auto dep_node = (map_node[to_csubstr(token::depends)] << ""); dep_node.change_type(c4::yml::SEQ); // Iterate over every element in the dependency list... @@ -140,24 +96,24 @@ void project::print(std::ostream& os) const noexcept { // Add the elements. // name if (!dep.name().empty()) - dep_map_node[to_csubstr(key::word::name)] << to_csubstr(dep.name()); + dep_map_node[to_csubstr(token::name)] << to_csubstr(dep.name()); // location if (!dep.location().empty()) - dep_map_node[to_csubstr(key::word::from)] << to_csubstr(dep.location()); + dep_map_node[to_csubstr(token::from)] << to_csubstr(dep.location()); // tag or commit hash if (!dep.tag().empty()) - dep_map_node[to_csubstr(key::word::tag)] << to_csubstr(dep.tag()); + dep_map_node[to_csubstr(token::tag)] << to_csubstr(dep.tag()); // release if (!dep.release().empty()) - dep_map_node[to_csubstr(key::word::release)] << to_csubstr(dep.release()); + dep_map_node[to_csubstr(token::release)] << to_csubstr(dep.release()); // hash if (!dep.hash().empty()) - dep_map_node[to_csubstr(key::word::hash)] << to_csubstr(dep.hash()); + dep_map_node[to_csubstr(token::hash)] << to_csubstr(dep.hash()); // Patch files. const auto& patch_files = dep.patch_files(); if (!patch_files.empty()) { - auto patch_node = dep_map_node[to_csubstr(key::word::patch)] << ""; + auto patch_node = dep_map_node[to_csubstr(token::patch)] << ""; patch_node.change_type(c4::yml::SEQ); for (size_t n = 0; n < patch_files.size(); ++n) patch_node[n] << patch_files[n]; @@ -166,7 +122,7 @@ void project::print(std::ostream& os) const noexcept { } // command if (!obj.command().empty()) { - map_node[to_csubstr(key::word::command)] << to_csubstr(obj.command()); + map_node[to_csubstr(token::command)] << to_csubstr(obj.command()); } } } @@ -189,10 +145,4 @@ void project::print(std::ostream& os) const noexcept { os << tree; } - -void project::print(std::ostream& os, pop e) noexcept { - os << magic_enum::enum_name(e); -} - - -} // namespace antler::project +} // namespace antler::project \ No newline at end of file diff --git a/project/src/project.cpp b/src/project.cpp similarity index 99% rename from project/src/project.cpp rename to src/project.cpp index d59e8d6..2ae896f 100644 --- a/project/src/project.cpp +++ b/src/project.cpp @@ -5,7 +5,6 @@ #include #include #include // find_if() -#include namespace antler::project { diff --git a/project/src/ryml.cpp b/src/ryml.cpp similarity index 100% rename from project/src/ryml.cpp rename to src/ryml.cpp diff --git a/project/src/semver.cpp b/src/semver.cpp similarity index 100% rename from project/src/semver.cpp rename to src/semver.cpp diff --git a/src/token.hpp b/src/token.hpp new file mode 100644 index 0000000..d03bf89 --- /dev/null +++ b/src/token.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include "token_def.hpp" + +namespace antler::project { + enum class token : uint8_t { + ANTLER_PROJ_TOKENS( ANTLER_PROJ_CREATE_ENUM, antler::project::token ) + }; + + inline static std::vector token_strings() { + static std::vector v = { ANTLER_PROJ_TOKENS( ANTLER_PROJ_CREATE_STRINGS, antler::project::token ) }; + return v; + } + + inline static std::unordered_map token_map() { + static std::unordered_map m = { ANTLER_PROJ_TOKENS( ANTLER_PROJ_CREATE_MAP, antler::project::token ) }; + return m; + } + + inline static std::string_view token_to_str(token tok) noexcept { + if (tok == token::error) + return "error"; + return token_strings()[static_cast(tok)]; + } + + inline static token token_from_str(std::string_view s) noexcept { + const auto itr = token_map().find(s); + if (itr == token_map().end()) { + return token::error; + } else { + return itr->second; + } + } + + inline static std::ostream& operator<<(std::ostream& os, token tok) { + os << token_to_str(tok); + return os; + } +} // namespace antler::project diff --git a/src/token_def.hpp b/src/token_def.hpp new file mode 100644 index 0000000..7ab2456 --- /dev/null +++ b/src/token_def.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +/* clang-format off */ +#define ANTLER_PROJ_TOKENS(tok_macro, E) \ + tok_macro(apps, 1, E) \ + tok_macro(command, 2, E) \ + tok_macro(depends, 3, E) \ + tok_macro(from, 4, E) \ + tok_macro(hash, 5, E) \ + tok_macro(lang, 6, E) \ + tok_macro(libraries, 7, E) \ + tok_macro(name, 8, E) \ + tok_macro(options, 9, E) \ + tok_macro(patch, 10, E) \ + tok_macro(project, 11, E) \ + tok_macro(release, 12, E) \ + tok_macro(tag, 13, E) \ + tok_macro(tests, 14, E) \ + tok_macro(version, 15, E) \ + tok_macro(error, std::numeric_limits::max(), E) +/* clang-format on */ \ No newline at end of file diff --git a/project/src/version.cpp b/src/version.cpp similarity index 87% rename from project/src/version.cpp rename to src/version.cpp index b59e31f..1cb6272 100644 --- a/project/src/version.cpp +++ b/src/version.cpp @@ -59,7 +59,7 @@ version& version::operator=(const self& rhs) { int64_t version::compare(const version& rhs) const noexcept { if (is_semver() && rhs.is_semver()) return m_semver->compare(*rhs.m_semver); - return raw_compare(m_raw, rhs.m_raw); + return raw_compare(rhs.m_raw); } version::operator semver() const noexcept { @@ -107,21 +107,17 @@ void version::load(const semver& sv) { m_semver = std::make_unique(sv); } - std::string_view version::raw() const noexcept { return m_raw; } -int64_t version::raw_compare(std::string_view l_in, std::string_view r_in) noexcept { - if (l_in == r_in) - return 0; - +int64_t version::raw_compare(std::string_view o) const noexcept { std::vector l; - boost::split(l, l_in, boost::is_any_of(".,-+;")); + boost::split(l, m_raw, boost::is_any_of(".,-+;")); std::vector r; - boost::split(r, r_in, boost::is_any_of(".,-+;")); + boost::split(r, o, boost::is_any_of(".,-+;")); for (size_t i = 0; i < std::min(l.size(), r.size()); ++i) { if (l[i] == r[i]) @@ -138,22 +134,22 @@ int64_t version::raw_compare(std::string_view l_in, std::string_view r_in) noexc int lnum = 0; std::string_view lremain; if (ln == std::string_view::npos) { - [[maybe_unused]] auto discard = string::from(l[i], lnum); + (void)string::from(l[i], lnum); lremain = l[i]; } else { - [[maybe_unused]] auto discard = string::from(l[i].substr(0, ln), lnum); + (void)string::from(l[i].substr(0, ln), lnum); lremain = l[i].substr(ln); } int rnum = 0; std::string_view rremain; if (rn == std::string_view::npos) { - [[maybe_unused]] auto discard = string::from(r[i], rnum); + (void)string::from(r[i], rnum); rremain = r[i]; } else { - [[maybe_unused]] auto discard = string::from(r[i].substr(0, rn), rnum); + (void)string::from(r[i].substr(0, rn), rnum); rremain = r[i].substr(rn); } diff --git a/project/src/version_compare.cpp b/src/version_compare.cpp similarity index 86% rename from project/src/version_compare.cpp rename to src/version_compare.cpp index 11475a0..3cd94a7 100644 --- a/project/src/version_compare.cpp +++ b/src/version_compare.cpp @@ -5,7 +5,6 @@ #include // boost::split() - namespace antler::project { int64_t raw_compare(std::string_view lhs, std::string_view rhs) noexcept { @@ -35,21 +34,21 @@ int64_t raw_compare(std::string_view lhs, std::string_view rhs) noexcept { std::string_view lremain; if (ln == std::string_view::npos) { - [[maybe_unused]] auto discard = string::from(l[i], lnum); + (void)string::from(l[i], lnum); lremain = l[i]; } else { - [[maybe_unused]] auto discard = string::from(l[i].substr(0, ln), lnum); + (void)string::from(l[i].substr(0, ln), lnum); lremain = l[i].substr(ln); } std::string_view rremain; if (rn == std::string_view::npos) { - [[maybe_unused]] auto discard = string::from(r[i], rnum); + (void)string::from(r[i], rnum); rremain = r[i]; } else { - [[maybe_unused]] auto discard = string::from(r[i].substr(0, rn), rnum); + (void)string::from(r[i].substr(0, rn), rnum); rremain = r[i].substr(rn); } diff --git a/project/src/version_constraint.cpp b/src/version_constraint.cpp similarity index 100% rename from project/src/version_constraint.cpp rename to src/version_constraint.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 126389c..a0dfa54 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -16,7 +16,7 @@ foreach(file_name IN ITEMS ${src_cpp}) #set(name "test-${name}") add_executable(${name} ${file_name}) - set_property(TARGET ${name} PROPERTY CXX_STANDARD 20) + set_property(TARGET ${name} PROPERTY CXX_STANDARD 17) target_link_libraries(${name} antler-project) target_include_directories(${name} PRIVATE ./) add_test(NAME ${name} COMMAND ${name}) diff --git a/3p_include/CLI11.hpp b/tools/CLI11.hpp similarity index 99% rename from 3p_include/CLI11.hpp rename to tools/CLI11.hpp index 79ad179..0a1dd56 100644 --- a/3p_include/CLI11.hpp +++ b/tools/CLI11.hpp @@ -9694,4 +9694,4 @@ CLI11_INLINE std::string Formatter::make_option_usage(const Option *opt) const { } -} // namespace CLI +} // namespace CLI \ No newline at end of file diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 0000000..8142875 --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,10 @@ +# @copyright See `LICENSE` in the root directory of this project. + +include(FindPkgConfig) + +pkg_check_modules(CURL libcurl REQUIRED) + +add_executable(antler-proj main.cpp) +set_property(TARGET antler-proj PROPERTY CXX_STANDARD 17) +target_link_libraries(antler-proj PUBLIC antler-project ${CURL_LIBRARIES}) +target_include_directories(antler-proj PRIVATE . ${CURL_INCLUDE_DIRS}) \ No newline at end of file diff --git a/aproj/README.md b/tools/README.md similarity index 100% rename from aproj/README.md rename to tools/README.md diff --git a/aproj/add_to.hpp b/tools/add_to.hpp similarity index 100% rename from aproj/add_to.hpp rename to tools/add_to.hpp diff --git a/aproj/common.hpp b/tools/common.hpp similarity index 74% rename from aproj/common.hpp rename to tools/common.hpp index 567ece8..70a4d10 100644 --- a/aproj/common.hpp +++ b/tools/common.hpp @@ -5,9 +5,9 @@ #include namespace antler { - using proj_ptr = std::optional; + using proj_t = std::optional; - inline proj_ptr load_project(const std::filesystem::path& path) { + inline proj_t load_project(const std::filesystem::path& path) { auto p = std::filesystem::canonical(std::filesystem::path(path)); if (!antler::project::project::update_path(p)) { std::cerr << "path either did not exist or no `project.yaml` file could be found." << std::endl; diff --git a/aproj/init.hpp b/tools/init.hpp similarity index 100% rename from aproj/init.hpp rename to tools/init.hpp diff --git a/aproj/aproj.cpp b/tools/main.cpp similarity index 81% rename from aproj/aproj.cpp rename to tools/main.cpp index 3f0f989..1eff103 100644 --- a/aproj/aproj.cpp +++ b/tools/main.cpp @@ -11,9 +11,6 @@ #include -#include // boost::split() -#include // boost::dll::program_location() - #include #include @@ -35,7 +32,7 @@ struct runner { template constexpr inline int exec() { if constexpr (I == sizeof...(Ts)) { - std::cerr << "Internal error, runner failure" << std::endl; + std::cerr << "Please run one of the subcommands available. Use --help to see what is available." << std::endl; return -1; } else { if (*std::get(tup).subcommand) { @@ -55,8 +52,6 @@ int main(int argc, char** argv) { const auto app_name = std::filesystem::path(argv[0]).filename().string(); CLI::App app{app_name}; - antler::project::location::is_reachable("https://github.com"); - runnerpopulate(pop_type) ? 0 : -1; + return proj->populate() ? 0 : -1; } CLI::App* subcommand; diff --git a/aproj/remove_from.hpp b/tools/remove_from.hpp similarity index 100% rename from aproj/remove_from.hpp rename to tools/remove_from.hpp diff --git a/aproj/update.hpp b/tools/update.hpp similarity index 100% rename from aproj/update.hpp rename to tools/update.hpp diff --git a/aproj/validate.hpp b/tools/validate.hpp similarity index 100% rename from aproj/validate.hpp rename to tools/validate.hpp From c627c4a140d8d54b5af506f1a33b5098b1e1418b Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Fri, 3 Mar 2023 22:38:23 -0500 Subject: [PATCH 06/14] more fixups and cleanup --- .gitmodules | 3 + CMakeLists.txt | 3 +- external/CMakeLists.txt | 9 +- external/Catch2 | 1 + include/antler/project/project.hpp | 1 + .../project/{semver.hpp => semver.hpp2} | 7 +- include/antler/project/version.hpp | 184 ++++++++++++++---- ...rsion_compare.hpp => version_compare.hpp2} | 0 include/antler/system/exec.hpp | 34 ---- src/CMakeLists.txt | 10 +- src/cmake.hpp | 33 +++- src/exec.cpp | 38 ---- src/location.cpp | 2 - src/project-parse.cpp | 2 +- src/project-populate.cpp | 41 ++-- src/project-print.cpp | 2 +- src/project.cpp | 21 +- src/version.cpp | 168 +--------------- src/version_constraint.cpp | 19 +- test/CMakeLists.txt | 28 +-- test/string_from.cpp | 2 +- test/version.cpp | 12 +- test/version_constraint.cpp | 2 +- ...version_semver.cpp => version_semver.cpp2} | 0 tools/main.cpp | 1 - 25 files changed, 259 insertions(+), 364 deletions(-) create mode 160000 external/Catch2 rename include/antler/project/{semver.hpp => semver.hpp2} (92%) rename include/antler/project/{version_compare.hpp => version_compare.hpp2} (100%) delete mode 100644 include/antler/system/exec.hpp delete mode 100644 src/exec.cpp rename test/{version_semver.cpp => version_semver.cpp2} (100%) diff --git a/.gitmodules b/.gitmodules index 1996e10..13044c8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "external/json"] path = external/json url = https://github.com/boostorg/json +[submodule "external/Catch2"] + path = external/Catch2 + url = https://github.com/catchorg/Catch2 diff --git a/CMakeLists.txt b/CMakeLists.txt index ed532e5..859240e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,8 @@ add_subdirectory( tools ) option(PEDANTIC_BUILD "Pedantic builds" On) -option(BUILD_TESTS "Build and run the tests." On) +option(BUILD_TESTS "Build and run the tests." Off) + if(BUILD_TESTS) enable_testing() add_subdirectory( test ) diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index a030868..8257fb9 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -3,4 +3,11 @@ add_subdirectory(rapidyaml) set(MAGIC_ENUM_OPT_BUILD_EXAMPLES OFF CACHE BOOL "Skip magic_enum examples") set(MAGIC_ENUM_OPT_BUILD_TESTS OFF CACHE BOOL "Skip magic_enum tests") set(MAGIC_ENUM_OPT_INSTALL OFF CACHE BOOL "Skip magic_enum install") -add_subdirectory(magic_enum) \ No newline at end of file +add_subdirectory(magic_enum) + +set(CATCH_INSTALL_DOCS OFF CACHE BOOL "Skip install docs") +set(CATCH_INSTALL_EXTRAS OFF CACHE BOOL "Skip install extras") +set(CATCH_BUILD_TESTING OFF CACHE BOOL "Skip build testing") +add_subdirectory(Catch2) +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/Catch2/contrib) +include(Catch) diff --git a/external/Catch2 b/external/Catch2 new file mode 160000 index 0000000..de6fe18 --- /dev/null +++ b/external/Catch2 @@ -0,0 +1 @@ +Subproject commit de6fe184a9ac1a06895cdd1c9b437f0a0bdf14ad diff --git a/include/antler/project/project.hpp b/include/antler/project/project.hpp index 04de703..b480859 100644 --- a/include/antler/project/project.hpp +++ b/include/antler/project/project.hpp @@ -18,6 +18,7 @@ class project { // parse a project from a project.yml constexpr static inline std::string_view manifest_name = "project.yml"; + constexpr static inline std::string_view magic_comment = "#antler-proj::generated"; // constructors project() = default; diff --git a/include/antler/project/semver.hpp b/include/antler/project/semver.hpp2 similarity index 92% rename from include/antler/project/semver.hpp rename to include/antler/project/semver.hpp2 index 3899ea4..8a6e3df 100644 --- a/include/antler/project/semver.hpp +++ b/include/antler/project/semver.hpp2 @@ -17,7 +17,7 @@ namespace antler::project { class semver { public: using self = semver; ///< Alias for self type because sometimes it's nice to know who you are. - using value_type = unsigned; ///< Alias for the internal type. + using value_type = uint16_t; ///< Alias for the internal type. /// Constructor: major.minor.patch-pre_release+build /// @param major The major value. @@ -62,6 +62,11 @@ class semver { /// @return An optional either containing a semver if the parse was successful or empty if s was unparseable. [[nodiscard]] static std::optional parse(std::string_view s) noexcept; + [[nodiscard]] inline uint16_t major() const noexcept { return m_xyz[0]; } + [[nodiscard]] inline uint16_t minor() const noexcept { return m_xyz[1]; } + [[nodiscard]] inline uint16_t patch() const noexcept { return m_xyz[2]; } + [[nodiscard]] inline std::string_view tweak() const noexcept { return m_pre; } + private: /// compare prerelease according to rule 12. lhs and rhs must both be the pre-release portion of the version and diff --git a/include/antler/project/version.hpp b/include/antler/project/version.hpp index 8699273..70c6990 100644 --- a/include/antler/project/version.hpp +++ b/include/antler/project/version.hpp @@ -2,13 +2,15 @@ /// @copyright See `LICENSE` in the root directory of this project. -#include "semver.hpp" +#include #include #include +#include #include #include #include +#include namespace antler::project { @@ -18,22 +20,32 @@ class version { public: using self = version; ///< Alias for self type. - /// Default constructor. - version() = default; - /// @parm ver A string to create this version with. ver is evaluated to see if it might be a semver. - version(std::string_view ver); - /// @param sv A semver version to create this version with. - explicit version(const semver& sv); + /// @param ver A string to create this version with. + inline version(std::string_view ver) { + if (!from_string(ver, major_comp, minor_comp, patch_comp, tweak_comp)) + throw std::runtime_error("version malformed"); + } + + /// @param maj Major version component. + /// @param min Minor version component. + /// @param pat Patch version component. + /// @param tweak Tweak version component. + inline version(uint16_t maj=0, uint16_t min=0, uint16_t pat=0, std::string tweak="") + : major_comp(maj), minor_comp(min), patch_comp(pat), tweak_comp(std::move(tweak)) {} + /// Copy constructor. - /// @parm rhs Source to copy from - version(const self& rhs); + /// @param rhs Source to copy from + version(const self& rhs) = default; + + /// @param ver A string to create this version with. ver is evaluated to see if it might be a semver. + inline version& operator=(std::string_view ver) { + if (!from_string(ver, major_comp, minor_comp, patch_comp, tweak_comp)) + throw std::runtime_error("version malformed"); + return *this; + } - /// @parm ver A string to create this version with. ver is evaluated to see if it might be a semver. - version& operator=(std::string_view ver); - /// @param sv A semver version to create this version with. - version& operator=(const semver& sv); - /// @parm rhs Source to copy from - version& operator=(const self& rhs); + /// @param rhs Source to copy from + version& operator=(const self& rhs) = default; /// comparison operator /// @param rhs The right hand side semver to compare against. @@ -48,41 +60,133 @@ class version { [[nodiscard]] inline bool operator==(const self& rhs) const noexcept { return compare(rhs) == 0; } [[nodiscard]] inline bool operator!=(const self& rhs) const noexcept { return compare(rhs) != 0; } - [[nodiscard]] int64_t compare(const self& rhs) const noexcept; - /// Clear any values. - void clear() noexcept; + inline void clear() noexcept { + major_comp = 0; + minor_comp = 0; + patch_comp = 0; + tweak_comp = ""; + } /// @return true if this version is empty. - [[nodiscard]] bool empty() const noexcept; - /// @return The raw string this version was built from. If created from a semver, the string equivalent. - [[nodiscard]] std::string_view raw() const noexcept; - - /// @return true if this version is a semver. - [[nodiscard]] bool is_semver() const noexcept; - /// @return The version in semver_t format. If is_semver() would return false, this value is invalid. - explicit operator semver() const noexcept; + [[nodiscard]] inline bool empty() const noexcept { + return major() == 0 && + minor() == 0 && + patch() == 0 && + tweak().empty(); + } + + /// @return The string this version was built from. + [[nodiscard]] std::string to_string() const noexcept { + return std::to_string(major_comp)+"."+std::to_string(minor_comp)+"."+std::to_string(patch_comp)+"-"+tweak_comp; + } + + [[nodiscard]] static inline bool to_component(uint16_t& c, std::string_view s) { + try { + c = std::atoi(s.data()); + } catch(const std::invalid_argument&) { + std::cerr << "component :" << s << " not a valid component." << std::endl; + return false; + } catch(const std::out_of_range&) { + std::cerr << "component :" << s << " not a valid component." << std::endl; + return false; + } + return true; + } + + /// Create the components of the version from a string. + /// @return false if version string is ill-formed. + [[nodiscard]] static bool from_string(std::string_view s, uint16_t& maj, uint16_t& min, uint16_t& patch, std::string& tweak) noexcept { + maj = 0; + min = 0; + patch = 0; + tweak = ""; + std::vector comps; + boost::split(comps, s, boost::is_any_of(".,-")); + + switch (comps.size()) { + case 1: + return to_component(maj, comps[0]); + case 2: + return to_component(maj, comps[0]) && to_component(min, comps[1]); + case 3: + return to_component(maj, comps[0]) && to_component(min, comps[1]) && to_component(patch, comps[2]); + case 4: + tweak = comps[3]; + return to_component(maj, comps[0]) && to_component(min, comps[1]) && to_component(patch, comps[2]); + default: + std::cerr << "Version string is malformed " << s << std::endl; + return false; + } + } + + /// Create the components of the version from a string. + /// @return false if version string is ill-formed. + [[nodiscard]] static version from_string(std::string_view s) { + version v; + if (!from_string(s, v.major_comp, v.minor_comp, v.patch_comp, v.tweak_comp)) + throw std::runtime_error("invalid version string"); + return v; + } + + /// @return The string this version was built from. + [[nodiscard]] inline explicit operator std::string() const noexcept { return to_string(); } /// compare the string value of this to rhs. Attempt to use semver rules. /// @param rhs The version to compare to. + /// @return the result of the comparison: eq, lt, gt. If rhs is illformed the return is 1; + [[nodiscard]] int64_t compare(std::string_view rhs) const noexcept { + version v; + if (from_string(rhs, v.major_comp, v.minor_comp, v.patch_comp, v.tweak_comp)) + return 1; + return compare(v); + } + + /// compare the string value of this to rhs. Attempt to use semver rules. `- suffixes will be ignored` + /// @param rhs The version to compare to. /// @return the result of the comparison: eq, lt, gt. - [[nodiscard]] int64_t raw_compare(std::string_view o) const noexcept; + [[nodiscard]] int64_t compare(const version& o) const noexcept { + const auto& compare_one = [](auto v1, auto v2, int64_t c) -> int64_t { + if (c == 0) { + if (v1 == v2) + return 0; + else if (v1 > v2) + return 1; + else + return -1; + } + return c; + }; + + return compare_one(patch(), o.patch(), + compare_one(minor(), o.minor(), + compare_one(major(), o.major(), 0))); + } + + /// Get the major component of the version. + /// @return The major component of the version. + [[nodiscard]] inline int16_t major() const noexcept { return major_comp; } + + /// Get the minor component of the version. + /// @return The major component of the version. + [[nodiscard]] inline int16_t minor() const noexcept { return minor_comp; } + + /// Get the patch component of the version. + /// @return The major component of the version. + [[nodiscard]] inline int16_t patch() const noexcept { return patch_comp; } + + /// Get the tweak component of the version. + /// @return The major component of the version. + [[nodiscard]] inline std::string_view tweak() const noexcept { return tweak_comp; } private: - - /// Load this version from a string. Attempts to parse and store as semver in the process. Either way, s is stored as m_raw. - /// @param s The string to store. - void load(std::string_view s); - /// Load this version from a semver. sv is stored in m_semver and printed into m_raw. - void load(const semver& sv); - -private: - std::string m_raw; ///< The raw, printable value of the version. - std::unique_ptr m_semver; ///< If valid, the semver equivalent of m_raw. + uint16_t major_comp; + uint16_t minor_comp; + uint16_t patch_comp; + std::string tweak_comp; + std::string raw_str; }; - } // namespace antler::project - -inline std::ostream& operator<<(std::ostream& os, const antler::project::version& o) { os << o.raw(); return os; } \ No newline at end of file +inline std::ostream& operator<<(std::ostream& os, const antler::project::version& o) { os << o.to_string(); return os; } \ No newline at end of file diff --git a/include/antler/project/version_compare.hpp b/include/antler/project/version_compare.hpp2 similarity index 100% rename from include/antler/project/version_compare.hpp rename to include/antler/project/version_compare.hpp2 diff --git a/include/antler/system/exec.hpp b/include/antler/system/exec.hpp deleted file mode 100644 index 5760545..0000000 --- a/include/antler/system/exec.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -/// @copyright See `LICENSE` in the root directory of this project. - -#include -#include - - -namespace antler::system { - -/// Structure to contain the results of exec(). -struct result { - - int return_code = 0; ///< This value contains the return value from the cmd. - std::string output; ///< This contains both stdout and stderr captured when cmd is called. - - - // bool operator returns true when error state is set, false otherwise. - // implementation for bool operator - typedef void (*unspecified_bool_type)(); - static void unspecified_bool_true() { ; } - explicit operator unspecified_bool_type() const { return (return_code != 0 ? 0 : unspecified_bool_true); } - - /// @return true when there is NO error; false for error (oppositie of the bool operator - bool operator!() const { return return_code != 0; } -}; - - -/// Call `system()` with the string cmd. Results are captured and returned. -/// @param cmd The command to call. -/// @return The result struct containing the integer result and captured stdout and stderr streams. -result exec(std::string_view cmd) noexcept; - -} // namespace antler::system \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 55e7305..6f0789f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,20 +2,18 @@ find_package( Boost REQUIRED COMPONENTS system) set(Boost_USE_STATIC_LIBS ON) add_library(antler-project - cmake.cpp + #cmake.cpp dependency.cpp - exec.cpp location.cpp object.cpp - project-is_valid.cpp project-parse.cpp project-populate.cpp project-print.cpp project.cpp - semver.cpp - version_compare.cpp + #semver.cpp + #version_compare.cpp version_constraint.cpp - version.cpp + #version.cpp ) target_include_directories(antler-project PUBLIC ../include ../external/json/ ${CMAKE_CURRENT_BINARY_DIR}/../include) diff --git a/src/cmake.hpp b/src/cmake.hpp index bc4c022..9138689 100644 --- a/src/cmake.hpp +++ b/src/cmake.hpp @@ -2,22 +2,43 @@ /// @copyright See `LICENSE` in the root directory of this project. -#include +#include #include // path -namespace antler::cmake { +namespace antler { + + struct cmake { + template + inline void emit_version(Stream& s) const noexcept { s << major << "." << minor; } + + template + inline void emit_add_subdirectory(Stream& s, const std::filesystem::path& p) const noexcept { + s << "add_subdirectory(" << p.string() << ")\n"; + } + + template + inline void emit_preamble(Stream& s, const project::project& proj) const noexcept { + s << "# Generated with antler-proj tool, modify at your own risk\n"; + s << "cmake_minimum_required(VERSION" << major << "." << minor << ")\n"; + s << "project(\"" << proj.name() << "\" VERSION" << proj.version().major() << "." << + proj.version().minor() << "." << proj.version().patch() << ")" << std::endl; + } + + uint16_t major = 3; + uint16_t minor = 11; + }; /// @return the cmake_minimum string with trailing newline. -[[nodiscard]] std::string minimum(unsigned major, unsigned minor = 0, unsigned patch = 0) noexcept; +[[nodiscard]] std::string minimum(unsigned major = 0, unsigned minor = 0, unsigned patch = 0) noexcept; /// @return the add_subdirectory string: add_subdirectory( "" )\n [[nodiscard]] std::string add_subdirectory(const std::filesystem::path& path) noexcept; /// @return A string including the project name: project("")\n -[[nodiscard]] std::string project(std::string_view proj_name) noexcept; +//[[nodiscard]] std::string project(std::string_view proj_name); /// @return A string including the project name: project("" VERSION )\n -[[nodiscard]] std::string project(std::string_view proj_name, const project::semver& ver) noexcept; +//[[nodiscard]] std::string project(std::string_view proj_name, const project::semver& ver) noexcept; -} // namespace antler::cmake +} // namespace antler diff --git a/src/exec.cpp b/src/exec.cpp deleted file mode 100644 index c8ce4cc..0000000 --- a/src/exec.cpp +++ /dev/null @@ -1,38 +0,0 @@ -/// @copyright See `LICENSE` in the root directory of this project. - -#include - -#include -#include -#include - -namespace antler::system { - -result exec(std::string_view cmd_in) noexcept { - - // We get stdout + stderr. - std::string cmd(cmd_in); - cmd += " 2>&1"; - - result rv; - - // Open the pipe... - FILE* pipe = popen(cmd.c_str(), "r"); - if (!pipe) { - // ...let the user know on failure. - rv.return_code = -1; - rv.output = "antler::system::exec() error: failed to open pipe."; - return rv; - } - - std::array buffer{}; - while (fgets(buffer.data(), buffer.size(), pipe) != nullptr) - rv.output += buffer.data(); - - rv.return_code = pclose(pipe); - - return rv; -} - - -} // namespace antler::system diff --git a/src/location.cpp b/src/location.cpp index cc25581..3762c4b 100644 --- a/src/location.cpp +++ b/src/location.cpp @@ -7,8 +7,6 @@ #include #include // boost::split() -#include - #include diff --git a/src/project-parse.cpp b/src/project-parse.cpp index b29b57f..7449f6c 100644 --- a/src/project-parse.cpp +++ b/src/project-parse.cpp @@ -393,7 +393,7 @@ std::optional project::parse(const std::filesystem::path& path, std::os return {}; } if (!rv.version().empty()) { - os << "Multiple version tags at root level: " << rv.version().raw() << ", " << i.val() << "\n"; + os << "Multiple version tags at root level: " << rv.version().to_string() << ", " << i.val() << "\n"; return {}; } rv.version(antler::project::version(sv_from_csubstr(i.val()))); diff --git a/src/project-populate.cpp b/src/project-populate.cpp index 48aab5a..e577e44 100644 --- a/src/project-populate.cpp +++ b/src/project-populate.cpp @@ -11,18 +11,16 @@ namespace antler::project { namespace { // anonymous const std::filesystem::path cmake_lists{"CMakeLists.txt"}; -constexpr std::string_view magic1{"# This file was AUTOGENERATED and is maintained using antler-proj tools."}; // Intentionally missing newline. -constexpr std::string_view magic2{"# Modification or removal of the above line will cause antler-proj to skip this line."}; -constexpr std::string_view magic3{"# Any changes made to this file will be lost when any antler-proj tool updates this project."}; -const std::string magic_all{ std::string(magic1) + "\n" + std::string(magic2) + "\n" + std::string(magic3) + "\n" }; +constexpr std::string_view magic = "#antler-proj::generated"; +constexpr std::string_view comment_preamble = "# This file was AUTOGENERATED and is maintained using antler-proj tools.\n" + "# Modification or removal of the above line will cause antler-proj to skip this line.\n" + "# Any changes made to this file will be lost when any antler-proj tool updates this project."; /// Test to see if a file has the magic maintenance string. /// @return true if the file either does NOT exist OR contains the magic [[nodiscard]] bool has_magic(const std::filesystem::path& path, std::ostream& error_stream = std::cerr) { - std::error_code sec; - - if (!std::filesystem::exists(path, sec)) + if (!std::filesystem::exists(path)) return true; // search path for magic1. @@ -32,14 +30,11 @@ const std::string magic_all{ std::string(magic1) + "\n" + std::string(magic2) + return false; } - for (std::array buffer{}; ifs.getline(buffer.data(), buffer.size()); /**/) { - // Sanity check size of search string against the buffer. - static_assert(magic1.size() < buffer.size(), "Buffer is to small to test for magic value."); - if (magic1 == buffer.data()) - return true; - } + std::array buffer{}; - return false; + ifs.read(buffer.data(), buffer.size()); + + return memcmp(buffer.data(), magic.data(), buffer.size()) == 0; } @@ -59,13 +54,11 @@ bool project::populate(bool replace, std::ostream& error_stream) noexcept { if (!init_dirs(project_path, false, error_stream)) // expect_empty is `false`, it's okay if everthing exists. return false; // But its not okay if the filesystem doesn't already exist AND can't be created. - std::error_code sec; - // Check to see if the top level cmake file needs to be created. { auto path = project_path / cmake_lists; bool create = true; - if (!replace && std::filesystem::exists(path, sec)) { + if (!replace && std::filesystem::exists(path)) { // Look to see if the header contains the magic, if it does we will not create the file. create = has_magic(path); } @@ -77,19 +70,17 @@ bool project::populate(bool replace, std::ostream& error_stream) noexcept { else { try { ofs - << magic_all - << "\n" - << cmake::minimum(3,11) - << "\n" - << (m_ver.is_semver() ? cmake::project(m_name) : cmake::project(m_name, static_cast(m_ver)) ) + << project::magic_comment << "\n" + << comment_preamble << "\n" + //<< //(m_ver.is_semver() ? cmake::project(m_name) : cmake::project(m_name, static_cast(m_ver)) ) << "\n" - << cmake::add_subdirectory("libs") - << cmake::add_subdirectory("apps") + //<< cmake::add_subdirectory("libs") + //<< cmake::add_subdirectory("apps") << "\n" << "option(BUILD_TESTS \"Build and run the tests.\" On)\n" << "if(BUILD_TESTS)\n" << " enable_testing()\n" - << " " << cmake::add_subdirectory("tests") + //<< " " << cmake::add_subdirectory("tests") << "endif()\n" ; } diff --git a/src/project-print.cpp b/src/project-print.cpp index 67d92d0..57cea1b 100644 --- a/src/project-print.cpp +++ b/src/project-print.cpp @@ -21,7 +21,7 @@ namespace antler::project { inline static c4::csubstr to_csubstr(std::string_view sv) noexcept { return {sv.data(), sv.size()}; } inline static c4::csubstr to_csubstr(token tok) noexcept { return to_csubstr(token_to_str(tok)); } -inline static c4::csubstr to_csubstr(version v) noexcept { return to_csubstr(v.raw()); } +inline static c4::csubstr to_csubstr(version v) noexcept { return to_csubstr(v.to_string()); } void project::print(std::ostream& os) const noexcept { diff --git a/src/project.cpp b/src/project.cpp index 2ae896f..b74d523 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -308,5 +308,24 @@ void project::version(const antler::project::version& ver) noexcept { m_ver = ver; } +bool project::is_valid(std::ostream& os) { + bool rv = true; -} // namespace antler::project + const auto& test_populated = [&](auto var, std::string_view s) { + if ((var).empty()) { + os << s << " is unpopulated." << std::endl; + rv = false; + } + }; + + // First, validate the members of this object. + test_populated(m_path, "path"); + test_populated(m_name, "name"); + test_populated(m_ver, "version"); + + // Now validate: apps, libs, and tests. + + return rv; +} + +} // namespace antler::project \ No newline at end of file diff --git a/src/version.cpp b/src/version.cpp index 1cb6272..61cbd82 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -11,172 +11,22 @@ namespace antler::project { - -//--- constructors/destruct ------------------------------------------------------------------------------------------ - -version::version(std::string_view ver) { - load(ver); -} - - -version::version(const semver& sv) { - load(sv); -} - - -version::version(const self& rhs) - : m_raw{ rhs.m_raw } -{ - if (rhs.m_semver) - m_semver = std::make_unique(*rhs.m_semver); -} - - -//--- operators --------------------------------------------------------------------------------------------------------- - -version& version::operator=(std::string_view ver) { - load(ver); - return *this; -} - - -version& version::operator=(const semver& sv) { - load(sv); - return *this; -} - - -version& version::operator=(const self& rhs) { - m_raw = rhs.m_raw; - if (rhs.m_semver) - m_semver = std::make_unique(*rhs.m_semver); - else - m_semver.reset(); - return *this; -} - - -int64_t version::compare(const version& rhs) const noexcept { - if (is_semver() && rhs.is_semver()) - return m_semver->compare(*rhs.m_semver); - return raw_compare(rhs.m_raw); -} - -version::operator semver() const noexcept { - if (m_semver) - return *m_semver; - return semver{}; -} - - //--- alphabetic -------------------------------------------------------------------------------------------------------- void version::clear() noexcept { - m_raw.clear(); - m_semver.reset(); + major_comp = 0; + minor_comp = 0; + patch_comp = 0; + tweak_comp = 0; } bool version::empty() const noexcept { - return m_raw.empty(); + return major() == 0 && + minor() == 0 && + patch() == 0 && + tweak().empty(); } - -bool version::is_semver() const noexcept { - if (m_semver) - return true; - return false; -} - - -void version::load(std::string_view s) { - m_raw = s; - auto temp = semver::parse(m_raw); - if (!temp) - m_semver.reset(); - else - m_semver = std::make_unique(temp.value()); -} - - -void version::load(const semver& sv) { - std::stringstream ss; - ss << sv; - m_raw = ss.str(); - m_semver = std::make_unique(sv); -} - -std::string_view version::raw() const noexcept { - return m_raw; -} - - -int64_t version::raw_compare(std::string_view o) const noexcept { - std::vector l; - boost::split(l, m_raw, boost::is_any_of(".,-+;")); - - std::vector r; - boost::split(r, o, boost::is_any_of(".,-+;")); - - for (size_t i = 0; i < std::min(l.size(), r.size()); ++i) { - if (l[i] == r[i]) - continue; - - // Can we convert the whole thing to a number? - auto ln = l[i].find_first_not_of("0123456789"); - auto rn = r[i].find_first_not_of("0123456789"); - if (ln != std::string_view::npos || rn != std::string_view::npos) { - // Nope, one or both of the strings contain non numeric chars. - - // Get the number portion into either lnum or rnum int and the trailing non numeric chars into the string lremain or - // rremain. - int lnum = 0; - std::string_view lremain; - if (ln == std::string_view::npos) { - (void)string::from(l[i], lnum); - lremain = l[i]; - } - else { - (void)string::from(l[i].substr(0, ln), lnum); - lremain = l[i].substr(ln); - } - - int rnum = 0; - std::string_view rremain; - if (rn == std::string_view::npos) { - (void)string::from(r[i], rnum); - rremain = r[i]; - } - else { - (void)string::from(r[i].substr(0, rn), rnum); - rremain = r[i].substr(rn); - } - - // If the numbers differ, return the difference between them. - if (lnum < rnum) - return -1; - else if (lnum > rnum) - return 1; - - // Otherwise, return the difference in the remaining values. - return lremain.compare(rremain); - } - - // Convert into ints and compare (or do a simple string compare). - if (int lnum = 0, rnum = 0; string::from(l[i], lnum) && string::from(r[i], rnum)) { - return lnum < rnum ? -1 : lnum > rnum ? 1 : 0; - } - - // Nope, STILL can't convert to JUST a number. Just do a raw string compare. - return l[i].compare(r[i]); - } - - // Thus far, all the splits are equal, so just compare the number of splits. - // Example: `a.b.c <=> a.b.c.d` finds the first 3 splits equal, so we just compare the split count: `3 <=> 4`. - return l.size() < r.size() ? -1 : l.size() > r.size() ? 1 : 0; -} - - - -} // namespace antler::project +} // namespace antler::project \ No newline at end of file diff --git a/src/version_constraint.cpp b/src/version_constraint.cpp index 0b7aa19..c388ee8 100644 --- a/src/version_constraint.cpp +++ b/src/version_constraint.cpp @@ -28,15 +28,10 @@ namespace antler::project { namespace { // anonymous -constexpr semver::value_type min_semver_val = std::numeric_limits::min(); -constexpr semver::value_type max_semver_val = std::numeric_limits::max(); - -const semver min_semver{ min_semver_val, min_semver_val, min_semver_val }; -const semver max_semver{ max_semver_val, max_semver_val, max_semver_val }; - -const version min_version{ min_semver }; -const version max_version{ max_semver }; +constexpr uint16_t max_component = std::numeric_limits::max(); +static inline const version min_version{}; +static inline const version max_version{max_component, max_component, max_component}; /// Trim whitespace from the front and back of string. /// @param s The string to trim @@ -68,14 +63,6 @@ version_constraint::version_constraint(std::string_view ver) { } -//--- operators --------------------------------------------------------------------------------------------------------- - -version_constraint& version_constraint::operator=(std::string_view ver) { - load(ver); - return *this; -} - - //--- alphabetic -------------------------------------------------------------------------------------------------------- void version_constraint::clear() { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a0dfa54..6cd9486 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -2,22 +2,12 @@ include(CTest) -# I know. But it's appropriate for now. -file(GLOB src_cpp ./*.cpp) - -foreach(file_name IN ITEMS ${src_cpp}) - if(${CMAKE_VERSION} VERSION_LESS "3.20") - get_filename_component(temp ${file_name} NAME) - else() - cmake_path(GET file_name FILENAME temp) - endif() - - string(REPLACE ".cpp" "" name ${temp}) - #set(name "test-${name}") - - add_executable(${name} ${file_name}) - set_property(TARGET ${name} PROPERTY CXX_STANDARD 17) - target_link_libraries(${name} antler-project) - target_include_directories(${name} PRIVATE ./) - add_test(NAME ${name} COMMAND ${name}) -endforeach() +add_executable(unit_tests + #string_from.cpp + version_constraint.cpp + version.cpp +) + +set_property(TARGET unit_tests PROPERTY CXX_STANDARD 17) +target_link_libraries(unit_tests PRIVATE antler-project Catch2::Catch2) +catch_discover_tests(unit_tests) \ No newline at end of file diff --git a/test/string_from.cpp b/test/string_from.cpp index f0861e2..b2dc89b 100644 --- a/test/string_from.cpp +++ b/test/string_from.cpp @@ -1,4 +1,4 @@ -#include +#include "test_common.hpp" #include #include #include diff --git a/test/version.cpp b/test/version.cpp index 71da4cb..d1646e1 100644 --- a/test/version.cpp +++ b/test/version.cpp @@ -1,7 +1,7 @@ /// @copyright See `LICENSE` in the root directory of this project. #include -#include +#include "test_common.hpp" #include #include @@ -103,12 +103,4 @@ void test_version() { } break; } } -} - - -int main(int, char**) { - - test_version(); - - return result(); -} +} \ No newline at end of file diff --git a/test/version_constraint.cpp b/test/version_constraint.cpp index b6d712c..0c0fc40 100644 --- a/test/version_constraint.cpp +++ b/test/version_constraint.cpp @@ -1,7 +1,7 @@ /// @copyright See `LICENSE` in the root directory of this project. #include -#include +#include "test_common.hpp" #include #include diff --git a/test/version_semver.cpp b/test/version_semver.cpp2 similarity index 100% rename from test/version_semver.cpp rename to test/version_semver.cpp2 diff --git a/tools/main.cpp b/tools/main.cpp index 1eff103..f9f331c 100644 --- a/tools/main.cpp +++ b/tools/main.cpp @@ -11,7 +11,6 @@ #include -#include #include #include "add_to.hpp" From f04c7181147d892e9fe5bbc85746f8fbe8fef981 Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Mon, 6 Mar 2023 00:43:08 -0500 Subject: [PATCH 07/14] adding cmake population, stub app and lib support --- .gitmodules | 6 +- external/CMakeLists.txt | 4 + external/json | 1 - external/nlohmann | 1 + include/antler/project/cmake.hpp | 99 +++++++++++++++++ include/antler/project/dependency.hpp | 2 +- include/antler/project/object.hpp | 28 +++-- include/antler/project/project.hpp | 28 ++++- include/antler/project/source.hpp | 42 +++++++ include/antler/system/preprocessor.hpp | 65 ----------- src/CMakeLists.txt | 10 +- src/cmake.hpp | 44 -------- src/dependency.cpp | 2 +- src/location.cpp | 18 ++- src/object.cpp | 48 +------- src/project-is_valid.cpp | 26 ----- src/project-parse.cpp | 28 ++++- src/project-populate.cpp | 116 ++++++++------------ src/project-print.cpp | 17 ++- src/project.cpp | 104 +++++++++--------- src/token.hpp | 18 +-- src/token_def.hpp | 24 ---- tests/CMakeLists.txt | 1 + tests/cmake_tests.cpp | 52 +++++++++ tools/add_to.hpp | 146 +++++++++++++------------ tools/app_version.hpp | 27 +++++ tools/common.hpp | 2 + tools/main.cpp | 5 +- tools/update.hpp | 27 +++-- tools/validate.hpp | 12 +- 30 files changed, 534 insertions(+), 469 deletions(-) delete mode 160000 external/json create mode 160000 external/nlohmann create mode 100644 include/antler/project/cmake.hpp create mode 100644 include/antler/project/source.hpp delete mode 100644 include/antler/system/preprocessor.hpp delete mode 100644 src/cmake.hpp delete mode 100644 src/project-is_valid.cpp delete mode 100644 src/token_def.hpp create mode 100644 tests/cmake_tests.cpp create mode 100644 tools/app_version.hpp diff --git a/.gitmodules b/.gitmodules index 13044c8..0ac94fe 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,9 +4,9 @@ [submodule "external/magic_enum"] path = external/magic_enum url = https://github.com/Neargye/magic_enum -[submodule "external/json"] - path = external/json - url = https://github.com/boostorg/json [submodule "external/Catch2"] path = external/Catch2 url = https://github.com/catchorg/Catch2 +[submodule "external/nlohmann"] + path = external/nlohmann + url = https://github.com/nlohmann/json diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 8257fb9..0339620 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -11,3 +11,7 @@ set(CATCH_BUILD_TESTING OFF CACHE BOOL "Skip build testing") add_subdirectory(Catch2) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/Catch2/contrib) include(Catch) + +set(JSON_BuildTests OFF CACHE INTERNAL "Skip json tests") +set(JSON_Install OFF CACHE INTERNAL "Skip json install") +add_subdirectory(nlohmann) diff --git a/external/json b/external/json deleted file mode 160000 index 4d4d5fc..0000000 --- a/external/json +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4d4d5fc14e25f4eb40dd091edb66656383d26fd6 diff --git a/external/nlohmann b/external/nlohmann new file mode 160000 index 0000000..b230614 --- /dev/null +++ b/external/nlohmann @@ -0,0 +1 @@ +Subproject commit b2306145e1789368e6f261680e8dc007e91cc986 diff --git a/include/antler/project/cmake.hpp b/include/antler/project/cmake.hpp new file mode 100644 index 0000000..a223cd7 --- /dev/null +++ b/include/antler/project/cmake.hpp @@ -0,0 +1,99 @@ +#pragma once + +/// @copyright See `LICENSE` in the root directory of this project. + +#include + +#include // path +#include + + +namespace antler::project { + + struct cmake { + constexpr inline static uint16_t minimum_major = 3; + constexpr inline static uint16_t minimum_minor = 13; + + inline static std::string cmake_target_name(const project& proj, const object& obj) { + using namespace std::literals; + return std::string(proj.name()) + "_"s + std::string(obj.name()); + } + + template + inline static void emit_add_subdirectory(Stream& s, const std::filesystem::path& p) noexcept { + s << "add_subdirectory(" << p.string() << ")\n"; + } + + template + inline static void emit_preamble(Stream& s, const project& proj) noexcept { + s << "# Generated with antler-proj tool, modify at your own risk\n"; + s << "cmake_minimum_required(VERSION " << minimum_major << "." << minimum_minor << ")\n"; + s << "project(\"" << proj.name() << "\" VERSION " << proj.version().major() << "." << + proj.version().minor() << "." << proj.version().patch() << ")\n"; + s << std::endl; + } + + template + inline static void emit_add_base_subdirs(Stream& s) noexcept { + s << "add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../libs ${CMAKE_CURRENT_BINARY_DIR}/libs)\n"; + s << "add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../tests ${CMAKE_CURRENT_BINARY_DIR}/tests)\n"; + s << std::endl; + } + + template + inline static void emit_dependencies(Stream& s, const project& proj, const object& obj) noexcept { + for (const auto& dep : obj.dependencies()) { + s << "target_link_libraries(" << cmake_target_name(proj, obj) << " PUBLIC " << dep.name() <<")\n"; + } + } + + template + inline static void emit_entry(Stream& s, const project& proj) noexcept { + s << "include(ExternalProject)\n"; + s << "if(CDT_ROOT STREQUAL \"\" OR NOT CDT_ROOT)\n"; + s << "\tfind_package(cdt)\n"; + s << "endif()\n\n"; + s << "ExternalProject_Add(\n"; + s << "\t" << proj.name() << "_project\n"; + s << "\tSOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/apps\n"; + s << "\tBINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/" << proj.name() << "\n"; + s << "\tCMAKE_ARGS -DCMAKE_TOOLCHAIN_FILE=${CDT_ROOT}/lib/cmake/cdt/CDTWasmToolchain.cmake\n"; + s << "\tUPDATE_COMMAND \"\"\n"; + s << "\tPATCH_COMMAND \"\"\n"; + s << "\tTEST_COMMAND \"\"\n"; + s << "\tINSTALL_COMMAND \"\"\n"; + s << "\tBUILD_ALWAYS 1\n"; + s << ")\n"; + s << std::endl; + } + + template + inline static void emit_app(Stream& s, const object& app, const project& proj) noexcept { + s << "add_executable(" << cmake_target_name(proj, app) << " " << app.name() << ".cpp" << ")\n"; + s << "target_include_directories(" << cmake_target_name(proj, app) + << " PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include " + << "${CMAKE_CURRENT_SOURCE_DIR}/include/" << app.name() << " ./ )\n\n"; + emit_dependencies(s, proj, app); + s << std::endl; + } + + template + inline static void emit_project(Stream& s, const project& proj) noexcept { + for (auto app : proj.apps()) { + emit_app(s, app, proj); + } + } + }; + +/// @return the cmake_minimum string with trailing newline. +[[nodiscard]] std::string minimum(unsigned major = 0, unsigned minor = 0, unsigned patch = 0) noexcept; + +/// @return the add_subdirectory string: add_subdirectory( "" )\n +[[nodiscard]] std::string add_subdirectory(const std::filesystem::path& path) noexcept; + +/// @return A string including the project name: project("")\n +//[[nodiscard]] std::string project(std::string_view proj_name); +/// @return A string including the project name: project("" VERSION )\n +//[[nodiscard]] std::string project(std::string_view proj_name, const project::semver& ver) noexcept; + +} // namespace antler::project diff --git a/include/antler/project/dependency.hpp b/include/antler/project/dependency.hpp index 8dc0432..7a4ec56 100644 --- a/include/antler/project/dependency.hpp +++ b/include/antler/project/dependency.hpp @@ -102,7 +102,7 @@ class dependency { /// Test to see if the dependency is valid. /// @return true if dependency is an archive, github repo, or local and is reachable - [[nodiscard]] bool is_valid() const noexcept; + [[nodiscard]] bool is_valid_location() const noexcept; /// Test to see if a location is valid. diff --git a/include/antler/project/object.hpp b/include/antler/project/object.hpp index 588c0e3..d85c411 100644 --- a/include/antler/project/object.hpp +++ b/include/antler/project/object.hpp @@ -36,8 +36,9 @@ class object { /// @param ot The type of this object. Must be app or lib. /// @param name The Name of the object. /// @param lang The language type of this object. - /// @param opts Compile time options for this object. May be empty. - object(type_t ot, std::string_view name, const std::string& lang, std::string_view opts); + /// @param copts Compile time options for this object. May be empty. + /// @param lopts Compile time options for this object. May be empty. + object(type_t ot, std::string_view name, const std::string& lang, std::string_view copts, std::string_view lopts); /// Object constructor for test type. /// @param name The Name of the object. /// @param command The command to run for this test. @@ -58,11 +59,18 @@ class object { /// @param lang The new language value to store. void language(std::string_view lang) noexcept; - /// @return Current options. - [[nodiscard]] std::string_view options() const noexcept; + /// @return Current compile options. + [[nodiscard]] inline std::string_view compile_options() const noexcept { return m_comp_options; } + /// @return Current link options. + [[nodiscard]] inline std::string_view link_options() const noexcept { return m_link_options; } + + /// Replace any existing options with the new value. + /// @param options The new options to store. + void compile_options(std::string_view options) noexcept { m_comp_options = options; } + /// Replace any existing options with the new value. /// @param options The new options to store. - void options(std::string_view options) noexcept; + void link_options(std::string_view options) noexcept { m_link_options = options; } /// @return The test command. [[nodiscard]] std::string_view command() const noexcept; @@ -73,6 +81,7 @@ class object { /// Update or insert a dependency. /// @param dep The dependency to upsert. void upsert_dependency(antler::project::dependency&& dep) noexcept; + /// Remove dependency if it exists. /// @return true if the dependency was found and removed; otherwise, false (i.e. dependency does not exist) bool remove_dependency(std::string_view name) noexcept; @@ -93,14 +102,11 @@ class object { // app, lib: std::string m_language = ""; ///< Language type, only valid for app or lib. - std::string m_options = ""; ///< Compile options, only valid for app or lib. + std::string m_comp_options = ""; + std::string m_link_options = ""; // test: std::string m_command = ""; ///< Test command, only valid for test. }; -} // namespace antler::project - - -std::ostream& operator<<(std::ostream& os, const antler::project::object::type_t& e); -std::istream& operator>>(std::istream& is, antler::project::object::type_t& e); \ No newline at end of file +} // namespace antler::project \ No newline at end of file diff --git a/include/antler/project/project.hpp b/include/antler/project/project.hpp index 9bf44e0..d5d3d07 100644 --- a/include/antler/project/project.hpp +++ b/include/antler/project/project.hpp @@ -19,7 +19,9 @@ class project { constexpr static inline std::string_view manifest_name = "project.yml"; constexpr static inline std::string_view magic_comment = "#antler-proj::generated"; - + constexpr static inline std::string_view comment_preamble = "# This file was AUTOGENERATED.\n" + "# Modification or removal of the above lines will cause antler-proj to deem the project as invalid.\n" + "# Any changes made to this file will be lost when the tool updates this project."; // constructors project() = default; project(const std::filesystem::path&); @@ -56,7 +58,18 @@ class project { /// update or insert a new object. It's type() is evaluated to determine which list it belongs in. If the object already /// exists, an update is performed by removing the old one and adding the new one. /// @param obj The object to update or insert. - void upsert(object&& obj) noexcept; + template + inline void upsert(object&& obj) noexcept { + if constexpr (Ty == object::type_t::app) + return upsert_app(std::move(obj)); + else if constexpr (Ty == object::type_t::lib) + return upsert_lib(std::move(obj)); + else if constexpr (Ty == object::type_t::test) + return upsert_test(std::move(obj)); + else + throw std::runtime_error("internal failure"); + } + /// update or insert a new application object. If the object already exists, an update is performed by removing the old one and /// adding the new one. /// @param obj The object to update or insert. @@ -98,10 +111,16 @@ class project { /// @return A ref to the test list. [[nodiscard]] inline antler::project::object::list_t& tests() noexcept { return m_tests; } + /// Validate a dependency + /// @param dep Dependency to check + /// @param error_stream Stream location for printing warnings and errors. + /// @return true if the project is valid; otherwise, false. + [[nodiscard]] bool validate_dependency(const dependency& dep, std::ostream& error_stream = std::cerr) const noexcept; + /// Validate the project. /// @param error_stream Stream location for printing warnings and errors. /// @return true if the project is valid; otherwise, false. - [[nodiscard]] bool is_valid(std::ostream& error_stream = std::cerr); + [[nodiscard]] bool has_valid_dependencies(std::ostream& error_stream = std::cerr) const noexcept; /// Print the yaml object to a stream. @@ -147,6 +166,9 @@ class project { [[nodiscard]] static bool update_path(std::filesystem::path& path) noexcept; private: + inline void create_app(std::string_view name) { + } + std::filesystem::path m_path; ///< path to the project.yaml file. std::string m_name; ///< The project name. antler::project::version m_ver; ///< The version information for this project. diff --git a/include/antler/project/source.hpp b/include/antler/project/source.hpp new file mode 100644 index 0000000..f2f73a1 --- /dev/null +++ b/include/antler/project/source.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include + +#include "project.hpp" + +namespace antler::project { + // TODO in the proper release of ANTLER this system will be + // replaced with a dynamic system the frontend dev (not smart contract dev) would supply + struct app_source { + inline static void create_source_file(std::filesystem::path p, const std::string& name) { + p /= std::filesystem::path("apps") / name / (name+".cpp"); + std::ofstream src{p.c_str()}; + src << "#include <" << name << ".hpp>\n\n"; + src << "[[eosio::action]]\n"; + src << "void " << name << "::hi( name nm ) {\n"; + src << " /* generated example action */\n"; + src << " print_f(\"Hello : %\n\", nm)\n"; + src << "}\n"; + src << std::endl; + src.close(); + } + + inline static void create_specification_file(std::filesystem::path p, const std::string& name) { + p /= std::filesystem::path("include") / name / (name+".hpp"); + std::ofstream hdr{p.c_str()}; + hdr << "#include \n\n"; + hdr << "using namespace eosio;\n\n"; + hdr << "class [[eosio::contract]] " << name << ": public contract {\n"; + hdr << " public:\n"; + hdr << " using contract::contract;\n\n"; + hdr << " [[eosio::action]]\n"; + hdr << " void hi( name nm );\n"; + hdr << " using hi_action = action_wrapper<\"hi\"_n, &" << name << "::hi>;\n"; + hdr << "};\n"; + hdr << std::endl; + hdr.close(); + } + }; +} \ No newline at end of file diff --git a/include/antler/system/preprocessor.hpp b/include/antler/system/preprocessor.hpp deleted file mode 100644 index 2e9605f..0000000 --- a/include/antler/system/preprocessor.hpp +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -// TODO future release use meta_refl for object reflection in general -// additional support for enums will be added - -/* clang-format off */ -#define _ANTLER_PROJ_CREATE_ENUM(N, A, E) N, -#define _ANTLER_PROJ_CREATE_STRINGS(N, A, E) #N, -#define _ANTLER_PROJ_CREATE_MAP(N, A, E) {#N, E::N}, - -#define ANTLER_PROJ_CREATE_ENUM(NAME) \ - enum class NAME : uint64_t { \ - ANTLER_PROJ_## NAME( _ANTLER_PROJ_CREATE_ENUM, NAME) \ - }; - -#define ANTLER_PROJ_CREATE_STRINGS(NAME) \ - static std::vector NAME ## _strings() { \ - static std::vector v = { \ - ANTLER_PROJ_ ## NAME( _ANTLER_PROJ_CREATE_STRINGS, \ - NAME ) }; \ - return v; \ - } - -#define ANTLER_PROJ_CREATE_MAP(NAME) \ - static std::unordered_map NAME ## _map() { \ - static std::unordered_map m = { \ - ANTLER_PROJ_ ## NAME( _ANTLER_PROJ_CREATE_MAP, \ - NAME ) }; \ - return m; \ - } - -#define ANTLER_PROJ_CREATE_TO_STR(NAME) \ - inline static std::string_view to_string(NAME o) noexcept { \ - const auto& strings = NAME ## _strings(); \ - if (static_cast(o) >= strings.size()) \ - return "error"; \ - return strings[static_cast(o)]; \ - } - -#define ANTLER_PROJ_CREATE_FROM_STR(NAME) \ - template \ - inline static NAME from_string(std::string_view s) noexcept { \ - return NAME ## _map()[s]; \ - } - -#define ANTLER_PROJ_CREATE_OPERATOR(NAME) \ - inline static std::ostream& operator<<(std::ostream& os, NAME o) { \ - os << to_string(o); \ - return os; \ - } - -#define ANTLER_PROJ_ENUM(NAME) \ - ANTLER_PROJ_CREATE_ENUM(NAME) \ - ANTLER_PROJ_CREATE_STRINGS(NAME) \ - ANTLER_PROJ_CREATE_MAP(NAME) \ - ANTLER_PROJ_CREATE_TO_STR(NAME) \ - ANTLER_PROJ_CREATE_FROM_STR(NAME) \ - ANTLER_PROJ_CREATE_OPERATOR(NAME) - -/* clang-format on */ \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4f8ad3e..fd5f633 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,6 +1,3 @@ -find_package( Boost REQUIRED COMPONENTS system) -set(Boost_USE_STATIC_LIBS ON) - include(FindPkgConfig) pkg_check_modules(CURL libcurl REQUIRED) @@ -16,6 +13,9 @@ add_library(antler-project version_constraint.cpp ) -target_include_directories(antler-project PUBLIC ../include ../external/json/ ${CMAKE_CURRENT_BINARY_DIR}/../include) -target_link_libraries(antler-project PUBLIC magic_enum ryml Boost::system ${CURL_LIBRARIES}) +target_include_directories(antler-project PUBLIC ../include + ${CMAKE_CURRENT_BINARY_DIR}/../include + ${CURL_INCLUDE_DIRS} +) +target_link_libraries(antler-project PUBLIC magic_enum ryml nlohmann_json::nlohmann_json ${CURL_LIBRARIES}) set_property(TARGET antler-project PROPERTY CXX_STANDARD 17) \ No newline at end of file diff --git a/src/cmake.hpp b/src/cmake.hpp deleted file mode 100644 index 9138689..0000000 --- a/src/cmake.hpp +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -/// @copyright See `LICENSE` in the root directory of this project. - -#include - -#include // path - - -namespace antler { - - struct cmake { - template - inline void emit_version(Stream& s) const noexcept { s << major << "." << minor; } - - template - inline void emit_add_subdirectory(Stream& s, const std::filesystem::path& p) const noexcept { - s << "add_subdirectory(" << p.string() << ")\n"; - } - - template - inline void emit_preamble(Stream& s, const project::project& proj) const noexcept { - s << "# Generated with antler-proj tool, modify at your own risk\n"; - s << "cmake_minimum_required(VERSION" << major << "." << minor << ")\n"; - s << "project(\"" << proj.name() << "\" VERSION" << proj.version().major() << "." << - proj.version().minor() << "." << proj.version().patch() << ")" << std::endl; - } - - uint16_t major = 3; - uint16_t minor = 11; - }; - -/// @return the cmake_minimum string with trailing newline. -[[nodiscard]] std::string minimum(unsigned major = 0, unsigned minor = 0, unsigned patch = 0) noexcept; - -/// @return the add_subdirectory string: add_subdirectory( "" )\n -[[nodiscard]] std::string add_subdirectory(const std::filesystem::path& path) noexcept; - -/// @return A string including the project name: project("")\n -//[[nodiscard]] std::string project(std::string_view proj_name); -/// @return A string including the project name: project("" VERSION )\n -//[[nodiscard]] std::string project(std::string_view proj_name, const project::semver& ver) noexcept; - -} // namespace antler diff --git a/src/dependency.cpp b/src/dependency.cpp index e831e5a..538feee 100644 --- a/src/dependency.cpp +++ b/src/dependency.cpp @@ -141,7 +141,7 @@ void dependency::tag(std::string_view s) noexcept { m_tag_or_commit = s; } -bool dependency::is_valid() const noexcept { +bool dependency::is_valid_location() const noexcept { if (!m_tag_or_commit.empty()) { if (!m_rel.empty()) { std::cerr << "release AND tag/commit flags are not valid at the same time for location."; diff --git a/src/location.cpp b/src/location.cpp index fa47deb..5d93ce2 100644 --- a/src/location.cpp +++ b/src/location.cpp @@ -8,6 +8,7 @@ #include #include +#include namespace antler::project::location { @@ -62,7 +63,6 @@ struct curl { curl_easy_setopt(curl_obj, CURLOPT_WRITEDATA, &buff); auto res = curl_easy_perform(curl_obj); - std::cerr << "Failure: " << curl_easy_strerror(res) << std::endl; if (res) { std::cerr << "Failure: " << curl_easy_strerror(res) << std::endl; throw std::runtime_error("internal curl failure"); @@ -103,7 +103,11 @@ struct github { inline bool is_reachable(std::string_view s) { try { - (void)request(s); + auto js = nlohmann::json::parse(request(s)); + auto msg = js["message"]; + if (!msg.is_null()) + if (msg == "Not Found") + return false; return true; } catch(...) { return false; @@ -132,8 +136,6 @@ struct github { std::string bearer_token; }; - - bool is_archive(std::string_view s) { return ends_with(s, ".tar.gz") || ends_with(s, ".tgz") || @@ -148,19 +150,15 @@ bool is_github_archive(std::string_view s) { return is_github(s) && is_archive(s bool is_url(std::string_view l) { return curl::is_url(l); } -bool is_local(std::string_view l) { return std::filesystem::exists(l); } - bool is_github_org_repo_shorthand(std::string_view s) { return github::is_shorthand(s); } bool is_github_repo(std::string_view s) { return is_github(s) && !is_archive(s); } bool is_reachable(std::string_view l) { - if (is_local(l)) { - return true; + if (is_github_repo(l) || is_github_org_repo_shorthand(l)) { + return github{}.is_reachable(l); } else if (is_archive(l) || is_url(l) || is_github_archive(l)) { return curl{}.is_reachable(l); - } else if (is_github_repo(l) || is_github_org_repo_shorthand(l)) { - return github{}.is_reachable(l); } else { return false; } diff --git a/src/object.cpp b/src/object.cpp index dfe102d..a2c6740 100644 --- a/src/object.cpp +++ b/src/object.cpp @@ -15,11 +15,12 @@ object::object(type_t ot) : m_type{ ot } {} -object::object(type_t ot, std::string_view name, const std::string& lang, std::string_view opts) +object::object(type_t ot, std::string_view name, const std::string& lang, std::string_view copts, std::string_view lopts) : m_type{ ot } , m_name{ name } , m_language{ system::to_upper(lang) } - , m_options{ opts } + , m_comp_options{ copts } + , m_link_options{ lopts } { } @@ -82,17 +83,6 @@ void object::name(std::string_view s) noexcept { m_name = s; } - -std::string_view object::options() const noexcept { - return m_options; -} - - -void object::options(std::string_view options) noexcept { - m_options = options; -} - - bool object::remove_dependency(std::string_view name) noexcept { // If possible, find a dependency with matching name and return it. auto i = std::find_if(m_dependencies.begin(), m_dependencies.end(), [name](const antler::project::dependency& d) { return d.name() == name; } ); @@ -109,7 +99,7 @@ object::type_t object::type() const noexcept { void object::upsert_dependency(antler::project::dependency&& dep) noexcept { - // If possible, find a dependency with matching name and reutrn it. + // If possible, find a dependency with matching name and reurrn it. auto i = std::find_if(m_dependencies.begin(), m_dependencies.end(), [dep](const antler::project::dependency& d) { return d.name() == dep.name(); } ); if( i == m_dependencies.end() ) m_dependencies.emplace_back(dep); @@ -117,32 +107,4 @@ void object::upsert_dependency(antler::project::dependency&& dep) noexcept { *i = dep; } - -} // namespace antler::project - - -//--- global operators ------------------------------------------------------------------------------------------ - - -std::ostream& operator<<(std::ostream& os, const antler::project::object::type_t& e) { - os << magic_enum::enum_name(e); - return os; -} - - -std::istream& operator>>(std::istream& is, antler::project::object::type_t& e) { - - std::string temp; - if (is >> temp) { - // Try string as is. - auto opt = magic_enum::enum_cast(temp); - if (opt.has_value()) { - e = opt.value(); - return is; - } - } - - // This might be an exceptional state and so maybe we should throw an exception? - e = antler::project::object::type_t::error; - return is; -} \ No newline at end of file +} // namespace antler::project \ No newline at end of file diff --git a/src/project-is_valid.cpp b/src/project-is_valid.cpp deleted file mode 100644 index f216f78..0000000 --- a/src/project-is_valid.cpp +++ /dev/null @@ -1,26 +0,0 @@ -/// @copyright See `LICENSE` in the root directory of this project. - -#include - - -#define TEST_POPULATED(X,Y) if((X).empty()) { os << (Y) << " is unpopulated.\n"; rv = false; } - -namespace antler::project { - -bool project::is_valid(std::ostream& os) { - - bool rv = true; - - // First, validate the members of this object. - TEST_POPULATED(m_path, "path"); - TEST_POPULATED(m_name, "name"); - TEST_POPULATED(m_ver, "version"); - - // Now validate: apps, libs, and tests. - - - return rv; -} - - -} // namespace antler::project diff --git a/src/project-parse.cpp b/src/project-parse.cpp index fca1c06..8a63352 100644 --- a/src/project-parse.cpp +++ b/src/project-parse.cpp @@ -185,7 +185,8 @@ template case token::apps: case token::tests: case token::lang: - case token::options: + case token::compile_options: + case token::link_options: case token::depends: case token::command: { os << "Unexpected tag in dependency list: " << tok << "\n"; @@ -260,14 +261,14 @@ template rv.language(sv_from_csubstr(lang)); } break; - case token::options: { + case token::compile_options: { // Sanity check before setting value. if (!i.has_val()) { os << tok << " tag in " << type << " list with no value.\n"; return {}; } - if (!rv.options().empty()) { - os << "Duplicate " << tok << " values in " << type << " list: " << rv.options() << ", " << i.val() << "\n"; + if (!rv.compile_options().empty()) { + os << "Duplicate " << tok << " values in " << type << " list: " << rv.compile_options() << ", " << i.val() << "\n"; return {}; } if (type == object::type_t::test) { @@ -275,9 +276,26 @@ template return {}; } - rv.options(sv_from_csubstr(i.val())); + rv.compile_options(sv_from_csubstr(i.val())); } break; + case token::link_options: { + // Sanity check before setting value. + if (!i.has_val()) { + os << tok << " tag in " << type << " list with no value.\n"; + return {}; + } + if (!rv.link_options().empty()) { + os << "Duplicate " << tok << " values in " << type << " list: " << rv.link_options() << ", " << i.val() << "\n"; + return {}; + } + if (type == object::type_t::test) { + os << type << " objects may not have an options tag."; + return {}; + } + + rv.compile_options(sv_from_csubstr(i.val())); + } break; case token::command: { // Sanity check before setting value. diff --git a/src/project-populate.cpp b/src/project-populate.cpp index 0a88ec2..e97344b 100644 --- a/src/project-populate.cpp +++ b/src/project-populate.cpp @@ -1,7 +1,7 @@ /// @copyright See `LICENSE` in the root directory of this project. #include -#include "cmake.hpp" +#include #include #include @@ -12,10 +12,7 @@ namespace antler::project { namespace { // anonymous const std::filesystem::path cmake_lists{"CMakeLists.txt"}; -constexpr std::string_view magic = "#antler-proj::generated"; -constexpr std::string_view comment_preamble = "# This file was AUTOGENERATED and is maintained using antler-proj tools.\n" - "# Modification or removal of the above line will cause antler-proj to skip this line.\n" - "# Any changes made to this file will be lost when any antler-proj tool updates this project."; + /// Test to see if a file has the magic maintenance string. /// @return true if the file either does NOT exist OR contains the magic @@ -31,11 +28,11 @@ constexpr std::string_view comment_preamble = "# This file was AUTOGENERATED and return false; } - std::array buffer{}; + std::array buffer{}; ifs.read(buffer.data(), buffer.size()); - return std::memcmp(buffer.data(), magic.data(), buffer.size()) == 0; + return std::memcmp(buffer.data(), project::magic_comment.data(), buffer.size()) == 0; } @@ -43,74 +40,57 @@ constexpr std::string_view comment_preamble = "# This file was AUTOGENERATED and bool project::populate(bool replace, std::ostream& error_stream) noexcept { - - // Sanity check: ensure path is valid. - if (m_path.empty()) { - error_stream << "Can not populate a project without a path.\n"; - return false; - } - // Find the project path, and make sure the subdirs/project directory tree exists. auto project_path = m_path.parent_path(); if (!init_dirs(project_path, false, error_stream)) // expect_empty is `false`, it's okay if everthing exists. return false; // But its not okay if the filesystem doesn't already exist AND can't be created. - // Check to see if the top level cmake file needs to be created. - { - auto path = project_path / cmake_lists; - bool create = true; - if (!replace && std::filesystem::exists(path)) { - // Look to see if the header contains the magic, if it does we will not create the file. - create = has_magic(path); + auto root_path = project_path / cmake_lists; + auto apps_path = project_path / std::filesystem::path("apps") / cmake_lists; + auto libs_path = project_path / std::filesystem::path("libs") / cmake_lists; + auto tests_path = project_path / std::filesystem::path("tests") / cmake_lists; + + bool create = true; + // Look to see if the header contains the magic, if it does we will not create the file. + std::ofstream rfs(root_path); + std::ofstream afs(apps_path); + std::ofstream lfs(libs_path); + std::ofstream tfs(tests_path); + + lfs << "\n"; + lfs.close(); + + tfs << "\n"; + tfs.close(); + + const auto& try_emit = [&](auto path, auto func) { + try { + func(); + return true; + } catch (const std::exception& e) { + error_stream << "Error writing to " << path << ": " << e.what() << "\n"; + return false; + } catch (...) { + error_stream << "Error writing to " << path << ": UNKNOWN\n"; + return false; } - if (create) { - std::ofstream ofs(path); - if (!ofs.good()) { - error_stream << "Can not open path for writing: << " << path << "\n"; - } - else { - try { - ofs - << project::magic_comment << "\n" - << comment_preamble << "\n" - //<< //(m_ver.is_semver() ? cmake::project(m_name) : cmake::project(m_name, static_cast(m_ver)) ) - << "\n" - //<< cmake::add_subdirectory("libs") - //<< cmake::add_subdirectory("apps") - << "\n" - << "option(BUILD_TESTS \"Build and run the tests.\" On)\n" - << "if(BUILD_TESTS)\n" - << " enable_testing()\n" - //<< " " << cmake::add_subdirectory("tests") - << "endif()\n" - ; - } - catch(std::exception& e) { - error_stream << "Error writing to " << path << ": " << e.what() << "\n"; - return false; - } - catch(...) { - error_stream << "Error writing to " << path << ": UNKNOWN\n"; - return false; - } - } - } - } - - - - // At each level we are going to want to create a CMakeLists.txt file and zero or more `.cmake` include files. - - // Each file includes a header indicating the user should not modify it. And if they do, changes will be lost unless they - // delete this line; however, if they delete the line, auto updates will no longer be possible. - - // Then we update everything. - - - - // Finally CMake the project. Ensure the output goes to a log file. - + }; + if (!rfs.good() && !afs.good()) { + error_stream << "Can not open path for writing\n"; + return false; + } else { + return try_emit(root_path, [&]() { + cmake::emit_preamble(rfs, *this); + cmake::emit_entry(rfs, *this); + }) + && + try_emit(apps_path, [&]() { + cmake::emit_preamble(afs, *this); + cmake::emit_add_base_subdirs(afs); + cmake::emit_project(afs, *this); + }); + } return false; } diff --git a/src/project-print.cpp b/src/project-print.cpp index 9edb1f2..8c37a2a 100644 --- a/src/project-print.cpp +++ b/src/project-print.cpp @@ -76,8 +76,10 @@ void project::print(std::ostream& os) const noexcept { if (!obj.language().empty()) map_node[to_csubstr(token::lang)] << to_csubstr(obj.language()); // options - if (!obj.options().empty()) - map_node[to_csubstr(token::options)] << to_csubstr(obj.options()); + if (!obj.compile_options().empty()) + map_node[to_csubstr(token::compile_options)] << to_csubstr(obj.compile_options()); + if (!obj.link_options().empty()) + map_node[to_csubstr(token::link_options)] << to_csubstr(obj.link_options()); // depends - dependencies are also a list. if (!obj.dependencies().empty()) { @@ -130,14 +132,9 @@ void project::print(std::ostream& os) const noexcept { // Add a header. - os << "# `" << (!m_name.empty() ? m_name : "UNNAMED") << "` project file.\n" - << "# generated by antler-proj v" - << system::version::full() << std::endl; - - // If we know the source file name, then add it to the header. - if (!m_path.empty()) - os << "# source: " << m_path.string() << std::endl; - + os << magic_comment << "\n"; + os << comment_preamble << "\n"; + os << "# generated with v" << system::version::full() << std::endl; os << "#\n" << "# This file was auto-generated. Be aware antler-proj may discard added comments.\n" diff --git a/src/project.cpp b/src/project.cpp index 1f21f32..7fe5277 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -48,44 +48,44 @@ void project::name(std::string_view s) noexcept { m_name = s; } +namespace detail { + template + inline static typename std::remove_reference_t::value_type* find_if(List&& objs, std::string_view name) { + auto tmp = std::find_if(objs.begin(), objs.end(), [name](const auto& o) { return o.name() == name; }); + if (tmp != objs.end()) + return &*tmp; + else + return nullptr; + } + +} antler::project::object& project::object(std::string_view name) { - auto temp = std::find_if(m_apps.begin(), m_apps.end(), [name](const auto& o) { return o.name() == name; }); - if (temp != m_apps.end()) - return *temp; - - temp = std::find_if(m_libs.begin(), m_libs.end(), [name](const auto& o) { return o.name() == name; }); - if (temp != m_libs.end()) - return *temp; - - temp= std::find_if(m_tests.begin(), m_tests.end(), [name](const auto& o) { return o.name() == name; }); - if (temp != m_tests.end()) - return *temp; + if (auto ptr = detail::find_if(m_apps, name); ptr != nullptr) + return *ptr; + if (auto ptr = detail::find_if(m_libs, name); ptr != nullptr) + return *ptr; + if (auto ptr = detail::find_if(m_tests, name); ptr != nullptr) + return *ptr; throw std::runtime_error("object not found."); } bool project::object_exists(std::string_view name, object::type_t type) const noexcept { - - if (type == object::type_t::any || type == object::type_t::app) { - auto i = std::find_if(m_apps.begin(), m_apps.end(), [name](const auto& o) { return o.name() == name; }); - if (i != m_apps.end()) - return true; - } - - if (type == object::type_t::any || type == object::type_t::lib) { - auto i = std::find_if(m_libs.begin(), m_libs.end(), [name](const auto& o) { return o.name() == name; }); - if (i != m_libs.end()) - return true; - } - - if (type == object::type_t::any || type == object::type_t::test) { - auto i = std::find_if(m_tests.begin(), m_tests.end(), [name](const auto& o) { return o.name() == name; }); - if (i != m_tests.end()) - return true; + const auto& exists = [&](auto objs) { return detail::find_if(objs, name) != nullptr; }; + + switch (type) { + case object::type_t::app: + return exists(m_apps); + case object::type_t::lib: + return exists(m_libs); + case object::type_t::test: + return exists(m_tests); + case object::type_t::any: + return exists(m_apps) || exists(m_libs) || exists(m_tests); + default: + return false; } - - return false; } @@ -196,25 +196,6 @@ bool project::update_path(std::filesystem::path& path) noexcept { return false; } - -void project::upsert(antler::project::object&& obj) noexcept { - - switch (obj.type()) { - case object::type_t::app: upsert_app(std::move(obj)); break; - case object::type_t::lib: upsert_lib(std::move(obj)); break; - case object::type_t::test: upsert_test(std::move(obj)); break; - - case object::type_t::any: - case object::type_t::error: - // error state! - std::cerr << "Failed to upsert object with name and type: " << obj.name() << ", " << obj.type() << std::endl; - return; - - // Never add a default. - } -} - - void project::upsert_app(antler::project::object&& app) noexcept { auto i = std::find_if(m_apps.begin(), m_apps.end(), [app](const antler::project::object& o) { return o.name() == app.name(); }); @@ -254,5 +235,30 @@ void project::version(const antler::project::version& ver) noexcept { m_ver = ver; } +bool project::validate_dependency(const dependency& dep, std::ostream& errs) const noexcept { + if (dep.location().empty()) { + std::cout << "DEP " << dep.name() << std::endl; + return object_exists(dep.name(), object::type_t::lib); + } else if (!dep.is_valid_location()) { + errs << "Error dependency: " << dep.name() << " is invalid." << std::endl; + return false; + } + return true; +} + +bool project::has_valid_dependencies(std::ostream& errs) const noexcept { + const auto& test_deps = [&](auto objs) { + for (const auto& o : objs) { + for (const auto& d : o.dependencies()) { + if (!validate_dependency(d, errs)) + return false; + } + } + return true; + }; + + return test_deps(m_apps) && test_deps(m_libs) && test_deps(m_tests); +} + } // namespace antler::project diff --git a/src/token.hpp b/src/token.hpp index 21e9026..760dff5 100644 --- a/src/token.hpp +++ b/src/token.hpp @@ -8,9 +8,6 @@ #include -//#include -//#include "token_def.hpp" - namespace antler::project { enum class token : uint8_t { apps, @@ -21,7 +18,8 @@ namespace antler::project { lang, libraries, name, - options, + compile_options, + link_options, patch, project, release, @@ -30,16 +28,4 @@ namespace antler::project { version, error }; - - //inline static to_string(token t) { - - //} - - //inline static std::ostream& operator<<(std::ostream& os, const token& e) { - - //} - - //inline static std::istream& operator>>(std::istream& is, token& e) { } // namespace antler::project - -using namespace magic_enum::ostream_operators; \ No newline at end of file diff --git a/src/token_def.hpp b/src/token_def.hpp deleted file mode 100644 index ac46f6a..0000000 --- a/src/token_def.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include -#include - -/* clang-format off */ -#define ANTLER_PROJ_tokens(macro, E) \ - macro(apps, 0, E) \ - macro(command, 1, E) \ - macro(depends, 2, E) \ - macro(from, 3, E) \ - macro(hash, 4, E) \ - macro(lang, 5, E) \ - macro(libraries, 6, E) \ - macro(name, 7, E) \ - macro(options, 8, E) \ - macro(patch, 9, E) \ - macro(project, 10, E) \ - macro(release, 11, E) \ - macro(tag, 12, E) \ - macro(tests, 13, E) \ - macro(version, 14, E) \ - macro(error, -1, E) -/* clang-format on */ \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e00c616..bfa2bde 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -4,6 +4,7 @@ include(CTest) add_executable(unit_tests main.cpp + cmake_tests.cpp init_tests.cpp #string_from.cpp version_constraint_tests.cpp diff --git a/tests/cmake_tests.cpp b/tests/cmake_tests.cpp new file mode 100644 index 0000000..e1f1261 --- /dev/null +++ b/tests/cmake_tests.cpp @@ -0,0 +1,52 @@ +/// @copyright See `LICENSE` in the root directory of this project. + +#include +#include + +#include + +#include + + +TEST_CASE("Testing cmake emission") { + using namespace antler::project; + + project proj = {}; + + proj.name("test_proj"); + proj.version({"v1.0.0"}); + + std::stringstream ss; + + cmake::emit_preamble(ss, proj); + + constexpr std::string_view cmake_preamble_expected = "# Generated with antler-proj tool, modify at your own risk\n" + "cmake_minimum_required(VERSION 3.13)\n" + "project(\"test_proj\" VERSION 1.0.0)\n" + "add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/apps)\n" + "add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/libs)\n" + "add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/tests)\n\n"; + + REQUIRE( ss.str() == cmake_preamble_expected ); + + + object app = {object::type_t::app, "test_app", "C++", "", ""}; + //inline dependency(std::string_view name, std::string_view loc, std::string_view tag="", + // std::string_view rel="", std::string_view hash="") { + + dependency dep = {"test_lib", "https://github.com/larryk85/fast_math"}; + + app.upsert_dependency(std::move(dep)); + + proj.upsert_app(std::move(app)); + + ss.str(""); + cmake::emit_project(ss, proj); + + constexpr std::string_view project_expected = "add_executable(test_app test_app.cpp)\n" + "target_include_directories(test_app PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include ./ )\n" + "target_link_libraries(test_app PUBLIC test_lib)\n\n"; + + REQUIRE( ss.str() == project_expected ); + std::cout << "SS " << ss.str() << "\n"; +} \ No newline at end of file diff --git a/tools/add_to.hpp b/tools/add_to.hpp index 39f729a..be9dae5 100644 --- a/tools/add_to.hpp +++ b/tools/add_to.hpp @@ -6,6 +6,7 @@ #include "CLI11.hpp" #include +#include #include "common.hpp" @@ -14,22 +15,31 @@ namespace antler { template bool add_obj(antler::project::project& proj) { - if (!obj_name.empty()) { - std::cout - << '\n' - << "name: " << obj_name << '\n' - << "language: " << lang << '\n' - << "options: " << options << '\n' - << std::endl; + if (proj.object_exists(obj_name, Ty)) { + std::cerr << "Application " << obj_name << " already exists in project. Can't add.\n\n"; + return false; + } - if (proj.object_exists(obj_name, Ty)) { - std::cerr << "Application " << obj_name << " already exists in project. Can't add.\n\n"; - return false; - } + auto obj = antler::project::object(Ty, obj_name, lang, copts, lopts); + proj.upsert(std::move(obj)); + std::cout + << '\n' + << "name: " << obj_name << '\n' + << "language: " << lang << '\n' + << "compile options: " << copts << '\n' + << "link options: " << lopts << '\n' + << std::endl; + + std::cout << "PATH " << proj.path() << "\n"; + + // we need to produce stub for the app + if (Ty == project::object::type_t::app) { + auto path = proj.path().parent_path(); + std::filesystem::create_directory(path / "apps" / obj_name); + project::app_source::create_source_file(path, obj_name); + project::app_source::create_specification_file(path, obj_name); } - auto obj = antler::project::object(Ty, obj_name, lang, options); - proj.upsert_app(std::move(obj)); return true; } @@ -37,63 +47,60 @@ namespace antler { inline bool add_lib(antler::project::project& proj) { return add_obj(proj); } bool add_test(antler::project::project& proj) { - if (!obj_name.empty()) { - - // Print values. - std::cout - << '\n' - << "test name: " << obj_name << '\n' - << "command: " << cmd << '\n' - << '\n'; - - // Check to see if name is a TEST duplicate. - if (proj.object_exists(obj_name, antler::project::object::type_t::test)) { - // Enform user of the duplicate. - std::cerr << "Test " << obj_name << " already exists in project. Can't add.\n\n"; - } else { - // Check to see if name is otherwise a duplicate, warn if so. - if (proj.object_exists(obj_name)) - std::cerr << "WARNING: " << obj_name << " already exists in project as app and/or lib.\n\n"; - } - return true; + // Check to see if name is a TEST duplicate. + if (proj.object_exists(obj_name, antler::project::object::type_t::test)) { + // Enform user of the duplicate. + std::cerr << "Test " << obj_name << " already exists in project. Can't add.\n\n"; + } else { + // Check to see if name is otherwise a duplicate, warn if so. + if (proj.object_exists(obj_name)) + std::cerr << "WARNING: " << obj_name << " already exists in project as app and/or lib.\n\n"; } - return false; + + std::cout + << '\n' + << "test name: " << obj_name << '\n' + << "command: " << cmd << '\n' + << '\n'; + return true; } bool add_dependency(antler::project::project& proj) { - if (!obj_name.empty() && !dep_name.empty() && antler::project::dependency::validate_location(location, tag, release, hash)) { - // Get the object to operate on. - try { - auto& obj = proj.object(obj_name); - - // We have values, so query the user if they want to apply. - std::cout - << '\n' - << "Object name (to update): " << obj_name << '\n' - << "Dependency name: " << dep_name << '\n' - << "Dependency location: " << location << '\n' - << "tag/commit hash: " << tag << '\n' - << "release version: " << release << '\n' - << "SHA256 hash: " << hash << '\n' - << std::endl; - - // Get object here and warn user if dep_name already exists. - if (!dep_name.empty() && obj.dependency_exists(dep_name)) { - std::cerr << dep_name << " already exists for " << obj_name << " in project.\n"; - return false; - } - - antler::project::dependency dep; - dep.set(dep_name, location, tag, release, hash); - obj.upsert_dependency(std::move(dep)); - //proj.upsert(std::move(obj_vec[0])); - - } catch(...) { - std::cerr << "Object: " << obj_name << " does not exist." << std::endl; + // Get the object to operate on. + try { + auto& obj = proj.object(obj_name); + + // Get object here and warn user if dep_name already exists. + if (!dep_name.empty() && obj.dependency_exists(dep_name)) { + std::cerr << dep_name << " already exists for " << obj_name << " in project.\n"; + return false; } - } - return true; + antler::project::dependency dep; + dep.set(dep_name, location, tag, release, hash); + if (proj.validate_dependency(dep)) { + std::cerr << "Dependency: " << dep_name << " is invalid." << std::endl; + return false; + } + + obj.upsert_dependency(std::move(dep)); + + // We have values, so query the user if they want to apply. + std::cout + << '\n' + << "Object name (to update): " << obj_name << '\n' + << "Dependency name: " << dep_name << '\n' + << "Dependency location: " << location << '\n' + << "tag/commit hash: " << tag << '\n' + << "release version: " << release << '\n' + << "SHA256 hash: " << hash << '\n' + << std::endl; + + return true; + } catch(...) { + std::cerr << "Object: " << obj_name << " does not exist." << std::endl; + return false; + } } add_to_project(CLI::App& app) { @@ -104,17 +111,19 @@ namespace antler { app_subcommand = subcommand->add_subcommand("app", "Add a new app to your project."); app_subcommand->add_option("-n, name", obj_name, "The name of the app to add.")->required(); app_subcommand->add_option("-l, lang", lang, "Language this app will use.")->required(); - app_subcommand->add_option("-o, opts", options, "Options for the compiler for this app."); + app_subcommand->add_option("--comp, compile_options", copts, "Options for the compiler for this app."); + app_subcommand->add_option("--link, link_options", lopts, "Options for the linker for this app."); lib_subcommand = subcommand->add_subcommand("lib", "Add a new library to your project."); lib_subcommand->add_option("-n, name", obj_name, "The name of the library to add.")->required(); lib_subcommand->add_option("-l, lang", lang, "Language this library will use.")->required(); - lib_subcommand->add_option("-o, opts", options, "Options for the compiler for this library."); + lib_subcommand->add_option("--comp, compile_options", copts, "Options for the compiler for this app."); + lib_subcommand->add_option("--link, link_options", lopts, "Options for the linker for this app."); dep_subcommand = subcommand->add_subcommand("dep", "Add a new dependency to the project."); dep_subcommand->add_option("-o, obj", obj_name, "The name of the object to attach dependency to.")->required(); dep_subcommand->add_option("-d, dep", dep_name, "The name of the dependency.")->required(); - dep_subcommand->add_option("-l, loc", location, "Location of the dependency.")->required(); + dep_subcommand->add_option("-l, loc", location, "Location of the dependency."); dep_subcommand->add_option("-t, tag", tag, "Tag associated with the dependency."); dep_subcommand->add_option("-r, release", release, "Release version of the depedency."); dep_subcommand->add_option("--digest, hash", hash, "Hash of the dependency."); @@ -169,7 +178,8 @@ namespace antler { std::string release; std::string hash; std::string lang; - std::string options; + std::string copts; + std::string lopts; std::string cmd; }; } // namespace antler \ No newline at end of file diff --git a/tools/app_version.hpp b/tools/app_version.hpp new file mode 100644 index 0000000..097e406 --- /dev/null +++ b/tools/app_version.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +#include "CLI11.hpp" + +#include + +#include "common.hpp" + +namespace antler { + struct app_version { + inline app_version(CLI::App& app) { + app_name = app.get_name(); + subcommand = app.add_subcommand("version", "get the version of antler-proj"); + } + + int32_t exec() { + std::cout << app_name << " v" << antler::system::version::full() << std::endl; + return 0; + } + + std::string app_name; + CLI::App* subcommand; + }; +} // namespace antler \ No newline at end of file diff --git a/tools/common.hpp b/tools/common.hpp index 70a4d10..54065a9 100644 --- a/tools/common.hpp +++ b/tools/common.hpp @@ -2,6 +2,8 @@ #include +#include "CLI11.hpp" + #include namespace antler { diff --git a/tools/main.cpp b/tools/main.cpp index 397fde2..cb486f2 100644 --- a/tools/main.cpp +++ b/tools/main.cpp @@ -15,6 +15,7 @@ #include #include "add_to.hpp" +#include "app_version.hpp" #include "init.hpp" #include "populate.hpp" #include "remove_from.hpp" @@ -50,9 +51,11 @@ int main(int argc, char** argv) { // using this as we will alias antler-proj to cdt-proj for the // next release of CDT. const auto app_name = std::filesystem::path(argv[0]).filename().string(); - CLI::App app{app_name}; + + CLI::App app{"Antelope Smart Contract Project Management Tool", app_name}; runnerempty()) obj.language(lang); - if (!opts_opt->empty()) - obj.options(opts); + if (!copts_opt->empty()) + obj.compile_options(copts); + if (!lopts_opt->empty()) + obj.link_options(lopts); std::cout << "Updating object: " << obj_name << "\n" << "language: " << obj.language() << "\n" - << "options: " << obj.options() << std::endl; + << "compile options: " << obj.compile_options() << "\n" + << "link options: " << obj.link_options() << std::endl; - //if (proj.remove(obj_name, Ty)) { - // std::cout << "Removing object: " << obj_name << " from the project." << std::endl; - // return true; - //} return true; } catch(...) { std::cerr << "Object: " << obj_name << " does not exist." << std::endl; @@ -70,7 +70,7 @@ namespace antler { dep->hash(digest); if (dep) { - if (!dep->is_valid()) { + if (!proj.validate_dependency(*dep)) { std::cerr << "Dependency: " << dep_name << " is invalid." << std::endl; return false; } @@ -115,12 +115,14 @@ namespace antler { app_subcommand = subcommand->add_subcommand("app", "Remove app from the project."); app_subcommand->add_option("-n, name", obj_name, "The name of the app to remove.")->required(); app_subcommand->add_option("-l, language", lang, "The language of the app."); - app_subcommand->add_option("-o, options", opts, "The options used to build the app."); + app_subcommand->add_option("--comp, compile_options", copts, "The compile options used to build the app."); + app_subcommand->add_option("--link, link_options", lopts, "The link options used to build the app."); lib_subcommand = subcommand->add_subcommand("lib", "Remove lib from the project."); lib_subcommand->add_option("-n, name", obj_name, "The name of the library to add.")->required(); lib_subcommand->add_option("-l, language", lang, "The language of the lib."); - lib_subcommand->add_option("-o, options", opts, "The options used to build the lib."); + lib_subcommand->add_option("--comp, compile_options", copts, "The compile options used to build the app."); + lib_subcommand->add_option("--link, link_options", lopts, "The link options used to build the app."); dep_subcommand = subcommand->add_subcommand("dep", "Remove a dependency from the project."); dep_subcommand->add_option("-d, dep", dep_name, "The name of the dependency.")->required(); @@ -176,7 +178,8 @@ namespace antler { std::string obj_name; std::string dep_name; std::string lang; - std::string opts; + std::string copts; + std::string lopts; std::string loc; std::string tag; std::string release; diff --git a/tools/validate.hpp b/tools/validate.hpp index 3390f16..58def45 100644 --- a/tools/validate.hpp +++ b/tools/validate.hpp @@ -28,14 +28,20 @@ namespace antler { if (verbose) { std::cout << proj->to_yaml() << std::endl; } else { - std::cout << "Valid format for an antler-proj project." << std::endl; + std::cout << "Valid format for an antler-proj project.yml." << std::endl; } + + if (!proj->has_valid_dependencies()) { + std::cerr << "Project contains invalid dependencies." << std::endl; + return -1; + } + + std::cout << "Valid project dependencies and all are reachable." << std::endl; + return 0; } catch(...) { std::cerr << "Path: " << path << " not found." << std::endl; return -1; } - - return 0; } CLI::App* subcommand; From eddf50a335dd3284230a18d6b785cd61dbd48ebc Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Mon, 6 Mar 2023 04:19:46 -0500 Subject: [PATCH 08/14] added building --- include/antler/project/cmake.hpp | 41 ++++++---------- include/antler/project/object.hpp | 15 ++++-- include/antler/project/source.hpp | 34 +++++++++++++- include/antler/system/utils.hpp | 17 +++++++ src/project-populate.cpp | 55 +++++++++++++++++----- tools/add_to.hpp | 59 ++++++++++++++++++----- tools/build.hpp | 78 +++++++++++++++++++++++++++++++ tools/main.cpp | 2 + tools/populate.hpp | 8 +++- 9 files changed, 252 insertions(+), 57 deletions(-) create mode 100644 tools/build.hpp diff --git a/include/antler/project/cmake.hpp b/include/antler/project/cmake.hpp index a223cd7..cd9e230 100644 --- a/include/antler/project/cmake.hpp +++ b/include/antler/project/cmake.hpp @@ -13,15 +13,18 @@ namespace antler::project { struct cmake { constexpr inline static uint16_t minimum_major = 3; constexpr inline static uint16_t minimum_minor = 13; + constexpr inline static std::string_view cmake_lists = "CMakeLists.txt"; + constexpr inline static std::string_view cmake_exe = "cmake"; inline static std::string cmake_target_name(const project& proj, const object& obj) { using namespace std::literals; - return std::string(proj.name()) + "_"s + std::string(obj.name()); + return std::string(proj.name()) + "-"s + std::string(obj.name()); } template - inline static void emit_add_subdirectory(Stream& s, const std::filesystem::path& p) noexcept { - s << "add_subdirectory(" << p.string() << ")\n"; + inline static void emit_add_subdirectory(Stream& s, std::string_view path, std::string_view name) noexcept { + s << "add_subdirectory(${CMAKE_SOURCE_DIR}/" << path << "/" << name << ")\n"; + s << std::endl; } template @@ -34,9 +37,10 @@ namespace antler::project { } template - inline static void emit_add_base_subdirs(Stream& s) noexcept { - s << "add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../libs ${CMAKE_CURRENT_BINARY_DIR}/libs)\n"; - s << "add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../tests ${CMAKE_CURRENT_BINARY_DIR}/tests)\n"; + inline static void emit_project(Stream& s, const project& proj) noexcept { + s << "find_package(cdt)\n\n"; + s << "add_subdirectory(${CMAKE_SOURCE_DIR}/../libs ${CMAKE_CURRENT_BINARY_DIR}/libs)\n"; + s << "add_subdirectory(${CMAKE_SOURCE_DIR}/../tests ${CMAKE_CURRENT_BINARY_DIR}/tests)\n"; s << std::endl; } @@ -45,6 +49,7 @@ namespace antler::project { for (const auto& dep : obj.dependencies()) { s << "target_link_libraries(" << cmake_target_name(proj, obj) << " PUBLIC " << dep.name() <<")\n"; } + s << std::endl; } template @@ -69,31 +74,15 @@ namespace antler::project { template inline static void emit_app(Stream& s, const object& app, const project& proj) noexcept { - s << "add_executable(" << cmake_target_name(proj, app) << " " << app.name() << ".cpp" << ")\n"; + s << "add_contract(" << app.name() << " " << cmake_target_name(proj, app) << " " << "${CMAKE_SOURCE_DIR}/../../apps/" + << app.name() << "/" << app.name() << ".cpp" << ")\n"; s << "target_include_directories(" << cmake_target_name(proj, app) - << " PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include " - << "${CMAKE_CURRENT_SOURCE_DIR}/include/" << app.name() << " ./ )\n\n"; + << " PUBLIC ${CMAKE_SOURCE_DIR}/../../include " + << "${CMAKE_SOURCE_DIR}/../../include/" << app.name() << " ./ )\n\n"; emit_dependencies(s, proj, app); s << std::endl; } - template - inline static void emit_project(Stream& s, const project& proj) noexcept { - for (auto app : proj.apps()) { - emit_app(s, app, proj); - } - } }; -/// @return the cmake_minimum string with trailing newline. -[[nodiscard]] std::string minimum(unsigned major = 0, unsigned minor = 0, unsigned patch = 0) noexcept; - -/// @return the add_subdirectory string: add_subdirectory( "" )\n -[[nodiscard]] std::string add_subdirectory(const std::filesystem::path& path) noexcept; - -/// @return A string including the project name: project("")\n -//[[nodiscard]] std::string project(std::string_view proj_name); -/// @return A string including the project name: project("" VERSION )\n -//[[nodiscard]] std::string project(std::string_view proj_name, const project::semver& ver) noexcept; - } // namespace antler::project diff --git a/include/antler/project/object.hpp b/include/antler/project/object.hpp index d85c411..fa1eccf 100644 --- a/include/antler/project/object.hpp +++ b/include/antler/project/object.hpp @@ -2,15 +2,14 @@ /// @copyright See `LICENSE` in the root directory of this project. +#include #include #include -#include "dependency.hpp" -//#include "object_type_def.hpp" -//#include "../system/preprocessor.hpp" - #include #include +#include "dependency.hpp" + namespace antler::project { /// This class represents one of the app, lib, or test objects in a `project.yaml` file. @@ -77,7 +76,6 @@ class object { /// @param s The new test command. void command(std::string_view s) noexcept; - /// Update or insert a dependency. /// @param dep The dependency to upsert. void upsert_dependency(antler::project::dependency&& dep) noexcept; @@ -95,6 +93,13 @@ class object { /// @return optional with a copy of the dependency. [[nodiscard]] std::optional dependency(std::string_view name); + /// If an object name is valid? I.e. valid C/C++ name. + /// @param name The name to check. + /// @return true if the name is valid, false otherwise. + [[nodiscard]] inline static bool is_valid_name(std::string_view name) { + return std::regex_match(name.data(), std::regex("[a-zA-z][_a-zA-Z0-9]+")); + } + private: type_t m_type = type_t::error; ///< Object type: app, lib, or test. std::string m_name = ""; ///< Object name. diff --git a/include/antler/project/source.hpp b/include/antler/project/source.hpp index f2f73a1..e76fa11 100644 --- a/include/antler/project/source.hpp +++ b/include/antler/project/source.hpp @@ -7,9 +7,25 @@ #include "project.hpp" namespace antler::project { + namespace detail { + template + inline static std::string_view dir() { + if constexpr (Ty == object::type_t::app) + return "apps"; + else if constexpr (Ty == object::type_t::lib) + return "libs"; + else + throw std::runtime_error("internal failure"); + } + } // namespace antler::project::detail + // TODO in the proper release of ANTLER this system will be // replaced with a dynamic system the frontend dev (not smart contract dev) would supply - struct app_source { + template + struct source; + + template <> + struct source { inline static void create_source_file(std::filesystem::path p, const std::string& name) { p /= std::filesystem::path("apps") / name / (name+".cpp"); std::ofstream src{p.c_str()}; @@ -17,7 +33,7 @@ namespace antler::project { src << "[[eosio::action]]\n"; src << "void " << name << "::hi( name nm ) {\n"; src << " /* generated example action */\n"; - src << " print_f(\"Hello : %\n\", nm)\n"; + src << " print_f(\"Hello : %\", nm)\n"; src << "}\n"; src << std::endl; src.close(); @@ -39,4 +55,18 @@ namespace antler::project { hdr.close(); } }; + + template <> + struct source { + inline static void create_source_file(std::filesystem::path p, const std::string& name) { + p /= std::filesystem::path("libs") / name / (name+".cpp"); + std::ofstream src{p.c_str()}; + src << "#include \n\n"; + src << "/// Add your code here for the library\n"; + src << std::endl; + src.close(); + } + + inline static void create_specification_file(std::filesystem::path p, const std::string& name) {} + }; } \ No newline at end of file diff --git a/include/antler/system/utils.hpp b/include/antler/system/utils.hpp index 2e99d43..a5db618 100644 --- a/include/antler/system/utils.hpp +++ b/include/antler/system/utils.hpp @@ -60,6 +60,23 @@ namespace antler::system { } } + inline static int32_t execute(std::string_view prog) { + FILE* h = popen(prog.data(), "r"); + if (h == nullptr) { + std::cerr << "internal failure, program " << prog << " not found." << std::endl; + return -1; + } + + std::array buff; + + std::size_t n; + + while ((n = fread(buff.data(), 1, buff.size(), h)) > 0) { + fwrite(buff.data(), 1, n, stdout); + } + return WEXITSTATUS(pclose(h)); + } + } // namespace antler::system // expose all enum operators diff --git a/src/project-populate.cpp b/src/project-populate.cpp index e97344b..6540a3c 100644 --- a/src/project-populate.cpp +++ b/src/project-populate.cpp @@ -44,11 +44,17 @@ bool project::populate(bool replace, std::ostream& error_stream) noexcept { auto project_path = m_path.parent_path(); if (!init_dirs(project_path, false, error_stream)) // expect_empty is `false`, it's okay if everthing exists. return false; // But its not okay if the filesystem doesn't already exist AND can't be created. - - auto root_path = project_path / cmake_lists; - auto apps_path = project_path / std::filesystem::path("apps") / cmake_lists; - auto libs_path = project_path / std::filesystem::path("libs") / cmake_lists; - auto tests_path = project_path / std::filesystem::path("tests") / cmake_lists; + + auto build_path = project_path / std::filesystem::path("build"); + std::filesystem::create_directory(build_path); + std::filesystem::create_directory(project_path / std::filesystem::path("build") / std::filesystem::path("apps")); + std::filesystem::create_directory(project_path / std::filesystem::path("build") / std::filesystem::path("libs")); + std::filesystem::create_directory(project_path / std::filesystem::path("build") / std::filesystem::path("tests")); + + auto root_path = build_path / cmake_lists; + auto apps_path = build_path / std::filesystem::path("apps") / cmake_lists; + auto libs_path = build_path / std::filesystem::path("libs") / cmake_lists; + auto tests_path = build_path / std::filesystem::path("tests") / cmake_lists; bool create = true; // Look to see if the header contains the magic, if it does we will not create the file. @@ -80,19 +86,44 @@ bool project::populate(bool replace, std::ostream& error_stream) noexcept { error_stream << "Can not open path for writing\n"; return false; } else { - return try_emit(root_path, [&]() { + if (!try_emit(root_path, [&]() { cmake::emit_preamble(rfs, *this); cmake::emit_entry(rfs, *this); - }) - && - try_emit(apps_path, [&]() { + })) { + error_stream << "Error emitting cmake for root.\n"; + return false; + } + + if (!try_emit(apps_path, [&]() { cmake::emit_preamble(afs, *this); - cmake::emit_add_base_subdirs(afs); cmake::emit_project(afs, *this); - }); + })) { + error_stream << "Error emitting base cmake for project.\n"; + return false; + } + + for (const auto& app : apps()) { + auto app_path = apps_path.parent_path() / std::filesystem::path(app.name()) / cmake_lists; + std::filesystem::create_directory(app_path.parent_path()); + std::ofstream apfs(app_path); + + if (!try_emit(apps_path, [&](){ + cmake::emit_add_subdirectory(afs, ".", app.name()); + })) { + error_stream << "Error emitting cmake for app: " << app.name() << "\n"; + return false; + } + + if (!try_emit(app_path, [&]() { + cmake::emit_app(apfs, app, *this); + })) { + error_stream << "Error emitting cmake for app: " << app.name() << "\n"; + return false; + } + } } - return false; + return true; } diff --git a/tools/add_to.hpp b/tools/add_to.hpp index be9dae5..c5931f8 100644 --- a/tools/add_to.hpp +++ b/tools/add_to.hpp @@ -20,6 +20,54 @@ namespace antler { return false; } + if (!project::object::is_valid_name(obj_name)) { + std::cerr << "Object name: " << obj_name << " is not a valid name.\n"; + std::cerr << "Valid names are of the form [a-ZA-Z][_a-zA-Z0-9]+\n"; + return false; + } + + // TODO for the next release we will remove the C++ restrictions + const auto& is_valid_cpp_lang = [](auto l) { + return l == "cpp" || + l == "CPP" || + l == "c++" || + l == "C++" || + l == "cxx" || + l == "CXX" || + l == "Cxx"; + }; + + const auto& is_valid_c_lang = [](auto l) { + return l == "C" || + l == "c"; + }; + + // we need to produce stub for the app + if (Ty == project::object::type_t::app) { + if (!is_valid_cpp_lang(lang)) { + std::cerr << "Sorry, as of this version only C++ is available. Given : " << lang << "\n"; + std::cerr << "This restriction will be removed in future releases.\n"; + return false; + } + lang = "CXX"; + } else { + if (is_valid_cpp_lang(lang)) + lang = "CXX"; + else if (is_valid_c_lang(lang)) + lang = "C"; + else { + std::cerr << "Sorry, as of this version only C or C++ is available. Given : " << lang << "\n"; + std::cerr << "This restriction will be removed in future releases.\n"; + return false; + } + } + + auto path = proj.path().parent_path(); + std::filesystem::create_directory(path / project::detail::dir() / obj_name); + std::filesystem::create_directory(path / std::filesystem::path("include") / obj_name); + project::source::create_source_file(path, obj_name); + project::source::create_specification_file(path, obj_name); + auto obj = antler::project::object(Ty, obj_name, lang, copts, lopts); proj.upsert(std::move(obj)); std::cout @@ -29,17 +77,6 @@ namespace antler { << "compile options: " << copts << '\n' << "link options: " << lopts << '\n' << std::endl; - - std::cout << "PATH " << proj.path() << "\n"; - - // we need to produce stub for the app - if (Ty == project::object::type_t::app) { - auto path = proj.path().parent_path(); - std::filesystem::create_directory(path / "apps" / obj_name); - project::app_source::create_source_file(path, obj_name); - project::app_source::create_specification_file(path, obj_name); - } - return true; } diff --git a/tools/build.hpp b/tools/build.hpp new file mode 100644 index 0000000..9d0d08e --- /dev/null +++ b/tools/build.hpp @@ -0,0 +1,78 @@ +#pragma once + +#include +#include + +#include "CLI11.hpp" + +#include +#include + +#include "common.hpp" + +namespace antler { + + struct build_project { + inline build_project(CLI::App& app) { + subcommand = app.add_subcommand("build", "Build a project."); + subcommand->add_option("-p, path", path, "This is the path to the root of the project.")->required(); + } + + bool should_repopulate() { + auto proj = std::filesystem::path(path) / std::filesystem::path(project::project::manifest_name); + auto build = std::filesystem::path(path) / std::filesystem::path("build") / std::filesystem::path(project::cmake::cmake_lists); + + auto last_manifest_time = std::filesystem::last_write_time(proj); + + if (!std::filesystem::exists(build)) { + return true; + } + + auto last_pop_time = std::filesystem::last_write_time(build); + + return last_pop_time < last_manifest_time; + } + + int32_t configure() noexcept { + auto build_dir = std::filesystem::path(path) / std::filesystem::path("build"); + auto bin_dir = build_dir / std::filesystem::path("antler-bin"); + + std::filesystem::create_directory(bin_dir); + std::string cmake_cmd = "cmake -S " + build_dir.string() + " -B " + bin_dir.string(); + + return system::execute("cmake -S " + build_dir.string() + " -B " + bin_dir.string()); //cmake_cmd); + } + + + int32_t build() noexcept { + auto bin_dir = std::filesystem::path(path) / std::filesystem::path("build") / std::filesystem::path("antler-bin"); + + std::filesystem::create_directory(bin_dir); + std::string make_cmd = "cmake --build " + bin_dir.string(); + + return system::execute(make_cmd); + } + + int32_t exec() { + auto proj = load_project(path); + if (!proj) { + return -1; + } + + if (should_repopulate()) { + if (!proj->populate()) { + std::cerr << "Population of the project failed." << std::endl; + return -1; + } + if (auto rv = configure(); rv != 0) { + std::cerr << "Configuring project build failed!" << std::endl; + } + } + return build(); + } + + CLI::App* subcommand; + std::string path; + bool verbose; + }; +} // namespace antler \ No newline at end of file diff --git a/tools/main.cpp b/tools/main.cpp index cb486f2..676c821 100644 --- a/tools/main.cpp +++ b/tools/main.cpp @@ -16,6 +16,7 @@ #include "add_to.hpp" #include "app_version.hpp" +#include "build.hpp" #include "init.hpp" #include "populate.hpp" #include "remove_from.hpp" @@ -56,6 +57,7 @@ int main(int argc, char** argv) { runnerpopulate() ? 0 : -1; + if (!proj->populate()) { + std::cerr << "Project population failed!" << std::endl; + return -1; + } + + std::cout << "Project population successful!" << std::endl; + return 0; } CLI::App* subcommand; From ab3514298198f9e93334a36fc6f81f6ed749d836 Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Mon, 6 Mar 2023 06:38:27 -0500 Subject: [PATCH 09/14] add lib support --- include/antler/project/cmake.hpp | 53 ++++++++++++++++++++++++++----- include/antler/project/object.hpp | 20 ++++++++---- include/antler/project/source.hpp | 6 ++-- include/antler/system/utils.hpp | 4 +-- src/object.cpp | 7 ++-- src/project-parse.cpp | 7 ++-- src/project-populate.cpp | 37 +++++++++++++++------ src/project-print.cpp | 12 +++++-- tests/cmake_tests.cpp | 3 -- tools/update.hpp | 6 ++-- 10 files changed, 111 insertions(+), 44 deletions(-) diff --git a/include/antler/project/cmake.hpp b/include/antler/project/cmake.hpp index cd9e230..3c5c270 100644 --- a/include/antler/project/cmake.hpp +++ b/include/antler/project/cmake.hpp @@ -12,10 +12,17 @@ namespace antler::project { struct cmake { constexpr inline static uint16_t minimum_major = 3; - constexpr inline static uint16_t minimum_minor = 13; + constexpr inline static uint16_t minimum_minor = 11; constexpr inline static std::string_view cmake_lists = "CMakeLists.txt"; constexpr inline static std::string_view cmake_exe = "cmake"; + inline static std::string extension(std::string_view l) { + if (l == "CXX") + return ".cpp"; + else + return ".c"; + } + inline static std::string cmake_target_name(const project& proj, const object& obj) { using namespace std::literals; return std::string(proj.name()) + "-"s + std::string(obj.name()); @@ -72,15 +79,45 @@ namespace antler::project { s << std::endl; } - template - inline static void emit_app(Stream& s, const object& app, const project& proj) noexcept { - s << "add_contract(" << app.name() << " " << cmake_target_name(proj, app) << " " << "${CMAKE_SOURCE_DIR}/../../apps/" - << app.name() << "/" << app.name() << ".cpp" << ")\n"; - s << "target_include_directories(" << cmake_target_name(proj, app) + template + inline static void emit_obj(Stream& s, const object& obj, const project& proj) noexcept { + if constexpr (Ty == object::type_t::app) { + s << "add_contract(" << obj.name() << " " << cmake_target_name(proj, obj) << " " << "${CMAKE_SOURCE_DIR}/../../apps/" + << obj.name() << "/" << obj.name() << ".cpp" << ")\n"; + } else { + s << "add_library(" << cmake_target_name(proj, obj) << " " << "${CMAKE_SOURCE_DIR}/../../libs/" + << obj.name() << "/" << obj.name() << extension(obj.language()) << ")\n"; + } + + s << "target_include_directories(" << cmake_target_name(proj, obj) << " PUBLIC ${CMAKE_SOURCE_DIR}/../../include " - << "${CMAKE_SOURCE_DIR}/../../include/" << app.name() << " ./ )\n\n"; - emit_dependencies(s, proj, app); + << "${CMAKE_SOURCE_DIR}/../../include/" << obj.name() << " ./ )\n\n"; s << std::endl; + + for (const auto& o : obj.compile_options()) { + s << "target_compile_options(" << cmake_target_name(proj, obj) + << " PUBLIC " << o << ")\n"; + } + s << std::endl; + + for (const auto& o : obj.link_options()) { + s << "target_link_libraries(" << cmake_target_name(proj, obj) + << " PUBLIC " << o << ")\n"; + } + s << std::endl; + + emit_dependencies(s, proj, obj); + s << std::endl; + } + + template + inline static void emit_app(Stream& s, const object& app, const project& proj) noexcept { + return emit_obj(s, app, proj); + } + + template + inline static void emit_lib(Stream& s, const object& lib, const project& proj) noexcept { + return emit_obj(s, lib, proj); } }; diff --git a/include/antler/project/object.hpp b/include/antler/project/object.hpp index fa1eccf..eeaf293 100644 --- a/include/antler/project/object.hpp +++ b/include/antler/project/object.hpp @@ -59,17 +59,25 @@ class object { void language(std::string_view lang) noexcept; /// @return Current compile options. - [[nodiscard]] inline std::string_view compile_options() const noexcept { return m_comp_options; } + [[nodiscard]] inline const std::vector& compile_options() const noexcept { return m_comp_options; } /// @return Current link options. - [[nodiscard]] inline std::string_view link_options() const noexcept { return m_link_options; } + [[nodiscard]] inline const std::vector& link_options() const noexcept { return m_link_options; } /// Replace any existing options with the new value. /// @param options The new options to store. - void compile_options(std::string_view options) noexcept { m_comp_options = options; } + inline void compile_options(std::string_view options) noexcept { m_comp_options = system::split<';'>(options); } + + /// Add a compile option. + /// @param option The new option to store. + inline void add_compile_option(const std::string& option) noexcept { m_comp_options.push_back(option); } /// Replace any existing options with the new value. /// @param options The new options to store. - void link_options(std::string_view options) noexcept { m_link_options = options; } + inline void link_options(std::string_view options) noexcept { m_link_options = system::split<';'>(options); } + + /// Add a link option. + /// @param option The new option to store. + void add_link_option(const std::string& option) noexcept { m_link_options.push_back(option); } /// @return The test command. [[nodiscard]] std::string_view command() const noexcept; @@ -107,8 +115,8 @@ class object { // app, lib: std::string m_language = ""; ///< Language type, only valid for app or lib. - std::string m_comp_options = ""; - std::string m_link_options = ""; + std::vector m_comp_options = {}; + std::vector m_link_options = {}; // test: std::string m_command = ""; ///< Test command, only valid for test. diff --git a/include/antler/project/source.hpp b/include/antler/project/source.hpp index e76fa11..5a25f32 100644 --- a/include/antler/project/source.hpp +++ b/include/antler/project/source.hpp @@ -33,7 +33,7 @@ namespace antler::project { src << "[[eosio::action]]\n"; src << "void " << name << "::hi( name nm ) {\n"; src << " /* generated example action */\n"; - src << " print_f(\"Hello : %\", nm)\n"; + src << " print_f(\"Hello : %\", nm);\n"; src << "}\n"; src << std::endl; src.close(); @@ -59,9 +59,9 @@ namespace antler::project { template <> struct source { inline static void create_source_file(std::filesystem::path p, const std::string& name) { - p /= std::filesystem::path("libs") / name / (name+".cpp"); + p /= std::filesystem::path("libs") / name / (name+".c"); std::ofstream src{p.c_str()}; - src << "#include \n\n"; + src << "#include \n\n"; src << "/// Add your code here for the library\n"; src << std::endl; src.close(); diff --git a/include/antler/system/utils.hpp b/include/antler/system/utils.hpp index a5db618..41395a0 100644 --- a/include/antler/system/utils.hpp +++ b/include/antler/system/utils.hpp @@ -23,8 +23,8 @@ namespace antler::system { } template - inline static std::vector split(std::string_view s) { - std::vector strings; + inline static std::vector split(std::string_view s) { + std::vector strings; std::size_t i=0, p=0; diff --git a/src/object.cpp b/src/object.cpp index a2c6740..5b5784b 100644 --- a/src/object.cpp +++ b/src/object.cpp @@ -19,10 +19,9 @@ object::object(type_t ot, std::string_view name, const std::string& lang, std::s : m_type{ ot } , m_name{ name } , m_language{ system::to_upper(lang) } - , m_comp_options{ copts } - , m_link_options{ lopts } -{ -} + , m_comp_options{ system::split<';'>(copts) } + , m_link_options{ system::split<';'>(lopts) } +{} object::object(std::string_view name, std::string_view command) diff --git a/src/project-parse.cpp b/src/project-parse.cpp index 8a63352..5d190ce 100644 --- a/src/project-parse.cpp +++ b/src/project-parse.cpp @@ -26,6 +26,7 @@ namespace antler::project { namespace { // anonymous inline static std::string_view sv_from_csubstr(const c4::csubstr& s) { return {s.data(), s.size()}; } +inline static std::string s_from_csubstr(const c4::csubstr& s) { return {s.data(), s.size()}; } /// Load a text file into a string. /// @@ -268,7 +269,7 @@ template return {}; } if (!rv.compile_options().empty()) { - os << "Duplicate " << tok << " values in " << type << " list: " << rv.compile_options() << ", " << i.val() << "\n"; + os << "Duplicate " << tok << " values in options\n"; return {}; } if (type == object::type_t::test) { @@ -286,7 +287,7 @@ template return {}; } if (!rv.link_options().empty()) { - os << "Duplicate " << tok << " values in " << type << " list: " << rv.link_options() << ", " << i.val() << "\n"; + os << "Duplicate " << tok << " values in options\n"; return {}; } if (type == object::type_t::test) { @@ -294,7 +295,7 @@ template return {}; } - rv.compile_options(sv_from_csubstr(i.val())); + rv.link_options(sv_from_csubstr(i.val())); } break; case token::command: { diff --git a/src/project-populate.cpp b/src/project-populate.cpp index 6540a3c..7c7cb5b 100644 --- a/src/project-populate.cpp +++ b/src/project-populate.cpp @@ -11,7 +11,6 @@ namespace antler::project { namespace { // anonymous -const std::filesystem::path cmake_lists{"CMakeLists.txt"}; /// Test to see if a file has the magic maintenance string. @@ -51,10 +50,10 @@ bool project::populate(bool replace, std::ostream& error_stream) noexcept { std::filesystem::create_directory(project_path / std::filesystem::path("build") / std::filesystem::path("libs")); std::filesystem::create_directory(project_path / std::filesystem::path("build") / std::filesystem::path("tests")); - auto root_path = build_path / cmake_lists; - auto apps_path = build_path / std::filesystem::path("apps") / cmake_lists; - auto libs_path = build_path / std::filesystem::path("libs") / cmake_lists; - auto tests_path = build_path / std::filesystem::path("tests") / cmake_lists; + auto root_path = build_path / cmake::cmake_lists; + auto apps_path = build_path / std::filesystem::path("apps") / cmake::cmake_lists; + auto libs_path = build_path / std::filesystem::path("libs") / cmake::cmake_lists; + auto tests_path = build_path / std::filesystem::path("tests") / cmake::cmake_lists; bool create = true; // Look to see if the header contains the magic, if it does we will not create the file. @@ -63,11 +62,7 @@ bool project::populate(bool replace, std::ostream& error_stream) noexcept { std::ofstream lfs(libs_path); std::ofstream tfs(tests_path); - lfs << "\n"; - lfs.close(); - tfs << "\n"; - tfs.close(); const auto& try_emit = [&](auto path, auto func) { try { @@ -103,7 +98,7 @@ bool project::populate(bool replace, std::ostream& error_stream) noexcept { } for (const auto& app : apps()) { - auto app_path = apps_path.parent_path() / std::filesystem::path(app.name()) / cmake_lists; + auto app_path = apps_path.parent_path() / std::filesystem::path(app.name()) / cmake::cmake_lists; std::filesystem::create_directory(app_path.parent_path()); std::ofstream apfs(app_path); @@ -121,6 +116,28 @@ bool project::populate(bool replace, std::ostream& error_stream) noexcept { return false; } } + + for (const auto& lib : libs()) { + std::cout << "LIB " << lib.name() << std::endl; + auto lib_path = libs_path.parent_path() / std::filesystem::path(lib.name()) / cmake::cmake_lists; + std::filesystem::create_directory(lib_path.parent_path()); + std::ofstream lpfs(lib_path); + + if (!try_emit(libs_path, [&](){ + cmake::emit_add_subdirectory(lfs, "../libs", lib.name()); + })) { + error_stream << "Error emitting cmake for lib: " << lib.name() << "\n"; + return false; + } + + if (!try_emit(lib_path, [&]() { + cmake::emit_lib(lpfs, lib, *this); + })) { + error_stream << "Error emitting cmake for lib: " << lib.name() << "\n"; + return false; + } + } + } return true; diff --git a/src/project-print.cpp b/src/project-print.cpp index 8c37a2a..ad11340 100644 --- a/src/project-print.cpp +++ b/src/project-print.cpp @@ -76,10 +76,18 @@ void project::print(std::ostream& os) const noexcept { if (!obj.language().empty()) map_node[to_csubstr(token::lang)] << to_csubstr(obj.language()); // options + + const auto& conjoin_opts = [&](const auto& opts) { + std::string s; + for (const auto& o : opts) + s += std::string(o) + ";"; + return s; + }; + if (!obj.compile_options().empty()) - map_node[to_csubstr(token::compile_options)] << to_csubstr(obj.compile_options()); + map_node[to_csubstr(token::compile_options)] << to_csubstr(conjoin_opts(obj.compile_options())); if (!obj.link_options().empty()) - map_node[to_csubstr(token::link_options)] << to_csubstr(obj.link_options()); + map_node[to_csubstr(token::link_options)] << to_csubstr(conjoin_opts(obj.link_options())); // depends - dependencies are also a list. if (!obj.dependencies().empty()) { diff --git a/tests/cmake_tests.cpp b/tests/cmake_tests.cpp index e1f1261..8db8181 100644 --- a/tests/cmake_tests.cpp +++ b/tests/cmake_tests.cpp @@ -31,8 +31,6 @@ TEST_CASE("Testing cmake emission") { object app = {object::type_t::app, "test_app", "C++", "", ""}; - //inline dependency(std::string_view name, std::string_view loc, std::string_view tag="", - // std::string_view rel="", std::string_view hash="") { dependency dep = {"test_lib", "https://github.com/larryk85/fast_math"}; @@ -48,5 +46,4 @@ TEST_CASE("Testing cmake emission") { "target_link_libraries(test_app PUBLIC test_lib)\n\n"; REQUIRE( ss.str() == project_expected ); - std::cout << "SS " << ss.str() << "\n"; } \ No newline at end of file diff --git a/tools/update.hpp b/tools/update.hpp index a766eee..0541216 100644 --- a/tools/update.hpp +++ b/tools/update.hpp @@ -36,8 +36,8 @@ namespace antler { std::cout << "Updating object: " << obj_name << "\n" << "language: " << obj.language() << "\n" - << "compile options: " << obj.compile_options() << "\n" - << "link options: " << obj.link_options() << std::endl; + << "compile options: " << copts << "\n" + << "link options: " << lopts << std::endl; return true; } catch(...) { @@ -110,7 +110,7 @@ namespace antler { update_project(CLI::App& app) { path = std::filesystem::current_path().string(); subcommand = app.add_subcommand("update", "Update an app, dependency, library or test to your project."); - subcommand->add_option("-p, path", path, "This must be the path to the `project.yml` or the path containing it.")->required(); + subcommand->add_option("-p, path", path, "This must be the path to the `project.yml` or the path containing it."); app_subcommand = subcommand->add_subcommand("app", "Remove app from the project."); app_subcommand->add_option("-n, name", obj_name, "The name of the app to remove.")->required(); From 9d9830a9ae8fc9bcf8069fd67c72882aa925c6cc Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Mon, 6 Mar 2023 07:59:35 -0500 Subject: [PATCH 10/14] fixes for deps --- include/antler/project/cmake.hpp | 15 +++++---------- include/antler/project/project.hpp | 5 +---- include/antler/project/source.hpp | 14 +++++++++----- include/antler/system/utils.hpp | 7 +++++++ src/project-populate.cpp | 3 +-- src/project.cpp | 13 ++++--------- tools/add_to.hpp | 8 ++++---- tools/build.hpp | 30 +++++++++++++++++++++++++++++- 8 files changed, 60 insertions(+), 35 deletions(-) diff --git a/include/antler/project/cmake.hpp b/include/antler/project/cmake.hpp index 3c5c270..18537eb 100644 --- a/include/antler/project/cmake.hpp +++ b/include/antler/project/cmake.hpp @@ -16,14 +16,8 @@ namespace antler::project { constexpr inline static std::string_view cmake_lists = "CMakeLists.txt"; constexpr inline static std::string_view cmake_exe = "cmake"; - inline static std::string extension(std::string_view l) { - if (l == "CXX") - return ".cpp"; - else - return ".c"; - } - - inline static std::string cmake_target_name(const project& proj, const object& obj) { + template + inline static std::string cmake_target_name(const project& proj, T&& obj) { using namespace std::literals; return std::string(proj.name()) + "-"s + std::string(obj.name()); } @@ -54,7 +48,8 @@ namespace antler::project { template inline static void emit_dependencies(Stream& s, const project& proj, const object& obj) noexcept { for (const auto& dep : obj.dependencies()) { - s << "target_link_libraries(" << cmake_target_name(proj, obj) << " PUBLIC " << dep.name() <<")\n"; + std::string dep_name = dep.location().empty() ? cmake_target_name(proj, dep) : std::string(dep.name()); + s << "target_link_libraries(" << cmake_target_name(proj, obj) << " PUBLIC " << dep_name <<")\n"; } s << std::endl; } @@ -86,7 +81,7 @@ namespace antler::project { << obj.name() << "/" << obj.name() << ".cpp" << ")\n"; } else { s << "add_library(" << cmake_target_name(proj, obj) << " " << "${CMAKE_SOURCE_DIR}/../../libs/" - << obj.name() << "/" << obj.name() << extension(obj.language()) << ")\n"; + << obj.name() << "/" << obj.name() << system::extension(obj.language()) << ")\n"; } s << "target_include_directories(" << cmake_target_name(proj, obj) diff --git a/include/antler/project/project.hpp b/include/antler/project/project.hpp index d5d3d07..fae1ad8 100644 --- a/include/antler/project/project.hpp +++ b/include/antler/project/project.hpp @@ -152,12 +152,9 @@ class project { /// Initialize the directories /// @param path The location of the project.yaml file or the path containing it. - /// @param expect_empty This boolean describes behavior when paths preexist: - /// when true, any existing path - excluding the root - will cause an immediate false return; - /// when false, only failures to create will generate a false return. /// @param error_stream The stream to print failure reports to. /// @return true for success; false indidates failure. - [[nodiscard]] static bool init_dirs(const std::filesystem::path& path, bool expect_empty = true, std::ostream& error_stream = std::cerr) noexcept; + [[nodiscard]] static bool init_dirs(const std::filesystem::path& path, std::ostream& error_stream = std::cerr) noexcept; /// Search this and directories above for `project.yaml` file. /// @note if path extension is `.yaml` no directory search is performed, instead return value indicating existence of path a regular file. diff --git a/include/antler/project/source.hpp b/include/antler/project/source.hpp index 5a25f32..d3d8c3b 100644 --- a/include/antler/project/source.hpp +++ b/include/antler/project/source.hpp @@ -26,7 +26,8 @@ namespace antler::project { template <> struct source { - inline static void create_source_file(std::filesystem::path p, const std::string& name) { + inline static void create_source_file(std::filesystem::path p, const object& obj) { + std::string name = std::string(obj.name()); p /= std::filesystem::path("apps") / name / (name+".cpp"); std::ofstream src{p.c_str()}; src << "#include <" << name << ".hpp>\n\n"; @@ -39,7 +40,8 @@ namespace antler::project { src.close(); } - inline static void create_specification_file(std::filesystem::path p, const std::string& name) { + inline static void create_specification_file(std::filesystem::path p, const object& obj) { + std::string name = std::string(obj.name()); p /= std::filesystem::path("include") / name / (name+".hpp"); std::ofstream hdr{p.c_str()}; hdr << "#include \n\n"; @@ -58,8 +60,10 @@ namespace antler::project { template <> struct source { - inline static void create_source_file(std::filesystem::path p, const std::string& name) { - p /= std::filesystem::path("libs") / name / (name+".c"); + inline static void create_source_file(std::filesystem::path p, const object& obj) { + std::string name = std::string(obj.name()); + std::string ext = system::extension(obj.language()); + p /= std::filesystem::path("libs") / name / (name+ext); std::ofstream src{p.c_str()}; src << "#include \n\n"; src << "/// Add your code here for the library\n"; @@ -67,6 +71,6 @@ namespace antler::project { src.close(); } - inline static void create_specification_file(std::filesystem::path p, const std::string& name) {} + inline static void create_specification_file(std::filesystem::path p, const object& obj) {} }; } \ No newline at end of file diff --git a/include/antler/system/utils.hpp b/include/antler/system/utils.hpp index 41395a0..43a1a9c 100644 --- a/include/antler/system/utils.hpp +++ b/include/antler/system/utils.hpp @@ -77,6 +77,13 @@ namespace antler::system { return WEXITSTATUS(pclose(h)); } + inline static std::string extension(std::string_view l) { + if (l == "CXX") + return ".cpp"; + else + return ".c"; + } + } // namespace antler::system // expose all enum operators diff --git a/src/project-populate.cpp b/src/project-populate.cpp index 7c7cb5b..6c3f390 100644 --- a/src/project-populate.cpp +++ b/src/project-populate.cpp @@ -41,7 +41,7 @@ namespace { // anonymous bool project::populate(bool replace, std::ostream& error_stream) noexcept { // Find the project path, and make sure the subdirs/project directory tree exists. auto project_path = m_path.parent_path(); - if (!init_dirs(project_path, false, error_stream)) // expect_empty is `false`, it's okay if everthing exists. + if (!init_dirs(project_path, error_stream)) // expect_empty is `false`, it's okay if everthing exists. return false; // But its not okay if the filesystem doesn't already exist AND can't be created. auto build_path = project_path / std::filesystem::path("build"); @@ -118,7 +118,6 @@ bool project::populate(bool replace, std::ostream& error_stream) noexcept { } for (const auto& lib : libs()) { - std::cout << "LIB " << lib.name() << std::endl; auto lib_path = libs_path.parent_path() / std::filesystem::path(lib.name()) / cmake::cmake_lists; std::filesystem::create_directory(lib_path.parent_path()); std::ofstream lpfs(lib_path); diff --git a/src/project.cpp b/src/project.cpp index 7fe5277..76317cf 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -9,7 +9,7 @@ namespace antler::project { //--- alphabetic -------------------------------------------------------------------------------------------------------- -bool project::init_dirs(const std::filesystem::path& path, bool expect_empty, std::ostream& error_stream) noexcept { +bool project::init_dirs(const std::filesystem::path& path, std::ostream& error_stream) noexcept { std::error_code sec; @@ -20,11 +20,6 @@ bool project::init_dirs(const std::filesystem::path& path, bool expect_empty, st return false; } - if (expect_empty && !std::filesystem::is_empty(path, sec)) { - error_stream << path << " is NOT empty!\n"; - return false; - } - // Create the directory structure. { const std::vector files = { "apps", "include", "ricardian", "libs", "tests" }; @@ -52,10 +47,11 @@ namespace detail { template inline static typename std::remove_reference_t::value_type* find_if(List&& objs, std::string_view name) { auto tmp = std::find_if(objs.begin(), objs.end(), [name](const auto& o) { return o.name() == name; }); - if (tmp != objs.end()) + if (tmp != objs.end()) { return &*tmp; - else + } else { return nullptr; + } } } @@ -237,7 +233,6 @@ void project::version(const antler::project::version& ver) noexcept { bool project::validate_dependency(const dependency& dep, std::ostream& errs) const noexcept { if (dep.location().empty()) { - std::cout << "DEP " << dep.name() << std::endl; return object_exists(dep.name(), object::type_t::lib); } else if (!dep.is_valid_location()) { errs << "Error dependency: " << dep.name() << " is invalid." << std::endl; diff --git a/tools/add_to.hpp b/tools/add_to.hpp index c5931f8..19f73b8 100644 --- a/tools/add_to.hpp +++ b/tools/add_to.hpp @@ -62,13 +62,13 @@ namespace antler { } } + auto obj = antler::project::object(Ty, obj_name, lang, copts, lopts); auto path = proj.path().parent_path(); std::filesystem::create_directory(path / project::detail::dir() / obj_name); std::filesystem::create_directory(path / std::filesystem::path("include") / obj_name); - project::source::create_source_file(path, obj_name); - project::source::create_specification_file(path, obj_name); + project::source::create_source_file(path, obj); + project::source::create_specification_file(path, obj); - auto obj = antler::project::object(Ty, obj_name, lang, copts, lopts); proj.upsert(std::move(obj)); std::cout << '\n' @@ -115,7 +115,7 @@ namespace antler { antler::project::dependency dep; dep.set(dep_name, location, tag, release, hash); - if (proj.validate_dependency(dep)) { + if (!proj.validate_dependency(dep)) { std::cerr << "Dependency: " << dep_name << " is invalid." << std::endl; return false; } diff --git a/tools/build.hpp b/tools/build.hpp index 9d0d08e..08e5f65 100644 --- a/tools/build.hpp +++ b/tools/build.hpp @@ -53,6 +53,25 @@ namespace antler { return system::execute(make_cmd); } + void move_artifacts(const project::project& proj) noexcept { + namespace sf = std::filesystem; + auto build_dir = sf::path(path) / sf::path("build"); + auto bin_dir = build_dir / sf::path("antler-bin"); + + for (const auto& app : proj.apps()) { + std::string app_nm = std::string(app.name()); + std::string proj_nm = std::string(proj.name()); + auto from_wasm = bin_dir / sf::path(proj.name()) / sf::path(app_nm+"/"+proj_nm+"-"+app_nm+".wasm"); + auto from_abi = bin_dir / sf::path(proj.name()) / sf::path(app_nm+"/"+proj_nm+"-"+app_nm+".abi"); + + auto to_wasm = build_dir / sf::path(app_nm+".wasm"); + auto to_abi = build_dir / sf::path(app_nm+".abi"); + + std::filesystem::copy(from_wasm, to_wasm); + std::filesystem::copy(from_abi, to_abi); + } + } + int32_t exec() { auto proj = load_project(path); if (!proj) { @@ -66,9 +85,18 @@ namespace antler { } if (auto rv = configure(); rv != 0) { std::cerr << "Configuring project build failed!" << std::endl; + return rv; } } - return build(); + + if ( auto rv = build(); rv != 0) { + std::cerr << "Building failed!" << std::endl; + return rv; + } + + move_artifacts(*proj); + + return 0; } CLI::App* subcommand; From 2c4df7ff22eb6200ea77d961c12774c68694651a Mon Sep 17 00:00:00 2001 From: Scott Bailey Date: Mon, 6 Mar 2023 13:58:10 -0600 Subject: [PATCH 11/14] Update to v2.13.9 for gcc10 and clang14 --- external/Catch2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/Catch2 b/external/Catch2 index de6fe18..182c910 160000 --- a/external/Catch2 +++ b/external/Catch2 @@ -1 +1 @@ -Subproject commit de6fe184a9ac1a06895cdd1c9b437f0a0bdf14ad +Subproject commit 182c910b4b63ff587a3440e08f84f70497e49a81 From f617a177477283ff05205f650aa3d3d5506c686c Mon Sep 17 00:00:00 2001 From: Scott Bailey Date: Mon, 6 Mar 2023 15:23:41 -0600 Subject: [PATCH 12/14] Correct version constraint decode and renable additional testing. --- src/version_constraint.cpp | 20 +++++++++++++++----- tests/version_constraint_tests.cpp | 17 +++++++++++++---- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/version_constraint.cpp b/src/version_constraint.cpp index c388ee8..5452667 100644 --- a/src/version_constraint.cpp +++ b/src/version_constraint.cpp @@ -45,7 +45,7 @@ inline std::string_view trim(std::string_view s) { auto last = std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {return !std::isspace(ch);}); // Convert the reverse iter last to the forward iterator containing end using base(). // See: https://en.cppreference.com/w/cpp/iterator/reverse_iterator/base - return std::string_view{first, s.size() - 1 - (last - s.rbegin())}; + return std::string_view{first, last.base() - first}; } @@ -106,9 +106,19 @@ void version_constraint::load(std::string_view sin, std::ostream& os) { if (el_list.size() == 1) { // One member MUST be a unique. - auto ver_str = trim(el_list[0]); - m_constraints.emplace_back(constraint{ version(ver_str), version(), bounds_inclusivity::unique }); - continue; + auto ver_str = std::string(trim(el_list[0])); + + if(std::isdigit(ver_str[0])) { + m_constraints.emplace_back(constraint{ version(ver_str), version(), bounds_inclusivity::unique }); + continue; + } + + // first char must have been an operation, right? + auto first_digit = ver_str.find_first_of("0123456789"); + if(first_digit != ver_str.npos) { + el_list.push_back( ver_str.substr(first_digit) ); + el_list[0] = ver_str.substr(0,first_digit); + } } if (el_list.size() == 2) { @@ -215,7 +225,7 @@ void version_constraint::load(std::string_view sin, std::ostream& os) { } os << __FILE__ << ":" << __LINE__ << " Failed to decode version_constraint: \"" << sin << "\" Too many elements in: \""; - + for (const auto& e : element) { os << e; } diff --git a/tests/version_constraint_tests.cpp b/tests/version_constraint_tests.cpp index fe05406..fd82571 100644 --- a/tests/version_constraint_tests.cpp +++ b/tests/version_constraint_tests.cpp @@ -12,7 +12,6 @@ using namespace antler::project; -#if 0 struct constraint_entry { std::string ver; // left comparison std::string constraint; // right comparison @@ -41,11 +40,17 @@ const std::vector compare_list = { { "2.2", ">= 2.0.12, < 2.1 | >= 2.1.3, < 2.2 | >= 2.2.3, < 2.3 | >= 2.3.2, < 3 | >= 3.2", false}, { "2.3", ">= 2.0.12, < 2.1 | >= 2.1.3, < 2.2 | >= 2.2.3, < 2.3 | >= 2.3.2, < 3 | >= 3.2", false}, { "3.0", ">= 2.0.12, < 2.1 | >= 2.1.3, < 2.2 | >= 2.2.3, < 2.3 | >= 2.3.2, < 3 | >= 3.2", false}, - { "3.2-rc0", ">= 2.0.12, < 2.1 | >= 2.1.3, < 2.2 | >= 2.2.3, < 2.3 | >= 2.3.2, < 3 | >= 3.2", false}, + // version does not support prerelease or build metadata at this time, so don't test for it. + // { "3.2-rc0", ">= 2.0.12, < 2.1 | >= 2.1.3, < 2.2 | >= 2.2.3, < 2.3 | >= 2.3.2, < 3 | >= 3.2", false}, }; -#endif inline bool test_constraint(std::string_view v, std::string_view c) { return version_constraint{c}.test(version{v}); } +inline bool test_constraint(std::string_view v, std::string_view c, bool expect) { + if (version_constraint{c}.test(version{v}) == expect) + return true; + std::cerr << "Failed: ver: " << v << " " << c << " expect: " << expect << "\n"; + return false; +} TEST_CASE("Testing version constraints") { REQUIRE(test_constraint({"999"}, {""})); @@ -54,4 +59,8 @@ TEST_CASE("Testing version constraints") { REQUIRE(test_constraint({"1.0.0"}, {">=1.0.0"})); REQUIRE(!test_constraint({"1.0.0"}, {"<1.0.0"})); REQUIRE(!test_constraint({"1.0.0"}, {">1.0.0"})); -} \ No newline at end of file + + for(const auto& a : compare_list) { + REQUIRE(test_constraint(a.ver, a.constraint, a.result)); + } +} From 9bfb93fcee1fe302ec7092049fe8dc842e8ad8c9 Mon Sep 17 00:00:00 2001 From: Scott Bailey Date: Mon, 6 Mar 2023 15:34:09 -0600 Subject: [PATCH 13/14] Correct version constraint decode and renable additional testing. --- src/version_constraint.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version_constraint.cpp b/src/version_constraint.cpp index 5452667..ca64ed9 100644 --- a/src/version_constraint.cpp +++ b/src/version_constraint.cpp @@ -45,7 +45,7 @@ inline std::string_view trim(std::string_view s) { auto last = std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {return !std::isspace(ch);}); // Convert the reverse iter last to the forward iterator containing end using base(). // See: https://en.cppreference.com/w/cpp/iterator/reverse_iterator/base - return std::string_view{first, last.base() - first}; + return std::string_view{first, static_cast(last.base() - first)}; } From 866a8f2dd30740f01d3bee824057e58b7a06f6e3 Mon Sep 17 00:00:00 2001 From: Michal Lesiak Date: Thu, 9 Mar 2023 13:59:46 +0100 Subject: [PATCH 14/14] Fix clang-tidy warnings --- include/antler/project/cmake.hpp | 2 +- include/antler/project/dependency.hpp | 3 +- include/antler/project/object.hpp | 7 ++- include/antler/project/project.hpp | 4 +- include/antler/project/source.hpp | 2 +- include/antler/project/version.hpp | 14 +++--- include/antler/project/version_constraint.hpp | 3 -- include/antler/system/utils.hpp | 16 +++--- src/location.cpp | 17 +++---- src/object.cpp | 1 - src/project-parse.cpp | 4 +- src/project-populate.cpp | 3 +- src/project-print.cpp | 2 +- src/version_constraint.cpp | 9 ++-- tests/cmake_tests.cpp | 2 +- tests/common.hpp | 2 +- tests/init_tests.cpp | 1 - tests/version_constraint_tests.cpp | 2 - tests/version_tests.cpp | 50 +++++++++---------- tools/build.hpp | 1 - tools/main.cpp | 7 --- tools/populate.hpp | 1 - 22 files changed, 65 insertions(+), 88 deletions(-) diff --git a/include/antler/project/cmake.hpp b/include/antler/project/cmake.hpp index 18537eb..40450cc 100644 --- a/include/antler/project/cmake.hpp +++ b/include/antler/project/cmake.hpp @@ -38,7 +38,7 @@ namespace antler::project { } template - inline static void emit_project(Stream& s, const project& proj) noexcept { + inline static void emit_project(Stream& s, const project&) noexcept { s << "find_package(cdt)\n\n"; s << "add_subdirectory(${CMAKE_SOURCE_DIR}/../libs ${CMAKE_CURRENT_BINARY_DIR}/libs)\n"; s << "add_subdirectory(${CMAKE_SOURCE_DIR}/../tests ${CMAKE_CURRENT_BINARY_DIR}/tests)\n"; diff --git a/include/antler/project/dependency.hpp b/include/antler/project/dependency.hpp index 7a4ec56..73e10c8 100644 --- a/include/antler/project/dependency.hpp +++ b/include/antler/project/dependency.hpp @@ -15,14 +15,13 @@ namespace antler::project { -/// This class models and containts the dependency portion of an ANTLER project. +/// This class models and contains the dependency portion of an ANTLER project. class dependency { public: using list_t = std::vector; ///< Alias for the list type. using patch_list_t = std::vector; ///< Alias for the patch file list type. -public: // use default constructors, copy and move constructors and assignments dependency() = default; inline dependency(std::string_view name, std::string_view loc, std::string_view tag="", diff --git a/include/antler/project/object.hpp b/include/antler/project/object.hpp index eeaf293..8f7e1ee 100644 --- a/include/antler/project/object.hpp +++ b/include/antler/project/object.hpp @@ -24,7 +24,6 @@ class object { }; using list_t = std::vector; -public: // use default constructors, copy and move constructors and assignments /// Create a n object. @@ -110,16 +109,16 @@ class object { private: type_t m_type = type_t::error; ///< Object type: app, lib, or test. - std::string m_name = ""; ///< Object name. + std::string m_name; ///< Object name. antler::project::dependency::list_t m_dependencies; ///< list of dependencies. // app, lib: - std::string m_language = ""; ///< Language type, only valid for app or lib. + std::string m_language; ///< Language type, only valid for app or lib. std::vector m_comp_options = {}; std::vector m_link_options = {}; // test: - std::string m_command = ""; ///< Test command, only valid for test. + std::string m_command; ///< Test command, only valid for test. }; } // namespace antler::project \ No newline at end of file diff --git a/include/antler/project/project.hpp b/include/antler/project/project.hpp index fae1ad8..80348c2 100644 --- a/include/antler/project/project.hpp +++ b/include/antler/project/project.hpp @@ -59,7 +59,7 @@ class project { /// exists, an update is performed by removing the old one and adding the new one. /// @param obj The object to update or insert. template - inline void upsert(object&& obj) noexcept { + inline void upsert(object&& obj) { if constexpr (Ty == object::type_t::app) return upsert_app(std::move(obj)); else if constexpr (Ty == object::type_t::lib) @@ -163,7 +163,7 @@ class project { [[nodiscard]] static bool update_path(std::filesystem::path& path) noexcept; private: - inline void create_app(std::string_view name) { + inline void create_app(std::string_view) { } std::filesystem::path m_path; ///< path to the project.yaml file. diff --git a/include/antler/project/source.hpp b/include/antler/project/source.hpp index d3d8c3b..2dc0940 100644 --- a/include/antler/project/source.hpp +++ b/include/antler/project/source.hpp @@ -71,6 +71,6 @@ namespace antler::project { src.close(); } - inline static void create_specification_file(std::filesystem::path p, const object& obj) {} + inline static void create_specification_file(const std::filesystem::path&, const object&) {} }; } \ No newline at end of file diff --git a/include/antler/project/version.hpp b/include/antler/project/version.hpp index 0e30dee..8822b32 100644 --- a/include/antler/project/version.hpp +++ b/include/antler/project/version.hpp @@ -19,7 +19,7 @@ class version { using self = version; ///< Alias for self type. /// @param ver A string to create this version with. - inline version(std::string_view ver) { + inline explicit version(std::string_view ver) { if (!from_string(ver, major_comp, minor_comp, patch_comp, tweak_comp)) throw std::runtime_error("version malformed"); } @@ -28,7 +28,7 @@ class version { /// @param min Minor version component. /// @param pat Patch version component. /// @param tweak Tweak version component. - inline version(uint16_t maj=0, uint16_t min=0, uint16_t pat=0, std::string tweak="") + inline explicit version(uint16_t maj=0, uint16_t min=0, uint16_t pat=0, std::string tweak="") : major_comp(maj), minor_comp(min), patch_comp(pat), tweak_comp(std::move(tweak)) {} /// Copy constructor. @@ -159,8 +159,8 @@ class version { return 0; else if (v1 > v2) return 1; - else - return -1; + + return -1; } return c; }; @@ -187,9 +187,9 @@ class version { [[nodiscard]] inline std::string_view tweak() const noexcept { return tweak_comp; } private: - uint16_t major_comp; - uint16_t minor_comp; - uint16_t patch_comp; + uint16_t major_comp = 0; + uint16_t minor_comp = 0; + uint16_t patch_comp = 0; std::string tweak_comp; std::string raw_str; }; diff --git a/include/antler/project/version_constraint.hpp b/include/antler/project/version_constraint.hpp index f901fc5..98965cc 100644 --- a/include/antler/project/version_constraint.hpp +++ b/include/antler/project/version_constraint.hpp @@ -20,9 +20,6 @@ class version_constraint { /// @param ver The string to parse into a constraint. explicit version_constraint(std::string_view ver); - /// @param ver The string to parse into a constraint. - self& operator=(std::string_view ver); - /// Clear this constraint. void clear(); diff --git a/include/antler/system/utils.hpp b/include/antler/system/utils.hpp index 43a1a9c..ddaac20 100644 --- a/include/antler/system/utils.hpp +++ b/include/antler/system/utils.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include #include @@ -55,9 +55,10 @@ namespace antler::system { auto sv = magic_enum::enum_cast(s); if (sv) { return *sv; - } else { - return Enum::error; } + + return Enum::error; + } inline static int32_t execute(std::string_view prog) { @@ -67,9 +68,10 @@ namespace antler::system { return -1; } - std::array buff; + constexpr size_t array_size = 64; + std::array buff{}; - std::size_t n; + std::size_t n = 0; while ((n = fread(buff.data(), 1, buff.size(), h)) > 0) { fwrite(buff.data(), 1, n, stdout); @@ -80,8 +82,8 @@ namespace antler::system { inline static std::string extension(std::string_view l) { if (l == "CXX") return ".cpp"; - else - return ".c"; + + return ".c"; } } // namespace antler::system diff --git a/src/location.cpp b/src/location.cpp index 5d93ce2..80c432f 100644 --- a/src/location.cpp +++ b/src/location.cpp @@ -3,7 +3,6 @@ #include #include -#include #include #include @@ -48,12 +47,12 @@ struct curl { return sz*nm; } - std::string authenticated_request(std::string_view token, std::string_view url) { + [[nodiscard]] std::string authenticated_request(std::string_view token, std::string_view url) const { curl_easy_setopt(curl_obj, CURLOPT_XOAUTH2_BEARER, token.data()); return request(url); } - std::string request(std::string_view url) { + [[nodiscard]] std::string request(std::string_view url) const { curl_easy_setopt(curl_obj, CURLOPT_URL, url.data()); curl_easy_setopt(curl_obj, CURLOPT_WRITEFUNCTION, &curl::write_data); curl_easy_setopt(curl_obj, CURLOPT_USERAGENT, "antler-proj"); @@ -71,7 +70,7 @@ struct curl { } inline static bool is_url(std::string_view url) { return starts_with(url, "http:") || starts_with(url, "https:"); } - inline bool is_reachable(std::string_view url) { + inline bool is_reachable(std::string_view url) const { try { (void)request(url); return true; @@ -115,21 +114,21 @@ struct github { } static std::string_view get_org(std::string_view s) { - auto sub = s.substr(0, s.find_last_of("/")); - auto pos = sub.find_last_of("/"); + auto sub = s.substr(0, s.find_last_of('/')); + auto pos = sub.find_last_of('/'); return pos == std::string_view::npos ? sub : sub.substr(pos+1); } static std::string_view get_repo(std::string_view s) { - auto pos = s.find_last_of("/"); + auto pos = s.find_last_of('/'); return s.substr(pos+1); } static inline bool is_shorthand(std::string_view s) { - auto sub = s.substr(0, s.find_last_of("/")); - return sub.size() != s.size() && sub.find_last_of("/") == std::string_view::npos; + auto sub = s.substr(0, s.find_last_of('/')); + return sub.size() != s.size() && sub.find_last_of('/') == std::string_view::npos; } curl sender; diff --git a/src/object.cpp b/src/object.cpp index 5b5784b..a35ce4b 100644 --- a/src/object.cpp +++ b/src/object.cpp @@ -1,7 +1,6 @@ /// @copyright See `LICENSE` in the root directory of this project. #include -#include #include // find_if() #include diff --git a/src/project-parse.cpp b/src/project-parse.cpp index 5d190ce..dec47fa 100644 --- a/src/project-parse.cpp +++ b/src/project-parse.cpp @@ -25,8 +25,8 @@ namespace antler::project { namespace { // anonymous -inline static std::string_view sv_from_csubstr(const c4::csubstr& s) { return {s.data(), s.size()}; } -inline static std::string s_from_csubstr(const c4::csubstr& s) { return {s.data(), s.size()}; } +inline std::string_view sv_from_csubstr(const c4::csubstr& s) { return {s.data(), s.size()}; } +inline std::string s_from_csubstr(const c4::csubstr& s) { return {s.data(), s.size()}; } /// Load a text file into a string. /// diff --git a/src/project-populate.cpp b/src/project-populate.cpp index 6c3f390..e01925d 100644 --- a/src/project-populate.cpp +++ b/src/project-populate.cpp @@ -38,7 +38,7 @@ namespace { // anonymous } // anonymous namespace -bool project::populate(bool replace, std::ostream& error_stream) noexcept { +bool project::populate(bool, std::ostream& error_stream) noexcept { // Find the project path, and make sure the subdirs/project directory tree exists. auto project_path = m_path.parent_path(); if (!init_dirs(project_path, error_stream)) // expect_empty is `false`, it's okay if everthing exists. @@ -55,7 +55,6 @@ bool project::populate(bool replace, std::ostream& error_stream) noexcept { auto libs_path = build_path / std::filesystem::path("libs") / cmake::cmake_lists; auto tests_path = build_path / std::filesystem::path("tests") / cmake::cmake_lists; - bool create = true; // Look to see if the header contains the magic, if it does we will not create the file. std::ofstream rfs(root_path); std::ofstream afs(apps_path); diff --git a/src/project-print.cpp b/src/project-print.cpp index ad11340..c45384d 100644 --- a/src/project-print.cpp +++ b/src/project-print.cpp @@ -21,7 +21,7 @@ namespace antler::project { inline static c4::csubstr to_csubstr(std::string_view sv) noexcept { return {sv.data(), sv.size()}; } inline static c4::csubstr to_csubstr(token tok) noexcept { return to_csubstr(system::to_string(tok)); } -inline static c4::csubstr to_csubstr(version v) noexcept { return to_csubstr(v.to_string()); } +inline static c4::csubstr to_csubstr(const version& v) noexcept { return to_csubstr(v.to_string()); } void project::print(std::ostream& os) const noexcept { diff --git a/src/version_constraint.cpp b/src/version_constraint.cpp index ca64ed9..f9fac66 100644 --- a/src/version_constraint.cpp +++ b/src/version_constraint.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include @@ -30,8 +29,8 @@ namespace { // anonymous constexpr uint16_t max_component = std::numeric_limits::max(); -static inline const version min_version{}; -static inline const version max_version{max_component, max_component, max_component}; +inline const version min_version{}; +inline const version max_version{max_component, max_component, max_component}; /// Trim whitespace from the front and back of string. /// @param s The string to trim @@ -115,7 +114,7 @@ void version_constraint::load(std::string_view sin, std::ostream& os) { // first char must have been an operation, right? auto first_digit = ver_str.find_first_of("0123456789"); - if(first_digit != ver_str.npos) { + if(first_digit != std::string::npos) { el_list.push_back( ver_str.substr(first_digit) ); el_list[0] = ver_str.substr(0,first_digit); } @@ -253,7 +252,7 @@ void version_constraint::print(std::ostream& os) const noexcept { case bounds_inclusivity::upper: os << ">" << a.lower_bound << ", <=" << a.upper_bound; break; case bounds_inclusivity::both: os << ">=" << a.lower_bound << ", <=" << a.upper_bound; break; case bounds_inclusivity::unique: os << a.lower_bound; break; - }; + } } } diff --git a/tests/cmake_tests.cpp b/tests/cmake_tests.cpp index 8db8181..6af5fa4 100644 --- a/tests/cmake_tests.cpp +++ b/tests/cmake_tests.cpp @@ -14,7 +14,7 @@ TEST_CASE("Testing cmake emission") { project proj = {}; proj.name("test_proj"); - proj.version({"v1.0.0"}); + proj.version(antler::project::version{"v1.0.0"}); std::stringstream ss; diff --git a/tests/common.hpp b/tests/common.hpp index 3c1d654..af74a91 100644 --- a/tests/common.hpp +++ b/tests/common.hpp @@ -8,7 +8,7 @@ #include -inline bool remove_file(std::string_view fn) { return std::filesystem::remove_all(fn); } +inline bool remove_file(std::string_view fn) { return std::filesystem::remove_all(fn) != 0U; } inline bool load_project(std::string_view fn, antler::project::project& proj) { auto p = std::filesystem::canonical(std::filesystem::path(fn)); diff --git a/tests/init_tests.cpp b/tests/init_tests.cpp index 4ab1e40..73ce24d 100644 --- a/tests/init_tests.cpp +++ b/tests/init_tests.cpp @@ -1,7 +1,6 @@ /// @copyright See `LICENSE` in the root directory of this project. #include -#include #include diff --git a/tests/version_constraint_tests.cpp b/tests/version_constraint_tests.cpp index fd82571..be027a9 100644 --- a/tests/version_constraint_tests.cpp +++ b/tests/version_constraint_tests.cpp @@ -3,12 +3,10 @@ #include #include -#include "common.hpp" #include #include #include -#include using namespace antler::project; diff --git a/tests/version_tests.cpp b/tests/version_tests.cpp index a1d9eb4..34f88a9 100644 --- a/tests/version_tests.cpp +++ b/tests/version_tests.cpp @@ -8,7 +8,7 @@ TEST_CASE("Testing version class") { using namespace antler::project; { - version v = {}; + version v = version{}; REQUIRE( v.major() == 0 ); REQUIRE( v.minor() == 0 ); REQUIRE( v.patch() == 0 ); @@ -17,7 +17,7 @@ TEST_CASE("Testing version class") { } { - version v = {1}; + version v = version{1}; REQUIRE( v.major() == 1 ); REQUIRE( v.minor() == 0 ); REQUIRE( v.patch() == 0 ); @@ -26,7 +26,7 @@ TEST_CASE("Testing version class") { } { - version v = {1, 1}; + version v = version{1, 1}; REQUIRE( v.major() == 1 ); REQUIRE( v.minor() == 1 ); REQUIRE( v.patch() == 0 ); @@ -35,7 +35,7 @@ TEST_CASE("Testing version class") { } { - version v = {1, 1, 1}; + version v = version{1, 1, 1}; REQUIRE( v.major() == 1 ); REQUIRE( v.minor() == 1 ); REQUIRE( v.patch() == 1 ); @@ -44,7 +44,7 @@ TEST_CASE("Testing version class") { } { - version v = {1, 1, 1, "rc3"}; + version v = version{1, 1, 1, "rc3"}; REQUIRE( v.major() == 1 ); REQUIRE( v.minor() == 1 ); REQUIRE( v.patch() == 1 ); @@ -54,7 +54,7 @@ TEST_CASE("Testing version class") { { uint16_t mv = std::numeric_limits::max(); - version v = {mv, mv, mv, "rc3"}; + version v = version{mv, mv, mv, "rc3"}; REQUIRE( v.major() == mv ); REQUIRE( v.minor() == mv ); REQUIRE( v.patch() == mv ); @@ -72,17 +72,13 @@ TEST_CASE("Testing version class") { } { - version v = {1, 1, 1, "rc3"}; + version v = version{1, 1, 1, "rc3"}; version v2 = v; - REQUIRE( v2.major() == 1 ); - REQUIRE( v2.minor() == 1 ); - REQUIRE( v2.patch() == 1 ); - REQUIRE( v2.tweak() == "rc3"); - REQUIRE( v2.to_string() == "1.1.1-rc3"); + REQUIRE( v == v2 ); } { - version v = {"1.2.3"}; + version v = version{"1.2.3"}; REQUIRE( v.major() == 1 ); REQUIRE( v.minor() == 2 ); REQUIRE( v.patch() == 3 ); @@ -91,7 +87,7 @@ TEST_CASE("Testing version class") { } { - version v = {"v1.2.3"}; + version v = version{"v1.2.3"}; REQUIRE( v.major() == 1 ); REQUIRE( v.minor() == 2 ); REQUIRE( v.patch() == 3 ); @@ -100,7 +96,7 @@ TEST_CASE("Testing version class") { } { - version v = {"V1.2.3"}; + version v = version{"V1.2.3"}; REQUIRE( v.major() == 1 ); REQUIRE( v.minor() == 2 ); REQUIRE( v.patch() == 3 ); @@ -109,7 +105,7 @@ TEST_CASE("Testing version class") { } { - version v = {"1.1.1-rc3"}; + version v = version{"1.1.1-rc3"}; REQUIRE( v.major() == 1 ); REQUIRE( v.minor() == 1 ); REQUIRE( v.patch() == 1 ); @@ -118,7 +114,7 @@ TEST_CASE("Testing version class") { } { - version v = {"v1.1.1-rc3"}; + version v = version{"v1.1.1-rc3"}; REQUIRE( v.major() == 1 ); REQUIRE( v.minor() == 1 ); REQUIRE( v.patch() == 1 ); @@ -127,7 +123,7 @@ TEST_CASE("Testing version class") { } { - version v = {"V1.1.1-rc3"}; + version v = version{"V1.1.1-rc3"}; REQUIRE( v.major() == 1 ); REQUIRE( v.minor() == 1 ); REQUIRE( v.patch() == 1 ); @@ -140,29 +136,29 @@ TEST_CASE("Testing version class comparisons") { using namespace antler::project; { - version v1 = {}; - version v2 = {1}; + version v1 = version{}; + version v2 = version{1}; REQUIRE( v1 < v2); REQUIRE( v1 <= v2); - v1 = {1,0}; + v1 = version{1,0}; REQUIRE( v1 >= v2 ); REQUIRE( v1 <= v2 ); REQUIRE( v1 == v2 ); - v1 = {1, 1}; + v1 = version{1, 1}; REQUIRE( v1 > v2 ); REQUIRE( v1 >= v2 ); - v2 = {1, 2}; + v2 = version{1, 2}; REQUIRE( v1 < v2 ); - v1 = {1, 0, 1}; - v2 = {1, 2}; + v1 = version{1, 0, 1}; + v2 = version{1, 2}; REQUIRE( v1 < v2 ); - v1 = {1, 2, 0, "rc3"}; - v2 = {1, 2, 0, "rc1"}; + v1 = version{1, 2, 0, "rc3"}; + v2 = version{1, 2, 0, "rc1"}; REQUIRE( v1 == v2 ); } diff --git a/tools/build.hpp b/tools/build.hpp index 08e5f65..58ae8fc 100644 --- a/tools/build.hpp +++ b/tools/build.hpp @@ -101,6 +101,5 @@ namespace antler { CLI::App* subcommand; std::string path; - bool verbose; }; } // namespace antler \ No newline at end of file diff --git a/tools/main.cpp b/tools/main.cpp index 676c821..7aa28f6 100644 --- a/tools/main.cpp +++ b/tools/main.cpp @@ -1,19 +1,12 @@ /// @copyright See `LICENSE` in the root directory of this project. #include -#include -#include #include -#include -#include #include #include #include "CLI11.hpp" -#include -#include - #include "add_to.hpp" #include "app_version.hpp" #include "build.hpp" diff --git a/tools/populate.hpp b/tools/populate.hpp index 775de59..ea4a58e 100644 --- a/tools/populate.hpp +++ b/tools/populate.hpp @@ -33,6 +33,5 @@ namespace antler { CLI::App* subcommand; std::string path; - bool verbose; }; } // namespace antler \ No newline at end of file