Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 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
21 changes: 14 additions & 7 deletions phlex/configuration.cpp
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
#include "phlex/configuration.hpp"
#include "phlex/core/product_query.hpp"
#include "phlex/model/identifier.hpp"
#include "phlex/model/product_specification.hpp"

#include <algorithm>
#include <array>
#include <string>
#include <string_view>

namespace {
[[maybe_unused]] std::optional<std::string> value_if_exists(
namespace phlex::detail {
std::optional<phlex::experimental::identifier> value_if_exists(
boost::json::object const& obj, // will be used later for new product_query
std::string_view parameter)
{
Expand Down Expand Up @@ -45,12 +46,12 @@ namespace {
// std::unreachable();
break;
}
throw std::runtime_error(
fmt::format("Error retrieving parameter '{}'. Should be a string but is instead a {}",
parameter,
kind));
throw std::runtime_error(fmt::format(
"Error retrieving parameter '{}'. Should be an identifier string but is instead a {}",
parameter,
kind));
}
return boost::json::value_to<std::string>(val);
return boost::json::value_to<phlex::experimental::identifier>(val);
}
}

Expand Down Expand Up @@ -79,4 +80,10 @@ namespace phlex {
auto layer = value_decorate_exception<std::string>(query_object, "layer");
return product_query{experimental::product_specification::create(product), layer};
}

experimental::identifier experimental::tag_invoke(boost::json::value_to_tag<identifier> const&,
boost::json::value const& jv)
{
return identifier{std::string_view(jv.as_string())};
}
}
10 changes: 10 additions & 0 deletions phlex/configuration.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include "boost/json.hpp"
#include "phlex/core/product_query.hpp"
#include "phlex/model/identifier.hpp"

#include <optional>
#include <string>
Expand All @@ -20,6 +21,10 @@ namespace phlex {
throw std::runtime_error("Error retrieving parameter '" + key + "':\n" + e.what());
}

// Used later for product_query
std::optional<phlex::experimental::identifier> value_if_exists(boost::json::object const& obj,
std::string_view parameter);

// helper for unpacking json array
template <typename T, std::size_t... I>
std::array<T, sizeof...(I)> unpack_json_array(boost::json::array const& array,
Expand Down Expand Up @@ -90,6 +95,11 @@ namespace phlex {
product_query tag_invoke(boost::json::value_to_tag<product_query> const&,
boost::json::value const& jv);

namespace experimental {
identifier tag_invoke(boost::json::value_to_tag<identifier> const&,
boost::json::value const& jv);
}

template <std::size_t N>
std::array<product_query, N> tag_invoke(
boost::json::value_to_tag<std::array<product_query, N>> const&, boost::json::value const& jv)
Expand Down
2 changes: 2 additions & 0 deletions phlex/model/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ cet_make_library(
data_cell_counter.cpp
data_layer_hierarchy.cpp
data_cell_index.cpp
identifier.cpp
product_matcher.cpp
product_store.cpp
products.cpp
Expand All @@ -30,6 +31,7 @@ install(
data_cell_counter.hpp
data_layer_hierarchy.hpp
data_cell_index.hpp
identifier.hpp
product_matcher.hpp
product_specification.hpp
product_store.hpp
Expand Down
46 changes: 46 additions & 0 deletions phlex/model/identifier.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#include "identifier.hpp"

#include <boost/hash2/hash_append.hpp>
#include <boost/hash2/xxhash.hpp>

namespace phlex::experimental {
identifier_query literals::operator""_idq(char const* lit, std::size_t len)
{
return {identifier::hash_string(std::string_view(lit, len))};
}

std::uint64_t identifier::hash_string(std::string_view str)
{
// Hash quality is very important here, since comparisons are done using only the hash
using namespace boost::hash2;
xxhash_64 h;
hash_append(h, {}, str);
return h.result();
}

identifier::identifier(std::string_view str) : content_(str), hash_(hash_string(content_)) {}
identifier& identifier::operator=(std::string_view str)
{
content_ = str;
hash_ = hash_string(content_);
return *this;
}

identifier::operator std::string_view() const noexcept { return std::string_view(content_); }
bool identifier::operator==(identifier const& rhs) const noexcept { return hash_ == rhs.hash_; }
std::strong_ordering identifier::operator<=>(identifier const& rhs) const noexcept
{
return hash_ <=> rhs.hash_;
}

bool operator==(identifier const& lhs, identifier_query rhs) { return lhs.hash_ == rhs.hash; }
std::strong_ordering operator<=>(identifier const& lhs, identifier_query rhs)
{
return lhs.hash_ <=> rhs.hash;
}

identifier literals::operator""_id(char const* lit, std::size_t len)
{
return identifier{std::string_view(lit, len)};
}
}
72 changes: 72 additions & 0 deletions phlex/model/identifier.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#ifndef PHLEX_MODEL_IDENTIFIER_H_
#define PHLEX_MODEL_IDENTIFIER_H_

#include <boost/json/fwd.hpp>

#include <fmt/format.h>

#include <cstdint>
#include <string>
#include <string_view>

namespace phlex::experimental {
/// If you're comparing to an identifier you know at compile time, you're probably not going to need
/// to print it.
struct identifier_query {
std::uint64_t hash;
};

/// Carries around the string itself (as a shared_ptr to string to make copies lighter)
/// along with a precomputed hash used for all comparisons
class identifier {
public:
static std::uint64_t hash_string(std::string_view str);
identifier(identifier const& other) = default;
identifier(identifier&& other) noexcept = default;

explicit identifier(std::string_view str);

identifier& operator=(identifier const& rhs) = default;
identifier& operator=(identifier&& rhs) noexcept = default;

// Assignment for identifiers read from a file
// Not sure we really need this
identifier& operator=(std::string_view str);

~identifier() = default;

// Conversion to std::string_view
explicit operator std::string_view() const noexcept;

bool operator==(identifier const& rhs) const noexcept;
std::strong_ordering operator<=>(identifier const& rhs) const noexcept;

// Comparison operators with _id queries
friend bool operator==(identifier const& lhs, identifier_query rhs);
friend std::strong_ordering operator<=>(identifier const& lhs, identifier_query rhs);
friend std::hash<identifier>;

private:
std::string content_;
std::uint64_t hash_;
};

// Identifier UDL
namespace literals {
identifier operator""_id(char const* lit, std::size_t len);
identifier_query operator""_idq(char const* lit, std::size_t len);
}

// Really trying to avoid the extra function call here
inline std::string_view format_as(identifier const& id) { return std::string_view(id); }
}

template <>
struct std::hash<phlex::experimental::identifier> {
std::size_t operator()(phlex::experimental::identifier const& id) const noexcept
{
return id.hash_;
}
};

#endif // PHLEX_MODEL_IDENTIFIER_H_
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ cet_test(type_deduction SOURCE type_deduction.cpp LIBRARIES
phlex::metaprogramming
)
cet_test(type_id SOURCE type_id.cpp LIBRARIES phlex::model fmt::fmt)
cet_test(identifier SOURCE identifier.cpp LIBRARIES phlex::model phlex::configuration fmt::fmt)
cet_test(
yielding_driver
SOURCE
Expand Down
72 changes: 72 additions & 0 deletions test/identifier.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#include "phlex/model/identifier.hpp"
#include <phlex/configuration.hpp>

#include <algorithm>
#include <array>
#include <cassert>
#include <string_view>

#include <boost/json.hpp>
#include <fmt/format.h>

int main()
{
using namespace phlex::experimental;
using namespace phlex::experimental::literals;
using namespace std::string_view_literals;
identifier a = "a"_id;
identifier a_copy = a;
identifier a2 = "a"_id;
identifier b = "b"_id;

boost::json::object parsed_json = boost::json::parse(R"( {"identifier": "b" } )").as_object();
auto b_from_json = phlex::detail::value_if_exists(parsed_json, "identifier");
assert(b_from_json);

fmt::print("a ({}) == \"a\"_idq: {}\n", a, a == "a"_idq);
fmt::print("a == a_copy ({}): {}\n", a_copy, a == a_copy);
fmt::print("a == a2 ({}): {}\n", a2, a == a2);
fmt::print("a != b ({}): {}\n", b, a != b);
fmt::print("b == *b_from_json ({}): {}\n", *b_from_json, b == *b_from_json);

assert(a == "a"_idq);
assert(a == a_copy);
assert(a == a2);
assert(a != b);
assert(b == *b_from_json);

// reassigning
a = "new a"_id;
a_copy = a;

// reassigning with a string_view
b = "new b"sv;

std::array<std::string_view, 9> strings{
"a"sv, "b"sv, "c"sv, "d"sv, "e"sv, "long-id-1"sv, "long-id-2"sv, "test"sv, "other_test"sv};
std::array<identifier, 9> identifiers{"a"_id,
"b"_id,
"c"_id,
"d"_id,
"e"_id,
"long-id-1"_id,
"long-id-2"_id,
"test"_id,
"other_test"_id};
std::ranges::sort(identifiers);
std::ranges::sort(strings);
fmt::print("Sorted identifiers (should not be in alphabetical order): \n");
for (auto const& id : identifiers) {
fmt::print("- {}\n", id);
}

bool ok = false;
for (std::size_t i = 0; i < identifiers.size(); ++i) {
if (strings.at(i) != std::string_view(identifiers.at(i))) {
ok = true;
break;
}
}
assert(ok);
return 0;
}
Loading