Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 1 addition & 14 deletions form/core/container_naming.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,19 +61,6 @@ namespace form::detail::experimental {
return result;
}

/// Returns the technology token used in physical names.
inline std::string technology_name(form::technology::id tech)
{
if (tech.major == form::technology::major::generic) {
return "generic";
}
auto name = form::technology::to_string(tech);
for (char& c : name) {
c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
}
return sanitize_name(name);
}

/// Throws if a creator or stage cannot be part of a physical row-space name.
inline void check_row_space_parts(std::string_view creator, std::string_view stage)
{
Expand All @@ -99,7 +86,7 @@ namespace form::detail::experimental {
std::string_view creator,
std::string_view stage)
{
std::string result = technology_name(tech);
std::string result{form::technology::on_disk_token(tech)};
result += row_space_field_separator;
result += build_stream_name(creator, stage);
return result;
Expand Down
185 changes: 164 additions & 21 deletions form/core/technology.hpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
#ifndef FORM_CORE_TECHNOLOGY_HPP
#define FORM_CORE_TECHNOLOGY_HPP

#include <algorithm>
#include <array>
#include <compare>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>

/* A storage technology, identified by a (major, minor) pair */
/* @file technology.hpp
* @brief Storage technology identifiers and their configuration and on-disk names.
* - A storage technology, identified by a (major, minor) pair.
* - Configuration names and on-disk tokens are independent: configuration names are user-facing;
* on-disk tokens are part of the FORM persistent format. Neither is derived from the other.
* - Add technologies to detail::registry. Compile-time checks enforce the registry invariants.
*/

namespace form::technology {

Expand All @@ -32,40 +42,173 @@ namespace form::technology {
constexpr auto operator<=>(id const&) const = default;
};

// Backends: valid (major, minor) pairs, stable numeric values as a future token may persist them
// Backends: valid (major, minor) pairs. The numeric values are stable, but nothing writes
// them to a file: a file names a technology by its on-disk token.
inline constexpr id root_ttree{.major = major::root, .minor = 1};
inline constexpr id root_rntuple{.major = major::root, .minor = 2};
inline constexpr id hdf5{.major = major::hdf5, .minor = 1};

// Canonical string -> technology mapping: the single place a technology string is parsed, replacing the copies that used to live in each module/source/test
inline id from_string(std::string_view name)
namespace detail {

struct registration {
id technology;
std::string_view configuration_name;
std::string_view on_disk_token;
bool is_implemented;
};

/// The single place a technology's spellings are declared. Nothing else may invent one.
inline constexpr std::array registry{
// Generic is not configurable but has an on-disk token.
registration{.technology = id{},
.configuration_name = {},
.on_disk_token = "generic",
.is_implemented = true},
registration{.technology = root_ttree,
.configuration_name = "ROOT_TTREE",
.on_disk_token = "root_ttree",
.is_implemented = true},
registration{.technology = root_rntuple,
.configuration_name = "ROOT_RNTUPLE",
.on_disk_token = "root_rntuple",
.is_implemented = true},
// Reserved for a future HDF5 backend.
registration{.technology = hdf5,
.configuration_name = "HDF5",
.on_disk_token = "hdf5",
.is_implemented = false},
};

/// Every technology must be nameable on disk: a row without a token could not be written.
consteval bool tokens_are_present()
{
return std::ranges::all_of(
registry, [](registration const& entry) { return !entry.on_disk_token.empty(); });
}

/// Checks that technology IDs, tokens and non-empty configuration names are unique.
consteval bool registry_is_unambiguous()
{
for (std::size_t i = 0; i < registry.size(); ++i) {
for (std::size_t j = i + 1; j < registry.size(); ++j) {
auto const& left = registry.at(i);
auto const& right = registry.at(j);
if (left.technology == right.technology) {
return false;
}
if (left.on_disk_token == right.on_disk_token) {
return false;
}
if (!left.configuration_name.empty() &&
left.configuration_name == right.configuration_name) {
return false;
}
}
}
return true;
}

static_assert(tokens_are_present(),
"every technology needs an on-disk token: it names the technology in a file");
static_assert(registry_is_unambiguous(),
"two technologies claim the same id or the same spelling");

constexpr registration const* lookup_by_id(id technology)
{
for (auto const& entry : registry) {
if (entry.technology == technology) {
return &entry;
}
}
return nullptr;
}

constexpr registration const* lookup_by_configuration_name(std::string_view name)
{
if (name.empty()) {
return nullptr;
}
for (auto const& entry : registry) {
if (entry.configuration_name == name) {
return &entry;
}
}
return nullptr;
}

// No empty guard is needed, unlike configuration names: every token is non-empty.
constexpr registration const* lookup_by_on_disk_token(std::string_view token)
{
for (auto const& entry : registry) {
if (entry.on_disk_token == token) {
return &entry;
}
}
return nullptr;
}

} // namespace detail

// ---- Configuration names: user-facing spellings, independent of on-disk tokens. ----

inline std::string to_string(id tech)
{
if (name == "ROOT_TTREE") {
return root_ttree;
auto const* const found = detail::lookup_by_id(tech);
if (found == nullptr || found->configuration_name.empty()) {
return "UNKNOWN";
}
if (name == "ROOT_RNTUPLE") {
return root_rntuple;
return std::string{found->configuration_name};
}

/// Parses an exact configuration name; throws if unknown or unavailable.
inline id from_string(std::string_view name)
{
auto const* const found = detail::lookup_by_configuration_name(name);
if (found == nullptr) {
if (detail::lookup_by_on_disk_token(name) != nullptr) {
throw std::runtime_error("Unknown technology: " + std::string{name} +
" (that is an on-disk token, not a configuration name)");
}
throw std::runtime_error("Unknown technology: " + std::string{name});
}
if (name == "HDF5") {
// HDF5 is a reserved technology but has no backend yet: reject it at parse time
throw std::runtime_error("Technology 'HDF5' is recognized but not yet implemented");
if (!found->is_implemented) {
throw std::runtime_error("Technology '" + std::string{name} +
"' is recognized but not yet implemented");
}
throw std::runtime_error("Unknown technology: " + std::string(name));
return found->technology;
}

// Canonical technology -> string mapping
inline std::string to_string(id tech)
// ---- On-disk tokens: persistent identifiers written into FORM files. ----

constexpr std::string_view on_disk_token(id tech)
{
if (tech == root_ttree) {
return "ROOT_TTREE";
auto const* const found = detail::lookup_by_id(tech);
if (found == nullptr) {
throw std::runtime_error("FORM: an unregistered technology has no on-disk token and "
"cannot be named in a file");
}
if (tech == root_rntuple) {
return "ROOT_RNTUPLE";
return found->on_disk_token;
}

constexpr std::optional<id> find_on_disk_token(std::string_view token)
{
auto const* const found = detail::lookup_by_on_disk_token(token);
return found == nullptr ? std::nullopt : std::optional<id>{found->technology};
}

/// Parses an exact on-disk token; throws if unknown or unavailable.
constexpr id from_on_disk_token(std::string_view token)
{
auto const* const found = detail::lookup_by_on_disk_token(token);
if (found == nullptr) {
throw std::runtime_error("Unknown FORM on-disk technology token: '" + std::string{token} +
"'");
}
if (tech == hdf5) {
return "HDF5";
if (!found->is_implemented) {
throw std::runtime_error("FORM technology '" + std::string{token} +
"' is recorded in the file but is not implemented");
}
return "UNKNOWN";
return found->technology;
}

} // namespace form::technology
Expand Down
4 changes: 2 additions & 2 deletions form/persistence/navigation_naming.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ namespace form::detail::experimental {
form::technology::id tech)
{
std::string name{navigation_prefix};
name += technology_name(tech);
name += form::technology::on_disk_token(tech);
name += "_cells_";
name += hierarchy_key(hierarchy);
return name;
Expand All @@ -69,7 +69,7 @@ namespace form::detail::experimental {
inline std::string navigation_dictionary_name(form::technology::id tech)
{
std::string name{navigation_prefix};
name += technology_name(tech);
name += form::technology::on_disk_token(tech);
name += "_products";
return name;
}
Expand Down
4 changes: 2 additions & 2 deletions form/persistence/persistence_writer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ namespace {
for (auto const& tech : actual->second) {
if (!planned.contains(tech)) {
throw std::runtime_error("persistence_writer: file '" + file_name + "' holds '" +
technology_name(tech) +
std::string{form::technology::on_disk_token(tech)} +
"' products that were never planned; its descriptor would be "
"inconsistent with its contents");
}
Expand All @@ -65,7 +65,7 @@ namespace {
if (!technology_names.empty()) {
technology_names += ',';
}
technology_names += technology_name(tech);
technology_names += form::technology::on_disk_token(tech);
}

return {{std::string{descriptor_layout_version_key}, std::string{form_layout_version}},
Expand Down
2 changes: 1 addition & 1 deletion test/form/dune_example_navigation_dump.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ int main(int const argc, char* argv[])
return 1;
}

auto const prefix = "nav_" + technology_token(tech_string);
auto const prefix = "nav_" + std::string{form::technology::on_disk_token(technology)};

checker checks;
column_reader reader{file_name, technology};
Expand Down
61 changes: 57 additions & 4 deletions test/form/form_basics_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,14 @@
#include "root_storage/root_rntuple_write_container.hpp"
#endif
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers.hpp>
#include <catch2/matchers/catch_matchers_string.hpp>

#include <algorithm>
#include <cstdint>
#include <map>
#include <memory>
#include <optional>
#include <stdexcept>
#include <string>
#include <typeinfo>
Expand Down Expand Up @@ -245,7 +248,7 @@ TEST_CASE("token basics", "[form]")
CHECK(t.id() == 42u);
}

TEST_CASE("technology::id string conversions", "[form]")
TEST_CASE("technology::id configuration names", "[form]")
{
using namespace form::technology;

Expand All @@ -264,6 +267,56 @@ TEST_CASE("technology::id string conversions", "[form]")
// An unknown name throws; an unknown id stringifies to the sentinel
CHECK_THROWS_AS(from_string("NOT_A_TECH"), std::runtime_error);
CHECK(to_string(id{}) == "UNKNOWN");

// An on-disk token is not a configuration name. The job configuration takes "ROOT_TTREE";
// "root_ttree" is what a file carries, and naming the confusion beats "Unknown technology".
CHECK_THROWS_AS(from_string("root_ttree"), std::runtime_error);
CHECK_THROWS_WITH(from_string("root_ttree"),
Catch::Matchers::ContainsSubstring("on-disk token, not a configuration name"));
CHECK_THROWS_AS(from_string("ROOT_Ttree"), std::runtime_error);
CHECK_THROWS_AS(from_string(""), std::runtime_error);

// Generic: not selectable from a configuration
CHECK_THROWS_AS(from_string("GENERIC"), std::runtime_error);
CHECK_THROWS_AS(from_string("generic"), std::runtime_error);
}

TEST_CASE("technology::id on-disk tokens", "[form]")
{
using namespace form::technology;

static_assert(on_disk_token(root_ttree) == "root_ttree");
static_assert(on_disk_token(root_rntuple) == "root_rntuple");
static_assert(on_disk_token(id{}) == "generic");
// hdf5 has no backend, but fixing its spelling now stops a future implementation from
// picking a different one. Every registered token is pinned here.
static_assert(on_disk_token(hdf5) == "hdf5");

CHECK(on_disk_token(root_ttree) == "root_ttree");
CHECK(on_disk_token(root_rntuple) == "root_rntuple");
CHECK(on_disk_token(id{}) == "generic");

for (auto const tech : {root_ttree, root_rntuple, id{}}) {
CHECK(find_on_disk_token(on_disk_token(tech)) == std::optional{tech});
CHECK(from_on_disk_token(on_disk_token(tech)) == tech);
}

static_assert(find_on_disk_token("ROOT_TTREE") == std::nullopt);
CHECK(find_on_disk_token("ROOT_TTREE") == std::nullopt);
CHECK(find_on_disk_token("Root_TTree") == std::nullopt);
CHECK(find_on_disk_token("GENERIC") == std::nullopt);
CHECK_THROWS_AS(from_on_disk_token("ROOT_TTREE"), std::runtime_error);

CHECK(find_on_disk_token("not_a_token") == std::nullopt);
CHECK(find_on_disk_token("") == std::nullopt);
CHECK_THROWS_AS(from_on_disk_token("not_a_token"), std::runtime_error);
CHECK_THROWS_AS(from_on_disk_token(""), std::runtime_error);

CHECK(find_on_disk_token("hdf5") == std::optional{hdf5});
CHECK_THROWS_AS(from_on_disk_token("hdf5"), std::runtime_error);

CHECK_THROWS_AS(on_disk_token(id{.major = form::technology::major::root, .minor = 99}),
std::runtime_error);
}

TEST_CASE("technology::id members and ordering", "[form]")
Expand Down Expand Up @@ -1024,9 +1077,9 @@ TEST_CASE("hierarchy keys and navigation column names", "[form]")

SECTION("a navigation name carries the technology that wrote it")
{
CHECK(technology_name(form::technology::root_ttree) == "root_ttree");
CHECK(technology_name(form::technology::root_rntuple) == "root_rntuple");
CHECK(technology_name(form::technology::id{}) == "generic");
CHECK(form::technology::on_disk_token(form::technology::root_ttree) == "root_ttree");
CHECK(form::technology::on_disk_token(form::technology::root_rntuple) == "root_rntuple");
CHECK(form::technology::on_disk_token(form::technology::id{}) == "generic");

CHECK(navigation_table_name(cell_hierarchy{{"event"}}, form::technology::root_ttree) ==
"nav_root_ttree_cells_event");
Expand Down
Loading
Loading