diff --git a/CHANGELOG.md b/CHANGELOG.md index 1484a8f..1a4da3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 stage of an outer graph (with `finish()` and the context reaching into it), a `Void`-terminated sink graph, and the in-band tick message. EXAMPLE.md walks through both in sections 8 and 9. +- **`apps/namedPipeline`** — a runnable example for names in a graph's wiring: + a merge whose same-typed slots are matched by name (`mergeInputNames()`), a + lambda merge with named slots (`registerTypedMergeFilter` with names), a graph + with several named inputs fed by `push()` and `filter(GraphInputs)`, the + build-time and feed-time checks the names enable, and `toMermaid` labelling a + merge's edges with its slot names. EXAMPLE.md section 10 walks through it; the + named-slot and named-input blocks move there from `apps/textPipeline`. - **Named merge slots** — a fan-in group can name the slots of a merge, `(raw: msg, checked: valid) -> Merge`, and is then matched by name, in any order: the merge receives its slots in the order it declares them. A merge diff --git a/EXAMPLE.md b/EXAMPLE.md index 6d60423..c838bde 100644 --- a/EXAMPLE.md +++ b/EXAMPLE.md @@ -1,6 +1,6 @@ # filterGraph by example -This walkthrough follows three runnable samples under [`apps/`](apps/), all of +This walkthrough follows four runnable samples under [`apps/`](apps/), all of them small text pipelines: | Sections | Sample | Covers | @@ -8,8 +8,9 @@ them small text pipelines: | 1–7 | [`textPipeline`](apps/textPipeline/main.cpp) | the core building blocks: a **compile-time** `FilterGraph`, **runtime graphs in the text DSL** (a tap, a merge, several named outputs, up-front diagnostics) and the same pipeline in the **JSON format** | | 8 | [`statefulPipeline`](apps/statefulPipeline/main.cpp) | stages that **carry state**: `finish()`, the `GraphContext`, a merge with per-instance state | | 9 | [`compositePipeline`](apps/compositePipeline/main.cpp) | **composition**: `JoinFilter`, a graph nested as a stage, a `Void` sink, in-band ticks | +| 10 | [`namedPipeline`](apps/namedPipeline/main.cpp) | **names in the wiring**: named merge slots, several named graph inputs, and the checks both make possible | -Start at the top: sections 8 and 9 assume the vocabulary of 1–7. +Start at the top: sections 8–10 assume the vocabulary of 1–7. ## Core concept: a chain of stages @@ -271,48 +272,8 @@ compiler; they are shortened here.) lambda, and `UniformMergeFilter` covers the other shape: any number of slots, all of the same type (N variants of one computation, combined). -### Named slots - -Slot types cannot catch everything: when two slots have the *same* type, -swapping them still passes validation. A merge can name its slots so that the -group says which edge goes where: - -```cpp -class CompareFilter : public TypedMergeFilter -{ -public: - std::optional merge(std::optional&& before, - std::optional&& after) override - { - return std::format("{} => {}", before.value_or("-"), after.value_or("-")); - } - - std::vector mergeInputNames() const override - { - return {"before", "after"}; - } -}; -static FilterRegistrar registerCompare("Compare"); -``` - -```cpp -DslFilterGraph pipeline(R"dsl( - in -> Uppercase -> upper - upper -> Reverse -> reversed - (after: reversed, before: upper) -> Compare -> compared -> Print(prefix="[named] ") -> out -)dsl"); - -pipeline.filter(std::string{"Hello, filterGraph!"}); -``` - -```text -[named] HELLO, FILTERGRAPH! => !HPARGRETLIF ,OLLEH -``` - -The group lists `after` first, but the slots are matched by name, so `merge()` -still receives `before` first. A misspelled name, or a slot the group leaves -out, is reported when the graph is built. A group without names still feeds -`Compare` by position. +When two slots have the *same* type, their types cannot tell them apart; a +merge can then name its slots. Section 10 shows how. ## 5. Several named outputs @@ -356,34 +317,7 @@ The graph's output type decides what shape is allowed: | `GraphOutputs` | one or more `-> out` / `-> out.` | all outputs; `std::nullopt` only if all were dropped | | `Void` | no outputs, only `-> end` | `Void{}` | -### Several named inputs - -The mirror image: a graph can read several named inputs, `in.`, with -`GraphInputs` as its input type. Each `push()` is one run that feeds a single -input; the stages that only an unfed input reaches are skipped, exactly as if -their path had dropped the message: - -```cpp -DslFilterGraph routes(R"dsl( - in.greeting -> Uppercase -> out.shouted - in.name -> Reverse -> out.reversed -)dsl", - GraphInputs::of("greeting", "name")); - -auto outputs = routes.push("name", std::string{"filterGraph"}); -std::cout << std::format("[inputs] shouted={} reversed={}\n", - outputs->has("shouted") ? "present" : "not fed", - *outputs->get("reversed")); -``` - -```text -[inputs] shouted=not fed reversed=hparGretlif -``` - -`GraphInputs::of` declares the input types, so they are checked when the graph -is built; it is optional, and without it each input takes the type of the -first stage that reads it. To feed several inputs in one run, pass them -together: `routes.filter(GraphInputs{}.set("greeting", a).set("name", b))`. +The mirror image, a graph with several named *inputs*, is in section 10. ## 6. Checking a graph before it runs @@ -824,6 +758,165 @@ DslFilterGraph graph("in -> Batch -> out"); [batch] 1 line(s) left unflushed at the end of the stream ``` +## 10. Names in the wiring: merge slots and graph inputs + +Positions are enough while a merge's slots have different types and a graph +has one input. The fourth sample, +[`apps/namedPipeline/main.cpp`](apps/namedPipeline/main.cpp), covers the cases +where they are not: a merge whose slots share a type, and a graph that +consumes several kinds of message. Both get names, and the names are checked. + +### Named slots + +Slot types cannot catch everything: when two slots have the *same* type, +swapping them still passes validation. A merge can name its slots so that the +group says which edge goes where: + +```cpp +class CompareFilter : public TypedMergeFilter +{ +public: + std::optional merge(std::optional&& before, + std::optional&& after) override + { + return std::format("{} => {}", before.value_or("-"), after.value_or("-")); + } + + std::vector mergeInputNames() const override + { + return {"before", "after"}; + } +}; +static FilterRegistrar registerCompare("Compare"); +``` + +```cpp +DslFilterGraph compare(R"dsl( + in -> Uppercase -> upper + upper -> Reverse -> reversed + (after: reversed, before: upper) -> Compare -> out +)dsl"); + +std::cout << "[slots] " << *compare.filter(std::string{"Hello, filterGraph!"}) << '\n'; +``` + +```text +[slots] HELLO, FILTERGRAPH! => !HPARGRETLIF ,OLLEH +``` + +The group lists `after` first, but the slots are matched by name, so `merge()` +still receives `before` first. A group without names still feeds `Compare` by +position. + +The names are checked when the graph is built. Misspell one and both +consequences are reported: the unknown name, and the slot now left unfed: + +```text +[slot check] 3:36: 'Compare' has no slot named 'befor' — did you mean 'before'? (its slots: before, after) +[slot check] 3:36: the fan-in group does not feed slot 'before' of 'Compare' +``` + +### Several named inputs + +The mirror image of section 5: a graph can read several named inputs, +`in.`, with `GraphInputs` as its input type. Each `push()` is one run that +feeds a single input; the stages that only an unfed input reaches are skipped, +exactly as if their path had dropped the message. `filter()` takes a +`GraphInputs` and feeds several inputs in the same run: + +```cpp +DslFilterGraph routes(R"dsl( + in.greeting -> Uppercase -> out.shouted + in.name -> Reverse -> out.reversed +)dsl", + GraphInputs::of("greeting", "name")); + +auto pushed = routes.push("name", std::string{"filterGraph"}); +auto both = routes.filter(GraphInputs{}.set("greeting", std::string{"hi"}).set("name", std::string{"filterGraph"})); +``` + +```text +[push] shouted=not fed reversed=hparGretlif +[filter] shouted=HI reversed=hparGretlif +``` + +`GraphInputs::of` declares the input types. It is optional; without it, each +input takes the type of the first stage that reads it. With it, the graph is +checked against the declaration when it is built, so a misspelled `in.` +shows up twice: + +```text +[input check] 1:1: input 'name' is declared but the graph never reads 'in.name' +[input check] 2:1: the graph reads 'in.nmae', but no input 'nmae' is declared +``` + +What is fed is checked when it arrives: an unknown key throws +`std::out_of_range`, a value of the wrong type `std::invalid_argument`: + +```text +[feed check] DslFilterGraph: the graph has no input named 'nmae' +[feed check] DslFilterGraph: input 'name' expects 'class std::basic_string' but received 'int' +``` + +### Both together + +Named inputs and named slots combine naturally: two inputs, each prepared by +its own stage, meet in a merge that names its slots. This one is registered +from a lambda; `registerTypedMergeFilter` takes the slot names, one per slot +type, before the function: + +```cpp +registerTypedMergeFilter( + "Greet", + {"greeting", "name"}, + [](std::optional&& greeting, std::optional&& name) -> std::optional { + return std::format("{}, {}!", greeting.value_or("Hello"), name.value_or("stranger")); + }); +``` + +```cpp +DslFilterGraph greet(R"dsl( + in.greeting -> Uppercase -> shouted + in.name -> Capitalize -> proper + (name: proper, greeting: shouted) -> Greet -> out +)dsl", + GraphInputs::of("greeting", "name")); + +greet.push("name", std::string{"aDA"}); +greet.push("greeting", std::string{"good morning"}); +greet.filter(GraphInputs{}.set("greeting", std::string{"hi"}).set("name", std::string{"grace"})); +``` + +An input that is not fed in a run leaves a hole in its slot, which `Greet` +fills with a default: + +```text +[greet] Hello, Ada! +[greet] GOOD MORNING, stranger! +[greet] HI, Grace! +``` + +`dsl::toMermaid(greet.program())` labels the merge's edges with the slot names: + +```mermaid +flowchart LR + s0["Uppercase"] + e0(["in.greeting"]) + e1(["shouted"]) + s1["Capitalize"] + e2(["in.name"]) + e3(["proper"]) + s2["Greet"] + e4(["out"]) + e0 --> s0 + s0 --> e1 + e2 --> s1 + s1 --> e3 + e3 -->|"name"| s2 + e1 -->|"greeting"| s2 + s2 --> e4 +``` + ## Build & run these examples ```powershell @@ -835,6 +928,7 @@ cmake --build --preset windows-msvc-release-user-mode ./out/build/windows-msvc-release-user-mode/apps/textPipeline/textPipeline ./out/build/windows-msvc-release-user-mode/apps/statefulPipeline/statefulPipeline ./out/build/windows-msvc-release-user-mode/apps/compositePipeline/compositePipeline +./out/build/windows-msvc-release-user-mode/apps/namedPipeline/namedPipeline ``` On Linux/macOS, use a matching preset such as `unixlike-gcc-release` or @@ -851,9 +945,7 @@ The complete output of `textPipeline` (sections 1–7): [typed] HELLO, FILTERGRAPH! (19 chars) [typed check] 3:20: slot 1 of 'Report' expects 'class std::basic_string' but edge 'length' carries 'unsigned __int64' [typed check] 3:20: slot 2 of 'Report' expects 'unsigned __int64' but edge 'upper' carries 'class std::basic_string' -[named] HELLO, FILTERGRAPH! => !HPARGRETLIF ,OLLEH [outputs] upper=HELLO, FILTERGRAPH! length=19 long=dropped -[inputs] shouted=not fed reversed=hparGretlif [check] 1:7: unknown stage type 'Uppercas' — did you mean 'Uppercase'? [check] 2:7: could not construct 'MinLength': [json.exception.out_of_range.403] key 'minLength' not found [json tap] HELLO, FILTERGRAPH! @@ -890,7 +982,7 @@ Of `statefulPipeline` (section 8): (15 words, not 12: in that last graph `Stamp` runs before `Collect`, so each line carries the session stamp as an extra word.) -And of `compositePipeline` (section 9): +Of `compositePipeline` (section 9): ```text [dsl] COMPOSE ME | em esopmoc @@ -908,3 +1000,23 @@ And of `compositePipeline` (section 9): [batch] third [batch] 1 line(s) left unflushed at the end of the stream ``` + +And of `namedPipeline` (section 10): + +```text +[slots] HELLO, FILTERGRAPH! => !HPARGRETLIF ,OLLEH +[slot check] 3:36: 'Compare' has no slot named 'befor' — did you mean 'before'? (its slots: before, after) +[slot check] 3:36: the fan-in group does not feed slot 'before' of 'Compare' +[push] shouted=not fed reversed=hparGretlif +[filter] shouted=HI reversed=hparGretlif +[feed check] DslFilterGraph: the graph has no input named 'nmae' +[feed check] DslFilterGraph: input 'name' expects 'class std::basic_string' but received 'int' +[input check] 1:1: input 'name' is declared but the graph never reads 'in.name' +[input check] 2:1: the graph reads 'in.nmae', but no input 'nmae' is declared +[greet] Hello, Ada! +[greet] GOOD MORNING, stranger! +[greet] HI, Grace! +``` + +followed by the Mermaid flowchart shown in section 10. (The `[feed check]` type +name is shortened here too.) diff --git a/README.md b/README.md index 8cfa259..8502bfb 100644 --- a/README.md +++ b/README.md @@ -11,11 +11,13 @@ paths, merge them back together, and drop/short-circuit messages — all without hard-coding the pipeline shape in source code. > **New here? Start with the [example walkthrough (EXAMPLE.md)](EXAMPLE.md)** — -> a step-by-step, diagrammed tour of three runnable samples: +> a step-by-step, diagrammed tour of four runnable samples: > [`apps/textPipeline`](apps/textPipeline/main.cpp) (the core building blocks), > [`apps/statefulPipeline`](apps/statefulPipeline/main.cpp) (stages that carry -> state) and [`apps/compositePipeline`](apps/compositePipeline/main.cpp) -> (composing graphs out of graphs). +> state), [`apps/compositePipeline`](apps/compositePipeline/main.cpp) +> (composing graphs out of graphs) and +> [`apps/namedPipeline`](apps/namedPipeline/main.cpp) (named merge slots and +> graph inputs). > **A note on the word "filter".** Here "filter" follows the Unix-pipeline and > media-graph (DirectShow / GStreamer / FFmpeg) tradition: a stage that reads a @@ -646,9 +648,10 @@ Run the bundled examples directly after building: ./out/build/windows-msvc-release-user-mode/apps/textPipeline/textPipeline ./out/build/windows-msvc-release-user-mode/apps/statefulPipeline/statefulPipeline ./out/build/windows-msvc-release-user-mode/apps/compositePipeline/compositePipeline +./out/build/windows-msvc-release-user-mode/apps/namedPipeline/namedPipeline ``` -[EXAMPLE.md](EXAMPLE.md) walks through all three, with their output. +[EXAMPLE.md](EXAMPLE.md) walks through all four, with their output. ## Roadmap diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index 9ed5350..9ab929c 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -1,3 +1,4 @@ add_subdirectory(textPipeline) add_subdirectory(statefulPipeline) add_subdirectory(compositePipeline) +add_subdirectory(namedPipeline) diff --git a/apps/namedPipeline/CMakeLists.txt b/apps/namedPipeline/CMakeLists.txt new file mode 100644 index 0000000..bfcfcad --- /dev/null +++ b/apps/namedPipeline/CMakeLists.txt @@ -0,0 +1,23 @@ +project(namedPipeline) + +set(CMAKE_CXX_STANDARD 20) +file(GLOB_RECURSE HEADER_FILES CONFIGURE_DEPENDS "*.h*") +file(GLOB_RECURSE CPP_FILES CONFIGURE_DEPENDS "*.cpp") + +add_executable(${PROJECT_NAME} ${HEADER_FILES} ${CPP_FILES} ) + + + +target_link_libraries(${PROJECT_NAME} + PUBLIC + filterGraph::filterGraph +) +target_include_directories(${PROJECT_NAME} + PRIVATE + $ + $ +) + +enable_coverage(${PROJECT_NAME}) + +install(TARGETS ${PROJECT_NAME}) \ No newline at end of file diff --git a/apps/namedPipeline/main.cpp b/apps/namedPipeline/main.cpp new file mode 100644 index 0000000..cad934c --- /dev/null +++ b/apps/namedPipeline/main.cpp @@ -0,0 +1,218 @@ +// Names in a graph's wiring — for graphs with more than one of something that +// positions alone cannot tell apart: +// - named merge slots: `(after: x, before: y) -> Compare`, matched by name +// - named slots on a merge registered from a lambda +// - several named graph inputs, `in.`, fed by push() or filter() +// - the build-time and run-time checks that the names make possible +// - toMermaid, which labels a merge's edges with its slot names +// +// See EXAMPLE.md, "Names in the wiring: merge slots and graph inputs". +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using filterGraph::DslFilterGraph; +using filterGraph::FilterRegistrar; +using filterGraph::GraphInputs; +using filterGraph::MessageFilter; +using filterGraph::TypedMergeFilter; +using filterGraph::registerTypedMergeFilter; +using filterGraph::validateDslGraph; + +namespace dsl = filterGraph::dsl; + +namespace { + +// --- Stages ------------------------------------------------------------ + +class UppercaseFilter : public MessageFilter +{ +public: + std::optional filter(std::string&& text) override + { + std::ranges::transform(text, text.begin(), [](unsigned char c) { return static_cast(std::toupper(c)); }); + return std::move(text); + } +}; + +class ReverseFilter : public MessageFilter +{ +public: + std::optional filter(std::string&& text) override + { + std::ranges::reverse(text); + return std::move(text); + } +}; + +// "aDA" -> "Ada": upper-case first letter, the rest lower-case. +class CapitalizeFilter : public MessageFilter +{ +public: + std::optional filter(std::string&& text) override + { + std::ranges::transform(text, text.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); + if (!text.empty()) + { + text.front() = static_cast(std::toupper(static_cast(text.front()))); + } + return std::move(text); + } +}; + +// A merge with two slots of the same type, where the order matters. The slot +// types cannot tell them apart, so it names them: a group can then match them +// by name, `(after: x, before: y) -> Compare`, in any order. +class CompareFilter : public TypedMergeFilter +{ +public: + std::optional merge(std::optional&& before, + std::optional&& after) override + { + return std::format("{} => {}", before.value_or("-"), after.value_or("-")); + } + + std::vector mergeInputNames() const override + { + return {"before", "after"}; + } +}; + +// --- Registration -------------------------------------------------------- + +static FilterRegistrar registerUppercase("Uppercase"); +static FilterRegistrar registerReverse("Reverse"); +static FilterRegistrar registerCapitalize("Capitalize"); +static FilterRegistrar registerCompare("Compare"); + +// The same kind of merge from a lambda: registerTypedMergeFilter takes the +// slot names, one per slot type, before the function. A hole (an input not +// fed in this run) falls back to a default. +static const bool sRegisterGreet = [] { + registerTypedMergeFilter( + "Greet", + {"greeting", "name"}, + [](std::optional&& greeting, std::optional&& name) -> std::optional { + return std::format("{}, {}!", greeting.value_or("Hello"), name.value_or("stranger")); + }); + return true; +}(); + +void printDiagnostics(const char* tag, const std::vector& diagnostics) +{ + for (const auto& diagnostic : diagnostics) + { + std::cout << std::format("[{}] {}\n", tag, dsl::formatDiagnostic(diagnostic)); + } +} + +} // namespace + +int main() +{ + // 1) Named slots: Compare's two slots have the same type, so only their + // names say which is which. The group lists them in the other order; + // the merge still receives `before` first. + { + DslFilterGraph compare(R"dsl( + in -> Uppercase -> upper + upper -> Reverse -> reversed + (after: reversed, before: upper) -> Compare -> out + )dsl"); + + std::cout << "[slots] " << *compare.filter(std::string{"Hello, filterGraph!"}) << '\n'; + } + + // 2) What the names catch: a misspelled slot name, and with it a slot the + // group no longer feeds. Positional wiring cannot report either. + { + printDiagnostics("slot check", + validateDslGraph( + "in -> Uppercase -> upper\n" + "upper -> Reverse -> reversed\n" + "(after: reversed, befor: upper) -> Compare -> out\n")); + } + + // 3) Several named inputs, with declared types. push() feeds one input + // and runs only the stages it reaches; the other input is empty. + // filter() takes a GraphInputs and feeds several inputs in one run. + { + DslFilterGraph routes(R"dsl( + in.greeting -> Uppercase -> out.shouted + in.name -> Reverse -> out.reversed + )dsl", + GraphInputs::of("greeting", "name")); + + auto pushed = routes.push("name", std::string{"filterGraph"}); + std::cout << std::format("[push] shouted={} reversed={}\n", + pushed->has("shouted") ? "present" : "not fed", + *pushed->get("reversed")); + + auto both = routes.filter(GraphInputs{}.set("greeting", std::string{"hi"}).set("name", std::string{"filterGraph"})); + std::cout << std::format("[filter] shouted={} reversed={}\n", + *both->get("shouted"), + *both->get("reversed")); + + // A key the graph does not have, or a value of the wrong type, is + // rejected when it is fed. + try + { + routes.push("nmae", std::string{"filterGraph"}); + } + catch (const std::out_of_range& error) + { + std::cout << "[feed check] " << error.what() << '\n'; + } + try + { + routes.push("name", 42); + } + catch (const std::invalid_argument& error) + { + std::cout << "[feed check] " << error.what() << '\n'; + } + } + + // 4) The declared inputs are checked against the graph when it is built: + // a misspelled `in.` is an undeclared input, and the declared one + // it was meant to be is never read. + { + printDiagnostics("input check", + validateDslGraph("in.greeting -> Uppercase -> out.shouted\n" + "in.nmae -> Reverse -> out.reversed\n", + GraphInputs::of("greeting", "name"))); + } + + // 5) Both together: two named inputs meet in a merge with named slots. An + // input that is not fed leaves a hole in its slot, and Greet fills it + // with a default. + { + DslFilterGraph greet(R"dsl( + in.greeting -> Uppercase -> shouted + in.name -> Capitalize -> proper + (name: proper, greeting: shouted) -> Greet -> out + )dsl", + GraphInputs::of("greeting", "name")); + + std::cout << "[greet] " << *greet.push("name", std::string{"aDA"}) << '\n'; + std::cout << "[greet] " << *greet.push("greeting", std::string{"good morning"}) << '\n'; + std::cout << "[greet] " + << *greet.filter(GraphInputs{}.set("greeting", std::string{"hi"}).set("name", std::string{"grace"})) + << '\n'; + + // 6) toMermaid labels the merge's edges with the slot names. + std::cout << '\n' << dsl::toMermaid(greet.program()); + } + + return 0; +} diff --git a/apps/textPipeline/main.cpp b/apps/textPipeline/main.cpp index 164bed5..1a1721b 100644 --- a/apps/textPipeline/main.cpp +++ b/apps/textPipeline/main.cpp @@ -3,8 +3,8 @@ // - MessageFilter: the base stage interface // - FilterGraph: compile-time chaining of stages // - FilterRegistry + DslFilterGraph: runtime graphs described in the text DSL, -// with fan-out (a "tap"), fan-in (merges, with typed and named slots), -// several named outputs and several named inputs +// with fan-out (a "tap"), fan-in (merges, with typed slots) and several +// named outputs (named slots and inputs: see apps/namedPipeline) // - validateDslGraph: every problem in a broken graph, located by line:column // - JsonFilterGraph: the JSON format, which remains supported #include @@ -25,12 +25,10 @@ #include #include #include -#include using filterGraph::DslFilterGraph; using filterGraph::FilterGraph; using filterGraph::FilterRegistrar; -using filterGraph::GraphInputs; using filterGraph::JsonFilterGraph; using filterGraph::MergeInputs; using filterGraph::MessageFilter; @@ -176,26 +174,6 @@ class ReportFilter : public TypedMergeFilter registerReport("Report"); -// A merge with two slots of the same type, where the order matters. The slot -// types cannot tell them apart, so it names them: a group can then match them -// by name, `(after: x, before: y) -> Compare`, in any order. -class CompareFilter : public TypedMergeFilter -{ -public: - std::optional merge(std::optional&& before, - std::optional&& after) override - { - return std::format("{} => {}", before.value_or("-"), after.value_or("-")); - } - - std::vector mergeInputNames() const override - { - return {"before", "after"}; - } -}; - -static FilterRegistrar registerCompare("Compare"); - // Only needed by the JSON example: in the DSL, fan-out is built in. static const bool sRegisterFanout = [] { registerFanoutFilter("Fanout"); @@ -263,19 +241,6 @@ int main() } } - // 3c) Named slots: Compare's two slots have the same type, so only their - // names say which is which. The group lists them in the other order; - // the merge still receives `before` first. - { - DslFilterGraph pipeline(R"dsl( - in -> Uppercase -> upper - upper -> Reverse -> reversed - (after: reversed, before: upper) -> Compare -> compared -> Print(prefix="[named] ") -> out - )dsl"); - - pipeline.filter(std::string{"Hello, filterGraph!"}); - } - // 4) Several named outputs of different types, returned as GraphOutputs. { DslFilterGraph analysis(R"dsl( @@ -291,21 +256,6 @@ int main() outputs->has("long") ? "present" : "dropped"); } - // 4b) Several named inputs, with declared types. push() feeds one input - // and runs only the stages it reaches; the other input is empty. - { - DslFilterGraph routes(R"dsl( - in.greeting -> Uppercase -> out.shouted - in.name -> Reverse -> out.reversed - )dsl", - GraphInputs::of("greeting", "name")); - - auto outputs = routes.push("name", std::string{"filterGraph"}); - std::cout << std::format("[inputs] shouted={} reversed={}\n", - outputs->has("shouted") ? "present" : "not fed", - *outputs->get("reversed")); - } - // 5) Problems are reported before anything runs: all of them at once, each // located by line:column. Constructing a DslFilterGraph from this text // would throw a GraphError carrying the same diagnostics.