Skip to content
Draft
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
2 changes: 1 addition & 1 deletion phlex/core/consumer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace phlex::detail {
}

phlex::experimental::algorithm_name const& consumer::name() const noexcept { return name_; }
phlex::experimental::identifier const& consumer::plugin() const noexcept
phlex::experimental::identifier_or_empty const& consumer::plugin() const noexcept
{
return name_.plugin();
}
Expand Down
9 changes: 8 additions & 1 deletion phlex/core/consumer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,25 @@
#define PHLEX_CORE_CONSUMER_HPP

#include "phlex/model/algorithm_name.hpp"
#include "phlex/model/identifier.hpp"
#include "phlex/phlex_core_export.hpp"

#include <string>
#include <vector>

namespace phlex::detail {
/// Base class of nodes that consume data products.
///
/// Accessor contract: `name()` is the complete creator name. `plugin()` is that name's plugin
/// component, which is explicitly empty (`empty_string`) for an unqualified name, such as one
/// registered without a configuration; it is never an invalid empty `identifier`.
/// `algorithm()` is the non-empty algorithm component.
class PHLEX_CORE_EXPORT consumer {
public:
consumer(phlex::experimental::algorithm_name name, std::vector<std::string> predicates);

phlex::experimental::algorithm_name const& name() const noexcept;
phlex::experimental::identifier const& plugin() const noexcept;
phlex::experimental::identifier_or_empty const& plugin() const noexcept;
phlex::experimental::identifier const& algorithm() const noexcept;
std::vector<std::string> const& when() const noexcept;

Expand Down
15 changes: 13 additions & 2 deletions phlex/core/detail/make_algorithm_name.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,23 @@
#include "phlex/model/algorithm_name.hpp"
#include "phlex/model/identifier.hpp"

#include <stdexcept>
#include <string_view>

namespace phlex::experimental::internal {
// The plugin component is absent (explicitly empty) only when there is no configuration. A
// configuration must supply a non-empty 'module_label': a missing label is an error reported by
// the configuration, and an empty label is rejected here rather than treated as an absent
// plugin.
algorithm_name make_algorithm_name(configuration const* config, std::string_view name)
{
return {config ? identifier(config->get<std::string_view>("module_label")) : ""_id,
identifier(name)};
if (config == nullptr) {
return {identifier_or_empty{empty_string{}}, identifier(name)};
}
auto const module_label = config->get<std::string_view>("module_label");
if (module_label.empty()) {
throw std::runtime_error("The configured 'module_label' must not be empty");
}
return {identifier_or_empty{identifier(module_label)}, identifier(name)};
}
}
25 changes: 23 additions & 2 deletions phlex/core/provider_node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,32 @@
#include <cstddef>
#include <functional>
#include <memory>
#include <stdexcept>
#include <utility>

namespace {
// A provider always names its creator, so an output specification without one is an error.
phlex::experimental::algorithm_name const& creator_of(
phlex::experimental::product_specification const& spec)
{
if (!spec.creator()) {
throw std::runtime_error("Cannot create a provider for the product specification '" +
spec.to_string() + "' because it has no creator");
}
return *spec.creator();
}

phlex::experimental::product_specification require_creator(
phlex::experimental::product_specification spec)
{
creator_of(spec);
return spec;
}
}

namespace phlex::detail {
provider_node::provider_node(tbb::flow::graph& g, provider_bundle bundle) :
provider_node{bundle.spec.creator(),
provider_node{creator_of(bundle.spec),
bundle.max_concurrency.value,
g,
std::move(bundle.provider_function),
Expand All @@ -33,7 +54,7 @@ namespace phlex::detail {
phlex::experimental::identifier output_layer,
phlex::experimental::identifier stage) :
name_{std::move(algo_name)},
output_{std::move(output_spec)},
output_{require_creator(std::move(output_spec))},
layer_{std::move(output_layer)},
stage_{std::move(stage)},
provider_{g,
Expand Down
4 changes: 4 additions & 0 deletions phlex/core/upstream_predicates.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ namespace phlex::detail {
return experimental_when({std::forward<decltype(names)>(names)...});
}

// Fluent suffix registration is textual: the strings given here, including the empty string
// and the default used when no suffixes are registered, stay strings in the registrar. They
// become `suffix_t` values (empty text as `empty_string`, non-empty text as a named
// identifier) when `to_product_specifications()` creates the product specifications.
template <std::size_t M>
requires(NumberOutputProducts > 0)
void output_product_suffixes(std::array<std::string, M> outputs)
Expand Down
17 changes: 9 additions & 8 deletions phlex/model/algorithm_name.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,13 @@ namespace {
}

namespace phlex::experimental {
algorithm_name::algorithm_name() = default;

algorithm_name::algorithm_name(char const* spec) : algorithm_name{std::string_view{spec}} {}
algorithm_name::algorithm_name(std::string const& spec) : algorithm_name{std::string_view{spec}}
{
}
algorithm_name::algorithm_name(std::string_view spec) { *this = create(spec); }

algorithm_name::algorithm_name(identifier plugin, identifier algorithm) :
algorithm_name::algorithm_name(identifier_or_empty plugin, identifier algorithm) :
plugin_{std::move(plugin)}, algorithm_{std::move(algorithm)}
{
}
Expand All @@ -63,13 +61,16 @@ namespace phlex::experimental {
algorithm_name algorithm_name::create(char const* spec) { return create(std::string_view{spec}); }
algorithm_name algorithm_name::create(std::string_view spec)
{
// Empty spec means nothing is specified
if (spec.empty()) {
return {};
throw std::runtime_error("An algorithm name must not be empty");
}

// Do we have a colon? If so, have both plugin and algorithm
if (spec.contains(':')) {
if (spec.starts_with(':')) {
throw std::runtime_error(
fmt::format("The specification '{}' has an empty plugin name", spec));
}
if (spec.ends_with(':')) {
throw std::runtime_error("Cannot create an algorithm name that ends with a colon (':')");
}
Expand All @@ -81,14 +82,14 @@ namespace phlex::experimental {
throw std::runtime_error(
fmt::format("The specification '{}' is not a valid algorithm name.", spec));
}
return {identifier(plugin), identifier(algorithm)};
return {identifier_or_empty(plugin), identifier(algorithm)};
}

// No colon -- could be plugin or algorithm
// No colon: the plugin component is explicitly empty
if (!words_only(spec)) {
throw std::runtime_error(
fmt::format("The specification '{}' is not a valid algorithm name.", spec));
}
return {"", identifier(spec)};
return {identifier_or_empty(empty_string{}), identifier(spec)};
}
}
22 changes: 18 additions & 4 deletions phlex/model/algorithm_name.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,43 @@
#include <cstdint>

namespace phlex::experimental {
/// The creator of a product or the name of an algorithm.
///
/// An algorithm name always has a non-empty algorithm component. Its plugin component is either
/// a non-empty plugin name or explicitly empty (for an unqualified name). A default-constructed
/// algorithm name is not valid, so one must always be constructed from a name.
class PHLEX_MODEL_EXPORT algorithm_name {
public:
algorithm_name();
algorithm_name() = delete;

// NOLINTBEGIN(google-explicit-constructor) - Implicit conversion is intentional
algorithm_name(char const* spec);
algorithm_name(std::string const& spec);
algorithm_name(std::string_view spec);
// NOLINTEND(google-explicit-constructor)
algorithm_name(identifier plugin, identifier algorithm);
algorithm_name(identifier_or_empty plugin, identifier algorithm);

std::string to_string() const;
identifier const& plugin() const noexcept { return plugin_; }
identifier_or_empty const& plugin() const noexcept { return plugin_; }
identifier const& algorithm() const noexcept { return algorithm_; }

bool match(algorithm_name const& other) const;
auto operator<=>(algorithm_name const&) const noexcept = default;

/// Parses a creator name.
///
/// Accepted forms:
/// - `algorithm`: the plugin component is explicitly empty.
/// - `plugin:algorithm`: both components are non-empty names.
///
/// Each component must consist of word characters only. Rejected inputs include the empty
/// string, a leading colon (`:algorithm`), a trailing colon (`plugin:`), and any empty
/// component. Throws `std::runtime_error` for every rejected input.
static algorithm_name create(char const* spec);
static algorithm_name create(std::string_view spec);

private:
identifier plugin_;
identifier_or_empty plugin_;
identifier algorithm_;
};

Expand Down
18 changes: 15 additions & 3 deletions phlex/model/handle.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#include <gsl/pointers>

#include <optional>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <variant>
Expand Down Expand Up @@ -54,6 +56,11 @@ namespace phlex {
using const_reference = value_type const&;
using const_pointer = value_type const*;

/// Borrowed textual views of the creator's components. Each view refers to the text owned
/// by the product specification that the handle references, so it is valid only while that
/// specification (and thus the handle's source) is alive. No copy is stored. A component that
/// is absent (the plugin of an unqualified creator, or both components when the specification
/// has no creator) is an empty view.
struct algorithm_name_view {
std::string_view plugin;
std::string_view algorithm;
Expand Down Expand Up @@ -87,12 +94,17 @@ namespace phlex {
// NOLINTEND(google-explicit-constructor)
auto const& data_cell_index() const noexcept { return *index_; }

// Product specification information
// Product specification information. The handle exposes borrowed textual views, not the
// domain types (`suffix_t`, `identifier_or_empty`), so callers need not depend on them.
/// Views of the creator's plugin and algorithm names. Absent components are empty views.
algorithm_name_view creator() const noexcept
{
return {std::string_view(spec_->plugin()), std::string_view(spec_->algorithm())};
auto const* const algorithm = spec_->algorithm();
return {spec_->plugin().text(),
algorithm ? std::string_view(*algorithm) : std::string_view{}};
}
std::string_view suffix() const noexcept { return std::string_view(spec_->suffix()); }
/// View of the suffix text; empty for the explicit empty suffix.
std::string_view suffix() const noexcept { return spec_->suffix().text(); }
std::string_view layer() const noexcept { return std::string_view(index_->layer_name()); }
std::string_view stage() const noexcept { return std::string_view(*stage_); }
std::string layer_path() const { return index_->layer_path().to_string(); }
Expand Down
31 changes: 22 additions & 9 deletions phlex/model/product_specification.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <fmt/format.h>

#include <cassert>
#include <optional>
#include <ranges>
#include <string>
#include <utility>
Expand All @@ -25,19 +26,25 @@ namespace phlex::experimental {
}
product_specification::product_specification(std::string_view name) { *this = create(name); }

product_specification::product_specification(experimental::algorithm_name creator,
experimental::identifier suffix,
product_specification::product_specification(std::optional<experimental::algorithm_name> creator,
experimental::suffix_t suffix,
type_id type) :
creator_{std::move(creator)}, suffix_{std::move(suffix)}, type_id_{std::move(type)}
{
}

identifier_or_empty const& product_specification::plugin() const noexcept
{
static identifier_or_empty const absent_plugin{empty_string{}};
return creator_ ? creator_->plugin() : absent_plugin;
}

std::string product_specification::to_string() const
{
if (creator_.plugin().empty() && creator_.algorithm().empty()) {
if (!creator_) {
return fmt::format("{}", suffix_);
}
return fmt::format("{}/{}", creator_.to_string(), suffix_);
return fmt::format("{}/{}", creator_->to_string(), suffix_);
}

product_specification product_specification::create(char const* c)
Expand All @@ -49,11 +56,15 @@ namespace phlex::experimental {
{
auto forward_slash = s.find('/');
if (forward_slash != std::string_view::npos) {
return {experimental::algorithm_name::create(s.substr(0, forward_slash)),
experimental::identifier(s.substr(forward_slash + 1)),
type_id{}};
// An empty creator part (e.g. "/hits") is an absent creator, not an invalid creator name.
auto const creator_text = s.substr(0, forward_slash);
std::optional<experimental::algorithm_name> creator;
if (!creator_text.empty()) {
creator = experimental::algorithm_name::create(creator_text);
}
return {std::move(creator), experimental::suffix_t(s.substr(forward_slash + 1)), type_id{}};
}
return {experimental::algorithm_name::create(""), experimental::identifier(s), type_id{}};
return {std::nullopt, experimental::suffix_t(s), type_id{}};
}

}
Expand All @@ -66,6 +77,8 @@ namespace phlex::detail {
{
static std::string const default_product_suffix;

// Fluent suffix registration is textual; empty text becomes `empty_string` and non-empty
// text a named identifier here, when the specifications are created.
if (output_suffixes.empty()) {
// This happens whenever the user does not specify output_product_suffixes(...), so we
// assign the default suffix to all output data products.
Expand All @@ -81,7 +94,7 @@ namespace phlex::detail {
return std::views::zip(output_suffixes, output_types) |
std::views::transform([&algo_name](auto const& p) {
return experimental::product_specification{
algo_name, experimental::identifier(std::get<0>(p)), std::get<1>(p)};
algo_name, experimental::suffix_t(std::get<0>(p)), std::get<1>(p)};
}) |
std::ranges::to<experimental::product_specifications>();
}
Expand Down
Loading
Loading