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
13 changes: 13 additions & 0 deletions form/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@

Prototype development for I/O infrastructure supporting Phlex

## Product suffix text

The Phlex FORM writer copies the textual value of each product specification's
`suffix_t` into FORM's product label. An explicit empty suffix (`empty_string{}`)
is written as `""`; a named suffix is written with its original spelling, including
case and underscores (for example, `Hits_2`). Empty text is a concrete product
suffix, not an omitted product or an unrestricted selector.

The persisted representation is unchanged: the product dictionary retains that
suffix text in `product_name`, and its `container_name` remains `row_space/suffix`.
An empty suffix therefore retains the trailing `/`, while a named suffix retains
its text after `/`. No placeholder name or file-format migration is introduced.

## build

`cmake -DUSE_FORM_ALONE=ON -DFORM_USE_ROOT_STORAGE=ON ../phlex/ ; make`
Expand Down
2 changes: 1 addition & 1 deletion form/form_module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ namespace {
std::cout << " Product: " << product_spec.to_string() << "\n";

// Create FORM product with metadata
products.emplace_back(product_spec.suffix().trans_get_string(), // label, from map key
products.emplace_back(std::string{product_spec.suffix().text()}, // label, from map key
product_ptr->address(), // data, from phlex product_base
&product_ptr->type() // type, from phlex product_base
);
Expand Down
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)};
}
}
3 changes: 3 additions & 0 deletions phlex/core/producer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
#include <oneapi/tbb/flow_graph.h>

namespace phlex::detail {
/// Independent producer interface used by the catalog, including translators.
/// Outputs carry explicit empty or named `suffix_t` values; `name()` retains the creator's
/// plugin component. The producer must outlive edges using its borrowed output port.
class PHLEX_CORE_EXPORT producer {
public:
virtual ~producer() = default;
Expand Down
8 changes: 4 additions & 4 deletions phlex/core/producer_catalog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ namespace {
using named_output_port = producer_catalog::named_output_port;

bool providers_only(product_selector const& query,
std::multimap<product_suffix_t, named_output_port> const& producers,
std::multimap<suffix_t, named_output_port> const& producers,
identifier const& stage)
{
if (query.stage.has_value() && query.stage.value() != "CURRENT"_idq &&
Expand Down Expand Up @@ -84,7 +84,7 @@ namespace {

void check_postconditions(product_selector const& query,
std::vector<named_output_port const*> const& candidates,
std::set<identifier> const& suffixes,
std::set<suffix_t> const& suffixes,
std::set<algorithm_name> const& creators,
std::set<type_id> const& types)
{
Expand Down Expand Up @@ -167,12 +167,12 @@ namespace phlex::detail {
if (b == e) {
spdlog::debug(
"Failed to find an algorithm that creates {} products. Assuming it comes from a provider",
query.suffix.value_or("*"_id));
query.suffix ? query.suffix->text() : "*");
return {};
}
std::vector<named_output_port const*> candidates;

std::set<experimental::identifier> suffixes;
std::set<experimental::suffix_t> suffixes;
if (query.suffix.has_value()) {
suffixes.insert(*query.suffix);
}
Expand Down
6 changes: 4 additions & 2 deletions phlex/core/producer_catalog.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@

namespace phlex::detail {
class producer;
using product_suffix_t = phlex::experimental::identifier;

/// Catalogs independent producers by their explicit empty or named `suffix_t` outputs.
/// An omitted selector suffix searches all keys; an explicit suffix searches only that key.
/// Creator values retain both plugin and algorithm components, and output ports are borrowed.
class PHLEX_CORE_EXPORT producer_catalog {
public:
explicit producer_catalog(std::span<producer* const> producers);
Expand All @@ -36,7 +38,7 @@ namespace phlex::detail {
auto values() const { return producers_ | std::views::values; }

private:
std::multimap<product_suffix_t, named_output_port> producers_;
std::multimap<phlex::experimental::suffix_t, named_output_port> producers_;
};
}

Expand Down
7 changes: 3 additions & 4 deletions phlex/core/product_selector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ namespace phlex {
// Check if a product_specification satisfies this query
bool product_selector::match(experimental::product_specification const& spec) const
{
if (!creator_match(spec.creator())) {
if (creator && (!spec.has_creator() || !creator_match(*spec.creator()))) {
return false;
}
if (type.valid() && type != spec.type()) {
Expand Down Expand Up @@ -73,7 +73,7 @@ namespace phlex {
bool product_selector::creator_match(experimental::algorithm_name const& alg) const
{
if (!creator) {
return true; // empty creator matches everything
return true; // an omitted creator criterion matches everything
}
experimental::identifier const& creator{*(this->creator)};
return alg.plugin() == creator || alg.algorithm() == creator;
Expand All @@ -83,8 +83,7 @@ namespace phlex {
{
// Will generate <<suffix>::<concept> by <creator> (of <stage>) in <layer>>
using experimental::identifier;
std::string_view suffix_str =
suffix.transform(&identifier::operator std::string_view).value_or("[ANY]");
std::string_view suffix_str = suffix.transform(&experimental::suffix_t::text).value_or("[ANY]");
std::string type_str = this->type.valid() ? fmt::format("<{}>", this->type)
: "[UNSET TYPE]"; // will later be concept
auto layer_str = std::string_view(layer);
Expand Down
13 changes: 10 additions & 3 deletions phlex/core/product_selector.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@ namespace phlex {

operator bool() const noexcept { return content_.has_value(); }
experimental::identifier const& operator*() const noexcept { return content_.operator*(); }
friend experimental::identifier format_as(creator_name const& me) noexcept
friend std::string_view format_as(creator_name const& me) noexcept
{
return me.content_.value_or("[ANY]");
return me.content_
.transform([](experimental::identifier const& id) { return std::string_view(id); })
.value_or("[ANY]");
}
auto operator<=>(creator_name const&) const noexcept = default;

Expand Down Expand Up @@ -97,9 +99,14 @@ namespace phlex {
}

struct PHLEX_CORE_EXPORT product_selector {
/// An omitted creator is unrestricted, including specifications without a creator.
/// A supplied name matches either the plugin or algorithm of a present creator.
detail::creator_name creator;
detail::layer_name layer;
std::optional<experimental::identifier> suffix;
/// Omitted matches any suffix; empty matches only empty; named matches only that name.
/// C-string literals convert implicitly; runtime strings require explicit suffix_t construction.
/// Equality, ordering, and diagnostics distinguish all three states.
std::optional<experimental::suffix_t> suffix;
std::optional<experimental::identifier> stage;
experimental::type_id type;

Expand Down
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
Loading
Loading